[Spring Boot] 유효성 검증 처리하는 다양한 방법 정리
# 서론
Bean Validation을 사용해서 DTO의 유효성을 검증할 때 적용할 수 있는 다양한 방법들을 정리한다.
# V1 - 일반적인 처리
class UserDto {
@NotBlank String username;
@NotBlank String password;
}
@PostMapping("/join")
public ResponseEntity<?> join(@Valid @RequestBody UserDto userDto, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return ResponseEntity.badRequest().body("잘못된 요청");
}
// 비즈니스 로직
}
- @Valid는 자바에서 제공하는 어노테이션, @Validated는 스프링에서 제공하는 어노테이션인데 둘 중 아무거나 사용해도 무방함 (@Validated가 좀 더 많은 기능을 제공하긴 하나 사용하지 않을거면 짧은 @Valid를 많이 사용하는 편)
- BindingResult는 @Valid가 붙은 객체 바로 뒤에 선언해 주어야 바인딩이 가능하다
가장 일반적인 유효성 검증 로직이다. 검증에 실패하면 실패한 결과가 BindingResult에 담기고, 이에 따라서 처리 로직을 진행하면 된다.
단, 이렇게 하면 모든 컨트롤러 핸들러 메서드에 중복되는 검증 로직 코드가 들어간다는 단점이 있다.
# V2 - AOP를 활용
@Aspect
@Component
public class ValidateAspect {
@Around("execution(* com.text.domain.*(..))")
public Object validate(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if(arg instanceof BindingResult) {
BindingResult bindingResult = (BindingResult) arg;
if (bindingResult.hasErrors()) {
throw new CustomValidationException("유효성 검사 실패.", bindingResult);
}
}
}
return joinPoint.proceed();
}
}
AOP를 활용하여 핸들러 메소드의 Argument를 검사한다.
이때, BindingResult가 있으면 발생하는 로직을 Aspect로 지정하여 핸들러 메소드 내부 로직에는 유효성 검사 로직을 제외시키는 방법이다.
@PostMapping("/join")
public ResponseEntity<?> join(@Valid @RequestBody UserDto userDto, BindingResult bindingResult) {
// 비즈니스 로직
}
이렇게 하면 바로 비즈니스 로직으로 넘어 갈 수 있다.
# V3 - ControllerAdvice 활용
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BindException.class)
public ResponseEntity<ErrorListResponse> bindErrorHandle(BindException ex) {
log.error("검증 예외 발생 및 처리 = {}", ex.getMessage());
return badRequest(ex.getBindingResult());
}
}
@Valid의 유효성 검사에 실패하면, MethodArgumentNotValidException이 발생한다. 이 예외를 잡아주는 전역 ControllerAdvice를 구현하는 방법이다.
BindException은 MethodArgumentNotValidException의 부모 클래스로, @ModelAttribute의 유효성 검증이 실패했을 경우 발생한다.
즉, MethodArgumentNotValidException = @RequestBody / BindException = @ModelAttribute 이다.
@ModelAttribute는 multipart/form-data를 전송 받을때 유효성 검증을 위해서 필요하므로, 확장성을 고려하여 BindException으로 모두 처리해 주는게 바람직 할 것이다.
@PostMapping("/join")
public ResponseEntity<?> join(@Valid @RequestBody UserDto userDto) {
// 비즈니스 로직
}
이렇게 하면, 유효성 처리 로직은 핸들러 메소드 단에서 관리를 할 필요 없어진다.