Spring MVC请求参数的深入解析

 更新时间:2021年5月6日 13:44  点击:1454

请求参数解析

客户端请求在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对应的适配器,处理参数映射、执行控制器逻辑、返回值处理的核心源码处理。

总结

到此这篇关于Spring MVC请求参数解析的文章就介绍到这了,更多相关SpringMVC请求参数内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

[!--infotagslink--]

相关文章

  • Java如何发起http请求的实现(GET/POST)

    这篇文章主要介绍了Java如何发起http请求的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-31
  • Nest.js参数校验和自定义返回数据格式详解

    这篇文章主要给大家介绍了关于Nest.js参数校验和自定义返回数据格式的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-28
  • 解决Java处理HTTP请求超时的问题

    这篇文章主要介绍了解决Java处理HTTP请求超时的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-03-29
  • Vue 组件复用多次自定义参数操作

    这篇文章主要介绍了Vue 组件复用多次自定义参数操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-07-27
  • C#模拟http 发送post或get请求的简单实例

    下面小编就为大家带来一篇C#模拟http 发送post或get请求的简单实例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • 解决Springboot get请求是参数过长的情况

    这篇文章主要介绍了解决Springboot get请求是参数过长的情况,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-09-17
  • PHP中empty和isset对于参数结构的判断及empty()和isset()的区别

    废话不多说了,直接给大家贴代码了。<&#63;php class test{} $a1 = null; $a2 = ""; //$a3 = $a4 = 0; $a5 = '0'; $a6 = false; $a7 = array(); //var $a8; $a9 = new test(); for ($i=1; $i <=9 ; $i++) {...2015-11-24
  • java正则表达式判断前端参数修改表中另一个字段的值

    这篇文章主要介绍了java正则表达式判断前端参数修改表中另一个字段的值,需要的朋友可以参考下...2021-05-07
  • mysql配置模板(my-*.cnf)参数详细说明

    mysql安装成功后有几个默认的配置模板,列表如下: my-huge.cnf : 用于高端产品服务器,包括1到2GB RAM,主要运行mysql my-innodb-heavy-4G.ini : 用于只有innodb的安装,最多有4GB RAM,支持大的查询和低流量 my-large.cnf : 用于...2015-03-15
  • 详解C#泛型的类型参数约束

    这篇文章主要介绍了C#泛型的类型参数约束的相关资料,文中讲解非常细致,帮助大家更好的理解和学习c#,感兴趣的朋友可以了解下...2020-07-31
  • C#中out参数、ref参数与值参数的用法及区别

    这篇文章主要给大家介绍了关于C#中out参数、ref参数与值参数的用法及区别的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-25
  • 处理@PathVariable注解允许参数为空、允许不传参数的问题

    这篇文章主要介绍了处理@PathVariable注解允许参数为空、允许不传参数的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-02-23
  • Java线程池中的各个参数如何合理设置

    这篇文章主要介绍了Java线程池中的各个参数如何合理设置操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-06-19
  • 详解Java后端优雅验证参数合法性

    这篇文章主要介绍了详解Java后端优雅验证参数合法性,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-18
  • vue+axios全局添加请求头和参数操作

    这篇文章主要介绍了vue+axios全局添加请求头和参数操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-07-24
  • SpringBoot接口接收json参数解析

    这篇文章主要介绍了SpringBoot接口接收json参数解析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-10-19
  • pytorch 实现冻结部分参数训练另一部分

    这篇文章主要介绍了pytorch 实现冻结部分参数训练另一部分,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-03-27
  • C#向线程中传递多个参数的解决方法(两种)

    这篇文章主要介绍了C#向线程中传递多个参数的解决方法(两种)的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • spring cloud gateway中如何读取请求参数

    这篇文章主要介绍了spring cloud gateway中如何读取请求参数的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-07-15
  • Postman参数化实现过程及原理解析

    这篇文章主要介绍了Postman参数化实现过程及原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-08-13