Spring MVC--参数解析与方法执行

承接上文,Spring MVC通过HandlerMapping定位到了具体的HandlerExecutionChain,也就是具体要执行的方法.本篇详细阐述Spring MVC执行具体方法的流程.

HandlerAdapter#

HandlerAdapter用来适配HandlerExecutionChain的一个接口,从名称来看这里是适配器模式,适配器模式的本质在于包装转换,对于HandlerExecutionChain中不同的Handler提供适配功能,并且提供统一的调用方法,该接口主要有以下方法:

1
2
3
4
5
6
7
8
9
10
11
public interface HandlerAdapter {
/**
* 判断是否支持当前的handler适配
*/
boolean supports(Object handler);
/**
* 具体的处理适配
*/
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
.....
}

DispatcherServlet中定位到具体的HandlerAdapter是类似HandlerMappings的处理,直接循环,判断是否支持处理,支持则返回,这类似一种标准责任链模式,查找出链中能够处理该事件的第一个节点类.

1
2
3
4
5
6
7
8
9
10
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
// 判断是否支持,支持则返回
if (ha.supports(handler)) {
return ha;
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

HandlerAdapter的继承体系如下所示:

  1. SimpleControllerHandlerAdapter处理实现了Controller接口的handler.
  2. SimpleServletHandlerAdapter处理实现了Servlet接口的handler.
  3. HttpRequestHandlerAdapter处理实现了HttpRequestHandler接口的handler.静态资源映射就是使用该Adapter进行处理.
  4. AbstractHandlerMethodAdapter处理HandlerMethod的子类,也就是我们的业务方法,也是本次分析的重点.

模板类AbstractHandlerMethodAdapter#

该模板类比较简单,主要声明了所支持处理的类型,然后把请求转到handleInternal方法中.

1
2
3
4
5
6
public final boolean supports(Object handler) {
return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
// 把请求转到具体的子类
protected abstract ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception;

实现类RequestMappingHandlerAdapter#

RequestMappingHandlerAdapter在作为实现类,主要是负责调用逻辑,如下所示,主要的调用逻辑在invokeHandlerMethod方法中.

1
2
3
4
5
6
7
8
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
...
// 调用invokeHandlerMethod方法拿到对应的结果
mav = invokeHandlerMethod(request, response, handlerMethod);
...
return mav;
}

invokeHandlerMethod方法,顾名思义是负责调用的实现,其会把HandlerMethod方法封装到ServletInvocableHandlerMethod中,然后准备上下文环境,比如参数解析器HandlerMethodArgumentResolver,参数转换器DataBinder等信息,一切准备好了之后完成调用,在对结果进行解析包装.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
// 参数转换以及绑定类工厂
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
// 结果转换生成工厂
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
// 包装要执行的方法HandlerMethod
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
// 加入参数解析器
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
// 加入返回值解析器
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
// 加入转换工厂
invocableMethod.setDataBinderFactory(binderFactory);
// 用户获取方法参数名的service
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
.....
// 调用方法
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
// 取出结果并进行封装
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}

上述流程可以确定以下几点:

  1. 参数解析依赖HandlerMethodArgumentResolver
  2. 返回值解析依赖HandlerMethodReturnValueHandler
  3. 参数转换与绑定(针对Bean)依赖WebDataBinderFactory

参数如何解析?#

org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues中定义了参数的解析流程.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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);
// 优先使用方法传来的参数
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) {
throw ex;
}
}
...
}
return args;
}

其中HandlerMethodArgumentResolver是负责参数解析的入口,其本身是组合设计模式中的Component接口类,HandlerMethodArgumentResolverComposite是组合模式中的Composite树枝节点类.而众多解析方法则是Leaf叶子类,组合模式的本质是为了组合多个过多的节点,统一叶子节点和组合节点,给客户端提供统一的访问形式在使用时不需要做区分,通过Composite类把众多解析操作组合一起.

HandlerMethodArgumentResolver接口定义如下,其中MethodParameter是对用户业务方法参数的封装,比如参数类型,所属Bean,修饰的Annotation等.

1
2
3
4
5
6
7
8
9
10
11
public interface HandlerMethodArgumentResolver {
/**
* 是否支持该参数的解析.
*/
boolean supportsParameter(MethodParameter parameter);
/**
* 解析操作
*/
Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
}

其中AbstractNamedValueMethodArgumentResolver作为其中之一的模板类定义了那些需要根据名称解析参数方式的一些模板,比如@RequestParam,@RequestHeader等注解,大概流程如下,把具体的获取过程利用抽象方法resolveName()延迟到了子类实现,让子类专注于从相应区域获取到对应的参数,拿到参数后使用WebDataBinder对参数进行类型转换,下面是该模板类的解析流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
...
// 获取要获取的参数名称
Object resolvedName = resolveStringValue(namedValueInfo.name);
// 根据参数名从request中取出结果,这一步延迟到子类中实现,降低子类的复杂度.
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
...
// 使用WebDataBinder转换到需要的类型
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
...
}
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}

这个是参数的大概解析流程,同理从playload中解析的大概也是如此设计,最底层的子类只负责从相应区域获取参数,上层的模板类负责参数的统一处理转换操作.

参数如何类型转换以及绑定?#

在一般的写法中,经常会使用一个对象来接收参数,Spring MVC会自动把参数设置到该参数对象对应的属性上,这个是怎么实现的呢?答案是ModelAttributeMethodProcessor参数解析器,解析流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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) {
if (!mavContainer.isBindingDisabled(name)) {
// 按照名称进行参数绑定
bindRequestParameters(binder, webRequest);
}
// 如果有必要则进行验证
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
// 转换为参数需要的类型
return binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}

其中参数的绑定和转换都依赖WebDataBinder这个类,相比DataBinder该类额外提供了从request中取出参数的能力,其绑定功能依赖BeanWrapper,转换功能依赖ConversionService.
图片来自网络

ConversionService是Spring3引进的类型转换系统,在Spring中想添加一个转换器有如下几种做法

  1. 实现interface Converter<S, T>接口,负责把S转换为T
  2. 实现interface ConverterFactory<S, R>接口,其负责把一类S转换为另一类R对象,与上面不同的是该方法是一个工厂类,其负责生产一类转换,比如String到Number,String到Integer等.
  3. 实现GenericConverter接口,是类型转换中最复杂最强大的存在,可以实现根据上下文信息转换,支持一个源或者多个源到目标的转换.

自定义转换器#

举个例子,实现字符到枚举类的转换:

1
2
3
4
5
6
7
public class StringToEnumConvert implements ConverterFactory<String,Enum> {
@Override
@SuppressWarnings("unchecked")
public <T extends Enum> Converter<String, T> getConverter(Class<T> aClass) {
return s -> (T)Enum.valueOf(aClass, s.trim());
}
}

然后再自定义配置中加入该转换器,参数解析是对于字符串到枚举类会自动利用该转换器进行转换处理.

1
2
3
4
5
6
7
8
9
@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {

@Override
public void addFormatters(FormatterRegistry registry) {
//添加字符串到枚举类的转换
registry.addConverterFactory(new StringToEnumConvert());
}
}

方法如何执行?#

解决了参数后直接利用反射执行.

1
2
3
4
5
6
7
8
protected Object doInvoke(Object... args) throws Exception {
ReflectionUtils.makeAccessible(getBridgedMethod());
try {
// 获取用户方法执行
return getBridgedMethod().invoke(getBean(), args);
}
....
}

参考#

https://www.jianshu.com/p/d64800baaa04

Spring MVC--请求定位
设计模式--观察者模式的思考