详解SpringMVC中Controller的方法中参数的工作原理
2021-05-16 18:27
标签:container eve mod mmap bool orm springmvc EDA form Spring MVC中Controller的处理方法的参数可以是Integer,String,自定义对象,ServletRequest,ServletResponse,ModelAndView等等,非常灵活。本文将分析SpringMVC是如何对这些参数进行处理的,使读者能够处理自定义的一些参数。 先来看几个示例: 首先这是一个Controller,有4个方法。他们对应的参数分别是带有@RequestBody的自定义对象、自定义对象、带有@RequestParam的自定义对象、日期对象。 接下来我们一个一个方法进行访问看对应的现象是如何的。 首先第一个testRequestBody:
第二个testCustomObj: 第三个testRequestParam: 第四个testDate: 为何User参数会被解析,带有@RequestParam的User参数不会被解析,甚至报错? 为何日期类型不能被解析? SpringMVC到底是如何处理这些方法的参数的? @RequestBody、@RequestParam这两个注解有什么区别? 带着这几个问题。我们开始进行分析。 在分析源码之前,首先让我们来看下SpringMVC中两个重要的接口。 两个接口分别对应请求方法参数的处理、响应返回值的处理,分别是HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler SpringMVC处理请求大致是这样的: 首先被DispatcherServlet截获,DispatcherServlet通过handlerMapping获得HandlerExecutionChain,然后请求HandlerAdapter。 HandlerAdapter在内部对于每个请求,都会实例化一个ServletInvocableHandlerMethod进行处理,ServletInvocableHandlerMethod在进行处理的时候,会分两部分别对请求跟响应进行处理。 之后HandlerAdapter得到ModelAndView,然后做相应的处理。 本文将重点介绍ServletInvocableHandlerMethod对请求以及响应的处理。 1. 处理请求的时候,会根据ServletInvocableHandlerMethod的属性argumentResolvers(这个属性是它的父类InvocableHandlerMethod中定义的)进行处理,其中argumentResolvers属性是一个HandlerMethodArgumentResolverComposite类(这里使用了组合模式的一种变形),这个类是实现了HandlerMethodArgumentResolver接口的类,里面有各种实现了HandlerMethodArgumentResolver的List集合。 2. 处理响应的时候,会根据ServletInvocableHandlerMethod的属性returnValueHandlers(自身属性)进行处理,returnValueHandlers属性是一个HandlerMethodReturnValueHandlerComposite类(这里使用了组合模式的一种变形),这个类是实现了HandlerMethodReturnValueHandler接口的类,里面有各种实现了HandlerMethodReturnValueHandler的List集合。 ServletInvocableHandlerMethod的returnValueHandlers和argumentResolvers这两个属性都是在ServletInvocableHandlerMethod进行实例化的时候被赋值的(使用RequestMappingHandlerAdapter的属性进行赋值)。 RequestMappingHandlerAdapter的argumentResolvers和returnValueHandlers这两个属性是在RequestMappingHandlerAdapter进行实例化的时候被Spring容器注入的。 其中默认的ArgumentResolvers: 默认的returnValueHandlers: 使用@ResponseBody注解的话最终返回值会被RequestResponseBodyMethodProcessor这个HandlerMethodReturnValueHandler实现类处理。 我们通过源码发现,RequestResponseBodyMethodProcessor这个类其实同时实现了HandlerMethodReturnValueHandler和HandlerMethodArgumentResolver这两个接口。 RequestResponseBodyMethodProcessor支持的请求类型是Controller方法参数中带有@RequestBody注解,支持的响应类型是Controller方法带有@ResponseBody注解。 RequestResponseBodyMethodProcessor响应的具体处理是使用消息转换器。 处理请求的时候使用内部的readWithMessageConverters方法。 然后会执行父类(AbstractMessageConverterMethodArgumentResolver)的readWithMessageConverters方法。 下面来我们来看看常用的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 详解SpringMVC中Controller的方法中参数的工作原理 标签:container eve mod mmap bool orm springmvc EDA form 原文地址:https://www.cnblogs.com/winner-0715/p/9748897.html@Controller
public 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;
}
}
/**
* 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;
}
/**
* 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;
}
}
public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
protected final Log logger = LogFactory.getLog(getClass());
private final List
public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {
protected final Log logger = LogFactory.getLog(getClass());
private final List
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;
}
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBody advice beans
initControllerAdviceCache();
if (this.argumentResolvers == null) {
List
/**
* Return the list of argument resolvers to use including built-in resolvers
* and custom resolvers provided via {@link #setCustomArgumentResolvers}.
*/
private List
/**
* Return the list of return value handlers to use including built-in and
* custom handlers provided via {@link #setReturnValueHandlers}.
*/
private List
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);
}
}
@Override
public 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);
}
@Override
public 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;
}
@Override
protected
/**
* Create the method argument value of the expected parameter type by reading
* from the given HttpInputMessage.*/
@SuppressWarnings("unchecked")
protected
文章标题:详解SpringMVC中Controller的方法中参数的工作原理
文章链接:http://soscw.com/essay/86355.html