Springboot MVC 파헤치기(11) Validation 동작과정
Validation은 @ModelAttribute 혹은 @RequestBody의 ArgumentResolver가 동작한 후, 검증을 진행합니다. 밑에서 알아보겠습니다.
ArgumentResolver의 자세한 동작과정은 아래 링크에서 확인 부탁드립니다.
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