在工作中,经常会出现前台的请求参数由于无法被正常转型,导致请求无法进到后台的问题。
比如,我有一个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属性限制需要参与转换的属性。