1. Validator 인터페이스 (jakarta.validation)
- 자바 애플리케이션에서 공통된 유효성 검증을 위한 Bean Validation API
- ✅ 어노테이션 기반 검증 (표준 애노테이션 지원)
- ✅ 검증 결과 반환 (Set<ConstraintViolation<T>>)
- ❌ 기본적으로 예외를 던지지 않음
예제
더보기
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());
}
2. ConstraintValidator (jakarta.validation)
- 제약조건 어노테이션을 검증할 때 쓰는 인터페이스
| 항목 | 설명 |
| 제네릭 타입 | - A: 애너테이션 타입 - T: 검증할 값의 타입 |
| 사용 위치 | @Constraint(validatedBy = ...)에서 지정됨 |
예제) 커스텀 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");
}
}
@Constraint
- 사용자 정의 제약 사항 어노테이션 선언할 때 쓰는 애노테이션
| 속성 | 설명 |
| validatedBy (필수) | 검사 로직을 구현한 클래스 지정 (ConstraintValidator<A, T> 구현체) |
| message 속성 | 기본 에러 메시지 지정 (메시지 커스터마이징 가능) |
예제) 커스텀 @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}
|
예제) Hibernate 제공
더보기
public class PersonForm {
@NotNull
@Size(max=64)
private String name;
@Min(0)
private int age;
}
- Hibernate라는 벤더에서 표준 제약 어노테이션의 jakarta.validation.ConstraintValidator 구현체들을 제공함
- ✅ Hibernate Validator가 내부적으로 표준 제약 애노테이션의 검증기들의 매핑을 등록해 둠
- ✅ @Size ↔️ SizeValidationForCollection, SizeValidationForShortArray 등
3. @Valid vs @Validated
| 구분 | @Valid (jakarta.validation) |
@Validated
(org.springframework.validation.annotation) |
| 소속 | 표준 Bean Validation 트리거 (Jakarta) | Spring의 AOP 기반 검증 활성화 |
| 주 용도 | - DTO/엔티티 필드 검증 - 컨트롤러 파라미터 검증 |
메서드 레벨 검증 그룹 검증 (파라미터/리턴값)
|
| 적용 위치 | 컨트롤러 파라미터 (@RequestBody, @ModelAttribute) |
- 클래스 레벨(@Service, @Controller)
- 메서드 파라미터/리턴 |
| 그룹 검증 | ❌ 지원 안 함 |
✅ 지원 (@Validated(Group.class))
|
| 발생 예외 | - MethodArgumentNotValidException (@RequestBody) - BindException (@ModelAttribute) |
- ConstraintViolationException (메서드 파라미터/리턴 검증 위반)
|
예제) @Validated - 그룹 검증
더보기
// 3-1) 그룹 인터페이스
public interface Create {}
public interface Update {}
// 3-2) DTO 제약에 그룹 지정
public record UserProfileCmd(
@NotBlank(groups = {Create.class}) String email,
@NotBlank(groups = {Create.class, Update.class}) String name,
@Size(min = 8, groups = {Create.class}) String password
) {}
@Validated
@Service
public class UserProfileService {
// 생성 시: Create 그룹으로 검증 (email, name, password 모두 체크)
@Validated(Create.class)
public void create(@Valid UserProfileCmd cmd) {
// ...
}
// 수정 시: Update 그룹으로 검증 (name만 필수)
@Validated(Update.class)
public void update(@Valid UserProfileCmd cmd) {
// ...
}
}
4. 예외 인터페이스
BindingResult
- 바인딩 결과를 표현하는 인터페이스 입니다.
- ✅ BindException의 부모 인터페이스
- ✅ Errors 인터페이스를 확장함
- ✅ 예외가 발생할 경우, 검증기는 유효하지 않은 검증결과를 BindingResult에 저장해줍니다.
예시) List<FieldError>
더보기
- 유효성을 위반한 필드의 에러내용을 담은 클래스의 리스트입니다.
- BindingResult를 접근하여 사용자에게 예외메시지를 생성하는데 활용됩니다.
public record ValidationResult(List<FieldErrorDetail> errors) {
public static ValidationResult of(Errors errors) {
List<FieldErrorDetail> details =
errors.getFieldErrors()
.stream()
.map(error -> FieldErrorDetail.of(error))
.collect(Collectors.toList());
return new ValidationResult(details);
}
}
public record FieldErrorDetail(String objectName, String field, String code, String message) {
public static FieldErrorDetail of(FieldError error) {
return new FieldErrorDetail(
error.getObjectName(),
error.getField(),
error.getCode(),
error.getDefaultMessage()
);
}
}
4. 예외 클래스
MethodArgumentNotValidException (org.springframework.web.bind)
- @RequestBody 데이터 바인딩 중, 검증 실패가 발생할 경우 던져지는 예외
- ✅ 기본적으로 HTTP 400 오류로 처리됩니다.
- ✅ 예외클래스 내부 필드에 BindingResult라는 객체가 있으며 검증 오류를 보관합니다.
BindException
- @ModelAttribute 데이터 바인딩 중, 검증 실패가 발생할 경우 던져지는 예외
ConstraintViolationException
- @Validated에 의해 트리거 된 검증에서 오류가 발생하는 경우 던져지는 예외
- ✅ 객체가 아닌 단일 필드에 메서드 시그니처에서 직접검사시에 사용됨 (@PathVariable, @RequestParam)
5. 예외 처리 커스터마이징
MethodArgumentNotValidException / BindException
예제) 커스터마이징
더보기
1. 전역 예외 처리 핸들러 등록
@ExceptionHandler(value = {MethodArgumentNotValidException.class, BindException.class})
public ResponseEntity<?> handleMethodArgumentNotValidException(Exception e) {
BindingResult bindingResult = e instanceof MethodArgumentNotValidException manv ? manv.getBindingResult() : (BindingResult) e;
// 예외 처리
}
ConstraintViolationException
- ConstraintViolationException은 매우 로우레벨의 예외
- ❌ 직접 핸들링할 경우 불편함이 발생함
| 문제점 | 설명 |
| 예외 메시지 파싱 필요 |
어떤 필드에서 어떤 문제가 생겼는지 파악하려면 constraintViolations를 직접 순회해야 함
|
| 비즈니스 예외와 구분 힘듦 |
ConstraintViolationException은 스프링의 일반적인 예외 처리 흐름과 조금 결이 다름
(ex. ResponseStatusException) |
| 공통 포맷 통일 어려움 |
모든 검증 실패 응답을 공통 JSON 구조로 만들고 싶을 때, 이걸 일일이 다뤄야 함.
|
예제) 커스터마이징
더보기
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;
}
}
- MethodValidationException 같은 커스텀 예외로 변환하기
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 (0) | 2025.04.06 |
| [Spring][Object] 1. Data Binding (0) | 2025.04.06 |
| [Spring][Field] 2. Formatting (0) | 2025.04.06 |