본문 바로가기

스프링

불필요한 객체 생성 막기

현재 이메일을 검증하는 기능은 ConstraintValidator를 구현한 EmailFormatValidator에서 수행하고 있다. 그리고 이메일 검증 메소드는 다음과 같이 구현되어 있다.

@Component
public class EmailFormatValidator implements ConstraintValidator<EmailValid, Email> {
 // ...
	private static boolean validateEmailAddress(Email email) {
		return !Pattern.matches(RegexConst.EMAIL_VALID_REGEX, email.getAddress());
	}
}

정규식으로 올바른 이메일인지 검증하기 위해서 Pattern.matches() 메소드를 사용하고 있는데, 해당 메소드의 코드는 다음과 같다.

public static boolean matches(String regex, CharSequence input) {
    Pattern p = Pattern.compile(regex);
    Matcher m = p.matcher(input);
    return m.matches();
}

즉, 이메일을 검증할 때마다 새로운 Pattern 객체를 생성하고 한 번 사용한 뒤 GC의 대상이 된다.

 

이처럼 불필요한 객체의 생성을 막기 위해 다음과 같이 해결할 수 있다.

 

1. 직접 Pattern 인스턴스 캐싱

검증하려는 정규표현식에 대한 정보를 가지고 있는 Pattern 인스턴스를 미리 만들어 놓고 검증 요청이 올 때마다 해당 인스턴스를 사용한다.

@Component
public class EmailFormatValidator implements ConstraintValidator<EmailValid, Email> {

	private static final Pattern EMAIL_PATTERN = Pattern.compile(RegexConst.EMAIL_VALID_REGEX);

	// ...

	private static boolean validateEmailAddress(Email email) {
		return EMAIL_PATTERN.matcher(email.getAddress())
				.matches();
	}
}

 

2. @Pattern 어노테이션 사용

javax.validation.constraints.Pattern 어노테이션을 사용하여 검증한다.

@Embeddable
public class Email {

    private static final String INVALID_EMAIL_MESSAGE = "이메일 형식이 일치하지 않습니다.";

    @Pattern(regexp = RegexConst.EMAIL_VALID_REGEX, message = INVALID_EMAIL_MESSAGE) // 추가
    @Length(max = 320)
    @NotBlank
    @Column(name = "email", unique = true, length = 320, nullable = false)
    private String address;
}

@Pattern 어노테이션을 적용하면, 요청이 왔을 때 PatternValidator에서 검증 로직을 수행한다.

public class PatternValidator implements ConstraintValidator<Pattern, CharSequence> {

	private java.util.regex.Pattern pattern;

	// ...

	@Override
	public boolean isValid(CharSequence value, ConstraintValidatorContext constraintValidatorContext) {
		if ( value == null ) {
			return true;
		}

		if ( constraintValidatorContext instanceof HibernateConstraintValidatorContext ) {
			constraintValidatorContext.unwrap( HibernateConstraintValidatorContext.class ).addMessageParameter( "regexp", escapedRegexp );
		}

		Matcher m = pattern.matcher( value );
		return m.matches();
	}
}

PatternValidator는 스프링 빈으로 등록되어 있기 때문에 싱글톤 객체이다. 거기에 pattern 필드값으로 @Pattern의 regexp에 입력한 값을 가지고 있다. 그리고 isValid() 메소드를 보면 해당 pattern 인스턴스를 사용하는 것을 알 수 있다.

 

즉, @Pattern 어노테이션을 통해 정규식을 검증할 때마다 애플리케이션 실행 시 미리 만들어 놨던 Pattern 인스턴스를 재사용한다.