1. jakarta.validation
- 자바 애플리케이션에서 공통된 유효성 검증을 위한 Bean Validation API
Validator
항목 | 설명 |
정의 | 도메인 객체에 정의된 제약 조건을 검증함 |
유효성 검증 방법 | 수동적으로 호출하여 사용 - validator.validate(object) |
예제)
더보기
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Person person = new Person(); // name=null, age=–1
Set<ConstraintViolation<Person>> violations = validator.validate(person);
for (ConstraintViolation<Person> violation : violations) {
System.out.println(violation.getPropertyPath() + ": " + violation.getMessage());
}
- 기본적으로 예외를 던지지 않고 Set<ConstraintViolation<T>> 반환 (검증 실패 항목들의 정보가 담긴 집합)
ConstraintValidator
항목 | 설명 |
정의 | 커스터마이징하여 도메인 객체에 정의된 제약 조건을 검증함 |
유효성 검증 방법 | 도메인 모델의 필드에 annotation(제약 조건)을 선언하여 유효성 검증 수행 |
제네릭 타입 | <A, T> - A: 애너테이션 타입 - T: 검증할 값의 타입입니다. |
사용 위치 | 커스텀 제약 애너테이션과 함께 사용되며, @Constraint(validatedBy = ...)에서 지정됩니다. |
빈 등록 여부 | Spring에서는 자동으로 등록되며, 별도로 Bean으로 등록할 필요는 없습니다. |
@Constraint
항목 | 설명 |
목적 |
사용자 정의 제약 사항 어노테이션을 선언할 때 사용
|
필수 속성 (validatedBy) | 검사 로직을 구현한 클래스 지정 → ConstraintValidator<A, T> 구현체 |
메시지 커스터마이징 | message 속성을 통해 기본 에러 메시지 지정 |
예제) 커스터마이징
더보기
1. 커스텀 @Constraint 정의
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ... })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MyConstraintValidator.class)
public @interface MyConstraint {
String message() default "유효하지 않습니다.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
속성 | 설명 | 사용 예시 |
message | 유효성 검사 실패 시 반환할 기본 에러 메시지입니다. - ResourceBundle로 다국어 처리 가능 |
message = "이름은 비어있을 수 없습니다"
|
groups | 검증 그룹을 지정할 때 사용합니다. 여러 그룹을 만들어 특정 조건에서만 유효성 검사를 수행 가능 |
groups = {Create.class, Update.class}
|
payload | 메타데이터 정보를 담기 위해 사용합니다. 보통은 잘 사용하지 않지만, 커스텀 처리가 필요한 경우 활용됩니다. |
payload = {Severity.High.class}
|
2. 커스텀 ConstraintValidator 정의
public class MyConstraintValidator implements ConstraintValidator<MyConstraint, String> {
@Autowired
private Foo aDependency;
@Override
public void initialize(MyConstraint constraintAnnotation) {
// 초기화 로직 (필요 없다면 생략 가능)
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// 실제 유효성 검사 로직
return value != null && value.startsWith("ABC");
}
}
예제) Hibernate
더보기
public class PersonForm {
@NotNull
@Size(max=64)
private String name;
@Min(0)
private int age;
}
- Hibernate라는 벤더에서 @Size와 같은 제약 어노테이션의 jakarta.validation.ConstraintValidator 구현체들을 제공함
2. Spring Bean Validation
- Spring은 Bean Validation API를 완전하게 지원
- Spring 환경에서 Validator들을 통합하고 자동으로 유효성 검증을 수행함
(jakarta.validation.Validator 및 org.springframework.validation.Validator)
Validator 제공자 빈 등록
LocalValidatorFactoryBean
@Configuration
public class AppConfig {
@Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
}
항목 | 설명 |
정의 |
Spring에서 Bean Validation 제공자를 감지하고 등록하는 기본 Validator Bean
|
구현 인터페이스 |
jakarta.validation.Validator
org.springframework.validation.Validator |
주요 역할 |
Bean Validation API(jakarta.validation.Validator)와
Spring Validation API(org.springframework.validation.Validator)를 통합하여 사용 가능하게 함 |
자동 감지 |
클래스패스에 있는 모든 Bean Validation 제공자들을 자동으로 감지하여 사용
|
적용 대상 |
도메인 모델 필드, 메서드 파라미터, 커맨드 객체 등
|
사용 예시 |
Spring MVC, Spring WebFlux, 메서드 유효성 검사 등
|
예제) LocalValidatorFactoryBean 사용
더보기
@Service
public class MyService {
@Autowired
private jakarta.validation.Validator validator;
// private org.springframework.validation.Validator validator; // 이것도 됨
}
메서드 유효성 검사 빈 등록
MethodValidationPostProcessor
@Configuration
public class ApplicationConfiguration {
@Bean
public static MethodValidationPostProcessor validationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
항목 | 설명 |
정의 |
메서드 호출 시, 인자나 반환값에 대한 Bean Validation을 수행하게 해주는 Spring Bean
|
역할 |
@Validated 또는 @Valid가 붙은 메서드의 파라미터, 반환값 검증 처리
|
내부 동작 |
AOP 기반으로 동작
- 메서드 호출 전에 validator로 유효성 검증을 수행 |
사용
@Service
@Validated
public class MyService {
public void addStudent(@Valid Person person, @Max(2) int degrees) {
// ...
}
@NotNull // 반환값이 null이면 예외 발생
public String getUserName(Long id) {
// DB에서 못 찾았다고 치자
return null;
}
}
- @Validated 필수 (있어야 MethodValidationPostProcessor가 작동함)
- 파라미터나 리턴값에 @Valid, 제약 어노테이션(@Size, @Min 등) 사용
3. Spring Bean Validation 부가기능
메시지 커스터마이징
- 제약 조건 실패 시 발생하는 에러 메시지를 MessageSource 기반으로 커스터마이징 가능
예제) ValidationMessages.properties
더보기
1. 메시지 프로퍼티 파일 생성
# src/main/resources/ValidationMessages.properties
Size.person.name=이름은 {2}자 이상 {1}자 이하여야 합니다.
NotBlank.person.name=이름은 필수 항목입니다.
Max.age=나이는 {1} 이하여야 합니다.
- {0}: 필드명
- {1}, {2}: 제약 조건 파라미터
2. 제약 조건 어노테이션에 메시지 키 지정 (@Constraint 속성)
public class Person {
@Size(min = 2, max = 10, message = "{Size.person.name}")
private String name;
@Max(value = 100, message = "{Max.age}")
private int age;
}
- message = "{메시지 키}" 형태로 지정하면 MessageSource에서 해당 메시지를 찾아 사용함.
3. MessageSource 빈 등록하기
@Configuration
public class MessageConfig {
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("ValidationMessages"); // 확장자 없이 basename만
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
}
- 커스터마이징 메시지 프로퍼티를 사용하기 위해 설정 필요
- 다국어 지원 시, ValidationMessages_ko.properties, ValidationMessages_en.properties 등 추가 가능
4. LocalValidatorFactoryBean에 MessageSource 연결
@Bean
public LocalValidatorFactoryBean getValidator(MessageSource messageSource) {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setValidationMessageSource(messageSource);
return validator;
}
예외 처리
ConstraintViolationException
- 검증에 실패할 경우 기본적으로 jakarta.validation.ConstraintViolationException 이 발생
- Validator에서 발생하는 매우 로우레벨의 예외
- 직접 핸들링할 경우 불편함이 발생함
문제점 | 설명 |
예외 메시지 파싱 필요 |
어떤 필드에서 어떤 문제가 생겼는지 파악하려면 constraintViolations를 직접 순회해야 해요.
|
비즈니스 예외와 구분 힘듦 |
ConstraintViolationException은 스프링의 일반적인 예외 처리 흐름과 조금 결이 달라요.
(ex. ResponseStatusException) |
공통 포맷 통일 어려움 |
모든 검증 실패 응답을 공통 JSON 구조로 만들고 싶을 때, 이걸 일일이 다뤄야 함.
|
커스터마이징
- 예외를 비즈니스 계층에 다루기 쉽게 만들기
- MethodValidationException 같은 커스텀 예외로 변환하기
예제
더보기
1. MethodValidationPostProcessor 커스터마이징
public class CustomMethodValidationPostProcessor extends MethodValidationPostProcessor {
@Override
protected MethodValidationInterceptor createMethodValidationInterceptor(Validator validator) {
return new MethodValidationInterceptor(validator) {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
try {
return super.invoke(invocation);
} catch (ConstraintViolationException ex) {
// 예외를 감싸서 던지기
throw new CustomValidationException(ex.getConstraintViolations());
}
}
};
}
}
2. Spring Bean으로 등록
@Configuration
public class ValidationConfig {
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor(Validator validator) {
CustomMethodValidationPostProcessor processor = new CustomMethodValidationPostProcessor();
processor.setValidator(validator);
return processor;
}
}
3. CustomValidationException 정의
public class CustomValidationException extends RuntimeException {
private final Set<ConstraintViolation<?>> violations;
public CustomValidationException(Set<ConstraintViolation<?>> violations) {
super("Validation failed");
this.violations = violations;
}
public Set<ConstraintViolation<?>> getViolations() {
return violations;
}
}
4. @ExceptionHandler로 통합 처리
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(CustomValidationException.class)
public ResponseEntity<Map<String, Object>> handleCustomValidation(CustomValidationException ex) {
Map<String, String> errors = new HashMap<>();
for (ConstraintViolation<?> v : ex.getViolations()) {
String field = v.getPropertyPath().toString();
String message = v.getMessage();
errors.put(field, message);
}
Map<String, Object> response = new HashMap<>();
response.put("code", "VALIDATION_FAILED");
response.put("errors", errors);
return ResponseEntity.badRequest().body(response);
}
}
출처
'Spring > Spring' 카테고리의 다른 글
[Spring][AOP] 2. Pointcut API (1) | 2025.04.09 |
---|---|
[Spring][AOP] 1. Advice API (0) | 2025.04.09 |
[Spring][Validation] 1. Validator Interface (0) | 2025.04.06 |
[Spring][Object] 1. Data Binding (0) | 2025.04.06 |
[Spring][Field] 2. Formatting (0) | 2025.04.06 |