使用springmvc的同学们对于如何在Controller的方法中来接收请求中的参数,应该都相当熟悉了。例如:
public void method1(String name)
public void method1(@ReqeustParam String name)
.....
当我们通过@RequestMapping映射后,那么为什么方法中有一个名为name的参数,就会把请求中的name的值传给方法中的参数name呢?为什么些@ReqeustName,他有什么作用呢?等等等的问题,本章,将看一下这里面的来龙去脉。
本章节涉及到的springmvc中的类有如下几个:
- RequestParamMethodArgumentResolver
- ConfigurableWebBindingInitializer
- SimpleTypeConverter extends TypeConverterSupport
- DefaultFormattingConversionService
- InvocableHandlerMethod
- HandlerMethodArgumentResolverComposite
下图是当请求发起后,springmvc是如何一步一步获取请求参数,然后调用controller总方法的总体过程。每步对应的原码都在下面的code中对应。其中核心就在HandlerMethodArgumentResolverComposite的resolveArgument,这里就是获取参数的入口
code1:
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
code2
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
invocableMethod.invokeAndHandle(webRequest, mavContainer);
}
code3
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
}
code4
public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
// 获取到reqeust中的参数的值
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
// 带着参数的值去调用controller中的方法,最后获取到方法的返回值
Object returnValue = doInvoke(args);
return returnValue;
}
/**
* Get the method argument values for the current request.
*/
private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
//对应我们controller中方法的参数,有几个参数,这里的数组就会有几个对象,观察下面图1。
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);
args[i] = resolveProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
// 调用HandlerMethodArgumentResolverComposite的supportsParameter
// 根据方法中的参数的写法(比如,是否使用了@RequestParam等注解)获取一个解析器resolver
// 此处进入到第4.1步
if (this.argumentResolvers.supportsParameter(parameter)) {
// 通过resolver获取request中对应的参数的值,
args[i] = this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
continue;
}
return args;
}
getMethodParameters()方法将获取到2个对象,如下:
图1
因为我的rest1这个方法中有两个参数,一个是name,一个是age,所以这里就会有两个对象,也就是说你的方法中有几个参数,这里就会有几个对象。
code5
HandlerMethodArgumentResolverComposite.java
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
//根据方法中的参数的写法(比如,是否使用了@RequestParam等注解)获取一个解析器resolver, 看下面图1
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
// 使用对应的resolver去获取request中参数的值。不同的resolver从request中获取参数值的方法各不相同
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
/**
* Find a registered HandlerMethodArgumentResolver that supports the given method parameter.
*/
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
// HandlerMethodArgumentResolver 是个啥,看下面的截图(1),说白了,
//当方法中的参数有不同的写法,比如@ReqeustParam注解,@PathVariable注解,
//需要不同的解析器,在这里根据parameter获取对应的resolver
for (HandlerMethodArgumentResolver methodArgumentResolver:this.argumentResolvers) {
//判断方法中的参数是否可以通过这个resolver来处理
if (methodArgumentResolver.supportsParameter(parameter)) {
// 因为我们例子中的方法的参数没有
// @RequestParam或者其他的东西,只是用了最简单类+参数名,所以这里的resolver会是
// RequestParamMethodArgumentResolver,可看下图 (2)
result = methodArgumentResolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
图1
图2
code6:
AbstractNamedValueMethodArgumentResolver.java
@Override
public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
Object resolvedName = resolveStringValue(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
// 将request中过去的参数的值,转换为方法中对应的参数的类型
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
// 转换类型后的参数返回,就可以在后面的代码中正常的赋值给方法中的参数了
return arg;
}
code7
@Override
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
MultipartHttpServletRequest multipartRequest =WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class);
Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
return mpArg;
}
Object arg = null;
if (multipartRequest != null) {
List<MultipartFile> files = multipartRequest.getFiles(name);
if (!files.isEmpty()) {
arg = (files.size() == 1 ? files.get(0) : files);
}
}
if (arg == null) {
// 从reqeust中获取到与方法参数名相同的那个参数的值
String[] paramValues = request.getParameterValues(name);
if (paramValues != null) {
arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
}
return arg;
}
code8
DefaultDataBinderFactory.java
@Override
public final WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName){
WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
if (this.initializer != null) {
// reqeust中参数会自动转换为方法中参数的类型,就是在这里的原因。这里设置了用于转换的类。
this.initializer.initBinder(dataBinder, webRequest);
}
initBinder(dataBinder, webRequest);
return dataBinder;
}
上面的部分讲述了整个过程的整体路程,下面来讲述这个过程中的一些关键点:
第一点:为什么controller中的方法里的参数前可以写@RequestParam,或者不写。为什么方法中的参数既可以是String类型,也可是是一个类类型(method(Person p))或者是Map,List等?
这是因为springmvc中定义了多种针对不同类型进行相对处理的Resolver。根据方法中参数的不同类型,会生成不同的Resolver,每个Resolver通过自己的方式解析request中参数的值,比如当我们的方法中的参数是Map类型时,便会调用下面的RequestParamMapMethodArgumentResolver
RequestParamMapMethodArgumentResolver.java
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
// 得到controller方法中参数的类型
Class<?> paramType = parameter.getParameterType();
// 从request中将请求的参数以map的形式返回
Map<String, String[]> parameterMap = webRequest.getParameterMap();
...省略部分代码...
// 遍历组成map返回
Map<String, String> result = new LinkedHashMap<String, String>(parameterMap.size());
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
if (entry.getValue().length > 0) {
result.put(entry.getKey(), entry.getValue()[0]);
}
}
return result;
}
这些Resolver都是在HandlerMethodArgumentResolverComposite的getArgumentResolver(MethodParameter parameter)方法中获取到的。根据controller中方法的参数类型的不同,这里得到的也不同。器实现方式就是遍历所有的resolver,然后通过supportsParameter(parameter)方法,找出适合的resolver,如下图:
对于这些resolver都是从缓存中拿到的,那么是什么时候存到缓存中的呢?就是在实例化ReqeustMappingHandlerAdapter后,自动调用afterPropertiesSet方法的时候,如下:
ReqeustMappingHandlerAdapter.java
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBody advice beans
initControllerAdviceCache();
if (this.argumentResolvers == null) {
// 这里就是获取所有默认的resolver,然后添加到缓存中
// 注意关注这个方法中的代码,看他默认加了多少的resolver
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
/**
*这里面就是初始化默认的那些resolver
*/
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
// 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(), this.requestResponseBodyAdvice));
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new SessionAttributeMethodArgumentResolver());
resolvers.add(new RequestAttributeMethodArgumentResolver());
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
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;
}
//同理,其实比如方法返回值的处理handler也是在这个类中添加的。不过这里就不贴代码了
这里介绍几个例子,来看一下不同的写法会调用那个resolver
method(String name)
调用 :AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver
子类的resolveArgument(),这种写法获取参数后,还会有一个类型转换的操作,第二点中会介绍。
method(@RequestParam Map myMap)
调用:RequestParamMapMethodArgumentResolver implements HandlerMethodArgumentResolver
自己的resolveArgument()
第二点:从reqeust中获取的参数的值,是怎么转换为controller中的方法中参数的类型的?
在第一点中说过,如果你的方法中是Integer,String这样的类型,springmvc会自动将reqeust中的值进行转换为你方法中参数的类型。
为什么请求中的参数可以转换为方法中参数的类型,是由那个类处理的呢,就是下面的这些类完成的转换工作:
下面来看看以下,转换相关的代码:
AbstractNamedValueMethodArgumentResolver.java
// 从request中解析出参数值
@Override
public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
Object resolvedName = resolveStringValue(namedValueInfo.name);
// 这里从reqeust获取参数的值
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
// 这里对值进行转换,parameter.getParameterType()就是controller中方法的参数对应的类型
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
类型转换这一步不是每个resolver都有的。具体的业务请参考resolver中的resolverArgument方法。
第三点:解析一下各种resolver,无法全部都解析,尽可能的写
1)RequestHeaderMapMethodArgumentResolver
// 使用@ReqeustParam注解时,便会用到这个resolver
public String rest4(@RequestHeader Map myMap){
return (String)myMap.get("name") ;
}
2)RequestHeaderMethodArgumentResolver
// 此处的参数名称可以为任何名字,因为最后是通过反射将参数传过来的。跟这里的名字没关系
public String rest4(@RequestHeader String host){
return host ;
}
3)ServletModelAttributeMethodProcessor
public String rest4( Person p){
return p.getName() ;
}
class Person {
private String name;
public String getName() {return name;}
public void setName(String name) {this.name = name;}
}
这里我们稍微来看一下这个ServletModelAttributeMethodProcessor获取request参数的业务是怎么做的
@Override
public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
String name = ModelFactory.getNameForParameter(parameter);
//整个下面的逻辑就是,如果在程序中使用过model就去覆盖原先里面的attribute
Object attribute = (mavContainer.containsAttribute(name) ? mavContainer.getModel().get(name) :
createAttribute(name, parameter, binderFactory, webRequest));
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
// Add resolved attribute and BindingResult at the end of the model
Map<String, Object> bindingResultModel = binder.getBindingResult().getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
return binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
4)RequestResponseBodyMethodProcessor
//参数使用@ReqeustBody注解,就会使用这个Resolver
public String rest4(@RequestBody Person p){
return p.getName() ;
}
class Person {
private String name;
public String getName() {return name;}
public void setName(String name) {this.name = name;}
}
postman中发送的数据如下:
这里把这个resolver类的解析方法粘贴了一部分代码,主要目的是为了展示,这个地方json转person对象使用了前面HttpMessageConverter章节的converter来实现转换的。
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
parameter = parameter.nestedIfOptional();
// 这里用到了我们之前讲的HttpMessageConverter来将reqeust中的body中json转换为Person
// 具体使用到的MappingJackson2HttpMessageConverter。这里请关注下
// arg便是person对象,里面放着json中的数据
// 这里面还会调用JsonViewRequestBodyAdvice,通过它可以实现将某些属性不赋值到person对象中
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
...省略其他....
return adaptArgumentIfNecessary(arg, parameter);
}
最后,简单总结一下,获取resolver获取参数的入口便是HandlerMethodArgumentResolverComposite的resolveArgument方法。
在方法的最后将resolver解析出的参数通过return返回。