SpringMVC之RequestMappingHandlerAdapter
本文参考自 https://my.oschina.net/zhangxufeng/blog/2222270
RequestMappingHandlerAdapter是解析mvc命令空间时,SpringMVC自动注册的三个Bean之一,可见其重要性
RequestMappingHandlerAdapter实现了HandlerAdapter接口,顾名思义,表示handler的adapter,这里的handler指的是Spring处理具体请求的某个Controller的方法,也就是说HandlerAdapter指的是将当前请求适配到某个Handler的处理器。RequestMappingHandlerAdapter是HandlerAdapter的一个具体实现,主要用于将某个请求适配给@RequestMapping类型的Handler处理。
如下是HandlerMapping接口的声明:
public interface HandlerAdapter { // 用于判断当前HandlerAdapter是否能够处理当前请求 boolean supports(Object handler); // 如果当前HandlerAdapter能够用于适配当前请求,那么就会处理当前请求中 // 诸如参数和返回值等信息,以便能够直接委托给具体的Handler处理 ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; // 获取当前请求的最后更改时间,主要用于供给浏览器判断当前请求是否修改过, // 从而判断是否可以直接使用之前缓存的结果 long getLastModified(HttpServletRequest request, Object handler); }
1. supports()
HandlerAdapter.supports()方法的主要作用在于判断当前的HandlerAdapter是否能够支持当前的handler的适配。这里的handler指的是某个Controller的方法,其是由HandlerExecutionChain HandlerMapping.getHandler(HttpServletRequest)方法获取到的。从这里可以看出,HandlerMapping的作用主要是根据request请求获取能够处理当前request的handler,而HandlerAdapter的作用在于将request中的各个属性,如request param适配为handler能够处理的形式。
关于HandlerAdapter.supports()方法,有这个方法的主要原因是,HandlerMapping是可以有多种实现的,Spring会遍历这些具体的实现类,判断哪一个能够根据当前request产生一个handler,因而对于HandlerAdapter而言,其是不知道当前获取到的handler具体是什么形式的,不同的HandlerMapping产生的handler形式是不一样的,比如RequestMappingHandlerMapping产生的handler则是封装在HandlerMethod对象中的,因而这里HandlerAdapter需要一个方法能够快速过滤掉当前产生的handler是否为其能够进行适配的,这个方法就是HandlerAdapter.supports()方法。如下是该方法的实现:
其实我觉得作者没有说清楚,我的理解是SpringMVC能处理的请求有很多种,常见的有请求一个方法的,还有请求字段文件的,比如js,那么处理这两种请求的handler肯定是不一样的。因此HandlerAdapter 可以屏蔽这些差异,一般来说写框架代码都这么写。
// AbstractHandlerMethodAdapter @Override public final boolean supports(Object handler) { // 判断当前handler是否为HandlerMethod类型,并且判断supportsInternal()方法返回值是否为true, // 这里supportsInternal()方法是提供给子类实现的一个方法,对于RequestMappingHandlerAdapter // 而言,其返回值始终是true,因为其只需要处理的handler是HandlerMethod类型的即可 return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler)); }
// RequestMappingHandlerAdapter @Override protected boolean supportsInternal(HandlerMethod handlerMethod) { // 这里RequestMappingHandlerAdapter只是对supportsInternal()返回true,因为其只需要 // 处理的handler类型是HandlerMethod类型即可 return true; }
2. handle()
在supports()方法判断了所处理的handler是HandlerMethod类型之后,RequestMappingHandlerAdapter就会调用handle()方法处理当前请求。该方法的主要作用在于有五点:
- 获取当前Spring容器中在方法上配置的标注了
@ModelAttribute但是没标注@RequestMapping注解的方法,在真正调用具体的handler之前会将这些方法依次进行调用; - 获取当前Spring容器中标注了
@InitBinder注解的方法,调用这些方法以对一些用户自定义的参数进行转换并且绑定; - 根据当前handler的方法参数标注的注解类型,如
@RequestParam,@ModelAttribute等,获取其对应的ArgumentResolver,以将request中的参数转换为当前方法中对应注解的类型; - 配合转换而来的参数,通过反射调用具体的handler方法;
- 通过
ReturnValueHandler对返回值进行适配,比如ModelAndView类型的返回值就由ModelAndViewMethodReturnValueHandler处理,最终将所有的处理结果都统一封装为一个ModelAndView类型的返回值,这也是RequestMappingHandlerAdapter.handle()方法的返回值类型。
这里我们首先看看RequestMappingHandlerAdapter.handle()方法的实现源码:
@Override protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ModelAndView mav; checkRequest(request); // 判断当前是否需要支持在同一个session中只能线性地处理请求 if (this.synchronizeOnSession) { // 获取当前请求的session对象 HttpSession session = request.getSession(false); if (session != null) { // 为当前session生成一个唯一的可以用于锁定的key Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { // 对HandlerMethod进行参数等的适配处理,并调用目标handler mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // 如果当前不存在session,则直接对HandlerMethod进行适配 mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // 如果当前不需要对session进行同步处理,则直接对HandlerMethod进行适配 mav = invokeHandlerMethod(request, response, handlerMethod); } // 判断当前请求头中是否包含Cache-Control请求头,如果不包含,则对当前response进行处理, // 为其设置过期时间 if (!response.containsHeader(HEADER_CACHE_CONTROL)) { // 如果当前SessionAttribute中存在配置的attributes,则为其设置过期时间。 // 这里SessionAttribute主要是通过@SessionAttribute注解生成的 if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers); } else { // 如果当前不存在SessionAttributes,则判断当前是否存在Cache-Control设置, // 如果存在,则按照该设置进行response处理,如果不存在,则设置response中的 // Cache的过期时间为-1,即立即失效 prepareResponse(response); } } return mav; }
上述代码主要做了两部分处理:①判断当前是否对session进行同步处理,如果需要,则对其调用进行加锁,不需要则直接调用;②判断请求头中是否包含Cache-Control请求头,如果不包含,则设置其Cache立即失效。可以看到,对于HandlerMethod的具体处理是在invokeHandlerMethod()方法中进行的,如下是该方法的具体实现:
@Nullable protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest(request, response); try { // 获取容器中全局配置的InitBinder和当前HandlerMethod所对应的Controller中 // 配置的InitBinder,用于进行参数的绑定 WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); // 获取容器中全局配置的ModelAttribute和当前当前HandlerMethod所对应的Controller // 中配置的ModelAttribute,这些配置的方法将会在目标方法调用之前进行调用 ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); // 将handlerMethod封装为一个ServletInvocableHandlerMethod对象, // 该对象用于对当前request的整体调用流程进行了封装 ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); if (this.argumentResolvers != null) { // 设置当前容器中配置的所有ArgumentResolver invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); } if (this.returnValueHandlers != null) { // 设置当前容器中配置的所有ReturnValueHandler invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); } // 将前面创建的WebDataBinderFactory设置到ServletInvocableHandlerMethod中 invocableMethod.setDataBinderFactory(binderFactory); // 设置ParameterNameDiscoverer,该对象将按照一定的规则获取当前参数的名称 invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); ModelAndViewContainer mavContainer = new ModelAndViewContainer(); mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); // 这里initModel()方法主要作用是调用前面获取到的@ModelAttribute标注的方法, // 从而达到@ModelAttribute标注的方法能够在目标Handler调用之前调用的目的 modelFactory.initModel(webRequest, mavContainer, invocableMethod); mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); // 获取当前的AsyncWebRequest,这里AsyncWebRequest的主要作用是用于判断目标 // handler的返回值是否为WebAsyncTask或DefferredResult,如果是这两种中的一种, // 则说明当前请求的处理应该是异步的。所谓的异步,指的是当前请求会将Controller中 // 封装的业务逻辑放到一个线程池中进行调用,待该调用有返回结果之后再返回到response中。 // 这种处理的优点在于用于请求分发的线程能够解放出来,从而处理更多的请求,只有待目标任务 // 完成之后才会回来将该异步任务的结果返回。 AsyncWebRequest asyncWebRequest = WebAsyncUtils .createAsyncWebRequest(request, response); asyncWebRequest.setTimeout(this.asyncRequestTimeout); // 封装异步任务的线程池,request和interceptors到WebAsyncManager中 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 + "]"); } // 封装异步任务的处理结果,虽然封装的是一个HandlerMethod,但只是Spring简单的封装 // 的一个Callable对象,该对象中直接将调用结果返回了。这样封装的目的在于能够统一的 // 进行右面的ServletInvocableHandlerMethod.invokeAndHandle()方法的调用 invocableMethod = invocableMethod.wrapConcurrentResult(result); } // 对请求参数进行处理,调用目标HandlerMethod,并且将返回值封装为一个ModelAndView对象 invocableMethod.invokeAndHandle(webRequest, mavContainer); if (asyncManager.isConcurrentHandlingStarted()) { return null; } // 对封装的ModelAndView进行处理,主要是判断当前请求是否进行了重定向,如果进行了重定向, // 还会判断是否需要将FlashAttributes封装到新的请求中 return getModelAndView(mavContainer, modelFactory, webRequest); } finally { // 调用request destruction callbacks和对SessionAttributes进行处理 webRequest.requestCompleted(); } }
上述代码是RequestMappingHandlerAdapter处理请求的主要流程,其主要包含四个部分:
①获取当前容器中使用@InitBinder注解注册的属性转换器;
②获取当前容器中使用@ModelAttribute标注但没有使用@RequestMapping标注的方法,并且在调用目标方法之前调用这些方法;
③判断目标handler返回值是否使用了WebAsyncTask或DefferredResult封装,如果封装了,则按照异步任务的方式进行执行;
④处理请求参数,调用目标方法和处理返回值。这里我们首先看RequestMappingHandlerAdapter是如何处理标注@InitBinder的方法的,如下是getDataBinderFactory()方法的源码:
这里我做下点评,InitBinder是处理表单的,但是现在都用JS应该没人用表单了。ModelAttribute是用来变更SpringMVC的Model,但是其实现在给前端都是JSON了,这部分说实话不太会有人用吧。
至于异步任务,没听说过。
所以这里跳过前三步,调用过程在ServletInvocableHandlerMethod.invokeAndHandle()方法中,如下是该方法的源码:
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 对目标handler的参数进行处理,并且调用目标handler Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); // 设置相关的返回状态 setResponseStatus(webRequest); // 如果请求处理完成,则设置requestHandled属性 if (returnValue == null) { if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) { mavContainer.setRequestHandled(true); return; } } else if (StringUtils.hasText(getResponseStatusReason())) { // 如果请求失败,但是有错误原因,那么也会设置requestHandled属性 mavContainer.setRequestHandled(true); return; } mavContainer.setRequestHandled(false); Assert.state(this.returnValueHandlers != null, "No return value handlers"); try { // 遍历当前容器中所有ReturnValueHandler,判断哪种handler支持当前返回值的处理, // 如果支持,则使用该handler处理该返回值 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; } }
对于handler的调用过程,这里主要分为三个步骤:①处理请求参数进行处理,将request中的参数封装为当前handler的参数的形式;②通过反射调用当前handler;③对方法的返回值进行处理,以将其封装为一个ModleAndView对象。这里第一步和第二步封装在了invokeForRequest()方法中,我们首先看该方法的源码:
@Nullable public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 将request中的参数转换为当前handler的参数形式 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) + "' with arguments " + Arrays.toString(args)); } // 这里doInvoke()方法主要是结合处理后的参数,使用反射对目标方法进行调用 Object returnValue = doInvoke(args); if (logger.isTraceEnabled()) { logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) + "] returned [" + returnValue + "]"); } return returnValue; } // 本方法主要是通过当前容器中配置的ArgumentResolver对request中的参数进行转化, // 将其处理为目标handler的参数的形式 private Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 获取当前handler所声明的所有参数,主要包括参数名,参数类型,参数位置,所标注的注解等等属性 MethodParameter[] parameters = getMethodParameters(); Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); // providedArgs是调用方提供的参数,这里主要是判断这些参数中是否有当前类型 // 或其子类型的参数,如果有,则直接使用调用方提供的参数,对于请求处理而言,默认情况下, // 调用方提供的参数都是长度为0的数组 args[i] = resolveProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } // 如果在调用方提供的参数中不能找到当前类型的参数值,则遍历Spring容器中所有的 // ArgumentResolver,判断哪种类型的Resolver支持对当前参数的解析,这里的判断 // 方式比较简单,比如RequestParamMethodArgumentResolver就是判断当前参数 // 是否使用@RequestParam注解进行了标注 if (this.argumentResolvers.supportsParameter(parameter)) { try { // 如果能够找到对当前参数进行处理的ArgumentResolver,则调用其 // resolveArgument()方法从request中获取对应的参数值,并且进行转换 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.getExecutable().toGenericString() + ": " + getArgumentResolutionErrorMessage("No suitable resolver for",i)); } } return args; }
浙公网安备 33010602011771号