Spring绑定请求参数过程以及使用@InitBinder来注册自己的属性处理器

在工作中,经常会出现前台的请求参数由于无法被正常转型,导致请求无法进到后台的问题。

比如,我有一个User。其性别的属性被定义成了枚举,如下:

 1 public enum Gender {
 2     MALE("男"),FEMALE("女");
 3     private Gender(String name) {
 4         this.name = name;
 5     }
 6     private String name;
 7     public String getName() {
 8         return name;
 9     }
10     public void setName(String name) {
11         this.name = name;
12     }
13 }

而前台的表单提交时,gender的属性值是一个字符串,Spring无法将“MALE”或是“FEMAL”直接转换成Gender,请求便无法到Ctronller。

于是,想看看Spring是如何帮助我们将httpquest中的参数绑定到方法的参数上的。

开始前,先声明本文的所用的spring版本是4.3。不同版本的实现可能有所不同。

我们都知道,请求都是由DispatchServlet的doDispatch方法进行分发。我们就从doDispatch里一层层往下找。

 1 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
 2         HttpServletRequest processedRequest = request;
 3         HandlerExecutionChain mappedHandler = null;
 4         boolean multipartRequestParsed = false;
 5 
 6         WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
 7 
 8         try {
 9             ModelAndView mv = null;
10             Exception dispatchException = null;
11 
12             try {
13                 processedRequest = checkMultipart(request);
14                 multipartRequestParsed = (processedRequest != request);
15 
16                 // Determine handler for the current request.
17                 mappedHandler = getHandler(processedRequest);
18                 if (mappedHandler == null || mappedHandler.getHandler() == null) {
19                     noHandlerFound(processedRequest, response);
20                     return;
21                 }
22 
23                 // Determine handler adapter for the current request.
24                 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
25 
26                 // Process last-modified header, if supported by the handler.
27                 String method = request.getMethod();
28                 boolean isGet = "GET".equals(method);
29                 if (isGet || "HEAD".equals(method)) {
30                     long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
31                     if (logger.isDebugEnabled()) {
32                         logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
33                     }
34                     if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
35                         return;
36                     }
37                 }
38 
39                 if (!mappedHandler.applyPreHandle(processedRequest, response)) {
40                     return;
41                 }
42 
43                 // Actually invoke the handler.
44                 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
45 
46                 if (asyncManager.isConcurrentHandlingStarted()) {
47                     return;
48                 }
49 
50                 applyDefaultViewName(processedRequest, mv);
51                 mappedHandler.applyPostHandle(processedRequest, response, mv);
52             }
53             catch (Exception ex) {
54                 dispatchException = ex;
55             }
56             catch (Throwable err) {
57                 // As of 4.3, we're processing Errors thrown from handler methods as well,
58                 // making them available for @ExceptionHandler methods and other scenarios.
59                 dispatchException = new NestedServletException("Handler dispatch failed", err);
60             }
61             processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
62         }
63         catch (Exception ex) {
64             triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
65         }
66         catch (Throwable err) {
67             triggerAfterCompletion(processedRequest, response, mappedHandler,
68                     new NestedServletException("Handler processing failed", err));
69         }
70         finally {
71             if (asyncManager.isConcurrentHandlingStarted()) {
72                 // Instead of postHandle and afterCompletion
73                 if (mappedHandler != null) {
74                     mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
75                 }
76             }
77             else {
78                 // Clean up any resources used by a multipart request.
79                 if (multipartRequestParsed) {
80                     cleanupMultipart(processedRequest);
81                 }
82             }
83         }
84     }

先是对请求进行了一些处理。然后根据getHandler方法,获得HandlerExecutionChain对象。这一过程是为了得到处理请求的执行链,以便对请求进行一系列的处理。执行链包含了处理器(Handler)和一系列的拦截器(HandlerInterceptor)。

拦截器很好理解,相信大家也都定义过自己的拦截器,那么拦截器是什么呢。我们来看一下:

它定位了某个请求所对应的控制器,甚至对应了具体的方法,已经方法的参数。

接下来继续。代码通过getHandlerAdapter方法获得HandlerAdapter方法。顾名思义,这是一个适配器。它会根据support方法确定适配器是够支持handler。并通过调用自己的handle方法来处理方法。所以说Handler对象是找到了对应的方法并记录,而HandlerAdapter则是去处理请求。所以参数绑定的过程应该就是在HandlerAdapter的handle实现里。

AbstractHandlerMethodAdapter实现了HandlerAdapter接口,做了大部分的实现。

他的handle方法调用了handleInternal方法。

1 public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
2             throws Exception {
3 
4         return handleInternal(request, response, (HandlerMethod) handler);
5     }

而handInternal则是交给了子类去具体实现。因为我们是RequestMapping请求,所以对应的实现是RequestMappingHandlerAdapter。

 1 @Override
 2     protected ModelAndView handleInternal(HttpServletRequest request,
 3             HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
 4 
 5         ModelAndView mav;
 6         checkRequest(request);
 7 
 8         // Execute invokeHandlerMethod in synchronized block if required.
 9         if (this.synchronizeOnSession) {
10             HttpSession session = request.getSession(false);
11             if (session != null) {
12                 Object mutex = WebUtils.getSessionMutex(session);
13                 synchronized (mutex) {
14                     mav = invokeHandlerMethod(request, response, handlerMethod);
15                 }
16             }
17             else {
18                 // No HttpSession available -> no mutex necessary
19                 mav = invokeHandlerMethod(request, response, handlerMethod);
20             }
21         }
22         else {
23             // No synchronization on session demanded at all...
24             mav = invokeHandlerMethod(request, response, handlerMethod);
25         }
26 
27         if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
28             if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
29                 applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
30             }
31             else {
32                 prepareResponse(response);
33             }
34         }
35 
36         return mav;
37     }

invokeHandleMethod方法就是去调用handler找到个方法去处理请求,并返回ModelAndView。

 1 protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
 2             HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
 3 
 4         ServletWebRequest webRequest = new ServletWebRequest(request, response);
 5         try {
 6             WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
 7             ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
 8 
 9             ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
10             invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
11             invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
12             invocableMethod.setDataBinderFactory(binderFactory);
13             invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
14 
15             ModelAndViewContainer mavContainer = new ModelAndViewContainer();
16             mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
17             modelFactory.initModel(webRequest, mavContainer, invocableMethod);
18             mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
19 
20             AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
21             asyncWebRequest.setTimeout(this.asyncRequestTimeout);
22 
23             WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
24             asyncManager.setTaskExecutor(this.taskExecutor);
25             asyncManager.setAsyncWebRequest(asyncWebRequest);
26             asyncManager.registerCallableInterceptors(this.callableInterceptors);
27             asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
28 
29             if (asyncManager.hasConcurrentResult()) {
30                 Object result = asyncManager.getConcurrentResult();
31                 mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
32                 asyncManager.clearConcurrentResult();
33                 if (logger.isDebugEnabled()) {
34                     logger.debug("Found concurrent result value [" + result + "]");
35                 }
36                 invocableMethod = invocableMethod.wrapConcurrentResult(result);
37             }
38 
39             invocableMethod.invokeAndHandle(webRequest, mavContainer);
40             if (asyncManager.isConcurrentHandlingStarted()) {
41                 return null;
42             }
43 
44             return getModelAndView(mavContainer, modelFactory, webRequest);
45         }
46         finally {
47             webRequest.requestCompleted();
48         }
49     }

这个方法主要做了两件事,一是绑定参数,而是调用方法,处理请求。

来看看这些对象都是啥,

可以发现invocableMethod就是处理请求的方法的封装。既然设置了DataBinderFactory作为属性,那我们就猜想一下DataBinderFactory这个工厂产生的对象会被用作绑定参数。

而代码中invocableMethod.invokeAndHandle(webRequest, mavContainer);应该就是绑定参数和封装的过程。

 1 public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
 2             Object... providedArgs) throws Exception {
 3 
 4         Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
 5         setResponseStatus(webRequest);
 6 
 7         if (returnValue == null) {
 8             if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
 9                 mavContainer.setRequestHandled(true);
10                 return;
11             }
12         }
13         else if (StringUtils.hasText(this.responseReason)) {
14             mavContainer.setRequestHandled(true);
15             return;
16         }
17 
18         mavContainer.setRequestHandled(false);
19         try {
20             this.returnValueHandlers.handleReturnValue(
21                     returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
22         }
23         catch (Exception ex) {
24             if (logger.isTraceEnabled()) {
25                 logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
26             }
27             throw ex;
28         }
29     }

显然:setResponseStatus(webRequest);应该已经是处理完请求了,才会设置响应的状态。

因此重点就落在了Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

 1 /**
 2      * Invoke the method after resolving its argument values in the context of the given request.
 3      * <p>Argument values are commonly resolved through {@link HandlerMethodArgumentResolver}s.
 4      * The {@code providedArgs} parameter however may supply argument values to be used directly,
 5      * i.e. without argument resolution. Examples of provided argument values include a
 6      * {@link WebDataBinder}, a {@link SessionStatus}, or a thrown exception instance.
 7      * Provided argument values are checked before argument resolvers.
 8      * @param request the current request
 9      * @param mavContainer the ModelAndViewContainer for this request
10      * @param providedArgs "given" arguments matched by type, not resolved
11      * @return the raw value returned by the invoked method
12      * @exception Exception raised if no suitable argument resolver can be found,
13      * or if the method raised an exception
14      */
15     public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
16             Object... providedArgs) throws Exception {
17 
18         Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
19         if (logger.isTraceEnabled()) {
20             logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
21                     "' with arguments " + Arrays.toString(args));
22         }
23         Object returnValue = doInvoke(args);
24         if (logger.isTraceEnabled()) {
25             logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
26                     "] returned [" + returnValue + "]");
27         }
28         return returnValue;
29     }

从这段注释来看我们应该是找对地方了。而getMethodArgumentValues方法就是处理参数,然后将得到的对象作为参数传给doInvoke方法,从而完成了绑定参数的过程。

 1 private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
 2             Object... providedArgs) throws Exception {
 3 
 4         MethodParameter[] parameters = getMethodParameters();
 5         Object[] args = new Object[parameters.length];
 6         for (int i = 0; i < parameters.length; i++) {
 7             MethodParameter parameter = parameters[i];
 8             parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
 9             args[i] = resolveProvidedArgument(parameter, providedArgs);
10             if (args[i] != null) {
11                 continue;
12             }
13             if (this.argumentResolvers.supportsParameter(parameter)) {
14                 try {
15                     args[i] = this.argumentResolvers.resolveArgument(
16                             parameter, mavContainer, request, this.dataBinderFactory);
17                     continue;
18                 }
19                 catch (Exception ex) {
20                     if (logger.isDebugEnabled()) {
21                         logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex);
22                     }
23                     throw ex;
24                 }
25             }
26             if (args[i] == null) {
27                 throw new IllegalStateException("Could not resolve method parameter at index " +
28                         parameter.getParameterIndex() + " in " + parameter.getMethod().toGenericString() +
29                         ": " + getArgumentResolutionErrorMessage("No suitable resolver for", i));
30             }
31         }
32         return args;
33     }

getMethodParamters返回了MethodParamters的数组。MethodParamter是HandlerMethod的内部类,用来对请求参数进行封装。

可以看到就是方法对应的参数。

来看下this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);

首先argumentResolvers是HandlerMethodArgumentResolverComposite对象。有两个重要的属性,

1 //Resolver列表,用来具体处理resolveArgument方法
2 private final List<HandlerMethodArgumentResolver> argumentResolvers =
3             new LinkedList<HandlerMethodArgumentResolver>();
4 //缓存,用来快速找到对应的Resolver
5     private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =
6             new ConcurrentHashMap<MethodParameter, HandlerMethodArgumentResolver>(256);

以下两端代码验证了我注解里说的两句话:

 1 //将方法的具体实现交给列表里合适的对象
 2 public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
 3             NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
 4 
 5         HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
 6         if (resolver == null) {
 7             throw new IllegalArgumentException("Unknown parameter type [" + parameter.getParameterType().getName() + "]");
 8         }
 9         return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
10     }
 1 //筛选合适对象并缓存
 2 private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
 3         HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
 4         if (result == null) {
 5             for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
 6                 if (logger.isTraceEnabled()) {
 7                     logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" +
 8                             parameter.getGenericParameterType() + "]");
 9                 }
10                 if (methodArgumentResolver.supportsParameter(parameter)) {
11                     result = methodArgumentResolver;
12                     this.argumentResolverCache.put(parameter, result);
13                     break;
14                 }
15             }
16         }
17         return result;
18     }

由于Resolver是根据参数形式的不同有不同的实现。

我们看最为常用的一个实现,AbstractNamedValueMethodArgumentResolver。也是我们这次请求所用的实现。

 1 public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
 2             NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
 3 
 4         NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
 5         MethodParameter nestedParameter = parameter.nestedIfOptional();
 6 
 7         Object resolvedName = resolveStringValue(namedValueInfo.name);
 8         if (resolvedName == null) {
 9             throw new IllegalArgumentException(
10                     "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
11         }
12 
13         Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
14         if (arg == null) {
15             if (namedValueInfo.defaultValue != null) {
16                 arg = resolveStringValue(namedValueInfo.defaultValue);
17             }
18             else if (namedValueInfo.required && !nestedParameter.isOptional()) {
19                 handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
20             }
21             arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
22         }
23         else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
24             arg = resolveStringValue(namedValueInfo.defaultValue);
25         }
26 
27         if (binderFactory != null) {
28             WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
29             try {
30                 arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
31             }
32             catch (ConversionNotSupportedException ex) {
33                 throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
34                         namedValueInfo.name, parameter, ex.getCause());
35             }
36             catch (TypeMismatchException ex) {
37                 throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
38                         namedValueInfo.name, parameter, ex.getCause());
39 
40             }
41         }
42 
43         handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
44 
45         return arg;
46     }

显示从reqeust取出对应的参数,然后在交给由binderFactory创建的WebDataBinder对象来转换成合适的类型,之后用来作为处理方法的参数。

 1     public final WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName)
 2             throws Exception {
 3 
 4         WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
 5         if (this.initializer != null) {
 6             this.initializer.initBinder(dataBinder, webRequest);
 7         }
 8         initBinder(dataBinder, webRequest);
 9         return dataBinder;
10     }

initializer的类型为我们常见的ConfigurableWebBindingInitializer即在mvc:annotation-driven时默认注册的最终设置为RequestMappingHandlerAdapter的webBindingInitializer属性值。

在看看initializer.initBuilder方法:

 1 public void initBinder(WebDataBinder binder, WebRequest request) {
 2         binder.setAutoGrowNestedPaths(this.autoGrowNestedPaths);
 3         if (this.directFieldAccess) {
 4             binder.initDirectFieldAccess();
 5         }
 6         if (this.messageCodesResolver != null) {
 7             binder.setMessageCodesResolver(this.messageCodesResolver);
 8         }
 9         if (this.bindingErrorProcessor != null) {
10             binder.setBindingErrorProcessor(this.bindingErrorProcessor);
11         }
12         if (this.validator != null && binder.getTarget() != null &&
13                 this.validator.supports(binder.getTarget().getClass())) {
14             binder.setValidator(this.validator);
15         }
16         if (this.conversionService != null) {
17             binder.setConversionService(this.conversionService);
18         }
19         if (this.propertyEditorRegistrars != null) {
20             for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {
21                 propertyEditorRegistrar.registerCustomEditors(binder);
22             }
23         }
24     }

我们看到ConversionSerivce已经提供了大量了转换方法。另外我们关注到validator的初始化也是在这。

最后一步则是将我们自己定义的PropertyEditor向binder注册。

继续看initBinder

 1 @Override
 2     public void initBinder(WebDataBinder binder, NativeWebRequest request) throws Exception {
 3         for (InvocableHandlerMethod binderMethod : this.binderMethods) {
 4             if (isBinderMethodApplicable(binderMethod, binder)) {
 5                 Object returnValue = binderMethod.invokeForRequest(request, null, binder);
 6                 if (returnValue != null) {
 7                     throw new IllegalStateException("@InitBinder methods should return void: " + binderMethod);
 8                 }
 9             }
10         }
11     }

执行那些适合我们已经创建的WebDataBinder。

最后看到转换类型的代码:

  1 public <T> T convertIfNecessary(String propertyName, Object oldValue, Object newValue,
  2             Class<T> requiredType, TypeDescriptor typeDescriptor) throws IllegalArgumentException {
  3 
  4         // Custom editor for this type?
  5         PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
  6 
  7         ConversionFailedException conversionAttemptEx = null;
  8 
  9         // No custom editor but custom ConversionService specified?
 10         ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
 11         if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
 12             TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
 13             if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
 14                 try {
 15                     return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
 16                 }
 17                 catch (ConversionFailedException ex) {
 18                     // fallback to default conversion logic below
 19                     conversionAttemptEx = ex;
 20                 }
 21             }
 22         }
 23 
 24         Object convertedValue = newValue;
 25 
 26         // Value not of required type?
 27         if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
 28             if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) &&
 29                     convertedValue instanceof String) {
 30                 TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor();
 31                 if (elementTypeDesc != null) {
 32                     Class<?> elementType = elementTypeDesc.getType();
 33                     if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) {
 34                         convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
 35                     }
 36                 }
 37             }
 38             if (editor == null) {
 39                 editor = findDefaultEditor(requiredType);
 40             }
 41             convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
 42         }
 43 
 44         boolean standardConversion = false;
 45 
 46         if (requiredType != null) {
 47             // Try to apply some standard type conversion rules if appropriate.
 48 
 49             if (convertedValue != null) {
 50                 if (Object.class == requiredType) {
 51                     return (T) convertedValue;
 52                 }
 53                 else if (requiredType.isArray()) {
 54                     // Array required -> apply appropriate conversion of elements.
 55                     if (convertedValue instanceof String && Enum.class.isAssignableFrom(requiredType.getComponentType())) {
 56                         convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
 57                     }
 58                     return (T) convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType());
 59                 }
 60                 else if (convertedValue instanceof Collection) {
 61                     // Convert elements to target type, if determined.
 62                     convertedValue = convertToTypedCollection(
 63                             (Collection<?>) convertedValue, propertyName, requiredType, typeDescriptor);
 64                     standardConversion = true;
 65                 }
 66                 else if (convertedValue instanceof Map) {
 67                     // Convert keys and values to respective target type, if determined.
 68                     convertedValue = convertToTypedMap(
 69                             (Map<?, ?>) convertedValue, propertyName, requiredType, typeDescriptor);
 70                     standardConversion = true;
 71                 }
 72                 if (convertedValue.getClass().isArray() && Array.getLength(convertedValue) == 1) {
 73                     convertedValue = Array.get(convertedValue, 0);
 74                     standardConversion = true;
 75                 }
 76                 if (String.class == requiredType && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) {
 77                     // We can stringify any primitive value...
 78                     return (T) convertedValue.toString();
 79                 }
 80                 else if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) {
 81                     if (conversionAttemptEx == null && !requiredType.isInterface() && !requiredType.isEnum()) {
 82                         try {
 83                             Constructor<T> strCtor = requiredType.getConstructor(String.class);
 84                             return BeanUtils.instantiateClass(strCtor, convertedValue);
 85                         }
 86                         catch (NoSuchMethodException ex) {
 87                             // proceed with field lookup
 88                             if (logger.isTraceEnabled()) {
 89                                 logger.trace("No String constructor found on type [" + requiredType.getName() + "]", ex);
 90                             }
 91                         }
 92                         catch (Exception ex) {
 93                             if (logger.isDebugEnabled()) {
 94                                 logger.debug("Construction via String failed for type [" + requiredType.getName() + "]", ex);
 95                             }
 96                         }
 97                     }
 98                     String trimmedValue = ((String) convertedValue).trim();
 99                     if (requiredType.isEnum() && "".equals(trimmedValue)) {
100                         // It's an empty enum identifier: reset the enum value to null.
101                         return null;
102                     }
103                     convertedValue = attemptToConvertStringToEnum(requiredType, trimmedValue, convertedValue);
104                     standardConversion = true;
105                 }
106                 else if (convertedValue instanceof Number && Number.class.isAssignableFrom(requiredType)) {
107                     convertedValue = NumberUtils.convertNumberToTargetClass(
108                             (Number) convertedValue, (Class<Number>) requiredType);
109                     standardConversion = true;
110                 }
111             }
112             else {
113                 // convertedValue == null
114                 if (javaUtilOptionalEmpty != null && requiredType == javaUtilOptionalEmpty.getClass()) {
115                     convertedValue = javaUtilOptionalEmpty;
116                 }
117             }
118 
119             if (!ClassUtils.isAssignableValue(requiredType, convertedValue)) {
120                 if (conversionAttemptEx != null) {
121                     // Original exception from former ConversionService call above...
122                     throw conversionAttemptEx;
123                 }
124                 else if (conversionService != null) {
125                     // ConversionService not tried before, probably custom editor found
126                     // but editor couldn't produce the required type...
127                     TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
128                     if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
129                         return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
130                     }
131                 }
132 
133                 // Definitely doesn't match: throw IllegalArgumentException/IllegalStateException
134                 StringBuilder msg = new StringBuilder();
135                 msg.append("Cannot convert value of type '").append(ClassUtils.getDescriptiveType(newValue));
136                 msg.append("' to required type '").append(ClassUtils.getQualifiedName(requiredType)).append("'");
137                 if (propertyName != null) {
138                     msg.append(" for property '").append(propertyName).append("'");
139                 }
140                 if (editor != null) {
141                     msg.append(": PropertyEditor [").append(editor.getClass().getName()).append(
142                             "] returned inappropriate value of type '").append(
143                             ClassUtils.getDescriptiveType(convertedValue)).append("'");
144                     throw new IllegalArgumentException(msg.toString());
145                 }
146                 else {
147                     msg.append(": no matching editors or conversion strategy found");
148                     throw new IllegalStateException(msg.toString());
149                 }
150             }
151         }
152 
153         if (conversionAttemptEx != null) {
154             if (editor == null && !standardConversion && requiredType != null && Object.class != requiredType) {
155                 throw conversionAttemptEx;
156             }
157             logger.debug("Original ConversionService attempt failed - ignored since " +
158                     "PropertyEditor based conversion eventually succeeded", conversionAttemptEx);
159         }
160 
161         return (T) convertedValue;
162     }

我们再来看看binderFactory做了啥。

回到一开始的WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); 这个方法上来。
针对每次对该handlerMethod请求产生一个绑定工厂,由这个工厂来完成数据的绑定。 

 1     private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
 2         Class<?> handlerType = handlerMethod.getBeanType();
 3         Set<Method> methods = this.initBinderCache.get(handlerType);
 4         if (methods == null) {
 5             methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
 6             this.initBinderCache.put(handlerType, methods);
 7         }
 8         List<InvocableHandlerMethod> initBinderMethods = new ArrayList<InvocableHandlerMethod>();
 9         // Global methods first
10         for (Entry<ControllerAdviceBean, Set<Method>> entry : this.initBinderAdviceCache.entrySet()) {
11             if (entry.getKey().isApplicableToBeanType(handlerType)) {
12                 Object bean = entry.getKey().resolveBean();
13                 for (Method method : entry.getValue()) {
14                     initBinderMethods.add(createInitBinderMethod(bean, method));
15                 }
16             }
17         }
18         for (Method method : methods) {
19             Object bean = handlerMethod.getBean();
20             initBinderMethods.add(createInitBinderMethod(bean, method));
21         }
22         return createDataBinderFactory(initBinderMethods);
23     }

这里的HandlerMethod就是我们知道Handler。

methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);

这个方法是为了找该控制下所有加了@InitBinder的方法,并把他夹到缓存中。

从注解中可以看出第一个for循环则是从全局中合适的method。

第二个for是自己控制器内的method。

然后通过createDataBinderFactory方法创建工厂。

回到一开始的问题,我们只需要在控制器里定义一个被@InitBinder方法注解的方法(返回值必须为空),并注册自己的PropertyEditor类就可以实现正确处理Gender类型了。

 1 @InitBinder
 2     protected void initBinder(WebDataBinder binder) {
 3         binder.registerCustomEditor(Gender.class,new PropertiesEditor(){
 4             @Override 
 5             public void setAsText(String text) throws IllegalArgumentException {
 6                 Gender value= Gender.valueOf(text);
 7                 setValue(value);
 8             }
 9         });
10     }

我们可以通过制定注解的value属性限制需要参与转换的属性。

原文地址:https://www.cnblogs.com/insaneXs/p/7701898.html