Spring validation을 사용할 경우 다음과 같이 bindingResult를 받아 처리할 수 있다.
@PostMapping
public ResponseEntity<?> testMethod(
@Valid FormRequest formRequest,
BindingResult bindingResult) {
...
}
그리고 validation을 수행해야하는 api 요청이 하나 둘씩 추가되면 추가된만큼 bindingResult를 처리하는 코드 또한 추가로 작성될 것이다. 이처럼 boilerplate 코드가 발생하게되면서 비효율적인 코드가 되는 것이다.
AOP
annotation 정의
annotation을 추가한 method만 bindingResult를 처리할 수 있게 annotation을 정의한다.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BindingResultHandler {
String message();
}
Aspect 정의
@Slf4j
@Component
@Aspect
public class BindingResultHandlerAspect {
@Pointcut("@annotation(BindingResultHandler)")
private void bindingResultPointcut() {}
@Around("bindingResultPointcut()")
public Object handle(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature signature = (MethodSignature) pjp.getSignature();
BindingResult bindingResult = getBindingResult(signature, pjp.getArgs());
Method method = signature.getMethod();
//현재 method에 추가한 annotation (annotation에 지정한 message를 가져오기위함)
BindingResultHandler annotation = method.getAnnotation(BindingResultHandler.class);
//bindingResult 공통 처리 부분 (개인에 따라 수정)
if (bindingResult.hasErrors()) {
log.error("bindingResult: {}", bindingResult);
throw new ValidatedException(
ErrorCode.INVALID_REQUEST,
ExceptionResponse.fromBindingResult(annotation.message(), bindingResult));
}
return pjp.proceed();
}
/**
* 현재 method의 parameter 중 bindingResult를 찾아 반환
*/
private BindingResult getBindingResult(MethodSignature signature, Object[] arguments) {
String[] parameterNames = signature.getParameterNames();
for (int i = 0; i < parameterNames.length; i++) {
if (parameterNames[i].equals("bindingResult")) {
return (BindingResult) arguments[i];
}
}
throw new NoSuchParameterException("bindingResult");
}
}
적용
@BindingResultHandler(message = "validation error during test")
@PostMapping
public ResponseEntity<?> testMethod(
@Valid FormRequest formRequest,
BindingResult bindingResult) {
...
}
이로써 validation error가 발생하면 일관성있게 ValidatedException
을 throw하게 될 것이고 ExceptionHandler(ControllerAdvice)를 통해 최종적으로 처리되게될 것이다.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = ValidatedException.class)
public ResponseEntity<ExceptionResponse> resolveException(ValidatedException e) {
return ResponseEntity.status(e.getErrorCode().getStatus()).body(e.getExceptionResponse());
}
}