SpringMVC中WebDataBinder的應用及原理
Controller方法的參數類型可以是基本類型,也可以是封裝后的普通Java類型。若這個普通Java類型沒有聲明任何注解,則意味著它的每一個屬性都需要到Request中去查找對應的請求參數。眾所周知,無論客戶端傳入的是什么類型的請求參數,最終都要以字節的形式傳給服務端。而服務端通過Request的getParameter方法取到的參數也都是字符串形式的結果。所以,需要有一個把字符串形式的參數轉換成服務端真正需要的類型的轉換工具,在spring中這個轉換工具為WebDataBinder。
WebDataBinder不需要我們自己去創建,我們只需要向它注冊參數類型對應的屬性編輯器PropertyEditor。PropertyEditor可以將字符串轉換成其真正的數據類型,它的void setAsText(String text)方法實現數據轉換的過程。
具體的做法是,在Controller中聲明一個InitBinder方法,方法中利用WebDataBinder將自己實現的或者spring自帶的PropertyEditor進行注冊。像下面這樣:
@InitBinderpublicvoidinitBinder(WebDataBinder binder)throwsException{? ? ? binder.registerCustomEditor(Long.class,newCustomNumberEditor(Long.class,true));? ? ? binder.registerCustomEditor(Date.class,newCustomDateEditor(newSimpleDateFormat("yyyy-MM-dd"),true));? }? ? ```? 處理沒有任何注解的普通Java類型的參數解析器是ModelAttributeMethodProcessor,下面是參加解析方法的代碼:```javapublicfinalObjectresolveArgument(
MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest request, WebDataBinderFactory binderFactory)throwsException{? ? ? ? String name = ModelFactory.getNameForParameter(parameter);? ? ? Object target = (mavContainer.containsAttribute(name)) ?? ? ? ? ? ? ? mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, request);? ? ? ? WebDataBinder binder = binderFactory.createBinder(request, target, name);if(binder.getTarget() !=null) {? ? ? ? ? bindRequestParameters(binder, request);? ? ? ? ? validateIfApplicable(binder, parameter);if(binder.getBindingResult().hasErrors()) {if(isBindExceptionRequired(binder, parameter)) {thrownewBindException(binder.getBindingResult());? ? ? ? ? ? ? }? ? ? ? ? }? ? ? }? ? ? ? mavContainer.addAllAttributes(binder.getBindingResult().getModel());returnbinder.getTarget();? }
每次請求到來后的參數解析都會利用WebDataBinderFactory創建一個binder對象,然后從這個binder中取得最終解析好的參數對象。WebDataBinderFactory是在InvocableHandlerMethod中定義的,即不同的Controller方法有著不同的WebDataBinderFactory。其實創建binder的同時還對binder進行了初始化,這個初始化過程就會執行Controller中的InitBinder方法。InitBinderDataBinderFactory實現了初始化binder的方法:
java
public void initBinder(WebDataBinder binder, NativeWebRequest request) throws Exception {
for (InvocableHandlerMethod binderMebinderMethod thod : this.binderMethods) {
if (isBinderMethodApplicable(binderMethod, binder)) {
Object returnValue = binderMethod.invokeForRequest(request, null, binder);
if (returnValue != null) {
throw new IllegalStateException("@InitBinder methods should return void: " + binderMethod);
}
}
}
}
上面方法中的binderMethods就是在Controller中定義的InitBinder方法,并且binderMethod 同Controller中的其他方法一樣也是InvocableHandlerMethod。從上面的代碼可以看出,InitBinder方法可以聲明多個,WebDataBinderFactory初始化binder的時候會分別調用每個InitBinder方法。而我們在初始化的過程中使用了binder.registerCustomEditor,間接地向BeanWrapperImpl中注冊了傳入的PropertyEditor,以便在參數類型轉換的時候使用。
還記得剛才的ModelAttributeMethodProcessor解析參數時,創建binder之后調用了bindRequestParameters實現了請求參數的綁定,它的子類ServletModelAttributeMethodProcessor重寫了這個方法:
protectedvoidbindRequestParameters(WebDataBinder binder, NativeWebRequest request){? ? ? ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);? ? ? ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;? ? ? servletBinder.bind(servletRequest);? }
不論是父類還是子類,其實都是調用了binder的bind方法。下面是ServletRequestDataBinder的bind方法
publicvoidbind(ServletRequest request){? ? ? MutablePropertyValues mpvs =newServletRequestParameterPropertyValues(request);? ? ? MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);if(multipartRequest !=null) {? ? ? ? ? bindMultipart(multipartRequest.getMultiFileMap(), mpvs);? ? ? }? ? ? addBindValues(mpvs, request);? ? ? doBind(mpvs);? }
這個方法跟依賴注入的過程非常相似,依賴注入是根據屬性在容器中找到滿足條件的對象,然后設置到當前的bean中。而上面的方法不是在容器中查找,而是從Request中獲取,即把Request中的請求參數注入到binder的target中去。此時進行類型轉換的就是剛剛注冊的PropertyEditor,因為InitBinder方法每次都會執行,所以使用者可以在每個Controller中對相同類型的參數定義不同的參數轉換方式。經過了bindRequestParameters方法的處理,現在binder中target(即HandlerMethod的參數)已經包含了Request中的請求參數。? 那么,現在還有一個問題,InvocableHandlerMethod中的WebDataBinderFactory是如何來的呢?它的創建過程在RequestMappingHandlerAdapter(本文所有邏輯過程均假定使用RequestMappingHandlerAdapter):
privateWebDataBinderFactorygetDataBinderFactory(HandlerMethod handlerMethod)throwsException{? ? ? Class handlerType = handlerMethod.getBeanType();? ? ? Set methods =this.dataBinderFactoryCache.get(handlerType);if(methods ==null) {? ? ? ? ? methods = HandlerMethodSelector.selectMethods(handlerType, INIT_BINDER_METHODS);this.dataBinderFactoryCache.put(handlerType, methods);? ? ? }? ? ? List binderMethods =newArrayList();for(Method method : methods) {? ? ? ? ? InvocableHandlerMethod binderMethod =newInvocableHandlerMethod(handlerMethod.getBean(), method);? ? ? ? ? binderMethod.setHandlerMethodArgumentResolvers(this.initBinderArgumentResolvers);? ? ? ? ? binderMethod.setDataBinderFactory(newDefaultDataBinderFactory(this.webBindingInitializer));? ? ? ? ? binderMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);? ? ? ? ? binderMethods.add(binderMethod);? ? ? }returncreateDataBinderFactory(binderMethods);? }