本文共 28755 字,大约阅读时间需要 95 分钟。
讲完了DispatchServlet(也可以说是SpringMVC框架)的初始化之后,我们再接着看DispatchServlet处理请求的原理,也可以说是SpringMVC处理请求的原理。今天就先来看看SpringMVC对方法参数的处理。
我们先给出一个测试的类
package com.wangcc.controller;import java.util.Date;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import com.wangcc.entity.Player;@Controller// 不是以/开头的,springmvc会自动帮你添加/@RequestMapping("/test")public class TestController { @RequestMapping("testRb") @ResponseBody public Player testRb(@RequestBody Player player) { return player; } @RequestMapping("testEntity") @ResponseBody public Player testEntity(Player player) { return player; } @RequestMapping("testEntityWithRp") @ResponseBody public Player testEntityWithRp(@RequestParam Player player) { return player; } @RequestMapping("/testDate") @ResponseBody public Date testDate(Date date) { return date; }}
我们之前已经讲过,对带有@Controller注解的Bean以及其方法上有@RequestMapping注解的对应的url请求的处理,调用的是RequestMappingHandlerAdapter的invokeHandleMethod方法。
private ModelAndView invokeHandleMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest(request, response); WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory); ModelAndViewContainer mavContainer = new ModelAndViewContainer(); mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); modelFactory.initModel(webRequest, mavContainer, requestMappingMethod); mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response); asyncWebRequest.setTimeout(this.asyncRequestTimeout); final WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.setTaskExecutor(this.taskExecutor); asyncManager.setAsyncWebRequest(asyncWebRequest); asyncManager.registerCallableInterceptors(this.callableInterceptors); asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors); if (asyncManager.hasConcurrentResult()) { Object result = asyncManager.getConcurrentResult(); mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0]; asyncManager.clearConcurrentResult(); if (logger.isDebugEnabled()) { logger.debug("Found concurrent result value [" + result + "]"); } requestMappingMethod = requestMappingMethod.wrapConcurrentResult(result); } requestMappingMethod.invokeAndHandle(webRequest, mavContainer); if (asyncManager.isConcurrentHandlingStarted()) { return null; } return getModelAndView(mavContainer, modelFactory, webRequest); }
就是通过这个方法得到了ModelAndView实例,所以当完整的走完这个方法之后,也就对请求的处理的主干部分走完了。今天我们就来看这个方法的一小部分。
ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory); requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory); private ServletInvocableHandlerMethod createRequestMappingMethod( HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) { ServletInvocableHandlerMethod requestMethod; requestMethod = new ServletInvocableHandlerMethod(handlerMethod); requestMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); requestMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); requestMethod.setDataBinderFactory(binderFactory); requestMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); return requestMethod; }
1.使用我们在初始化RequestMappingHandlerMapping时注册到AbstractHandlerMapping的urlMap时封装的HandlerMethod实例handlerMethod为参数构建一个ServletInvocableHandlerMethod实例。
2.分别以RequestMappingHandlerAdapter的argumentResolvers和returnValueHandlers属性注入到requestMethod的argumentResolvers属性和returnValueHandlers属性中。
这里需要讲解下RequestMappingHandlerAdapter的argumentResolvers和returnValueHandlers怎么得到的以及内容是什么。
我们在讲解RequestMappingHandlerMapping的时候提到了InitializingBean接口,而我们发现RequestMappingHandlerAdapter也实现了这个接口,那么我们就知道了在初始化这个类的时候是需要执行他的afterPropertiesSet方法,而这两个属性的注入就是在这个方法里完成的。
afterPropertiesSet
public void afterPropertiesSet() { // Do this first, it may add ResponseBody advice beans initControllerAdviceCache(); if (this.argumentResolvers == null) { Listresolvers = getDefaultArgumentResolvers(); this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } if (this.initBinderArgumentResolvers == null) { List resolvers = getDefaultInitBinderArgumentResolvers(); this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } if (this.returnValueHandlers == null) { List handlers = getDefaultReturnValueHandlers(); this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers); } }
我们先通过getDefaultArgumentResolvers得到一个HandlerMethodArgumentResolver集合
private ListgetDefaultArgumentResolvers() { List resolvers = new ArrayList (); // Annotation-based argument resolution resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false)); resolvers.add(new RequestParamMapMethodArgumentResolver()); resolvers.add(new PathVariableMethodArgumentResolver()); resolvers.add(new PathVariableMapMethodArgumentResolver()); resolvers.add(new MatrixVariableMethodArgumentResolver()); resolvers.add(new MatrixVariableMapMethodArgumentResolver()); resolvers.add(new ServletModelAttributeMethodProcessor(false)); resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters())); resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters())); resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory())); resolvers.add(new RequestHeaderMapMethodArgumentResolver()); resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory())); resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory())); // Type-based argument resolution resolvers.add(new ServletRequestMethodArgumentResolver()); resolvers.add(new ServletResponseMethodArgumentResolver()); resolvers.add(new HttpEntityMethodProcessor(getMessageConverters())); resolvers.add(new RedirectAttributesMethodArgumentResolver()); resolvers.add(new ModelMethodProcessor()); resolvers.add(new MapMethodProcessor()); resolvers.add(new ErrorsMethodArgumentResolver()); resolvers.add(new SessionStatusMethodArgumentResolver()); resolvers.add(new UriComponentsBuilderMethodArgumentResolver()); // Custom arguments if (getCustomArgumentResolvers() != null) { resolvers.addAll(getCustomArgumentResolvers()); } // Catch-all resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true)); resolvers.add(new ServletModelAttributeMethodProcessor(true)); return resolvers; }
然后将这个集合放入到HandlerMethodArgumentResolverComposite实例中。然后把这个实例赋给argumentResolvers属性。
那么returnValueHandlers属性同理。
接着分析requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); setResponseStatus(webRequest); if (returnValue == null) { if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) { mavContainer.setRequestHandled(true); return; } } else if (StringUtils.hasText(this.responseReason)) { mavContainer.setRequestHandled(true); return; } mavContainer.setRequestHandled(false); try { this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex); } throw ex; }}
我们看下第一行
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
这一行就已经得到了这个方法的返回值了。我们进入这个方法看。
public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { StringBuilder sb = new StringBuilder("Invoking ["); sb.append(getBeanType().getSimpleName()).append("."); sb.append(getMethod().getName()).append("] method with arguments "); sb.append(Arrays.asList(args)); logger.trace(sb.toString()); } Object returnValue = doInvoke(args); if (logger.isTraceEnabled()) { logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]"); } return returnValue;}
我们发现第一行就是对方法参数的处理,嗯,终于找到我们今天要重点讲解的地方了。
private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { MethodParameter[] parameters = getMethodParameters(); Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); GenericTypeResolver.resolveParameterType(parameter, getBean().getClass()); args[i] = resolveProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } if (this.argumentResolvers.supportsParameter(parameter)) { try { args[i] = this.argumentResolvers.resolveArgument( parameter, mavContainer, request, this.dataBinderFactory); continue; } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(getArgumentResolutionErrorMessage("Error resolving argument", i), ex); } throw ex; } } if (args[i] == null) { String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i); throw new IllegalStateException(msg); } } return args;}
1.先通过this.argumentResolvers.supportsParameter(parameter)来找到能处理该方法参数的HandlerMethodArgumentResolver实例。
2.然后通过this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);来调用HandlerMethodArgumentResolver实例的resolveArgument方法来处理参数。
supportsParameter
HandlerMethodArgumentResolverComposite
@Overridepublic boolean supportsParameter(MethodParameter parameter) { return getArgumentResolver(parameter) != null;}private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) { HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter); if (result == null) { for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) { if (logger.isTraceEnabled()) { logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" + parameter.getGenericParameterType() + "]"); } if (methodArgumentResolver.supportsParameter(parameter)) { result = methodArgumentResolver; this.argumentResolverCache.put(parameter, result); break; } } } return result;}
这段代码不难理解,就是在我们开始讲解的注入的HandlerMethodArgumentResolver集合里面筛选出能够处理参数的实例。
我们以这个方法为例:
@RequestMapping("testRb")@ResponseBodypublic Player testRb(@RequestBody Player player) { return player;}
相应的实例就是RequestResponseBodyMethodProcessor,我们瞅一眼他的supportsParameter方法就一目了然了。
@Overridepublic boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(RequestBody.class);}
所以参数上有@RequestBody注解的都会使用这个实例来处理参数。
接着看看是如何调用resolveArgument方法的
@Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter); Assert.notNull(resolver, "Unknown parameter type [" + parameter.getParameterType().getName() + "]"); return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); }
所以调用的就是RequestResponseBodyMethodProcessor的resolveArgument方法了。
resolveArgument
@Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { Object arg = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType()); String name = Conventions.getVariableNameForParameter(parameter); WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name); if (arg != null) { validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new MethodArgumentNotValidException(parameter, binder.getBindingResult()); } } mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); return arg; }
我们先使用readWithMessageConverters来处理参数
readWithMessageConverters
@Override protectedObject readWithMessageConverters(NativeWebRequest webRequest, MethodParameter methodParam, Type paramType) throws IOException, HttpMediaTypeNotSupportedException { HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); HttpInputMessage inputMessage = new ServletServerHttpRequest(servletRequest); InputStream inputStream = inputMessage.getBody(); if (inputStream == null) { return handleEmptyBody(methodParam); } else if (inputStream.markSupported()) { inputStream.mark(1); if (inputStream.read() == -1) { return handleEmptyBody(methodParam); } inputStream.reset(); } else { final PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream); int b = pushbackInputStream.read(); if (b == -1) { return handleEmptyBody(methodParam); } else { pushbackInputStream.unread(b); } inputMessage = new ServletServerHttpRequest(servletRequest) { @Override public InputStream getBody() { // Form POST should not get here return pushbackInputStream; } }; } return super.readWithMessageConverters(inputMessage, methodParam, paramType); }
在对数据做一些封装处理后,最后会调用父类AbstractMessageConverterMethodArgumentResolver的readWithMessageConverters方法
@SuppressWarnings("unchecked") protectedObject readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter methodParam, Type targetType) throws IOException, HttpMediaTypeNotSupportedException { MediaType contentType; try { contentType = inputMessage.getHeaders().getContentType(); } catch (InvalidMediaTypeException ex) { throw new HttpMediaTypeNotSupportedException(ex.getMessage()); } if (contentType == null) { contentType = MediaType.APPLICATION_OCTET_STREAM; } Class contextClass = methodParam.getContainingClass(); Class targetClass = (Class ) ResolvableType.forMethodParameter(methodParam, targetType).resolve(Object.class); for (HttpMessageConverter converter : this.messageConverters) { if (converter instanceof GenericHttpMessageConverter) { GenericHttpMessageConverter genericConverter = (GenericHttpMessageConverter ) converter; if (genericConverter.canRead(targetType, contextClass, contentType)) { if (logger.isDebugEnabled()) { logger.debug("Reading [" + targetType + "] as \"" + contentType + "\" using [" + converter + "]"); } return genericConverter.read(targetType, contextClass, inputMessage); } } if (converter.canRead(targetClass, contentType)) { if (logger.isDebugEnabled()) { logger.debug("Reading [" + targetClass.getName() + "] as \"" + contentType + "\" using [" + converter + "]"); } return ((HttpMessageConverter ) converter).read(targetClass, inputMessage); } } throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes); }
这里会在messageConverters集合中选择一个合适的HttpMessageConverter来处理数据,这里的messageConverters就是RequestMappingHandlerAdapter中的属性,该属性的注入具体在SpringMVC配置文件解析(六)中有说明。
而RequestResponseBodyMethodProcessor是在初始化的时候注入messageConverters属性的,回头看getDefaultArgumentResolvers,有一句
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters()));
但是遗憾的是,我们在所有的messageConverters集合中都找不到能够处理这个数据的HttpMessageConverter,其中ByteArrayHttpMessageConverter能处理这种MediaType(使用get方式的的MediaType是application/octet-stream),但是他只支持byte类型,而我们这里的参数是Player这种自定义类型。
没有任何的HttpMessageConverter可以处理,所以就导致了报错,本来按照程序应该是报如下错误。
public HttpMediaTypeNotSupportedException(MediaType contentType, ListsupportedMediaTypes) { this(contentType, supportedMediaTypes, "Content type '" + contentType + "' not supported"); }
但是实际结果却是http 400 bad request。这个我们需要再看看到底是什么原因。
到这里,就把第一个方法的参数处理过程分析完了。
那么需要如何更改才能使得不报错了,我们可以不使用application/octet-stream这中MediaType来传输数据了,我们只要把他改成application/json,使用json格式来传递输出就可以使用处理Json格式的Convert来处理数据了,而处理完参数之后的操作我们留到以后再分析。
@RequestMapping("testEntity") @ResponseBody public Player testEntity(Player player) { return player; }
我们接着看第二个方法,先还是在HandlerMethodArgumentResolverComposite的supportsParameter方法中筛选出适合的HandlerMethodArgumentResolver来处理。
得到的答案是ServletModelAttributeMethodProcessor
他的注册方式如下:
resolvers.add(new ServletModelAttributeMethodProcessor(false)); resolvers.add(new ServletModelAttributeMethodProcessor(true));
我们看看这个类先
public ServletModelAttributeMethodProcessor(boolean annotationNotRequired) { super(annotationNotRequired); } //ModelAttributeMethodProcessor public ModelAttributeMethodProcessor(boolean annotationNotRequired) { this.annotationNotRequired = annotationNotRequired; }
初始化ServletModelAttributeMethodProcessor时,会调用父类ModelAttributeMethodProcessor的骨构造方法,而且supportsParameter也是在父类中,我们看看这个方法。
@Override public boolean supportsParameter(MethodParameter parameter) { if (parameter.hasParameterAnnotation(ModelAttribute.class)) { return true; } else if (this.annotationNotRequired) { return !BeanUtils.isSimpleProperty(parameter.getParameterType()); } else { return false; } }
如果参数是有@ModelAttribute注解的就支持,如果没有这个注解,当构造方法的实参是true时,如果Method的参数类型不是简单类型也支持,因为有一个实参为true的ServletModelAttributeMethodProcessor被注册,并且Method的参数类型是Player,不是简单类型,所以符合。
直接看resolveArgument方法,这个方法的实现还是在父类中
@Override public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { String name = ModelFactory.getNameForParameter(parameter); Object attribute = (mavContainer.containsAttribute(name) ? mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, webRequest)); WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); if (binder.getTarget() != null) { bindRequestParameters(binder, webRequest); validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new BindException(binder.getBindingResult()); } } // Add resolved attribute and BindingResult at the end of the model MapbindingResultModel = binder.getBindingResult().getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); return binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter); }
这个过程的具体实现先不分析了,以后有空再细说,主要就是
通过DataBinder实例化了Employee对象,并写入了对应的属性,最后把这个实例对象返回给我们。
@RequestMapping("testEntityWithRp") @ResponseBody public Player testEntityWithRp(@RequestParam Player player) { return player; }
还是一样,通过筛选,得到了对应的HandlerMethodArgumentResolver是RequestParamMethodArgumentResolver,对应的注册代码
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false)); resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
看看他的构造方法
public RequestParamMethodArgumentResolver(ConfigurableBeanFactory beanFactory, boolean useDefaultResolution) { super(beanFactory); this.useDefaultResolution = useDefaultResolution; } public AbstractNamedValueMethodArgumentResolver(ConfigurableBeanFactory beanFactory) { this.configurableBeanFactory = beanFactory; this.expressionContext = (beanFactory != null ? new BeanExpressionContext(beanFactory, new RequestScope()) : null); }
实例化的时候调用了父类AbstractNamedValueMethodArgumentResolver的构造方法。
supportsParameter方法在本类中实现,resolveArgument在父类中实现。
先看supportsParameter
@Override public boolean supportsParameter(MethodParameter parameter) { Class paramType = parameter.getParameterType(); if (parameter.hasParameterAnnotation(RequestParam.class)) { if (Map.class.isAssignableFrom(paramType)) { String paramName = parameter.getParameterAnnotation(RequestParam.class).value(); return StringUtils.hasText(paramName); } else { return true; } } else { if (parameter.hasParameterAnnotation(RequestPart.class)) { return false; } else if (MultipartFile.class.equals(paramType) || "javax.servlet.http.Part".equals(paramType.getName())) { return true; } else if (this.useDefaultResolution) { return BeanUtils.isSimpleProperty(paramType); } else { return false; } } }
当Method参数有@RequestParam注解时,如果此时参数实现了Map接口的时候,@RequestParam注解需要具有value属性,否则不支持,如果有@RequestPart注解,不支持,如果参数是简单类型,支持。如果是MultipartFile类型,且是javax.servlet.http.Part,支持。
显然我们这个方法符合这个要求。我们接着看resolveArgument
@Override public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { Class paramType = parameter.getParameterType(); NamedValueInfo namedValueInfo = getNamedValueInfo(parameter); Object arg = resolveName(namedValueInfo.name, parameter, webRequest); if (arg == null) { if (namedValueInfo.defaultValue != null) { arg = resolveDefaultValue(namedValueInfo.defaultValue); } else if (namedValueInfo.required && !parameter.getParameterType().getName().equals("java.util.Optional")) { handleMissingValue(namedValueInfo.name, parameter); } arg = handleNullValue(namedValueInfo.name, arg, paramType); } else if ("".equals(arg) && namedValueInfo.defaultValue != null) { arg = resolveDefaultValue(namedValueInfo.defaultValue); } if (binderFactory != null) { WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name); arg = binder.convertIfNecessary(arg, paramType, parameter); } handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest); return arg; }
仔细阅读源码,你会发现在处理参数的时候会使用request.getParameter(参数名)即request.getParameter(“player”)得到,很明显我们的参数传的是name=1&age=3。因此得到null,RequestParamMethodArgumentResolver处理missing value会触发MissingServletRequestParameterException异常。
那需要如何处理呢,很简单,去掉@RequestParam注解就好了,这样就给方法2一样了。
我们继续看方法四
@RequestMapping("/testDate") @ResponseBody public Date testDate(Date date) { return date; }
我们用这样的链接去调用,会返回错误,400 bad request。为什么呢?来分析一下。
上面我们分析过RequestParamMethodArgumentResolver和ServletModelAttributeMethodProcessor,他们一个支持简单类型一个支持非简单类型,那么这里的Date类型到底是简单类型还是非简单类型呢,看下BeanUtils.isSimpleProperty(paramType);的源码就知道了,他属于简单类型,所以使用的是RequestParamMethodArgumentResolver。这时我们使用request.getParameter(“date”)得到了日期字符串,到这里还是一切正常的,但是后面的使用DataBinder找到合适的属性编辑器进行类型转换时,最终找到java.util.Date对象的构造函数 public Date(String s),而由于我们传递的格式不是标准的UTC时间格式,因此最终触发了IllegalArgumentException异常。
所以要解决这个问题的方法最简单就是把日期格式设置为标准的UTC时间格式,但是这样并不符合我们的日常习惯,我们肯定是想能够使用请求中的那种日期格式的,那我们能怎么办呢。其实要实现这个功能并不难。在TestController中添加如下代码
@InitBinderpublic void initBinder(WebDataBinder binder) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));}
是的只要添加这几行代码就可以了。
那到底是为什么呢?
@InitBinder注解在实例化ServletInvocableHandlerMethod的时候被注入到WebDataBinderFactory中的,而WebDataBinderFactory是ServletInvocableHandlerMethod的一个属性。在RequestMappingHandlerAdapter的invokeHandleMethod方法中的getDataBinderFactory就是得到的WebDataBinderFactory。我们来把目光转向getDataBinderFactory方法
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception { Class handlerType = handlerMethod.getBeanType(); Setmethods = this.initBinderCache.get(handlerType); if (methods == null) { methods = HandlerMethodSelector.selectMethods(handlerType, INIT_BINDER_METHODS); this.initBinderCache.put(handlerType, methods); } List initBinderMethods = new ArrayList (); // Global methods first for (Entry > entry : this.initBinderAdviceCache .entrySet()) { if (entry.getKey().isApplicableToBeanType(handlerType)) { Object bean = entry.getKey().resolveBean(); for (Method method : entry.getValue()) { initBinderMethods.add(createInitBinderMethod(bean, method)); } } } for (Method method : methods) { Object bean = handlerMethod.getBean(); initBinderMethods.add(createInitBinderMethod(bean, method)); } return createDataBinderFactory(initBinderMethods); }
这个方法就是筛选出有@InitBinder注解的方法,将其注入到DataBinderFactory中。
我们需要重点关注的就是
methods = HandlerMethodSelector.selectMethods(handlerType, INIT_BINDER_METHODS); public static final MethodFilter INIT_BINDER_METHODS = new MethodFilter() { @Override public boolean matches(Method method) { return AnnotationUtils.findAnnotation(method, InitBinder.class) != null; } };
筛选出有@InitBinder注解的方法。
之后RequestParamMethodArgumentResolver通过WebDataBinderFactory创建的WebDataBinder里的自定义属性编辑器找到合适的属性编辑器(我们自定义的属性编辑器是用CustomDateEditor处理Date对象,而testDate的参数刚好是Date),最终CustomDateEditor把这个String对象转换成Date对象。
转载地址:http://bdmvb.baihongyu.com/