@ModelAttribute
는 요청 파라미터를 객체로 바인딩할때 사용된다. 그리고 생략이 가능하다 는 특징이 있다.
어떻게 생략이 가능한걸까? spring 내부를 뜯어보자.
먼저 빈이 등록되는 과정중에 afterPropertiesSet() 메서드를 통해 ArgumentResolver들이 등록된다.
이때 RequestMappingHandlerAdapter가 getDefaultArgumentResolvers() 메서드를 실행해서 ArgumentResolver들을 초기화한다.
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);
// 중간 코드 생략
// Catch-all
resolvers.add(new PrincipalMethodArgumentResolver());
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
return resolvers;
}
위의 ServletModelAttributeMethodProcessor 가 바로 @ModelAttribute
를 사용할때 사용되는 ArgumentResolver 이다. 이때 생성자 파라미터로 true 가 들어가는것을 눈여겨보자. 이 값은 annotationNotRequired의 값으로 전달된다.
//ServletModelAttributeMethodProcessor
public ServletModelAttributeMethodProcessor(boolean annotationNotRequired) {
super(annotationNotRequired);
}
//ModelAttributeMethodProcessor
public ModelAttributeMethodProcessor(boolean annotationNotRequired) {
this.annotationNotRequired = annotationNotRequired;
}
자 이제 웹 요청이 왔을때 간략하게 흐름을 생각해보자. DispatcherServlet은 요청을 받고 그 요청을 처리할 수 있는 HandlerAdapter를 찾은뒤 handle()메서드를 실행한다. (그전의 과정들은 설명을 생략하겠습니다)
이때 위에서 등록된 ArgumentResolver들을 하나씩 순회하며 supportsParameter()를 실행한다.
//HandlerMethodArgumentResolverComposite
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
ServletModelAttributeMethodProcessor의 경우 파라미터에 @ModelAttribute
가 붙어있거나 annotationNotRequired의 값과 Handler의 파라미터 타입이 SimpleProperty가 아니라면 ServletModelAttributeMethodProcessor가 선택되고 바인딩을 진행하게 된다.
@Override
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}
즉 @ModelAttribute
를 붙이지 않더라도 파라미터가 SimpleValueType이 아니라면 ServletModelAttributeMethodProcessor가 동작하게된다.
참고로 SimpleValueType은 원시타입, 이를 감싸는 wrapper 타입, ENUM, String, CharSequence, Number, Date, Temporal, URI, URL, Locale, Class 를 말한다.
public static boolean isSimpleValueType(Class<?> type) {
return (Void.class != type && void.class != type &&
(ClassUtils.isPrimitiveOrWrapper(type) ||
Enum.class.isAssignableFrom(type) ||
CharSequence.class.isAssignableFrom(type) ||
Number.class.isAssignableFrom(type) ||
Date.class.isAssignableFrom(type) ||
Temporal.class.isAssignableFrom(type) ||
URI.class == type ||
URL.class == type ||
Locale.class == type ||
Class.class == type));
}
이거슨... 쩌는군요....👍👍👍