미리 보기 페이지를 테스트 하는 도중 의문이 생겼다.
@Valid 로 DTO 객체의 유효성 검증을 하고 BindingResult 를 통해 에러를 리턴하는 이유는 뭘까?
우선 선언은 각각 다음과 같다.
import org.springframework.validation.BindingResult;
import javax.validation.Valid;
Spring에서 제공하는 BindingResult 와 달리 Valid는 자바에서 제공한다.
BindingResult :
스프링 공식문서의 설명에 따르면, 바인딩 결과를 나타내는 것으로,
Errors 오류 등록인터페이스를 확장하고, @validator 를 적용할 수 있도록 한다고 한다.
* 즉 오류를 확장하는 목적이다.
@Valid : ( spring boot 2.3 이상일 경우 validation 의존성을 따로 추가해야한다. )
자바 표준 검증 애노테이션이다. @Validated와 동일한 역할이되, @Validated는 그룹을 지정해서 특정 그룹에 해당하는 유효성 검증을 따로 체크 할 수 있다.
또한 @Valid 는 단순히 어노테이션만 붙였다고 해서 적용되지 않으며 후행하는 데이터 바인딩 객체에도 유효성 검사를 위한,
어노테이션 선언을 해야한다.
EX :
@NotNull , NotEmpty 등으로 객체 바인딩시에 어노테이션이 검증하는 값이 아니라면 오류를 뱉어낸다.
여기서는 억지로 @Email 어노테이션을 걸고, 유효성 검증이 어떻게 이루어지는지만 확인한다.
덧붙여서 테스트의 요청값은 성공으로 리턴이 오던 요청 데이터들이다.
실제로 위의 두 설정을 걸고 API 호출 테스트를 할 경우 사용자는 다음과 같은 리턴을 받게 된다.
@Valid 설정 + BindingResult를 통한 에러 바인딩
import javax.validation.Valid;
import org.springframework.validation.BindingResult;
@PostMapping(value = "/getPreview")
@ApiOperation(value ="[PRG-API-STD-PROD-021] - 페이지 미리보기", notes = "페이지 미리보기")
public Object getPreview(@Valid @RequestBody PagePreviewDto.Request reqDto, BindingResult bindingResult) {
validationExecutor.validateCheck(bindingResult);
PagePreviewDto.Response resDto = previewService.getPagePreview(reqDto);
resDto.setResultCd(RESULT_SUCCESS);
resDto.setErrCd(SUCCESS_CD);
resDto.setErrMsg(SUCCESS_MSG);
return resDto;
}
요청 리턴 :
서버 응답 로그 :
uidMoudle 항목에 값은 있지만 이메일 형식이 아니므로 에러를 리턴하는 것을 볼 수 있다.
@Valid 설정만 사용 할 경우
@PostMapping(value = "/getPreview")
@ApiOperation(value ="[PRG-API-STD-PROD-021] - 페이지 미리보기", notes = "페이지 미리보기")
public Object getPreview(@Valid @RequestBody PagePreviewDto.Request reqDto) {
// validationExecutor.validateCheck(bindingResult);
PagePreviewDto.Response resDto = previewService.getPagePreview(reqDto);
resDto.setResultCd(RESULT_SUCCESS);
resDto.setErrCd(SUCCESS_CD);
resDto.setErrMsg(SUCCESS_MSG);
return resDto;
}
요청 리턴 :
내부 설정인 익셉션 핸들러를 통해 리턴 값을 리턴하고 있음을 알 수 있다.
서버 응답 로그 :
BadRequestException이 발생한다.
* Exception Handler 를 통한 리턴이 아니라면 클라이언트는 400 BadRequestException을 리턴받게 된다.
2023-06-29 14:43:45.645 [http-nio-28080-exec-1] ERROR c.f.u.ICNLogUtility - BadRequestException(Controller) : Validation failed for argument [0] in public java.lang.Object cals.cms.api.studio.product.page.page.controller.PageController.getPreview(cals.cms.api.studio.product.page.page.dto.PagePreviewDto$Request) with 2 errors: [Field error in object 'request' on field 'pageId': rejected value []; codes [NotEmpty.request.pageId,NotEmpty.pageId,NotEmpty.java.lang.String,NotEmpty]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [request.pageId,pageId]; arguments []; default message [pageId]]; default message [비어 있을 수 없습니다]] [Field error in object 'request' on field 'uidModule': rejected value [P15MA20821]; codes [Email.request.uidModule,Email.uidModule,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [request.uidModule,uidModule]; arguments []; default message [uidModule],[Ljavax.validation.constraints.Pattern$Flag;@644de77d,.*]; default message [올바른 형식의 이메일 주소여야 합니다]]
org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public java.lang.Object cals.cms.api.studio.product.page.page.controller.PageController.getPreview(cals.cms.api.studio.product.page.page.dto.PagePreviewDto$Request) with 2 errors: [Field error in object 'request' on field 'pageId': rejected value []; codes [NotEmpty.request.pageId,NotEmpty.pageId,NotEmpty.java.lang.String,NotEmpty]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [request.pageId,pageId]; arguments []; default message [pageId]]; default message [비어 있을 수 없습니다]] [Field error in object 'request' on field 'uidModule': rejected value [P15MA20821]; codes [Email.request.uidModule,Email.uidModule,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [request.uidModule,uidModule]; arguments []; default message [uidModule],[Ljavax.validation.constraints.Pattern$Flag;@644de77d,.*]; default message [올바른 형식의 이메일 주소여야 합니다]]
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:139)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:170)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1063)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:681)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:228)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:113)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:327)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:115)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:81)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:121)
BindingResult 설정만 사용 할 경우
@PostMapping(value = "/getPreview")
@ApiOperation(value ="[PRG-API-STD-PROD-021] - 페이지 미리보기", notes = "페이지 미리보기")
public Object getPreview(@RequestBody PagePreviewDto.Request reqDto, BindingResult bindingResult) {
validationExecutor.validateCheck(bindingResult);
PagePreviewDto.Response resDto = previewService.getPagePreview(reqDto);
resDto.setResultCd(RESULT_SUCCESS);
resDto.setErrCd(SUCCESS_CD);
resDto.setErrMsg(SUCCESS_MSG);
return resDto;
}
요청 리턴:
당연하지만 @Valid 를 쓰지않았으므로 바인딩 객체에 대한 어노테이션을 통한 검증도 이루어지지 않았다.
검증이 없었으니 에러가 안났고 BindingResult 에서 오류 내용을 확인 할 수 없다.
따라서, 요청을 확인하지 않고 서버는 응답값을 리턴한다.
@Valid 는 컨트롤러에서만 작동한다?
😲 ArgumentResolver에서 처리하는데 이는 컨트롤러 메소드 객체 생성시 동작한다. 따라서 컨트롤러에서만 동작한다.
컨트롤러에서만 동작하는 것 외에 서비스단에서 검증하고자 한다면 @Validated를 통해 처리해야하는데,
보통 컨트롤러에서 검증 이후 서비스 로직중에 유효성 검증에 실패하는 경우는 잘못된 로직일 가능성이 크므로, 사용 할 일이 많지 않다.
출처 :
BindingResult (Spring Framework 6.0.10 API)
Return the wrapped target object, which may be a bean, an object with public fields, a Map - depending on the concrete binding strategy.
docs.spring.io
https://bluemint.tistory.com/62
spring boot- validation (@Valid) 스프링에서 밸리데이션 처리하기
spring boot에서 제공하는 기본 메소드 사용하기 1. DTO 에 원하는 변수 위에다가 @ @Email private String email; @Pattern(regexp = "^\\d{2,3}-\\d{3,4}-\\d{4}$",message = "폰 번호 오류") private String phoneNumber; @Size(min=6, max=
bluemint.tistory.com
https://mangkyu.tistory.com/174
[Spring] @Valid와 @Validated를 이용한 유효성 검증의 동작 원리 및 사용법 예시 - (1/2)
Spring으로 개발을 하다 보면 DTO 또는 객체를 검증해야 하는 경우가 있습니다. 이를 별도의 검증 클래스로 만들어 사용할 수 있지만 간단한 검증의 경우에는 JSR 표준을 이용해 간결하게 처리할 수
mangkyu.tistory.com
잘못된 내용 지적 부탁드려요.
'JAVA > SPRING' 카테고리의 다른 글
[Spring] Spring Security setting과 에러 사항 수정 과정 기록과 OAuth2 (0) | 2023.08.10 |
---|---|
[Spring] Spring / Spring boot 특징 간단 정리. (0) | 2023.01.11 |
[NAVER maps 길 찾기] springboot + webClient 로 API 호출 (0) | 2022.09.02 |
springBoot / mapstruct (0) | 2022.06.08 |
spring boot / H2 DB 세팅 (0) | 2022.05.29 |