您现在的位置是:首页 >技术杂谈 >Spring MVC 参数解析(13)网站首页技术杂谈
Spring MVC 参数解析(13)
目录
1. 首先,还是需要进行到前端控制器的doDispatch方法,这是我们的调用Spring MVC的核心入口方法
2. 在doDispatch方法内部,我们调用到了HandlerAdapter.handle(*****) 方法
3. 最终,我们会来到 RequestMappingHandlerAdapter 的 invokeHandlerMethod 方法;在这个方法内部,我们会设置 参数解析器 和 返回值解析器
4. 来到当前方法 invokeHandlerMethod 调用 Controller业务类处,进行业务类的调用;进行 参数解析 与 返回值处理
5. 进入 ServletInvocableHandlerMethod 的 invokeAndHandle 方法处,进行参数解析 与 返回值处理
case 2 :@PathVariable 注解的参数解析器
简介
据我所知:
参数解析器共分26种,针对不同的注解参数,有不同的参数解析器;
返回值解析器共分15种,针对不同的返回值,调用不同的返回值解析器;
其实,参数解析的核心代码在 HandlerMethodArgumentResolverComposite类resolveArgument 方法处,以后直接锁定这个方法就可以快速进行debug操作:
上一篇博客Spring MVC 的调用(12)_chen_yao_kerr的博客-CSDN博客已经对Spring MVC整体调用流程进行了梳理,但是在上一篇中关于HandlerAdapter调用到业务类,最终返回modelAndView的步骤中,我们只是梳理了整体流程, 并没有详细的分析如何进行参数传递的。本篇则是对上一篇的第五、六、七步中进行参数解析的补充.
其实,参数解析就是发生在HandlerAdapter调用到具体的业务类之前进行的,看下图:
调用流程
1. 首先,还是需要进行到前端控制器的doDispatch方法,这是我们的调用Spring MVC的核心入口方法
2. 在doDispatch方法内部,我们调用到了HandlerAdapter.handle(*****) 方法
3. 最终,我们会来到 RequestMappingHandlerAdapter 的 invokeHandlerMethod 方法;在这个方法内部,我们会设置 参数解析器 和 返回值解析器
参数解析器有26种:
返回值解析器:
源码如下:
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
//获取数据绑定工厂 @InitBinder注解支持,没太多用
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
//Model工厂,收集了@ModelAttribute注解的方法
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
//可调用的方法对象
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
//设置参数解析器
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
//设置返回值解析器
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
//设置参数绑定工厂
invocableMethod.setDataBinderFactory(binderFactory);
//设置参数名称解析类
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
//调用有@ModelAttribute注解的方法。每次请求都会调用有@ModelAttribute注解的方法
//把@ModelAttribute注解的方法的返回值存储到 ModelAndViewContainer对象的map中了
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
//异步处理
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
//Controller方法调用,重点看看
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
4. 来到当前方法 invokeHandlerMethod 调用 Controller业务类处,进行业务类的调用;进行 参数解析 与 返回值处理
5. 进入 ServletInvocableHandlerMethod 的 invokeAndHandle 方法处,进行参数解析 与 返回值处理
参数解析
参数解析,需要针对不同的注解传递的参数进行解析,不能一概而论;首先来到参数解析的方法中。即 ServletInvocableHandlerMethod 类的 invokeForRequest 方法中:
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//获取参数数组,重点看
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
return doInvoke(args);
}
就是获取到所有的参数进行反射调用,反射调用在分析Spring源码的时候已经说过很多次了,就不再累赘了。下面针对不同的case分析不同的参数解析流程:
case1:@RequestParam注解的参数解析器
测试的业务方法:
@RequestMapping("/queryUser")
public String queryUser(@RequestParam String language, HttpSession session) {
session.setAttribute("language",language);
return "ok";
}
URL: http://localhost:9090/user/queryUser?language=en
测试结果:
源码分析:
1、首先,肯定是来到参数解析的核心方法处
2. 进入核心方法
a, 首先,拿到当前方法对应的参数名称、参数类型、参数使用的注解、当前方法属于的哪个类、参数解析器等各种各样信息;
b. 进入参数解析的核心方法
c. 根据参数获取对应的参数解析器
其实,就是根据步骤 a 获取到的 MethodParameter 对象,直接从缓存中拿到对应的参数解析器而已。拿到的参数解析器是 RequestParamMethodArgumentResolver。
d. 最后,自然是调用这个类进行参数解析了。具体调用的是 resolveArgument 方法进行 参数解析并返回数组了。
其实参数解析很简单,就是获取到参数的包装类,根据包装类获取到当前参数名、根据参数名到Request中获取参数值(此处就是调用 request.getParameterValues(name))
最终返回获取到的实际参数值并且返回。因为每个方法的参数可能是多个,因此我们是按照方法的参数进行遍历操作的,而且每个参数都是有对应下标的,这样返回值就不会乱、也不会少 。
case 2 :@PathVariable 注解的参数解析器
测试业务方法:
@RequestMapping("/queryUser2/{id}")
public @ResponseBody String queryUser2(@PathVariable("id") String userId) {
return "hello user :" + userId;
}
URL:http://localhost:9090/user/queryUser2/1234
测试结果:
源码分析:
文章开头就说了, 参数解析的核心代码在HandlerMethodArgumentResolverComposite类resolveArgument 方法处,直接把断点打在此处:
获取参数解析器,@PathVariable 对应的参数解析器为 PathVariableMethodArgumentResolver
根据参数解析器进行参数解析:
我们发现,这是相同的代码,进去以后,其实会调用父类的钩子方法,返回到当前参数解析器 PathVariableMethodArgumentResolver中调用钩子方法中
其实,它就是根据拿 requet.getAttribute(***)获取参数值而已。所谓的Spring MVC,其实就是对 Servlet调用的封装而已。传统的 jsp + servlet调用,我们都是通过 request 直接拿参数的。而Spring mvc 自己封装了 request拿参数的过程而已。
下面列出常用的参数解析器以及对应的注解,我们直接看解析器的 resolveName 方法,就可以知道具体的解析过程。
- RequestHeaderMethodArgumentResolver 用来
处理使用了 @RequestHeader 注解,并且参数类型不是 Map 的参数
- RequestHeaderMapMethodArgumentResolver 很明显,这个用来
处理使用 @RequestHeader 注解,并且参数类型是 Map 的参数
- PathVariableMethodArgumentResolver
处理使用 @PathVariable 注解并且参数类型不为 Map 的参数
- PathVariableMapMethodArgumentResolver
处理使用 @PathVariable 注解并且参数类型为 Map 的参数
- RequestAttributeMethodArgumentResolver
处理使用 @RequestAttribute 注解的参数
- ServletCookieValueMethodArgumentResolver 处理使用 @CookieValue 的参数
- SessionAttributeMethodArgumentResolver
处理使用 @SessionAttribute 注解的参数
- RequestParamMethodArgumentResolver 这个功能就比较广了。
使用了 @RequestParam 注解的参数、文件上传的类型 MultipartFile、或者一些没有使用任何注解的基本类型(Long、Integer)以及 String 等,都使用该参数解析器处理。变量不能是Map
- RequestParamMapMethodArgumentResolver 针对上一个,提供Map支持
至于不常用的参数解析器,按照我说的方法,直接找到以下方法打断点,就可以快速锁定: