天天看點

springMVC源碼分析--@ModelAttribute使用及運作原理

這一篇部落格我們簡單的介紹一下ModelAttribute的使用和運作原理。

1、首先@ModelAttribute是使用在方法或者上的,當使用在方法上時其作用于本身所在的Controller,在通路Controller中的所有請求時都會執行到@ModelAttribute所注解的方法。

@Controller
public class ModelAttributeController {
  
  @ModelAttribute
  public void init(Model model){
    model.addAttribute("test", "測試資訊");
  }
  
  @RequestMapping("/modelAttribute")
  public String modelAttribute(Model model){
    model.addAttribute("test1", "測試資訊1");
    return "modelAttribute";
  }
}      

當通路連接配接http://localhost/modelAttribute時會在頁面中看到test和test1的值。

springMVC源碼分析--@ModelAttribute使用及運作原理

2、@ModelAttribute也是可以作用于參數上的,我們在上面的代碼中再添加一個作用于參數的參數。

@Controller
public class ModelAttributeController {
  
  @ModelAttribute
  public void init(Model model){
    model.addAttribute("test", "測試資訊");
  }
  
  @RequestMapping("/modelAttribute")
  public String modelAttribute(Model model,@ModelAttribute("test3")String test3){
    model.addAttribute("test1", "測試資訊1");
    model.addAttribute("test3", test3);
    return "modelAttribute";
  }
  
}      

當通路如下連結時就可以獲得如下資訊了。

springMVC源碼分析--@ModelAttribute使用及運作原理

3、@ModelAttribute注釋傳回具體類,如下:

@Controller  
public class Hello2ModelController {  
      
    @ModelAttribute  
    public User populateModel() {    
       User user=new User();  
       user.setAccount("ray");  
       return user;  
    }    
      
    @RequestMapping(value = "/helloWorld2")    
    public String helloWorld(User user) {  
        user.setName("老王");  
       return "helloWorld.jsp";    
    }    
}      

也可以指定屬性名稱

@Controller  
public class Hello2ModelController {  
      
    @ModelAttribute(value="myUser")  
    public User populateModel() {    
       User user=new User();  
       user.setAccount("ray");  
       return user;  
    }    
    @RequestMapping(value = "/helloWorld2")    
    public String helloWorld(Model map) {    
       return "helloWorld.jsp";    
    }    
}      

總結:

@ModelAttribute一個具有如下三個作用:
①綁定請求參數到指令對象:放在功能處理方法的入參上時,用于将多個請求參數綁定到一個指令對象,進而簡化綁
定流程,而且自動暴露為模型資料用于視圖頁面展示時使用;
②暴露表單引用對象為模型資料:放在處理器的一般方法(非功能處理方法)上時,是為表單準備要展示的表單引用
對象,如注冊時需要選擇的所在城市等,而且在執行功能處理方法(@RequestMapping 注解的方法)之前,自動添加
到模型對象中,用于視圖頁面展示時使用;
③暴露@RequestMapping 方法傳回值為模型資料:放在功能處理方法的傳回值上時,是暴露功能處理方法的傳回值為
模型資料,用于視圖頁面展示時使用。      

為什麼@ModelAttribute注解的方法是作用于整個Controller的,實際上是在執行Controller的每個請求時都會執行@ModelAttribute注解的方法。

執行過程在RequestMappingHandlerAdapter中,每次執行Controller時會執行@ModelAttribute注解的方法

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
      HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ......
    //執行@ModelAttribute注解的方法
    modelFactory.initModel(webRequest, mavContainer, invocableMethod);
    
    ......
    //執行Controller中的方法
    invocableMethod.invokeAndHandle(webRequest, mavContainer);
    ......
  
  }      

initModel中會執行@ModelAttribute注解的方法

public void initModel(NativeWebRequest request, ModelAndViewContainer mavContainer, HandlerMethod handlerMethod)
      throws Exception {

    Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
    mavContainer.mergeAttributes(sessionAttributes);
    //執行@ModelAttribute注解的方法
    invokeModelAttributeMethods(request, mavContainer);

    //方法執行結果的值放到mavContainer
    for (String name : findSessionAttributeArguments(handlerMethod)) {
      if (!mavContainer.containsAttribute(name)) {
        Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
        if (value == null) {
          throw new HttpSessionRequiredException("Expected session attribute '" + name + "'");
        }
        mavContainer.addAttribute(name, value);
      }
    }
  }      

在invokeModelAttributeMethods中會判斷方法上是否被@ModelAttribute注解,如果是則會執行這個方法,并将傳回值放到mavContainer中

private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer mavContainer)
      throws Exception {

    while (!this.modelMethods.isEmpty()) {
      InvocableHandlerMethod attrMethod = getNextModelMethod(mavContainer).getHandlerMethod();
      //判斷方法是否被@ModelAttribute注解
      String modelName = attrMethod.getMethodAnnotation(ModelAttribute.class).value();
      if (mavContainer.containsAttribute(modelName)) {
        continue;
      }
      //執行被@ModelAttribute注解的方法
      Object returnValue = attrMethod.invokeForRequest(request, mavContainer);
      //傳回值放到mavContainer
      if (!attrMethod.isVoid()){
        String returnValueName = getNameForReturnValue(returnValue, attrMethod.getReturnType());
        if (!mavContainer.containsAttribute(returnValueName)) {
          mavContainer.addAttribute(returnValueName, returnValue);
        }
      }
    }
  }      

總結:這邊部落格簡單地介紹了一下@ModelAttribute的用法,當其注解方法時,這個方法在每次通路Controller時都會被執行,其執行到的原理就是在每次執行Controller時都會判斷一次,并執行@ModelAttribute的方法,将執行後的結果值放到mavContainer中,現在看來其實作機制也還是比較容易了解的。

繼續閱讀