0. 이 글을 쓰게 된 이유
개발을 할 때, 데이터의 유효성 검증은 굉장히 중요하고 애플리케이션 전체에서 발생한다.
클라이언트에서 넘어온 데이터가 유효한지, 서비스 레이어에서 파라미터 값이 제대로 된 값인지 등 여러 곳에서 데이터 검증은 필수다.
하지만 유효성 검증에는 이런 특징이 있는 만큼 문제점이 존재한다.
문제점
- 애플리케이션 전체에 분산되어 있다.
- 코드 중복이 심하다.
- 비즈니스 로직에 섞여있어 복잡하다.
이러한 문제점들 때문에 유효성 검증은 유지/보수가 어렵다.
해결책
Java에서도 이러한 문제를 알고 Bean Validation이라는 유효성 검증 프레임워크를 제공하고 있다(JSR-303 표준 스펙). Bean Validation 프레임 워크는 위에서 말한 문제들을 해결하기 위해 다양한 제약을 모델(domain model)에 어노테이션으로 해결 할 수 있게 한다. 이 제약을 유효성 검증이 필요한 객체에 직접 어노테이션으로 정의하는 방법으로 기존 유효성 검사 로직의 문제를 해결한다. 제약은 구현체를 따로 놓아 어노테이션에 매핑 될 수 있게 한다.
1. @Valid과 @Validated
1.1 @Valid
@Valid는 Bean Validator를 통해 객체의 데이터 유효성 검증을 지시하는 어노테이션이다. 하지만 이는 Bean Validation의 표준 명세일 뿐 구현체를 가지고 있지 않는다. 그래서 구현체를 따로 연결해서 사용해야 한다.
Spring에서는 이 구현체를 Hibernate Validator를 사용한다. Hibernate Validator는 Bean Validator의 모든 어노테이션을 구현하고 있다고 보면 된다.
Bean Validator 어노테이션과 Hibernate Validator 구현체와의 연결은 spring의 어플리케이션 컨텍스트가 뜰 때, ConstraintHelper에서 연결해주는 것 같다.(뜰 때인지 호출될 때인지 잘 모르겠음)
1.1.1 @Valid의 동작 과정
알다싶이 Client에서 요청이 들어오면 Dispatcher Servlet에서 요청에 맞는 컨트롤러에 요청을 전달한다. 이때, 전달 과정에서 컨트롤러 메소드의 객체를 전달해주는 HandlerMethodArgumentResolver 동작하는데, @Valid 역시 HandlerMethodArgumentResolver 의해 처리가 된다.(ArgumentResolver가 하는 일로 대표적인 것은 @RequestBody가 붙은 데이터를 Json 포멧으로 변경해준다)
HandlerMethodArgumentResolver의 resolveArgument 메소드에서 @Valid로 시작하는 어노테이션이 붙어있으면 valid를 진행하는 식으로 동작한다. 즉, aop 방식이라기 보다는 dispathcher servlet에서 검사를 한번 해준다는게 맞다.
다음과 같이 유효성 검증을 할 파라미터에 @Valid를 붙여줘야 진행된다.
@PostMapping("/")
public String add(@Valid Request request) {
...
}
그리고 오류가 발생하면 MethodArgumentNotValidException이 발생하고, 디스패치 서블릿에 기본으로 등록된 Exception Resolver인 DefaultHandlerExceptionResolver에 의해 400 에러가 발생한다.
spring에서 @Valid는 기본적으로 컨트롤러에서만 동작하도록 설계되어있고 다른 계층에서는 검증이 되지 않는다.
다른 계층에서 검증하기 위해서는 @Validated와 결합되어야하는데, 이제 살펴보도록 하자.
1.1.2 @Validated의 동작 과정
어플리케이션 개발을 하다보면 Controller 뿐만 아니라 다른 계층에서도 데이터 유효성 검증을 해야할 필요가 있다. Spring에서는 이를 위해 AOP 기반으로 메소드 요청을 가로채서 유효성 검증을 해주는 @Validated를 제공하고 있다. 즉, @Validated는 JSR 표준 기술이 아니며 Spring에서 제공하는 AOP 기반 기능이다.
사용법은 다음과 같이 유효성 검증이 필요한 클래스에 @Validated를 붙이고, 유효성 검증을 할 파라미터에 @Valid를 붙여주면 진행된다.
@Service
@Validated
public class Service {
public void add(@Valid Request request) {
...
}
}
오류가 발생하면 @Valid와 다르게 ConstraintViolationException이 발생한다.
정리하자면 @Validated를 클래스에 선언하면 해당 클래스에 유효성 검증을 위한 인터셉터인 MethodValidationInterceptor가 등록된다. 그리고 해당 클래스의 메소드가 호출 될 때 AOP가 포인트컷으로 확인을 하고 요청을 중간에 가로채어 유효성 검증을 진행하는 코드가 들어간다. @Validated는 스프링의 기능이기 때문에 스프링 빈이라면 모두 유효성 검증이 가능하다. 그리고 MethodValidationInterceptor에 Validator이 의존성 주입으로 들어가므로 @Valid와 똑같이 Hibernate Validator를 사용한다. 즉, Bean Validation을 그대로 이용할 수 있는 것이다.
2. 정리
유효성 검증을 할 떄에는 Bean Validation을 이용하면 편하다. java에서 제공하는 어노테이션을 잘 확인하면서 사용하면 어플리케이션 어디서든지 유효성 검증을 효율적으로 할 수 있다.
java validation 공식문서 : https://javaee.github.io/javaee-spec/javadocs/javax/validation/constraints/package-summary.html
'Spring' 카테고리의 다른 글
[Spring] ViewResolver 코드 파보기 (0) | 2023.05.04 |
---|---|
[Spring] 필터(Filter)와 인터셉터(Interceptor) (0) | 2023.04.19 |
[Spring] @(Rest)ControllerAdvice 활용 (0) | 2023.04.13 |
[Spring] Spring Security에 대해서 알아보자(로그인 인증 구조) (0) | 2022.09.28 |
[Spring] Spring Security에 대해서 알아보자(동작 과정편) (0) | 2022.09.28 |