Spring MVC请求参数的深入解析
作者:是冯吉荣呀 发布时间:2021-11-26 22:55:25
请求参数解析
客户端请求在handlerMapping中找到对应handler后,将会继续执行DispatchServlet的doPatch()方法。
首先是找到handler对应的适配器。
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
进入到getHandlerAdapter(mappedHandler.getHandler())
方法中
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
这里存在多个适配器,如图:
其中使用@RequestMaping注解修饰的控制器都将适配第一个适配器;而函数式方法将会使用第二个适配器。
跟踪请求,这里将会获得第一个适配器,判断也简单,如下:
public final boolean supports(Object handler) {
return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
如果是HandlerMethod类型的处理器就采用这个适配器,而客户端请求正好对应的是HandlerMethod处理器。
找到适配器后,将会真正执行处理器逻辑。如下:
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
进入RequestMappingHandlerAdapter,执行适配器核心方法:
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
其核心代码为实际执行处理器方法:
mav = invokeHandlerMethod(request, response, handlerMethod);
同样,我们打开RequestMappingHandlerAdapter中的invokeHandlerMethod方法:
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
//通过处理器获得真正的执行方法及其参数列表
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
//给执行方法对象添加参数解析器
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
//给执行方法对象添加返回值处理器
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
//处理器对象装配完成,执行控制器方法
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
在这个关键方法中,首先执行请求对应的控制器逻辑,之后进行系列处理,根据返回值处理器处理返回值。
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//执行控制器方法
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
//处理返回值
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
以下给出部分参数解析器及返回值处理器截图:
参数解析器。对应每一个参数(路径变量、矩阵变量、获得请求头、请求域等)的获取方式
返回值处理器。ModelAndView、ResponseBody等。每个处理器处理不同类别的返回值类型。
接下来,真正进入到最终执行method方法invocableMethod.invokeAndHandle(webRequest, mavContainer);
,这里是真是执行控制器中映射的方法。
以下为获得参数列表对应值的逻辑,参数获取完成后将会执行真正的控制器逻辑。
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);
}
进入解析逻辑,解析是对比每一个参数绑定的注解,如果注解一致将会使用对应的解析器将请求传递的参数值获取到。
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//获得控制器参数列表,每一个参数包含其参数类型,参数次序、注解修饰(用于使用对应解析器)
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
//轮循获得参数
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
//判断解析器是否支持当前参数的解析
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
//获得参数值的核心方法
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}
查看解析器是否指出当前参数部分代码,可以了解到SpringMVC的缓存策略。
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
//从缓存中获取当前参数的解析器
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
//缓存中不存在则将这个参数对应的解析器加到缓存中,提升后续相同请求响应速度。
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
参数列表
以下为获得的参数列表第一个参数部分属性
其对应的是控制器中id参数:
@GetMapping(value = "/student01/{id}/car/{name}")
public Map<String, Object> testAnnotation(@PathVariable(name = "id") String id){
Map<String, Object> map = new HashMap<>();
map.put("id", id);
return map;
}
以上即为获得请求Handler对应的适配器,处理参数映射、执行控制器逻辑、返回值处理的核心源码处理。
总结
来源:https://www.cnblogs.com/fjrgg/p/14545595.html
猜你喜欢
- 本文实例讲述了C#获取网页源代码的方法。分享给大家供大家参考。具体如下:public string GetPageHTML(string u
- 1.Java进程的创建 Java提供了两种方法用来启动进程或其它程序: (1)使用Runtime的exec()方法 (2)使用Process
- 前言相信很多Java开发都遇到过一个面试题:Resource和Autowired的区别是什么?这个问题的答案相信基本都清楚,但是这两者在Sp
- 首先 下载 jedis.jar包然后再 工程设置里面找到Libraries,点击+。添加下载好的jedis.jar包。点击OK退出即可创建J
- 自从接触javascript以来,对this参数的理解一直是模棱两可。虽有过深入去理解,但却也总感觉是那种浮于表面,没有完全理清头绪。但对于
- 本文实例为大家分享了java使用字符画一个海绵宝宝的具体代码,供大家参考,具体内容如下用字符画一个海绵宝宝用" &ldqu
- 官网教程一、翻转(镜像)头文件 quick_opencv.h:声明类与公共函数#pragma once#include <opencv
- 本文需求实现了java通过方向键控制小球移动的具体过程,供大家参考,具体内容如下需求分析:第一 要画出一个小球第二 要能通过控制方向键控制它
- Spring介绍:spring 使用基本的 JavaBean 来完成以前只可能由 EJB 完成的事情。然而, Spring的用途不仅限于服务
- 五子棋游戏(Java),供大家参考,具体内容如下思路:1.首先创建一个棋盘,建立一个二维数组,此文中为一个15*15的二维数组,2.初始化棋
- 使用HTTPclient访问url获得数据最近项目上有个小功能需要调用第三方的http接口取数据,用到了HTTPclient,算是做个笔记吧
- 本文实例讲述了Java截取字符串的方法。分享给大家供大家参考。具体实现方法如下:public static void main(String
- 前言基本语法首先我们要知道java的基础语法。1.由26个英文字母大小写,0-9,_或$组成2.数字不可以开头3.不可以使用关键字和保留字,
- 具体代码如下所示:***web.xml***<?xml version="1.0" encoding="
- IDEA 新手使用手册1 简介IDEA的全称是IntelliJ IDEA,这是一个java编程语言开发的集成环境。IDEA的每一个方面都是为
- 目录简单介绍具体实现首先,咱们先配置一下相关的jar包(pom.xml)创建一个Pet接口,存放方法say()创建一个Person类创建一个
- Android5.0之后提供了JobService和JobScheduler,用于在稍后的某个时间点或者当满足某个特定的条件时执行一个任务
- 有时候因为安全问题,需要把配置文件的中数据库用户名密码由明文改成密文,大多数其实是为了应付甲方而已。1.pom.xml引入依赖<dep
- 前几篇主要集中在注册中心eureka的使用上,接下来可以创建服务提供者provider来注册到eureka。demo源码见: https:/
- CSRF介绍CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click atta