현재 이메일을 검증하는 기능은 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 인스턴스를 재사용한다.