今天很难,明天更难,后天也许更难

回顾

上期说道,HandlerAdapter存在的意义,就是充当request,reponse与我们定义的各种形式handler之间的参数适配,返回值适配。

当我们在享受着SpringMVC带来的多样化参数接收形式,以及简便的返回值操作时,殊不知,HandlerAdapter在背后默默的为我们奉献着。

RequestMappingHandlerAdapter

适用于@RequestMapping注解标注的Handler,是处理我们定义的controller接口最重要的HandlerAdapter。基于他的重要地位,本文讲讲他是如何工作的。

1.初始化

先从其初始化开始下手

为了应对handler的各种样式的参数接收,在handlerApater里,就需要大量的转换工具来类处理这些东西。

所以:初始化的过程中,就是要设置各种参数工具,返回值处理工具等等,这些工具可以是Spring 提供的或者开发人员自己定义的。

1public RequestMappingHandlerAdapter() {
		StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
		stringHttpMessageConverter.setWriteAcceptCharset(false);  // see SPR-7316

		this.messageConverters = new ArrayList<HttpMessageConverter<?>>(4);
		this.messageConverters.add(new ByteArrayHttpMessageConverter());
		this.messageConverters.add(stringHttpMessageConverter);
		this.messageConverters.add(new SourceHttpMessageConverter<Source>());
		this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
}



public void afterPropertiesSet() {2initControllerAdviceCache();3if (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);
		}
	}
1.1 设置HttpMessageConverter

HttpMessageConverter负责将请求信息转换为一个对象(类型为 T),将对象(类型为 T)输出为响应信息

1.2 initControllerAdviceCache

这一步目的是对 @ControllerAdvice标注的Bean的处理

这里简单介绍下@ControllerAdvice注解意义,通过@ControllerAdvice标注的类一般作为全局异常处理,全局数据绑定,全局数据预处理等。
例如:我们在一个Controller里抛出一个自定义异常时,通常都会有一个@ControllerAdvice定义的类中处理此异常。

也就是说:@ControllerAdvice标注类 后,他已经成为一种处理工具。自然的SpringMVC在初始化时会把这些 开发人员定义的处理工具找到 缓存起来。

源码中initControllerAdviceCache()的逻辑是

  • 找到该Advice Bean内所有的标注有@ModelAttribute但没标注@RequestMapping的缓存到一起
  • 找到该Advice Bean内所有的标注有@InitBinder的方法缓存在一起
  • 实现了接口RequestBodyAdvice/ResponseBodyAdvice们,缓存到requestResponseBodyAdvice集合的前面
1.3 设置默认参数解析工具

getDefaultArgumentResolvers()方法中设置了大量的SpringMVC已经准备好的参数解析工具。为handlerApater处理做准备。

列如:我们常用@PathVariable注解。其实就是在此处设置的PathVariableMethodArgumentResolver解析工具帮我们解析的。

 @PostMapping("/post/{id}/{name}")
    public void Post(@PathVariable(value = "id") String id,@PathVariable(value = "name") String name, HttpServletRequest request, HttpServletResponse response){
      
    }

工具设置逻辑为:

  • 先设置针对注解的参数解析工具
  • 基于type类型的解析工具设置
  • 设置用户定义的参数解析工具
  • 最终解析工具,如果上边都没解析,此处设置解析工具最终处理

这些参数解析工具统一交给HandlerMethodArgumentResolverComposite。从他的名字也可以看出解析工具混合器,说白了就是解析工具集合

这里提一下自己的一点小理解: 我在很多框架中都见过类型xxxComposite混合器,本质上就是同一个东西的集合。
既然有了集合为啥还要多一层出来呢?
个人觉得混合器就好比工头,当我们跟工人打交道时,直接与工头交涉,比跟多个工人交涉方便的多。
总结为:分工明确,高内聚低耦合

1.4 设置返回值处理工具

提供对HandlerMethod返回值的支持,比如@ResponseBody等同参数解析器逻辑差不多,
最终初始化HandlerMethodReturnValueHandlerComposite混合器,承载返回值处理器集合

1.5 其他

除了上述初始化外,当然还有其他细节,感兴趣可以去阅读源码

2.handle处理

一切准备就绪后,下面就是调用了

再回头看下DispatcherServlet中,HandlerAdapter的调用起点

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
获取当前hangdler的HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

走过拦截器的前置处理
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
}

通过适配器执行调用
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

过拦截器后置处理
mappedHandler.applyPostHandle(processedRequest, response, mv);
}

RequestMappingHandlerAdapterhandle方法在父类中AbstractHandlerMethodAdapter回调RequestMappingHandlerAdapter.handleInternal方法

2.1 handleInternal
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) {
			...
		}
		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;
	}

这里只要是

  • request请求方法的检查,
  • 同一个Session下是否要串行(同步)
  • 调用invokeHandlerMethod
  • 处理Cache-Control这个请求头
2.1 invokeHandlerMethod

执行handler方法,好戏正式登场

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);
			invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

			ModelAndViewContainer mavContainer = new ModelAndViewContainer();
			mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
			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();
				if (logger.isDebugEnabled()) {
					logger.debug("Found concurrent result value [" + result + "]");
				}
				invocableMethod = invocableMethod.wrapConcurrentResult(result);
			}
			//调用
			invocableMethod.invokeAndHandle(webRequest, mavContainer);
			if (asyncManager.isConcurrentHandlingStarted()) {
				return null;
			}

			return getModelAndView(mavContainer, modelFactory, webRequest);
		}
		finally {
			webRequest.requestCompleted();
		}
	}

由于invokeHandlerMethod方法旁系逻辑比较多。我们抓住主线来讲
首先 会将handlerMethod封装成ServletInvocableHandlerMethod

那么,为啥要做此封装呢?
享读SpringMVC源码4-感谢RequestMappingHandlerAdapter_Java
从其继承关系说起:

  • handlerMethod: 是我们定义的接口方法的变体,只是数据的承载,不具有执行能力
  • InvocableHandlerMethod: 赋予HandlerMethod可被执行能力。既然执行就涉及到参数的处理。其实吧,InvocableHandlerMethod就是为了参数的解析
  • ServletInvocableHandlerMethod: 它是对InvocableHandlerMethod的扩展,它增加了返回值和响应状态码的处理 。本质就是为了返回值的处理

分工明确,很舒服。

调用ServletInvocableHandlerMethod.invokeAndHandle方法开启两大工作

invocableMethod.invokeAndHandle(webRequest, mavContainer);

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()) {
				mavContainer.setRequestHandled(true);
				return;
			}
		}
		else if (StringUtils.hasText(getResponseStatusReason())) {
			mavContainer.setRequestHandled(true);
			return;
		}

		mavContainer.setRequestHandled(false);
		try {
		//返回值的处理
			this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
		}
		catch (Exception ex) {
			if (logger.isTraceEnabled()) {
				logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
			}
			throw ex;
		}
	}
2.3 处理参数执行目标方法

上面说到参数的处理是放到InvocableHandlerMethod完成的,invokeForRequest方法逻辑就在InvocableHandlerMethod

invokeForRequest(webRequest, mavContainer, providedArgs);


public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
		//解析参数
		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
		//反射调用目标方法
		Object returnValue = doInvoke(args);
		//目标方法返回值返回
		return returnValue;
	}
private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
		1
		MethodParameter[] parameters = getMethodParameters();
		Object[] args = new Object[parameters.length];
		2
		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;
			}
			3
			if (this.argumentResolvers.supportsParameter(parameter)) {
				try {
					args[i] = this.argumentResolvers.resolveArgument(
							parameter, mavContainer, request, this.dataBinderFactory);
					continue;
				}
				catch (Exception ex) {
					if (logger.isDebugEnabled()) {
						logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex);
					}
					throw ex;
				}
			}
			if (args[i] == null) {
				throw new IllegalStateException("Could not resolve method parameter at index " +
						parameter.getParameterIndex() + " in " + parameter.getMethod().toGenericString() +
						": " + getArgumentResolutionErrorMessage("No suitable resolver for", i));
			}
		}
		return args;
	}

这里主要有3个点:
(1. 反射获取目标方法上的参数集合
(2. 遍历每个参数信息
(3. 对每个参数信息,都使用argumentResolvers参数处理混合器查找一遍是否有符合当前参数的参数处理器,有就用找到的参数处理器,从request中把参数值解析出来;没有就抛出异常

参数值解析完成后,就可以调用目标方法。获得返回值

Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

执行目标方法,到这里才算是真正的进入咱们开发人员写Controller里面了

Object returnValue = doInvoke(args);
2.4 返回值处理

在上面调用完成目标方法后,获得了返回值,回到ServletInvocableHandlerMethod进行返回值的处理

this.returnValueHandlers.handleReturnValue(
	 returnValue,
	 getReturnValueType(returnValue),
	 mavContainer, webRequest
	 );

handleReturnValue方法

public void handleReturnValue(Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

		HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
		if (handler == null) {
			throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
		}
		handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
	}

返回值处理逻辑: 从返回值处理混合器中,找到支持目标方法返回的处理器,进行处理。

需要注意的是
当我们使用@RequestBody 注解时,RequestResponseBodyMethodProcessor.handleReturnValue 处理返回值,会将返回值写入到输出流时,此时请求已经有了返回。

但是:HadnlerAdapter的逻辑是没有走完的。

再回到RequestMappingHandlerAdapter.invokeHandlerMethod方法的后半部分

invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
		return null;
}

return getModelAndView(mavContainer, modelFactory, webRequest);

我们看到他依然会调用getModelAndView方法做一个结果统一性处理,返回一个ModelAndView对象。

DispatcherServlet里会根据ModelAndView对象的值进行处理。为null不渲染页面,不为空渲染页面。


至此:HandlerApater的执行原理大轮廓基本就是这样了。更多细节可以去阅读源码去体会

总结:

想想写个接口时,参数接收时如此方便,看完RequestMappingHandlerAdapter后,不得不不感谢RequestMappingHandlerAdapter背后所做的工作

最后再次总结一下HandlerApater的主要工作:request 与 handler之间的参数映射,返回值处理,适配双方的不兼容问题

再次对Spring 框架体系膜拜,一个优秀的轮子,应该是分工明确可扩展性好

Spring 我愿称你为最强


如果本文任何错误,请批评指教,不胜感激 !
如果文章哪些点不懂,可以联系我交流学习!

微信公众号:源码行动
享学源码,行动起来,来源码行动

享读SpringMVC源码4-感谢RequestMappingHandlerAdapter_Spring_02