博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
详解SpringMVC中Controller的方法中参数的工作原理
阅读量:5973 次
发布时间:2019-06-19

本文共 20559 字,大约阅读时间需要 68 分钟。

Spring MVC中Controller的处理方法的参数可以是Integer,String,自定义对象,ServletRequest,ServletResponse,ModelAndView等等,非常灵活。本文将分析SpringMVC是如何对这些参数进行处理的,使读者能够处理自定义的一些参数。

先来看几个示例:

@Controllerpublic class UserController {    @RequestMapping(value = "/user/testRequestBody")    @ResponseBody    public User testRequestBody(@RequestBody User user) {        return user;    }    @RequestMapping(value = "/user/testCustomObj")    @ResponseBody    public User testCustomObj(User user) {        return user;    }    @RequestMapping(value = "/user/testRequestParam")    @ResponseBody    public User testRequestParam(@RequestParam User user) {        return user;    }    @RequestMapping("/user/testDate")    @ResponseBody    public Date testDate(Date date) {        return date;    }}

首先这是一个Controller,有4个方法。他们对应的参数分别是带有@RequestBody的自定义对象、自定义对象、带有@RequestParam的自定义对象、日期对象。

接下来我们一个一个方法进行访问看对应的现象是如何的。

首先第一个testRequestBody:

第二个testCustomObj:

 

第三个testRequestParam:

 

第四个testDate:

 

为何User参数会被解析,带有@RequestParam的User参数不会被解析,甚至报错?

为何日期类型不能被解析?

SpringMVC到底是如何处理这些方法的参数的?

@RequestBody、@RequestParam这两个注解有什么区别?

带着这几个问题。我们开始进行分析。

在分析源码之前,首先让我们来看下SpringMVC中两个重要的接口。

两个接口分别对应请求方法参数的处理、响应返回值的处理,分别是HandlerMethodArgumentResolverHandlerMethodReturnValueHandler

/** * Strategy interface for resolving method parameters into argument values in * the context of a given request. */public interface HandlerMethodArgumentResolver {    /**     * Whether the given {
@linkplain MethodParameter method parameter} is * supported by this resolver. */ boolean supportsParameter(MethodParameter parameter); /** * Resolves a method parameter into an argument value from a given request. */ Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;}
/** * Strategy interface to handle the value returned from the invocation of a * handler method . */public interface HandlerMethodReturnValueHandler {    /**     * Whether the given {
@linkplain MethodParameter method return type} is * supported by this handler. */ boolean supportsReturnType(MethodParameter returnType); /** * Handle the given return value by adding attributes to the model and * setting a view or setting the * {
@link ModelAndViewContainer#setRequestHandled} flag to {
@code true} * to indicate the response has been handled directly. */ void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;}

SpringMVC处理请求大致是这样的:

首先被DispatcherServlet截获,DispatcherServlet通过handlerMapping获得HandlerExecutionChain,然后请求HandlerAdapter。

HandlerAdapter在内部对于每个请求,都会实例化一个ServletInvocableHandlerMethod进行处理,ServletInvocableHandlerMethod在进行处理的时候,会分两部分别对请求跟响应进行处理

之后HandlerAdapter得到ModelAndView,然后做相应的处理。

本文将重点介绍ServletInvocableHandlerMethod对请求以及响应的处理。

/** * Invokes the method and handles the return value through one of the * configured {
@link HandlerMethodReturnValueHandler}s. */public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { /** * Invoke the method after resolving its argument values in the context of the given request. * Argument values are commonly resolved through HandlerMethodArgumentResolvers. */ 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 { /** * Handle the given return value by adding attributes to the model and * setting a view or setting the ModelAndViewContainer#setRequestHandled flag to true * to indicate the response has been handled directly. */ 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; }}

1. 处理请求的时候,会根据ServletInvocableHandlerMethod的属性argumentResolvers(这个属性是它的父类InvocableHandlerMethod中定义的)进行处理,其中argumentResolvers属性是一个HandlerMethodArgumentResolverComposite类(这里使用了组合模式的一种变形),这个类是实现了HandlerMethodArgumentResolver接口的类,里面有各种实现了HandlerMethodArgumentResolver的List集合。

public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {    protected final Log logger = LogFactory.getLog(getClass());    private final List
argumentResolvers = new LinkedList
(); private final Map
argumentResolverCache = new ConcurrentHashMap
(256);}

2. 处理响应的时候,会根据ServletInvocableHandlerMethod的属性returnValueHandlers(自身属性)进行处理,returnValueHandlers属性是一个HandlerMethodReturnValueHandlerComposite类(这里使用了组合模式的一种变形),这个类是实现了HandlerMethodReturnValueHandler接口的类,里面有各种实现了HandlerMethodReturnValueHandler的List集合。

public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {    protected final Log logger = LogFactory.getLog(getClass());    private final List
returnValueHandlers = new ArrayList
();}

ServletInvocableHandlerMethod的returnValueHandlers和argumentResolvers这两个属性都是在ServletInvocableHandlerMethod进行实例化的时候被赋值的(使用RequestMappingHandlerAdapter的属性进行赋值)。

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;}

RequestMappingHandlerAdapter的argumentResolvers和returnValueHandlers这两个属性是在RequestMappingHandlerAdapter进行实例化的时候被Spring容器注入的。

@Overridepublic 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); }}

其中默认的ArgumentResolvers:

/** * Return the list of argument resolvers to use including built-in resolvers * and custom resolvers provided via {
@link #setCustomArgumentResolvers}. */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;}

默认的returnValueHandlers:

/** * Return the list of return value handlers to use including built-in and * custom handlers provided via {
@link #setReturnValueHandlers}. */private List
getDefaultReturnValueHandlers() { List
handlers = new ArrayList
(); // Single-purpose return value types handlers.add(new ModelAndViewMethodReturnValueHandler()); handlers.add(new ModelMethodProcessor()); handlers.add(new ViewMethodReturnValueHandler()); handlers.add(new HttpEntityMethodProcessor( getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice)); handlers.add(new HttpHeadersReturnValueHandler()); handlers.add(new CallableMethodReturnValueHandler()); handlers.add(new DeferredResultMethodReturnValueHandler()); handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory)); handlers.add(new ListenableFutureReturnValueHandler()); // Annotation-based return value types handlers.add(new ModelAttributeMethodProcessor(false)); handlers.add(new RequestResponseBodyMethodProcessor( getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice)); // Multi-purpose return value types handlers.add(new ViewNameMethodReturnValueHandler()); handlers.add(new MapMethodProcessor()); // Custom return value types if (getCustomReturnValueHandlers() != null) { handlers.addAll(getCustomReturnValueHandlers()); } // Catch-all if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) { handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers())); } else { handlers.add(new ModelAttributeMethodProcessor(true)); } return handlers;}

使用@ResponseBody注解的话最终返回值会被RequestResponseBodyMethodProcessor这个HandlerMethodReturnValueHandler实现类处理。

我们通过源码发现,RequestResponseBodyMethodProcessor这个类其实同时实现了HandlerMethodReturnValueHandler和HandlerMethodArgumentResolver这两个接口。

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {    @Override    public boolean supportsParameter(MethodParameter parameter) {        return parameter.hasParameterAnnotation(RequestBody.class);    }    @Override    public boolean supportsReturnType(MethodParameter returnType) {        return (AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null ||                returnType.getMethodAnnotation(ResponseBody.class) != null);    }}

RequestResponseBodyMethodProcessor支持的请求类型是Controller方法参数中带有@RequestBody注解,支持的响应类型是Controller方法带有@ResponseBody注解。 

@Overridepublic void handleReturnValue(Object returnValue, MethodParameter returnType,        ModelAndViewContainer mavContainer, NativeWebRequest webRequest)        throws IOException, HttpMediaTypeNotAcceptableException {    mavContainer.setRequestHandled(true);    // Try even with null return value. ResponseBodyAdvice could get involved.    writeWithMessageConverters(returnValue, returnType, webRequest);}

RequestResponseBodyMethodProcessor响应的具体处理是使用消息转换器。

@Overridepublic Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,        NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {    Object argument = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());    String name = Conventions.getVariableNameForParameter(parameter);    WebDataBinder binder = binderFactory.createBinder(webRequest, argument, name);    if (argument != null) {        validate(binder, parameter);    }    mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());    return argument;}

处理请求的时候使用内部的readWithMessageConverters方法。

@Overrideprotected 
Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter methodParam, Type paramType) throws IOException, HttpMediaTypeNotSupportedException { final 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() throws IOException { // Form POST should not get here return pushbackInputStream; } }; } return super.readWithMessageConverters(inputMessage, methodParam, paramType);}private Object handleEmptyBody(MethodParameter param) { if (param.getParameterAnnotation(RequestBody.class).required()) { throw new HttpMessageNotReadableException("Required request body content is missing: " + param); } return null;}

然后会执行父类(AbstractMessageConverterMethodArgumentResolver)的readWithMessageConverters方法。

/** * Create the method argument value of the expected parameter type by reading * from the given HttpInputMessage.*/@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(); 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); } } Class
targetClass = (Class
) ResolvableType.forMethodParameter(methodParam, targetType).resolve(Object.class); 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);}

下面来我们来看看常用的HandlerMethodArgumentResolver实现类(本文粗略讲下,有兴趣的读者可自行研究)。

1. RequestParamMethodArgumentResolver

 支持带有@RequestParam注解的参数或带有MultipartFile类型的参数

2. RequestParamMapMethodArgumentResolver

  支持带有@RequestParam注解的参数 && @RequestParam注解的属性value存在 && 参数类型是实现Map接口的属性

3. PathVariableMethodArgumentResolver

支持带有@PathVariable注解的参数 且如果参数实现了Map接口,@PathVariable注解需带有value属性

4. MatrixVariableMethodArgumentResolver

支持带有@MatrixVariable注解的参数 且如果参数实现了Map接口,@MatrixVariable注解需带有value属性 

5. RequestResponseBodyMethodProcessor

 本文已分析过

6. ServletRequestMethodArgumentResolver

 参数类型是实现或继承或是WebRequest、ServletRequest、MultipartRequest、HttpSession、Principal、Locale、TimeZone、InputStream、Reader、HttpMethod这些类。

(这就是为何我们在Controller中的方法里添加一个HttpServletRequest参数,Spring会为我们自动获得HttpServletRequest对象的原因)

7. ServletResponseMethodArgumentResolver

 参数类型是实现或继承或是ServletResponse、OutputStream、Writer这些类

8. RedirectAttributesMethodArgumentResolver

 参数是实现了RedirectAttributes接口的类

9. HttpEntityMethodProcessor

 参数类型是HttpEntity

从名字我们也看的出来, 以Resolver结尾的是实现了HandlerMethodArgumentResolver接口的类,以Processor结尾的是实现了HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler的类。

 

下面来我们来看看常用的HandlerMethodReturnValueHandler实现类。

1. ModelAndViewMethodReturnValueHandler

返回值类型是ModelAndView或其子类

2. ModelMethodProcessor

返回值类型是Model或其子类

3. ViewMethodReturnValueHandler

返回值类型是View或其子类 

4. HttpHeadersReturnValueHandler

返回值类型是HttpHeaders或其子类  

5. ModelAttributeMethodProcessor

返回值有@ModelAttribute注解

6. ViewNameMethodReturnValueHandler

返回值是void或String

其余没讲过的读者可自行查看源码。

下面开始解释为何本文开头出现那些现象的原因:

1. 第一个方法testRequestBody以及地址 http://localhost:8080/user/testRequestBody?name=zhangsan&age=10

这个方法的参数使用了@RequestBody,被RequestResponseBodyMethodProcessor进行处理。之后根据http请求头部的contentType然后选择合适的消息转换器进行读取。

很明显,我们的消息转换器只有默认的那些跟部分json以及xml转换器,且传递的参数name=1&age=3,传递的头部中没有content-type,默认使用了application/octet-stream,因此触发了HttpMediaTypeNotSupportedException异常

解放方案: 我们将传递数据改成json,同时http请求的Content-Type改成application/json即可(post请求?)。

2. testCustomObj方法以及地址 http://localhost:8080/user/testCustomObj?name=zhangsan&age=10

这个请求会找到ServletModelAttributeMethodProcessor这个resolver。默认的resolver中有两个ServletModelAttributeMethodProcessor,只不过实例化的时候属性annotationNotRequired一个为true,1个为false。这个ServletModelAttributeMethodProcessor处理参数支持@ModelAttribute注解,annotationNotRequired属性为true的话,参数不是简单类型就通过,因此选择了ServletModelAttributeMethodProcessor,最终通过DataBinder实例化User对象,并写入对应的属性。

3 testRequestParam方法以及地址 http://localhost:8080/user/testRequestParam?name=zhangsan&age=10

这个请求会找到RequestParamMethodArgumentResolver(使用了@RequestParam注解)。RequestParamMethodArgumentResolver在处理参数的时候使用request.getParameter(参数名)即request.getParameter("user")得到,很明显我们的参数传的是name=zhangsan&age=10。因此得到null,RequestParamMethodArgumentResolver处理missing value会触发MissingServletRequestParameterException异常。 

解决方案:去掉@RequestParam注解,让ServletModelAttributeMethodProcessor来处理。

4. testDate方法以及地址 http://localhost:8080/user/testDate?date=2014-05-15

这个请求会找到RequestParamMethodArgumentResolver。因为这个方法与第二个方法一样,有两个RequestParamMethodArgumentResolver,属性useDefaultResolution不同。RequestParamMethodArgumentResolver支持简单类型,ServletModelAttributeMethodProcessor是支持非简单类型。最终步骤跟第三个方法一样,我们的参数名是date,于是通过request.getParameter("date")找到date字符串(这里参数名如果不是date,那么最终页面是空白的,因为没有@RequestParam注解,参数不是必须的,RequestParamMethodArgumentResolver处理null值返回null)。最后通过DataBinder找到合适的属性编辑器进行类型转换。最终找到java.util.Date对象的构造函数 public Date(String s),由于我们传递的格式不是标准的UTC时间格式,因此最终触发了IllegalArgumentException异常。

 Ref:

https://www.cnblogs.com/fangjian0423/p/springMVC-request-param-analysis.html

你可能感兴趣的文章
Alluxio在多级分布式缓存系统中的应用
查看>>
依赖类型语言Idris发布1.0版本
查看>>
秒杀业务架构优化之路
查看>>
简析Uber的可伸缩监控:uMonitor和Neris
查看>>
Roman to Integer
查看>>
Stimulsoft报表控件 v2016.1版本全线更新【附下载】
查看>>
[LintCode] Count 1 in Binary [典型位运算题目]
查看>>
微信公众平台JSSDK分享接口开发(PHP实现)
查看>>
JavaScript原型和继承
查看>>
AI切入养老大健康,漫谷科技发布“正钛” 健康服务机器人 ...
查看>>
Java Spring AOP的两种配置方式
查看>>
Magix中的缓存模块
查看>>
xinetd被动服务唤醒
查看>>
iView 3.4.1 发布,基于 Vue.js 的企业级 UI 组件库
查看>>
HoloLens获得新专利,可共享空间地图提升扫描建模效率
查看>>
让SAP云平台上的Web应用使用destination服务
查看>>
淘宝未来3年核心方向曝光!新的万亿级市场正在爆发
查看>>
有道词典命令行查询工具(Mac/Ubuntu)
查看>>
orm2 中文文档 3. 定义模型
查看>>
在业务过程中使用区块链和智能合约
查看>>