先看實例,controller代碼如下:
- @Controller
- public class FormAction{
- // 這樣的方法裏,一般是用來註冊一些PropertyEditor
- @InitBinder
- public void initBinder(WebDataBinder binder) throws Exception {
- DateFormat df = new SimpleDateFormat("yyyy---MM---dd HH:mm:ss");
- CustomDateEditor dateEditor = new CustomDateEditor(df, true);
- binder.registerCustomEditor(Date.class, dateEditor);
- }
- @RequestMapping(value="/test/json",method=RequestMethod.GET)
- @ResponseBody
- public Map<String,Object> getFormData(Date date){
- Map<String,Object> map=new HashMap<String,Object>();
- map.put("name","lg");
- map.put("age",23);
- map.put("date",new Date());
- return map;
- }
- }
xml文件僅僅開啓mvc:ananotation-driven:
- <mvc:annotation-driven />
然後訪問 http://localhost:8080/test/json?date=2014---08---3 03:34:23,便看到成功的獲取到了數據。接下來源代碼代碼分析這一過程:
由於使用了@RequestMapping所以會選擇RequestMappingHandlerAdapter來調度執行相應的方法,如下:
- /**
- * Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView}
- * if view resolution is required.
- */
- private ModelAndView invokeHandleMethod(HttpServletRequest request,
- HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
- ServletWebRequest webRequest = new ServletWebRequest(request, response);
- //我們關注的重點重點重點重點重點重點重點重點
- WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
- ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
- ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory);
- ModelAndViewContainer mavContainer = new ModelAndViewContainer();
- mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
- modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
- mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
- AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
- asyncWebRequest.setTimeout(this.asyncRequestTimeout);
- final WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
- asyncManager.setTaskExecutor(this.taskExecutor);
- asyncManager.setAsyncWebRequest(asyncWebRequest);
- asyncManager.registerCallableInterceptors(this.callableInterceptors);
- asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
- if (asyncManager.hasConcurrentResult()) {
- Object result = asyncManager.getConcurrentResult();
- mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
- asyncManager.clearConcurrentResult();
- if (logger.isDebugEnabled()) {
- logger.debug("Found concurrent result value [" + result + "]");
- }
- requestMappingMethod = requestMappingMethod.wrapConcurrentResult(result);
- }
- requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
- if (asyncManager.isConcurrentHandlingStarted()) {
- return null;
- }
- return getModelAndView(mavContainer, modelFactory, webRequest);
- }
這裏面就是整個執行過程。首先綁定請求參數到方法的參數上,然後執行方法,接下來根據方法返回的類型來選擇合適的HandlerMethodReturnValueHandler來進行處理,最後要麼走view路線,要麼直接寫入response的body中返回。
我們此時關注的重點是:如何綁定請求參數到方法的參數上的呢?
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
針對每次對該handlerMethod請求產生一個綁定工廠,由這個工廠來完成數據的綁定。
這裏的handlerMethod包含了 controller對象FormAction和、test/json映射到的方法即getFormData。
然後詳細看下getDataBinderFactory的實現:
- private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
- //這裏的handlerType便是controller的類型FormAction
- Class<?> handlerType = handlerMethod.getBeanType();
- Set<Method> methods = this.initBinderCache.get(handlerType);
- if (methods == null) {
- //關注點1:找出FormAction類的所有的含有@InitBinder的方法(方法的返回類型必須爲void),找到後同時緩存起來
- methods = HandlerMethodSelector.selectMethods(handlerType, INIT_BINDER_METHODS);
- this.initBinderCache.put(handlerType, methods);
- }
- List<InvocableHandlerMethod> initBinderMethods = new ArrayList<InvocableHandlerMethod>();
- // Global methods first
- //關注點2:再尋找出全局的初始化Binder的方法
- for (Entry<ControllerAdviceBean, Set<Method>> entry : this.initBinderAdviceCache .entrySet()) {
- if (entry.getKey().isApplicableToBeanType(handlerType)) {
- Object bean = entry.getKey().resolveBean();
- for (Method method : entry.getValue()) {
- initBinderMethods.add(createInitBinderMethod(bean, method));
- }
- }
- }
- for (Method method : methods) {
- Object bean = handlerMethod.getBean();
- initBinderMethods.add(createInitBinderMethod(bean, method));
- }
- //關注點3:找到了所有的與該handlerMethod有關的初始化binder的方法,保存起來
- return createDataBinderFactory(initBinderMethods);
- }
上面稍微做了些註釋,然後看下詳細的內容:
關注點1:就是使用過濾,過濾類爲:INIT_BINDER_METHODS,如下
- /**
- * MethodFilter that matches {@link InitBinder @InitBinder} methods.
- */
- public static final MethodFilter INIT_BINDER_METHODS = new MethodFilter() {
- @Override
- public boolean matches(Method method) {
- return AnnotationUtils.findAnnotation(method, InitBinder.class) != null;
- }
- };
這個過濾類就是在handlerType即FormAction中過濾那些含有@InitBinder註解的方法。找到了之後就緩存起來,供下次使用。key爲:handlerType,value爲找到的方法。存至initBinderCache中。
關注點2:從initBinderAdviceCache中獲取所有支持這個handlerType的method。這一塊有待繼續研究,這個initBinderAdviceCache是如何初始化來的等等。針對目前的工程來說,initBinderAdviceCache是爲空的。
關注點3:遍歷所有找到的和handlerType有關的method,然後封裝成InvocableHandlerMethod,如下:
- for (Method method : methods) {
- Object bean = handlerMethod.getBean();
- initBinderMethods.add(createInitBinderMethod(bean, method));
- }
- private InvocableHandlerMethod createInitBinderMethod(Object bean, Method method) {
- InvocableHandlerMethod binderMethod = new InvocableHandlerMethod(bean, method);
- binderMethod.setHandlerMethodArgumentResolvers(this.initBinderArgumentResolvers);
- binderMethod.setDataBinderFactory(new DefaultDataBinderFactory(this.webBindingInitializer));
- binderMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
- return binderMethod;
- }
在封裝的過程中,同時設置一些RequestMappingHandlerAdapter的一些參數進去initBinderArgumentResolvers、webBindingInitializer、parameterNameDiscoverer。
封裝完所有的方法後,創建出最終的WebDataBinderFactory。如下:
- protected InitBinderDataBinderFactory createDataBinderFactory(List<InvocableHandlerMethod> binderMethods)
- throws Exception {
- return new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer());
- }
getWebBindingInitializer()也是RequestMappingHandlerAdapter的webBindingInitializer參數。
至此綁定數據的工廠完成了,包含了這個handlerType的所有的PropertyEditor。這是準備工作,然後就是等待執行這個我們自己的方法getFormData執行時來完成參數的綁定過程。
綁定參數過程即getFormData的執行過程如下:
- ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory);
- 略
- requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
其中的requestMappingMethod經過了進一步的包裝,已經包含剛纔已經創建的綁定工廠。
執行過程如下:
- public final Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
- Object... providedArgs) throws Exception {
- Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
- if (logger.isTraceEnabled()) {
- StringBuilder sb = new StringBuilder("Invoking [");
- sb.append(getBeanType().getSimpleName()).append(".");
- sb.append(getMethod().getName()).append("] method with arguments ");
- sb.append(Arrays.asList(args));
- logger.trace(sb.toString());
- }
- Object returnValue = invoke(args);
- if (logger.isTraceEnabled()) {
- logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]");
- }
- return returnValue;
- }
分兩大步,綁定參數和執行方法體。最重要的就是如何來綁定參數呢?
- private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
- Object... providedArgs) throws Exception {
- MethodParameter[] parameters = getMethodParameters();
- Object[] args = new Object[parameters.length];
- for (int i = 0; i < parameters.length; i++) {
- MethodParameter parameter = parameters[i];
- parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
- GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
- args[i] = resolveProvidedArgument(parameter, providedArgs);
- if (args[i] != null) {
- continue;
- }
- if (this.argumentResolvers.supportsParameter(parameter)) {
- try {
- args[i] = this.argumentResolvers.resolveArgument(
- parameter, mavContainer, request, this.dataBinderFactory);
- continue;
- }
- catch (Exception ex) {
- if (logger.isTraceEnabled()) {
- logger.trace(getArgumentResolutionErrorMessage("Error resolving argument", i), ex);
- }
- throw ex;
- }
- }
- if (args[i] == null) {
- String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i);
- throw new IllegalStateException(msg);
- }
- }
- return args;
- }
綁定參數又引出來另一個重要名詞:HandlerMethodArgumentResolver。args[i] = this.argumentResolvers.resolveArgument(
parameter, mavContainer, request, this.dataBinderFactory);的具體內容如下:
- /**
- * Iterate over registered {@link HandlerMethodArgumentResolver}s and invoke the one that supports it.
- * @exception IllegalStateException if no suitable {@link HandlerMethodArgumentResolver} is found.
- */
- @Override
- public Object resolveArgument(
- MethodParameter parameter, ModelAndViewContainer mavContainer,
- NativeWebRequest webRequest, WebDataBinderFactory binderFactory)
- throws Exception {
- HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
- Assert.notNull(resolver, "Unknown parameter type [" + parameter.getParameterType().getName() + "]");
- return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
- }
遍歷所有已註冊的HandlerMethodArgumentResolver,然後找出一個適合的來進行參數綁定,對於本工程來說,getFormData(Date date)的參數date默認是request params級別的,所以使用RequestParamMethodArgumentResolver來處理這一過程。處理過程如下:
- @Override
- public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
- NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
- Class<?> paramType = parameter.getParameterType();
- NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
- Object arg = resolveName(namedValueInfo.name, parameter, webRequest);
- if (arg == null) {
- if (namedValueInfo.defaultValue != null) {
- arg = resolveDefaultValue(namedValueInfo.defaultValue);
- }
- else if (namedValueInfo.required) {
- handleMissingValue(namedValueInfo.name, parameter);
- }
- arg = handleNullValue(namedValueInfo.name, arg, paramType);
- }
- else if ("".equals(arg) && (namedValueInfo.defaultValue != null)) {
- arg = resolveDefaultValue(namedValueInfo.defaultValue);
- }
- if (binderFactory != null) {
- WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
- arg = binder.convertIfNecessary(arg, paramType, parameter);
- }
- handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
- return arg;
- }
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);獲取參數信息,就是按照@RequestParam的3個屬性來收集的,即defaultValue=null、required=false、name=date,
Object arg = resolveName(namedValueInfo.name, parameter, webRequest);然後就是獲取原始數據,獲取過程如下:
- @Override
- protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest webRequest) throws Exception {
- Object arg;
- HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
- MultipartHttpServletRequest multipartRequest =
- WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class);
- if (MultipartFile.class.equals(parameter.getParameterType())) {
- assertIsMultipartRequest(servletRequest);
- Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
- arg = multipartRequest.getFile(name);
- }
- else if (isMultipartFileCollection(parameter)) {
- assertIsMultipartRequest(servletRequest);
- Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
- arg = multipartRequest.getFiles(name);
- }
- else if(isMultipartFileArray(parameter)) {
- assertIsMultipartRequest(servletRequest);
- Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
- arg = multipartRequest.getFiles(name).toArray(new MultipartFile[0]);
- }
- else if ("javax.servlet.http.Part".equals(parameter.getParameterType().getName())) {
- assertIsMultipartRequest(servletRequest);
- arg = servletRequest.getPart(name);
- }
- else if (isPartCollection(parameter)) {
- assertIsMultipartRequest(servletRequest);
- arg = new ArrayList<Object>(servletRequest.getParts());
- }
- else if (isPartArray(parameter)) {
- assertIsMultipartRequest(servletRequest);
- arg = RequestPartResolver.resolvePart(servletRequest);
- }
- else {
- arg = null;
- if (multipartRequest != null) {
- List<MultipartFile> files = multipartRequest.getFiles(name);
- if (!files.isEmpty()) {
- arg = (files.size() == 1 ? files.get(0) : files);
- }
- }
- if (arg == null) {
- //對於本工程,我們的重點在這裏這裏這裏這裏這裏這裏
- String[] paramValues = webRequest.getParameterValues(name);
- if (paramValues != null) {
- arg = paramValues.length == 1 ? paramValues[0] : paramValues;
- }
- }
- }
- return arg;
- }
通過webRequest.getParameterValues(name)來獲取原始的字符串。這裏便有涉及到了容器如tomcat的處理過程,這一獲取參數的過程在本系列的第五篇文章tomcat的獲取參數中進行了詳細的源碼介紹,那一篇主要是介紹亂碼的。本文章不再介紹,接着說,這樣就可以獲取到我們請求的原始字符串"2014---08---3 03:34:23",接下來便是執行轉換綁定的過程:
- if (binderFactory != null) {
- WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
- arg = binder.convertIfNecessary(arg, paramType, parameter);
- }
這一過程就是要尋找我們已經註冊的所有的PropertyEditor來進行轉換,如果還沒有找到,則使用另一套轉換流程,使用conversionService來進行轉換。我們慢慢來看這一過程,有了binderFactory便可以創建出WebDataBinder,具體的創建過程如下:
- public final WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName)
- throws Exception {
- WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
- if (this.initializer != null) {
- this.initializer.initBinder(dataBinder, webRequest);
- }
- initBinder(dataBinder, webRequest);
- return dataBinder;
- }
先創建出WebDataBinder,然後使用initializer的initBinder方法來初始化一些PropertyEditor,initializer的類型爲我們常見的ConfigurableWebBindingInitializer即在mvc:annotation-driven時默認註冊的最終設置爲RequestMappingHandlerAdapter的webBindingInitializer屬性值。this.initializer.initBinder(dataBinder, webRequest);過程如下:
- @Override
- public void initBinder(WebDataBinder binder, WebRequest request) {
- binder.setAutoGrowNestedPaths(this.autoGrowNestedPaths);
- if (this.directFieldAccess) {
- binder.initDirectFieldAccess();
- }
- if (this.messageCodesResolver != null) {
- binder.setMessageCodesResolver(this.messageCodesResolver);
- }
- if (this.bindingErrorProcessor != null) {
- binder.setBindingErrorProcessor(this.bindingErrorProcessor);
- }
- if (this.validator != null && binder.getTarget() != null &&
- this.validator.supports(binder.getTarget().getClass())) {
- binder.setValidator(this.validator);
- }
- if (this.conversionService != null) {
- binder.setConversionService(this.conversionService);
- }
- if (this.propertyEditorRegistrars != null) {
- for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {
- propertyEditorRegistrar.registerCustomEditors(binder);
- }
- }
- }
即設置一些我們conversionService、messageCodesResolver、validator 等,這些參數即我們在mvc:annotation中進行設置的,若無設置,採用默認的。
繼續執行initBinder(dataBinder, webRequest);
- public void initBinder(WebDataBinder binder, NativeWebRequest request) throws Exception {
- for (InvocableHandlerMethod binderMethod : 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);
- }
- }
- }
- }
執行那些適合我們已經創建的WebDataBinder,怎樣才叫適合的呢?看isBinderMethodApplicable(binderMethod, binder)方法
- protected boolean isBinderMethodApplicable(HandlerMethod initBinderMethod, WebDataBinder binder) {
- InitBinder annot = initBinderMethod.getMethodAnnotation(InitBinder.class);
- Collection<String> names = Arrays.asList(annot.value());
- return (names.size() == 0 || names.contains(binder.getObjectName()));
- }
當initBinderMethod上的@InitBinder註解指定了value,該value可以是多個,當它包含了我們的方法的參數date,則這個initBinderMethod就會被執行。當@InitBinder註解沒有指定value,則也會被執行。所以爲了不用執行一些不必要的initBinderMethod,我們最好爲這些initBinderMethod上的@InitBinder加上value限定。對於我們寫的initBinder便因此開始執行了。
由binderFactory創建出來的WebDataBinder就此完成,然後纔是詳細的轉換過程:
- public <T> T convertIfNecessary(String propertyName, Object oldValue, Object newValue,
- Class<T> requiredType, TypeDescriptor typeDescriptor) throws IllegalArgumentException {
- Object convertedValue = newValue;
- // Custom editor for this type?
- PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
- ConversionFailedException firstAttemptEx = null;
- // No custom editor but custom ConversionService specified?
- ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
- //略
- }
這裏首先使用已註冊的PropertyEditor,當仍然沒有找到時才使用ConversionService。對於本工程來說,由於已經手動註冊了對於Date的轉換的PropertyEditor即CustomDateEditor,然後便會執行CustomDateEditor的具體的轉換過程。至此,大體過程就算是完了。