• 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属性限制需要参与转换的属性。

  • 相关阅读:
    python核心编程学习记录之基础知识
    VC++6.0出现no compile tool is associated with the extension.解决方法
    内存记号(Memory Trail)[自定义的名字] --调试方法
    Console 窗口
    C++ Builder创建和调用dll中的资源
    C++ builder 书籍推荐
    Qt书籍推荐
    消息队列数据结构
    Qt工程文件说明
    .obj : error LNK2001: unresolved external symbol "public: static unsigned long __stdcall ReadWrite::readData(void *)" (?readData@ReadWrite@@SGKPAX@Z)
  • 原文地址:https://www.cnblogs.com/insaneXs/p/7701898.html
Copyright © 2020-2023  润新知