1、扩展spring-mvc,实现自定义参数转换




需要明白的问题

1. Spring MVC处理@RequestBody和@ResponseBody的流程分析

在SpringMVC 默认处理JSON格式数据是通过@RequestBody和@ResponseBody注解进行参数绑定,RequestResponseBodyMethodProcessor 这个类实现了对RequestBody和ResponseBody注解的处理。

RequestResponseBodyMethodProcessor类的层级关系如下:
RequestResponseBodyMethodProcessor类结构

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//RequestResponseBodyMethodProcessor 源码
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
//中间省略了部分代码
...
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestBody.class);
}

@Override
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class));
}

//处理请求参数
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

parameter = parameter.nestedIfOptional();
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
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 adaptArgumentIfNecessary(arg, parameter);
}

//处理返回参数
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

// Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
}

可以看到它是继承了 AbstractMessageConverterMethodProcessor 抽象类,那么我们可以参考上面考虑自己继承这个抽象类以达到自定义我们自己的注解功能实现不同请求参数和返回参数的处理。知道这一步了怎么处理是很容易的,但是又引申出了下面这个问题,就是我们扩展了这个类之后那么在那一步进行调用呢?

我们可以先看一下默认的是怎么处理的,如 RequestResponseBodyMethodProcessor 是在哪里被进行调用的,可以看到是在 RequestMappingHandlerAdapter 这么一个适配器类 进行调用的,还有另外一个类也进行了调用但是和这里流程无关。看一下 RequestMappingHandlerAdapter 类的介绍:

An AbstractHandlerMethodAdapter (Abstract base class for HandlerAdapter implementations that support handlers of type HandlerMethod.) that supports HandlerMethod with their method argument and return type signature, as defined via @RequestMapping.

Support for custom argument and return value types can be added via setCustomArgumentResolvers and setCustomReturnValueHandlers. Or alternatively, to re-configure all argument and return value types, use setArgumentResolvers and setReturnValueHandlers.

其继承了 AbstractHandlerMethodAdapter,实现了通过 @RequestMapping 注解支持HandlerMethods及其方法参数和返回类型签名。
且支持自定义参数和返回值类型可以通过 setCustomArgumentResolverssetCustomReturnValueHandlers 添加; 如果要重新配置所有参数和返回值类型,则使用 setArgumentResolverssetReturnValueHandlers,因为有一些SpringMVC默认处理请求参数和返回值的处理类,如 RequestResponseBodyMethodProcessor 就是其中 默认之一,建议是不进行设置,而是设置自定义即可,可以从 RequestMappingHandlerAdapter 类源码的相关方法看出什么原因:

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
//设置参数处理器
public void setArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
if (argumentResolvers == null) {
this.argumentResolvers = null;
}
else {
this.argumentResolvers = new HandlerMethodArgumentResolverComposite();
this.argumentResolvers.addResolvers(argumentResolvers);
}
}
//初始化参数和返回值处理器
public void afterPropertiesSet() {
// Do this first, it may add ResponseBody advice beans
initControllerAdviceCache();

if (this.argumentResolvers == null) {
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);
}
}

深入的看下去可以发现还实现了 HandlerAdapter 接口。如下图所示:
RequestMappingHandlerAdapter结构图

看到这应该知道 RequestMappingHandlerAdapter 类的作用了,用来处理 @RequestMapping 标识的方法。
HandlerAdapter 主要就是功能实际就是执行我们的具体的Controller、Servlet或者HttpRequestHandler中的方法。可以看到这个接口也是一个适配接口,每一个处理请求的Handler必须实现这个这个接口,以达到无限扩展 DispatcherServlet。那么这个接口的执行过程其实就是在 DispatcherServlet 中的 doDispatch()方法中调用的。

可以在eclipse中使用 ctrl+shift+g查询一下这个接口在那些类里面有被引用就知道了。
HandlerAdapter被调用的类

如上图可以看到,除了 DispatcherServlet 其余几个都是其实现类。

再来看一下这个 doDispatch()方法:

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
33
34
35
36
37
/**
* Process the actual dispatching to the handler.
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
---> 这就是为什么AbstractHandlerMethodAdapter实现了Order接口的原因。
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
//中间省略了部分代码
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);

// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// Process last-modified header, if supported by the handler.
...
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request,
}
// interceptor preHandle
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
...
// interceptor postHandle
mappedHandler.applyPostHandle(processedRequest, response, mv);
}

可以看到getHandlerAdapter的操作就是选择合适的HandlerAdapter来执行,这就是设计模式中的适配器模式。
handlerAdapters中的内容就是所有的HandlerAdapter的实现类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Return the HandlerAdapter for this handler object.
* @param handler the handler object to find an adapter for
* @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
*/
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
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");
}

后面调用handle方法,里面完成对请求参数的封装,调用和返回处理的流程处理,这样就完成了Handler的真正调用过程,最终调用过程是返回一个ModelAndView对象。

看到这里应该也了解了SpringMVC的一个具体请求流程内部大概是怎么样处理的了,这里从一个反向的过程去推导的。

可以参考HandlerAdapter源码分析详细了解这个类的作用。

2. Spring MVC如何处理json请求参数

那么首先需要知道@RequestBody的工作原理

经过上面的分析可以知道处理请求参数的是在 RequestResponseBodyMethodProcessor中resolveArgument方法进行相关参数的处理。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
//返回此MethodParameter的一个变体,该变体指向相同的参数,但在java.util.Optional声明的情况下进行一层嵌套。
parameter = parameter.nestedIfOptional();

//请求参数转换器
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());

//确定提供的参数的常规变量名称,并考虑通用集合类型(如果有的话)。
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 adaptArgumentIfNecessary(arg, parameter);
}

3. Spring MVC如何处理json返回对象

那么首先需要知道@ResponseBody 的工作原理

经过上面的分析可以知道处理返回参数是在 RequestResponseBodyMethodProcessor中handleReturnValue方法进行相关参数的处理。代码如下:

1
2
3
4
5
6
7
8
9
10
11
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

// Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

3. 如何进行自定义扩展

TODO: 贴出流程图、为什么要进行扩展、扩展之后的好处与不扩展的对比、还有没有其他的方式,为什么选择这一种进行扩展,后续分析…

TODO: 分析实例:方法参数转换实现接口自动化验签和签名