@ModelAttribute

tokkaiiii·2025년 4월 29일

spring-mvc

목록 보기
9/27

요청시작
DistpatcherServlet 에서 doDispatch 호출
적절한 HandlerAdapter 얻어야 함

HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());

get 메소드로 호출
RequestMappingHandlerAdapter 받음
실제 handler 호출

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
 mav = this.invokeHandlerMethod(request, response, handlerMethod);

invokeHandlerMethod 로직이 많지만 modelAttribute 관련로직 살펴볼 것

WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod);
    ModelFactory modelFactory = this.getModelFactory(handlerMethod, binderFactory);
    ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod);

handlerMethod 와 binderFactory 를 넣어서 modelFactory 를 구한다
getModelFactory 자세히 보면

 Set<Method> methods = (Set)this.modelAttributeCache.get(handlerType);

handlerType은 ModelAttributeController 다
조건을 통해 method를 뽑아낸다

 methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);

메소드 얻어서 저장

e, MODEL_ATTRIBUTE_METHODS);

for 문을 돌면서 modelAttribute 메소드에 넣는다

 for(Method method : methods) {
     Object bean = handlerMethod.getBean();
     attrMethods.add(this.createModelAttributeMethod(binderFactory, bean, method));
   }

   return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
private InvocableHandlerMethod createModelAttributeMethod(WebDataBinderFactory factory, Object bean, Method method) {
   InvocableHandlerMethod attrMethod = new InvocableHandlerMethod(bean, method);
   if (this.argumentResolvers != null) {
     attrMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
   }

   attrMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
   attrMethod.setDataBinderFactory(factory);
   return attrMethod;
 }

HandlerMethod 엔 bean 과 method를 가지고 있어 bean 에 해당하는 메소드를 호출한다
HandlerMethod 를 상속한 클래스가 InvocableHandlerMethod 다 이건 reflaction 기술을 사용해서 handler 메소드를 호출한다
이 클래스는 servlet(웹) 전용은 아니다 InvocableHandlerMethod 이걸 상속해서 만든 ServletInvocableHandlerMethod 클래스는 웹전용
bean 타입과 메소드 정보만 있으면 InvocalbleHandlerMethod로 타겟 메소드를 호출할 수 있다

 InvocableHandlerMethod attrMethod = new InvocableHandlerMethod(bean, method);

그리고 메소드에 파라미터가 있다면 argumentResolver 찾아서 set 한다

 if (this.argumentResolvers != null) {
     attrMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
   }

결국 bean 정보, 메소드, 파라미터 세 속성을 가지고 attrMethod를 뽑아낸다
이렇게 해서 ModelFactory를 만든다

return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
 ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod);

handler 메소드 호출
ServletInvocableHandlerMethod 에는 returnValuesHandler도 넣어야 한다
웹전용이기에 응답부분도 처리 그래서 4개 처리

 ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod);
   if (this.argumentResolvers != null) {
     invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
   }

   if (this.returnValueHandlers != null) {
     invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
   }

모델을 담을 수 있는 컨테이너 생성

 ModelAndViewContainer mavContainer = new ModelAndViewContainer();

initModel 하면 모델을 호출한다

 modelFactory.initModel(webRequest, mavContainer, invocableMethod);

this.invokeModelAttributeMethods(request, container);

private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer container) throws Exception {
 while(!this.modelMethods.isEmpty()) {
   InvocableHandlerMethod modelMethod = this.getNextModelMethod(container).getHandlerMethod();
   ModelAttribute ann = (ModelAttribute)modelMethod.getMethodAnnotation(ModelAttribute.class);
   Assert.state(ann != null, "No ModelAttribute annotation");
   if (container.containsAttribute(ann.name())) {
     if (!ann.binding()) {
       container.setBindingDisabled(ann.name());
     }
   } else {
     Object returnValue = modelMethod.invokeForRequest(request, container, new Object[0]);
     if (modelMethod.isVoid()) {
       if (StringUtils.hasText(ann.value()) && logger.isDebugEnabled()) {
         logger.debug("Name in @ModelAttribute is ignored because method returns void: " + modelMethod.getShortLogMessage());
       }
     } else {
       String returnValueName = getNameForReturnValue(returnValue, modelMethod.getReturnType());
       if (!ann.binding()) {
         container.setBindingDisabled(returnValueName);
       }

       if (!container.containsAttribute(returnValueName)) {
         container.addAttribute(returnValueName, returnValue);
       }
     }
   }
 }

}

modelMethods 에 정의한 메소드가 들어있다
modelAttribute 가 선언돼있는지 확인

 InvocableHandlerMethod modelMethod = this.getNextModelMethod(container).getHandlerMethod();
      ModelAttribute ann = (ModelAttribute)modelMethod.getMethodAnnotation(ModelAttribute.class);
 Object returnValue = modelMethod.invokeForRequest(request, container, new Object[0]);

파라미터가 있으면 파라미터 만들어서 넣어줌

  Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
 Object returnValue = this.doInvoke(args);

doInvoke 해서 handler의 실제 메소드 호출한다
이제 리턴한 값을 모델에 담는다

 return returnValue;

String returnValueName = getNameForReturnValue(returnValue, modelMethod.getReturnType());
returnValueName 은 객체 소문자로 만든다
retunrValueName 으로 저장된 것이 없으면 저장한다

 if (!container.containsAttribute(returnValueName)) {
           container.addAttribute(returnValueName, returnValue);
         }

이제 정말로 호출하기 위한 메소드 실행

 invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);

똑같이 invokeForRequest 실행

 public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
   Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
   this.setResponseStatus(webRequest);
   if (returnValue == null) {
     if (this.isRequestNotModified(webRequest) || this.getResponseStatus() != null || mavContainer.isRequestHandled()) {
       this.disableContentCachingIfNecessary(webRequest);
       mavContainer.setRequestHandled(true);
       return;
     }
   } else if (StringUtils.hasText(this.getResponseStatusReason())) {
     mavContainer.setRequestHandled(true);
     return;
   }

   mavContainer.setRequestHandled(false);
   Assert.state(this.returnValueHandlers != null, "No return value handlers");

   try {
     this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);
   } catch (Exception var6) {
     if (logger.isTraceEnabled()) {
       logger.trace(this.formatErrorForReturnValue(returnValue), var6);
     }

     throw var6;
   }
 }
profile
풀스택 자바 개발자입니다

0개의 댓글