Springboot MVC 파헤치기(11) Validation 동작과정
Validation은 @ModelAttribute 혹은 @RequestBody의 ArgumentResolver가 동작한 후, 검증을 진행합니다. 밑에서 알아보겠습니다.
ArgumentResolver의 자세한 동작과정은 아래 링크에서 확인 부탁드립니다.
Springboot MVC 파헤치기(8) @ModelAttribute, @RequestParam, @PathVariable @ResponseBody @RequestBody 동작 과정
@ModelAttribute: 클라이언트가 전달하는 값을 객체로 맵핑해주는 역할을 합니다. HTTP Body 데이터 혹은 HTTP 파라미터를 주입합니다. 이때 생성자나 Setter로 주입하기 때문에 Setter혹은 생성자가 있어야
dingdingmin-back-end-developer.tistory.com
1. RequestMappingHandlerAdapter
invocableMethod.invokeAndHandle(webRequest, mavContainer); if (asyncManager.isConcurrentHandlingStarted()) { return null; }
@RequestMapping으로 찾은 핸들러(Controller)를 Adapter를 이용해서 실행합니다. 해당 invokeAndHandle은 ServletInvocableHandlerMethod의 메서드입니다.
2. ServletInvocableHandlerMethod
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { logger.trace("Arguments: " + Arrays.toString(args)); } return doInvoke(args); }
해당 요청을 실행하기 위해 invokeForRequest를 실행합니다. getMethodArgumentValues를 통해서 결과 값을 반환받기 위해서 ArgumentResolver를 동작을 준비합니다.
try { args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); } catch (Exception ex) { // Leave stack trace for later, exception may actually be resolved and handled... if (logger.isDebugEnabled()) { String exMsg = ex.getMessage(); if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) { logger.debug(formatArgumentError(parameter, exMsg)); } } throw ex; }
resolvers.resolveArgument()를 통해서 ArgumentResolver가 동작하게 됩니다. 여기서 데이터 바인딩 Factory 가 넘어가게 되고, 여기에는 BeanValidation과 Validator가 담겨있습니다.
resolvers는 HandlerMethodArgumentResolverComposite로서 모든 ArgumentResolver를 가지고 있고, Resolver를 수행할 수 없는 요청이라면 예외를 발생시킵니다.
아래의 경우에 따라 다른 ArgumentResolver가 동작합니다.
@ModelAttribute -> ModelAttributeMethodProcessor
@RequestBody -> RequestResponseBodyMethodProcessor
@ModelAttribute의 과정을 예로 보겠습니다.
3. ModelAttributeMethodProcessor
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { Object attribute = null; BindingResult bindingResult = null; if (mavContainer.containsAttribute(name)) { attribute = mavContainer.getModel().get(name); } else { // Create attribute instance try { attribute = createAttribute(name, parameter, binderFactory, webRequest); } catch (BindException ex) { if (isBindExceptionRequired(parameter)) { // No BindingResult parameter -> fail with BindException throw ex; } // Otherwise, expose null/empty value and associated BindingResult if (parameter.getParameterType() == Optional.class) { attribute = Optional.empty(); } else { attribute = ex.getTarget(); } bindingResult = ex.getBindingResult(); } } if (bindingResult == null) { // Bean property binding and validation; // skipped in case of binding failure on construction. WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); if (binder.getTarget() != null) { if (!mavContainer.isBindingDisabled(name)) { bindRequestParameters(binder, webRequest); } validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new BindException(binder.getBindingResult()); } } // Value type adaptation, also covering java.util.Optional if (!parameter.getParameterType().isInstance(attribute)) { attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter); } bindingResult = binder.getBindingResult(); } // Add resolved attribute and BindingResult at the end of the model Map<String, Object> bindingResultModel = bindingResult.getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); return attribute; } protected Object createAttribute(String attributeName, MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest webRequest) throws Exception { MethodParameter nestedParameter = parameter.nestedIfOptional(); Class<?> clazz = nestedParameter.getNestedParameterType(); Constructor<?> ctor = BeanUtils.getResolvableConstructor(clazz); Object attribute = constructAttribute(ctor, attributeName, parameter, binderFactory, webRequest); if (parameter != nestedParameter) { attribute = Optional.of(attribute); } return attribute; }
createAttribute를 통해서 binding을 시도합니다. 여기서 잡는 예외는
1. 매개변수가 있는 생성자가 있는 경우
2. 기본 생성자 + setter가 있는 경우
1, 2번 중 하나라도 있으면 예외를 발생시키지 않지만 없을 경우 처리할 수 없으므로 예외를 발생시킵니다.
if (bindingResult == null) { // Bean property binding and validation; // skipped in case of binding failure on construction. WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); if (binder.getTarget() != null) { if (!mavContainer.isBindingDisabled(name)) { bindRequestParameters(binder, webRequest); } validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new BindException(binder.getBindingResult()); } } // Value type adaptation, also covering java.util.Optional if (!parameter.getParameterType().isInstance(attribute)) { attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter); } bindingResult = binder.getBindingResult(); }
여기서 binderFactory가 동작하여, BeanValidation 혹은 Validator로 설정해놓은 유효성 검증을 수행하게 됩니다.
만약 유효성 검증에서 실패하여 예외가 발생할 경우 위에서 볼 수 있듯이 BindingResult에 담기게 됩니다.
정리하기
유효성 검증 Validation은 ArgumentResolver가 동작하는 과정에서 수행되게 되며, @ModelAttribte, @RequestBody모두 다른 ArgumentResolver가 동작하게 되지만, binding과정에서 발생하는 예외는 BindingResult에 담습니다.
'SpringBoot > spring mvc' 카테고리의 다른 글
Springboot MVC 파헤치기(13) Collection Validation 적용 (0) | 2022.05.19 |
---|---|
Springboot MVC 파헤치기(12) ControllerAdvice 알아보기 (0) | 2022.05.17 |
Springboot MVC 파헤치기(10) Validation 유효성 검증 (0) | 2022.04.20 |
Springboot MVC 파헤치기(9) ViewResolver, HttpMessageConverters (0) | 2022.03.16 |
Springboot MVC 파헤치기(8) @ModelAttribute, @RequestParam, @PathVariable @ResponseBody @RequestBody 동작 과정 (0) | 2022.03.15 |
댓글
이 글 공유하기
다른 글
-
Springboot MVC 파헤치기(13) Collection Validation 적용
Springboot MVC 파헤치기(13) Collection Validation 적용
2022.05.19 -
Springboot MVC 파헤치기(12) ControllerAdvice 알아보기
Springboot MVC 파헤치기(12) ControllerAdvice 알아보기
2022.05.17 -
Springboot MVC 파헤치기(10) Validation 유효성 검증
Springboot MVC 파헤치기(10) Validation 유효성 검증
2022.04.20 -
Springboot MVC 파헤치기(9) ViewResolver, HttpMessageConverters
Springboot MVC 파헤치기(9) ViewResolver, HttpMessageConverters
2022.03.16
댓글을 사용할 수 없습니다.