博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringMVC请求处理之对方法参数的处理
阅读量:2350 次
发布时间:2019-05-10

本文共 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);

SpringMVC处理方法参数

  • 我们先看下上面ServletInvocableHandlerMethod实例的获取
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) {            List
resolvers = 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 List
getDefaultArgumentResolvers() { 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方法就一目了然了。

    RequestResponseBodyMethodProcessor

    @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    protected 
Object 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")    protected 
Object 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, List
supportedMediaTypes) { this(contentType, supportedMediaTypes, "Content type '" + contentType + "' not supported"); }

但是实际结果却是http 400 bad request。这个我们需要再看看到底是什么原因。

到这里,就把第一个方法的参数处理过程分析完了。

那么需要如何更改才能使得不报错了,我们可以不使用application/octet-stream这中MediaType来传输数据了,我们只要把他改成application/json,使用json格式来传递输出就可以使用处理Json格式的Convert来处理数据了,而处理完参数之后的操作我们留到以后再分析。

ServletModelAttributeMethodProcessor

@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        Map
bindingResultModel = binder.getBindingResult().getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); return binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter); }

这个过程的具体实现先不分析了,以后有空再细说,主要就是

通过DataBinder实例化了Employee对象,并写入了对应的属性,最后把这个实例对象返回给我们。

RequestParamMethodArgumentResolver

@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一样了。

@InitBinder注解

我们继续看方法四

@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(); Set
methods = 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/

你可能感兴趣的文章
VC++ UDP转TCP互发数据 UDP为服务端 TCP为客户端 可修改IP和端口最小化 2TCP/UDP中转
查看>>
仿养生网 帝国CMS 更新后域名栏目链接一直没变 解决方法:在后台地图--模板标签替换里直接全部替换
查看>>
微信扫码自动群发消息给所有人技术分析 之通过https请求获取微信网页版登录二维码图
查看>>
C#源码刷新网页 最小化托盘http get和post请求配置保存版权时间限制定时调用 单实例运行,如果已经运行则激活窗口到最前显示
查看>>
域名解密 商家联盟会员消费管理系统_连锁店会员积分系统 带微信(域名加密的) aqinxiaodian
查看>>
android蓝牙4.0BLE及2.0 2.1 apk 串口助手带16个自定义按键和自定义指令 字符接收 十六进制或字符发送
查看>>
爬虫采集 通用正则表达式
查看>>
织梦学习 变量的运用 添加新变量 删除新变量 添加上传视频mp4
查看>>
CocosCreator+VS2017提示“要求的 VS 版本:[2013, 2015, 2017]”解决办法 无法找到 v140_xp 的生成工具
查看>>
助学贷款系统导入预申请时问题解决办法汇总
查看>>
FTP连接阿里云不能获得列表目录等功能,能连接,21端口也打开了。原因FTP是双向的,阿里云入出方向安全组规则必须添加本地随机端口
查看>>
读书程序标准化建模--高效阅读学习,越学越有劲/趣
查看>>
不翻qiang搞定Android Studio Google库加载不下来的问题 打包生成apk android studio 3.2打灰机程序源码带详细注释
查看>>
仿照利用android系统源码资源文件,修改SeekBar颜色 前景与背景
查看>>
printf及String.format格式化测试
查看>>
android java 经典字符模式通信接收处理,标准modbus通讯协议接收处理提取数据
查看>>
10055自动进刀水钻机android蓝牙2.0SSP项目源码结构使用说明【版本更新、自动连接、控件批量处理、接收解析】
查看>>
Android Studio导入项目时常见问题的解决汇总,Eclipse项目转为Android Studio项目步骤报错万能解决方法汇总
查看>>
Widget.Material.Light.ProgressBar.Horizontal" (10302b8) is not a Drawable (color or path)错误解决
查看>>
解决java中文乱码,编码识别测试,汇总
查看>>