SpringMVC中WebDataBinder的應用及原理

 Controller方法的參數類型能夠是基本類型,也能夠是封裝後的普通Java類型。若這個普通Java類型沒有聲明任何註解,則意味着它的每個屬性都須要到Request中去查找對應的請求參數。衆所周知,不管客戶端傳入的是什麼類型的請求參數,最終都要以字節的形式傳給服務端。而服務端經過Request的getParameter方法取到的參數也都是字符串形式的結果。因此,須要有一個把字符串形式的參數轉換成服務端真正須要的類型的轉換工具,在spring中這個轉換工具爲WebDataBinder。
 
   WebDataBinder不須要咱們本身去建立,咱們只須要向它註冊參數類型對應的屬性編輯器PropertyEditor。PropertyEditor能夠將字符串轉換成其真正的數據類型,它的void setAsText(String text)方法實現數據轉換的過程。
 
  具體的作法是,在Controller中聲明一個InitBinder方法,方法中利用WebDataBinder將本身實現的或者spring自帶的PropertyEditor進行註冊。像下面這樣:
  
[java]  view plain  copy
 
  1. @InitBinder  
  2. public void initBinder(WebDataBinder binder) throws Exception {  
  3.     binder.registerCustomEditor(Long.class, new CustomNumberEditor(Long.class, true));  
  4.     binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));  
  5. }  
  
  處理沒有任何註解的普通Java類型的參數解析器是ModelAttributeMethodProcessor,下面是參加解析方法的代碼:
[java]  view plain  copy
 
  1. public final Object resolveArgument(  
  2.         MethodParameter parameter, ModelAndViewContainer mavContainer,  
  3.         NativeWebRequest request, WebDataBinderFactory binderFactory)  
  4.         throws Exception {  
  5.   
  6.     String name = ModelFactory.getNameForParameter(parameter);  
  7.     Object target = (mavContainer.containsAttribute(name)) ?  
  8.             mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, request);  
  9.   
  10.     WebDataBinder binder = binderFactory.createBinder(request, target, name);  
  11.     if (binder.getTarget() != null) {  
  12.         bindRequestParameters(binder, request);  
  13.         validateIfApplicable(binder, parameter);  
  14.         if (binder.getBindingResult().hasErrors()) {  
  15.             if (isBindExceptionRequired(binder, parameter)) {  
  16.                 throw new BindException(binder.getBindingResult());  
  17.             }  
  18.         }  
  19.     }  
  20.   
  21.     mavContainer.addAllAttributes(binder.getBindingResult().getModel());  
  22.     return binder.getTarget();  
  23. }  

  每次請求到來後的參數解析都會利用WebDataBinderFactory建立一個binder對象,而後從這個binder中取得最終解析好的參數對象。WebDataBinderFactory是在InvocableHandlerMethod中定義的,即不一樣的Controller方法有着不一樣的WebDataBinderFactory。其實建立binder的同時還對binder進行了初始化,這個初始化過程就會執行Controller中的InitBinder方法。InitBinderDataBinderFactory實現了初始化binder的方法:
[java]  view plain  copy
 
  1. public void initBinder(WebDataBinder binder, NativeWebRequest request) throws Exception {  
  2.     for (InvocableHandlerMethod binderMebinderMethod thod : this.binderMethods) {  
  3.         if (isBinderMethodApplicable(binderMethod, binder)) {  
  4.             Object returnValue = binderMethod.invokeForRequest(request, null, binder);  
  5.             if (returnValue != null) {  
  6.                 throw new IllegalStateException("@InitBinder methods should return void: " + binderMethod);  
  7.             }  
  8.         }  
  9.     }  
  10. }  
   
      上面方法中的binderMethods就是在Controller中定義的InitBinder方法,而且binderMethod 同Controller中的其餘方法同樣也是InvocableHandlerMethod。從上面的代碼能夠看出,InitBinder方法能夠聲明多個,WebDataBinderFactory初始化binder的時候會分別調用每一個InitBinder方法。而咱們在初始化的過程當中使用了binder.registerCustomEditor,間接地向BeanWrapperImpl中註冊了傳入的PropertyEditor,以便在參數類型轉換的時候使用。
 
      還記得剛纔的ModelAttributeMethodProcessor解析參數時,建立binder以後調用了bindRequestParameters實現了請求參數的綁定,它的子類ServletModelAttributeMethodProcessor重寫了這個方法:
[java]  view plain  copy
 
  1. protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {  
  2.     ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);  
  3.     ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;  
  4.     servletBinder.bind(servletRequest);  
  5. }  
    
     不管是父類仍是子類,其實都是調用了binder的bind方法。下面是ServletRequestDataBinder的bind方法
[java]  view plain  copy
 
  1. public void bind(ServletRequest request) {  
  2.     MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);  
  3.     MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);  
  4.     if (multipartRequest != null) {  
  5.         bindMultipart(multipartRequest.getMultiFileMap(), mpvs);  
  6.     }  
  7.     addBindValues(mpvs, request);  
  8.     doBind(mpvs);  
  9. }  
 
     這個方法跟依賴注入的過程很是類似,依賴注入是根據屬性在容器中找到知足條件的對象,而後設置到當前的bean中。而上面的方法不是在容器中查找,而是從Request中獲取,即把Request中的請求參數注入到binder的target中去。此時進行類型轉換的就是剛剛註冊的PropertyEditor,由於InitBinder方法每次都會執行,因此使用者能夠在每一個Controller中對相同類型的參數定義不一樣的參數轉換方式。

    通過了bindRequestParameters方法的處理,如今binder中target(即HandlerMethod的參數)已經包含了Request中的請求參數。
 
      那麼,如今還有一個問題,InvocableHandlerMethod中的WebDataBinderFactory是如何來的呢?它的建立過程在RequestMappingHandlerAdapter(本文全部邏輯過程均假定使用RequestMappingHandlerAdapter):
 
[java]  view plain  copy
 
  1. private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {  
  2.     Class<?> handlerType = handlerMethod.getBeanType();  
  3.     Set<Method> methods = this.dataBinderFactoryCache.get(handlerType);  
  4.     if (methods == null) {  
  5.         methods = HandlerMethodSelector.selectMethods(handlerType, INIT_BINDER_METHODS);  
  6.         this.dataBinderFactoryCache.put(handlerType, methods);  
  7.     }  
  8.     List<InvocableHandlerMethod> binderMethods = new ArrayList<InvocableHandlerMethod>();  
  9.     for (Method method : methods) {  
  10.         InvocableHandlerMethod binderMethod = new InvocableHandlerMethod(handlerMethod.getBean(), method);  
  11.         binderMethod.setHandlerMethodArgumentResolvers(this.initBinderArgumentResolvers);  
  12.         binderMethod.setDataBinderFactory(new DefaultDataBinderFactory(this.webBindingInitializer));  
  13.         binderMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);  
  14.         binderMethods.add(binderMethod);  
  15.     }  
  16.     return createDataBinderFactory(binderMethods);  
  17. }  
      
相關文章
相關標籤/搜索