@ModelAttribute를 생략할 수 있는 이유

0_0_yoon·2022년 7월 2일
4
post-thumbnail

@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));
	}
profile
꾸준하게 쌓아가자

3개의 댓글

comment-user-thumbnail
2022년 7월 3일

이거슨... 쩌는군요....👍👍👍

1개의 답글
comment-user-thumbnail
2024년 6월 28일

좋은 글 잘 읽었습니다!

답글 달기