前言
上篇讲的是,HandlerMapping 初始化过程中,是如何将 Handler 与 请求的 url 建立起映射的,我们可以假想一下,“ http://localhost/test ” 的请求过来了,通过映射关系我们找到了 Handler,但这个 Handler 具体是什么类型呢,是基于 @RequestMapping 注解的?还是实现了接口 org.springframework.web.servlet.mvc.Controller 的?不同的实现,处理方式也不一样
这篇讲的 HandlerAdapter 就是对 Handler 的一个适配,来看源码。
源码解读
首先来看下接口定义。
public interface HandlerAdapter { // 支持适配的类型 boolean supports(Object handler); // 调用具体的处理逻辑 ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; // 获取 Header:Last-Modified,用于页面缓存 long getLastModified(HttpServletRequest request, Object handler);}
下面会重点分析下 support 和 handle 方法。前者是逻辑比较简单,将 Handler 实例传入,具体实现类来判断是否支持适配。而后者涉及到的处理就相对麻烦一些。
来此之前,依然先回顾下 HandlerAdapter 的初始化过程。
public class DispatcherServlet extends FrameworkServlet { // 存放加载的 HandlerAdapter实现 private ListhandlerAdapters; private boolean detectAllHandlerAdapters = true; // 衔接第一节 mvc初始化 @Override protected void onRefresh(ApplicationContext context) { initStrategies(context); } protected void initStrategies(ApplicationContext context) { ...... // 关注该方法:HandlerAdapter的初始化 initHandlerAdapters(context); ...... } private void initHandlerAdapters(ApplicationContext context) { this.handlerAdapters = null; // 默认探测容器内所有的 HandlerAdapter类型 if (this.detectAllHandlerAdapters) { // beansOfTypeIncludingAncestors:通过 getBeansOfType获取子容器和父容器内的 HandlerAdapter // getBeansOfType会首先调用 getBeanNamesForType获取指定类型的所有 beanName // 然后遍历这些 beanName,使用 getBean创建实例 Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerAdapters = new ArrayList (matchingBeans.values()); AnnotationAwareOrderComparator.sort(this.handlerAdapters); } } else { try { // 找 beanName为 handlerAdapter的 HandlerAdapter HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class); this.handlerAdapters = Collections.singletonList(ha); } catch (NoSuchBeanDefinitionException ex) { } } if (this.handlerAdapters == null) { // 默认策略:见 DispatcherServlet.properties this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class); if (logger.isDebugEnabled()) { .....// 省略日志 } } }}
跟“ ”相同的处理方式,这里不做赘述。那么接下来,就看看 HandlerAdapter 到底有多少种实现。
HttpRequestHandlerAdapter
public class HttpRequestHandlerAdapter implements HandlerAdapter { @Override public boolean supports(Object handler) { return (handler instanceof HttpRequestHandler); } @Override public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ((HttpRequestHandler) handler).handleRequest(request, response); return null; }}
支持 HttpRequestHandler 类型的处理器,handle 方法实现也只是简单的向下转型,然后调用接口方法。从源码也能猜测出,如果要使用这样的处理器,只需要实现 HttpRequestHandler.handleRequest 即可。
SimpleControllerHandlerAdapter
public class SimpleControllerHandlerAdapter implements HandlerAdapter { @Override public boolean supports(Object handler) { return (handler instanceof Controller); } @Override public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return ((Controller) handler).handleRequest(request, response); }}
对 Controller 类型的支持。
SimpleServletHandlerAdapter
public class SimpleServletHandlerAdapter implements HandlerAdapter { @Override public boolean supports(Object handler) { return (handler instanceof Servlet); } @Override public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ((Servlet) handler).service(request, response); return null; }}
对 Servlet 类型的支持。
AnnotationMethodHandlerAdapter
@Deprecatedpublic class AnnotationMethodHandlerAdapter extends WebContentGenerator implements HandlerAdapter, Ordered, BeanFactoryAware { @Override public boolean supports(Object handler) { // Handler 下的方法有被 @RequestMapping标识的就返回 true return getMethodResolver(handler).hasHandlerMethods(); }}
该类就是 Spring 3.2 版本以前,对于注解形式 Handler 的适配器。它的 handle 方法就不像前面提及的,只是简单做了转型而调用方法就行了。因为还要涉及到很多其他 mvc 注解的解析工作,以及响应信息的处理。
但它带来的优势是,没有像接口那样,限制了一个 Handler 只能实现对应的一个方法,入参以及返回类型都被接口定义,而不能自定义。由此看来,我们所见灵活性其实是基于框架层面复杂逻辑的封装处理。
由于这个类已被声明废弃,接替它的是 RequestMappingHandlerAdapter ,所以我们直接来看最新的实现。
RequestMappingHandlerAdapter
public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered { @Override public final boolean supports(Object handler) { // supportsInternal总返回 true,所以只要满足 Handler为 HandlerMethod即可 return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler)); } @Override public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return handleInternal(request, response, (HandlerMethod) handler); } protected abstract ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception;}
从其抽象父类实现来看,支持的类型为 HandlerMethod,是不是很眼熟,就是我们上篇 “ ”时创建的。具体的 handle 调用了抽象方法 handleInternal,实现委托给了子类。
// RequestMappingHandlerAdapter实现 @Override protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ModelAndView mav; // 如果有必要:对请求 method、以及 session进行校验 checkRequest(request); // 会话级别的同步:默认为 false if (this.synchronizeOnSession) { HttpSession session = request.getSession(false); if (session != null) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { mav = invokeHandlerMethod(request, response, handlerMethod); } } else { mav = invokeHandlerMethod(request, response, handlerMethod); } } else { mav = invokeHandlerMethod(request, response, handlerMethod); } /** * 如果响应头里不包含 “Cache-Control”处理 * - private/must-revalidate:仅在首次访问时访问服务器 * - no-cache:每次访问都会访问服务器 * - max-age:过期之前不会访问服务器 */ if (!response.containsHeader(HEADER_CACHE_CONTROL)) { if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { /** * 该类被 @SessionAttributes标识并指定了 names 或 types * 根据 cacheSecondsForSessionAttributeHandlers大小不同来给头赋值 * HTTP 1.0使用的是 Expires头,HTTP 1.1使用的是 Cache-Control */ applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers); } else { // 未被 @SessionAttributes标识类,请求缓存处理 prepareResponse(response); } } return mav; }
这个方法主要调用了 invokeHandlerMethod 来调用方法来获取结果,然后就是缓存相关 Header 信息的设置。
需要注意的是,HTTP 1.0 版本,是通过 “expired” 来记录缓存时间的,它记录的是一个时间戳,在时间戳到期之前,都不会去请求服务端,但时间戳为服务端返回的,并不是客户端生成的,因此存在误差。
HTTP 1.1 版本,通过 “Cache-Control” 来记录过期时长的,使用 max-age = 秒数,来表示资源在客户端缓存的时长。使用 no-cache 表示不缓存。
// ServletInvocableHandlerMethod实现 protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest(request, response); try { // 根据类中被 @InitBinder标识的方法,创建一个 WebDataBinder工厂 WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); // 根据类中被 @ModelAttribute标识的方法,创建一个 Model工厂 ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); // 这里传入的 handlerMethod就是 HandlerMapping初始化时创建的 // 至于如何找到请求 url对应的 HandlerMethod,之后的章节会讲解 // 继承体系:ServletInvocableHandlerMethod——>InvocableHandlerMethod——>HandlerMethod ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); // 赋值操作,用于请求的解析和相应的处理 invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); invocableMethod.setDataBinderFactory(binderFactory); invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); // 用于记录请求解析和相应 ModelAndViewContainer mavContainer = new ModelAndViewContainer(); // FlashMap:重定向保存上次请求的参数 mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); // 调用被 @ModelAttribute标识的方法,填充 mavContainer // 将 @SessionAttributes指定的 Session中的属性,填充 mavContainer // 将 @ModelAttribute标识的参数,如果 @SessionAttributes中存在,同样填充 mavContainer 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); // 通过调用 startCallableProcessing启动并发请求处理 // 直到请求处理完前,hasConcurrentResult返回的都是 false // 初始值为:RESULT_NONE,所以判断 (this.concurrentResult != RESULT_NONE)为 false if (asyncManager.hasConcurrentResult()) { Object result = asyncManager.getConcurrentResult(); mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0]; // 已经获取异步结果,所以要清除维护的结果 asyncManager.clearConcurrentResult(); if (logger.isDebugEnabled()) { logger.debug("Found concurrent result value [" + result + "]"); } // 创建一个嵌套的 ServletInvocableHandlerMethod子类,返回给定的值 // 而不是实际调用控制器方法。 在处理异步返回值时,这非常有用 invocableMethod = invocableMethod.wrapConcurrentResult(result); } // 调用对应方法,并使用 HandlerMethodReturnValueHandler处理返回值 invocableMethod.invokeAndHandle(webRequest, mavContainer); if (asyncManager.isConcurrentHandlingStarted()) { return null; } // mavContainer存储了响应的 viewName、Model数据、Http状态码等信息 // 根据这些信息创建 ModelAndView返回 // 如果是重定向,还会转存 FlashMap数据 return getModelAndView(mavContainer, modelFactory, webRequest); } finally { webRequest.requestCompleted(); } }
方法的最开始,有对 @InitBinder、@ModelAttribute 标识的方法进行收集并创建一个工厂,因为这些方法将作用于 Handler 中所有的方法。
之后就用上篇 时创建的 HandlerMethod 来创建一个 ServletInvocableHandlerMethod,因为下面的赋值方法是该子类特有的。
ModelAndViewContainer,这个类就像一个承载请求和相应的容器,请求前将放置一些参数(重定向前参数、@ModelAttribute注解方法返回等等),这些会在调用 invokeAndHandle 时传入,以便处理时需要。
接下来来看 invokeAndHandle 的实现。
// ServletInvocableHandlerMethod实现 public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 调用方法并得到返回结果 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); // 根据 @ResponseStatus注解来设置相应状态 setResponseStatus(webRequest); if (returnValue == null) { // 没有返回值 if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) { mavContainer.setRequestHandled(true); return; } } // 如果有返回值,且被 @ResponseStatus指定了 reason else if (StringUtils.hasText(getResponseStatusReason())) { mavContainer.setRequestHandled(true); return; } // 设置本次请求是否已被完全处理,通常都是 false // 例如:@ResponseBody标识的返回需要额外处理 mavContainer.setRequestHandled(false); try { // 挑选一个 HandlerMethodReturnValueHandler处理返回值(策略) // 例如:RequestResponseBodyMethodProcessor.handleReturnValue(源码在下方) 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; } }
这里的 invokeForRequest 方法下边我们会讲,其实就是通过一次反射调用,来获取到方法的返回值(例如,UserController.select 返回了查询到的 User 对象)。
当然,也有 void 无返回值方法,那么判断请求是否处于缓存期内、或指定了 @ResponseStatus.code 属性,就直接返回了。
如果有返回值,但指定了 @ResponseStatus.reason 属性,那也直接返回了。
Ps:关于 @ResponseStatus,它一般和 @ExceptionHandler 结合使用,用于异常的统一处理,以及响应码、提示信息的统一返回。
否则就要通过 HandlerMethodReturnValueHandler 来对响应结果进行处理。
// InvocableHandlerMethod实现 public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 根据请求解析出方法入参 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) + "' with arguments " + Arrays.toString(args)); } // 通过反射调用方法 Object returnValue = doInvoke(args); if (logger.isTraceEnabled()) { logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) + "] returned [" + returnValue + "]"); } return returnValue; }
可以看到 invokeForRequest 方法逻辑很简单,第一步调用方法 getMethodArgumentValues 对入参进行解析,第二步直接通过反射调用相应方法拿到响应结果。所以难点在参数的解析上。
// InvocableHandlerMethod实现 private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 获取调用方法的参数 MethodParameter[] parameters = getMethodParameters(); Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; // ParameterNameDiscovery:用于获取方法入参名称 // - LocalVariableTableParameterNameDiscoverer:根据 class文件中的 LocalVariableTable解析 // - AspectJAnnotationParameterNameDiscoverer:根据 aspectj注解的 argNames // - StandardReflectionParameterNameDiscoverer:基于反射获取 parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); // 根据入参提供的 providedArgs,找出符合类型的 // 根据上面代码追溯,入参 providedArgs为 null,因此直接返回 null args[i] = resolveProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } // argumentResolvers类型为:HandlerMethodArgumentResolverComposite // 组合模式将所有 HandlerMethodArgumentResolver实现包装 // 例如:RequestResponseBodyMethodProcessor,支持 @RequestBody、@ResponseBody if (this.argumentResolvers.supportsParameter(parameter)) { try { // 调用实现方法对参数进行解析 args[i] = this.argumentResolvers.resolveArgument( parameter, mavContainer, request, this.dataBinderFactory); continue; } catch (Exception ex) { if (logger.isDebugEnabled()) { ....// 省略日志 } throw ex; } } if (args[i] == null) { ....// 未解析出的参数会抛异常 IllegalStateException } } return args; }
Spring 会将方法的入参相关信息封装成 MethodParameter 对象,具体的解析逻辑交给了不同的 HandlerMethodArgumentResolver 实现类,比如我们常使用的 Json 请求响应,我们来看 RequestResponseBodyMethodProcessor 是如何解析的。
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor { @Override public boolean supportsParameter(MethodParameter parameter) { // 支持解析参数的条件:被 @RequestBody标识 return parameter.hasParameterAnnotation(RequestBody.class); } @Override public boolean supportsReturnType(MethodParameter returnType) { // 支持处理返回的条件:返回方法或类型被 @ResponseBody标识 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 { // 如果是参数类型 Optional,返回一个嵌套级别的 MethodParameter parameter = parameter.nestedIfOptional(); // 调用 HttpMessageConverter解析请求参数 // 根据 contentType选择不同的转换器处理,比如 application/json,可以使用 MappingJackson2HttpMessageConverter Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType()); // 获取参数(包括数组和集合)常规名称:见 ClassUtils.getShortNameAsProperty String name = Conventions.getVariableNameForParameter(parameter); WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name); if (arg != null) { // 对 @Validated或 @Valid的校验 // 实现:调用 validate.validate validateIfApplicable(binder, parameter); // 如果有校验未通过的,抛出 MethodArgumentNotValidException if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new MethodArgumentNotValidException(parameter, binder.getBindingResult()); } } mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); // 对于入参类型为 Optional的,要封装一下 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); // 调用 HttpMessageConverter处理响应结果 writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); }}
支持的入参和返回,从方法可以很容易看出,就是支持 body 体数据解析的 @RequestBody,和支持 body 体数据响应的 @ResponseBody。但具体支持的是 Json 格式还是 Xml 格式,是由 HttpMessageConverter 来支持的。
这里还有一点就是对 @Validate 和 @Valid 标识入参的校验,如果校验不通过,会以 MethodArgumentNotValidException 异常抛出,具体的提示信息(例如 @NotNull( message="不能为空" ))也会封装在内。
到这里,一次请求的大致流程也基本解读完毕。涉及到一些具体的像“Json数据解析过程”,“响应数据的处理”本篇没有展开讲解。
总结
这一节主要的内容就是 Handler 的适配以及调用逻辑,这些逻辑将作为后续章节介绍一次具体请求流程的铺垫。