본문 바로가기

Study/개발일지

[백엔드온라인TIL] java 학습 26일차

1. @Valid와 @Validated


[ @Valid를 이용한 유효성 검증 ]

@Valid의 개념 및 사용법

@Valid는 JSR-303 표준 스펙(자바 진영 스펙)으로써 빈 검증기(Bean Validator)를 이용해 객체의 제약 조건을 검증하도록 지시하는 어노테이션이다. JSR 표준의 빈 검증 기술의 특징은 객체의 필드에 달린 어노테이션으로 편리하게 검증을 한다는 것이다.

Spring에서는 일종의 어댑터인 LocalValidatorFactoryBean가 제약 조건 검증을 처리한다. 이를 이용하려면 LocalValidatorFactoryBean을 빈으로 등록해야 하는데, SpringBoot에서는 아래의 의존성만 추가해주면 해당 기능들이 자동 설정된다.

// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-validation'

 

 

예를 들어 @NotNull 어노테이션은 필드의 값이 null이 아님을 확인하도록 하며 @Min은 해당 값의 최솟값을 지정할 수 있도록 한다.

@Getter
@RequiredArgsConstructor
public class AddUserRequest {

	@Email
	private final String email;

	@NotBlank
	private final String pw;

	@NotNull
	private final UserRole userRole;

	@Min(12)
	private final int age;

}

 

 

그리고 다음과 같이 컨트롤러의 메소드에 @Valid를 붙여주면 유효성 검증이 진행된다.

@PostMapping("/user/add") 
public ResponseEntity<Void> addUser(@RequestBody @Valid AddUserRequest addUserRequest) {
      ...
}

 

 

@Valid의 동작 원리

모든 요청은 프론트 컨트롤러인 디스패처 서블릿을 통해 컨트롤러로 전달된다. 전달 과정에서는 컨트롤러 메소드의 객체를 만들어주는 ArgumentResolver가 동작하는데, @Valid 역시 ArgumentResolver에 의해 처리가 된다.

대표적으로 @RequestBody는 Json 메세지를 객체로 변환해주는 작업이 ArgumentResolver의 구현체인  

RequestResponseBodyMethodProcessor가 처리하며, 이 내부에서 @Valid로 시작하는 어노테이션이 있을 경우에 유효성 검사를 진행한다. (이러한 이유로 @Valid가 아니라 커스톰 어노테이션인 @ValidMangKyu여도 동작한다.) 만약 @ModelAttribute를 사용중이라면 ModelAttributeMethodProcessor에 의해 @Valid가 처리된다.

 

그리고 검증에 오류가 있다면 MethodArgumentNotValidException 예외가 발생하게 되고, 디스패처 서블릿에 기본으로 등록된 예외 리졸버(Exception Resolver)인 DefaultHandlerExceptionResolver에 의해 400 BadRequest 에러가 발생한다.

org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public org.springframework.http.ResponseEntity<java.lang.Void> com.example.testing.validator.UserController.addUser(com.example.testing.validator.AddUserRequest) with 2 errors: [Field error in object 'addUserRequest' on field 'email': rejected value [asdfad]; codes [Email.addUserRequest.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [addUserRequest.email,email]; arguments []; default message [email],[Ljavax.validation.constraints.Pattern$Flag;@18c5ad90,.*]; default message [올바른 형식의 이메일 주소여야 합니다]] [Field error in object 'addUserRequest' on field 'age': rejected value [5]; codes [Min.addUserRequest.age,Min.age,Min.int,Min]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [addUserRequest.age,age]; arguments []; default message [age],12]; default message [12 이상이어야 합니다]] 
	at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:141) ~[spring-webmvc-5.3.15.jar:5.3.15]
	at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122) ~[spring-web-5.3.15.jar:5.3.15]

 

 

이러한 이유로 @Valid는 기본적으로 컨트롤러에서만 동작하며 기본적으로 다른 계층에서는 검증이 되지 않는다. 다른 계층에서 파라미터를 검증하기 위해서는 @Validated와 결합되어야 하는데, 아래에서 @Validated와 함께 자세히 살펴보도록 하자.

 

 

 

 

[ @Validated를 이용한 유효성 검증 ]

@Validated의 개념 및 사용법

입력 파라미터의 유효성 검증은 컨트롤러에서 최대한 처리하고 넘겨주는 것이 좋다. 하지만 개발을 하다보면 불가피하게 다른 곳에서 파라미터를 검증해야 할 수 있다. Spring에서는 이를 위해 AOP 기반으로 메소드의 요청을 가로채서 유효성 검증을 진행해주는 @Validated를 제공하고 있다. @Validated는 JSR 표준 기술이 아니며 Spring 프레임워크에서 제공하는 어노테이션 및 기능이다.

다음과 같이 클래스에 @Validated를 붙여주고, 유효성을 검증할 메소드의 파라미터에 @Valid를 붙여주면 유효성 검증이 진행된다.

@Service
@Validated
public class UserService {

	public void addUser(@Valid AddUserRequest addUserRequest) {
		...
	}
}

 

 

유효성 검증에 실패하면 에러가 발생하는데, 로그를 확인해보면 이전의 MethodArgumentNotValidException 예외가 아닌 ConstraintViolationException 예외가 발생했다. 이는 앞서 잠깐 설명한대로 동작 원리가 다르기 때문인데, 자세히 살펴보도록 하자.

javax.validation.ConstraintViolationException: getQuizList.category: 널이어서는 안됩니다 
    at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:120) ~[spring-context-5.3.14.jar:5.3.14] 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.14.jar:5.3.14] 
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.14.jar:5.3.14] 
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698) ~[spring-aop-5.3.14.jar:5.3.14] 
    at com.mangkyu.employment.interview.app.quiz.controller.QuizController$$EnhancerBySpringCGLIB$$b23fe1de.getQuizList(<generated>) ~[main/:na] 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~

 

 

 

EC2 인스턴스 중지(Stop), 절전(Hibernate), 종료(Terminate)의 차이

 

EC2 중지(Stop)

실행중인 EC2 인스턴스를 중지(Stop) 시키면, 인스턴스는 정상적인 종료 프로세스를 수행한 다음 중지 상태로 전환됩니다. 인스턴스를 중지(Stop)해도 연결된 Amazon EBS 볼륨은 그대로 유지됩니다.

 

EC2 종료(Terminate)

반면 인스턴스를 종료(Terminate)하면 중지와 마찬가지로 정상적인 종료 프로세스를 수행한 다음 종료 상태로 전환되지만, 볼륨의 deleteOnTermination 특성이 False로 설정되어있지 않다면, 연결되어 있는 Amazon EBS 볼륨 또한 함께 삭제됩니다.

 

EC2 절전(Hibernate)

절전(Hibernate-사전적 의미로 동면을 뜻합니다)의 경우 중지(Stop)과 비슷한 개념입니다. 하지만 EC2 인스턴스의 상태는 실행(Running)과 중지됨(Stopped) 사이에 별도로 중지중인(Stopping) 상태가 있습니다. 즉 중지중인(Stopping)은 Suspend-to-Disk 상태와 같은 개념입니다.

 

인스턴스를 중지(Stop)하거나 절전(Hibernate)해도 중지중(Stopping).... 을 거쳐 최종적으로 중지됨(Stopped) 상태로 전환되지만, 절전(Hibernate)은 Stopped로 표시되어도 실제로는 중지중인(Stopping) 상태가 유지되고 있는 차이가 있습니다. 

 

Hibernate 상태의 EC2 인스턴스는 Amazon EBS 루트 볼륨 메모리상의 컨텐츠 저장 정보를 날리지 않고 유지합니다. 따라서 Stopping 상태의 인스턴스를 다시 실행(Running) 상태로 전환했을 때, Hibernate 이전에 실행중이던 프로세스 정보를 빠르게 다시 불러올 수 있습니다. 

 

인스턴스가 중지된 상태(Stopped)면 사용 요금이 부과되지 않습니다. 절전(Hibernate)은 중지(Stop)와 마찬가지로 Stopping 상태를 거쳐 Stopped 상태가 되지만, 중지중인(Stopping)인 상태로 유지되는 개념이기 때문에 인스턴스 중지(Stopped)와는 달리 사용요금이 부과됩니다. 

 

728x90