SpringMVC源码解析(三)——HandlerAdapter

前言

    上篇讲的是,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 List<HandlerAdapter> handlerAdapters;

    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<String, HandlerAdapter> matchingBeans =
                    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerAdapters = new ArrayList<HandlerAdapter>(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()) {
                .....// 省略日志
            }
        }
    }
}

    跟“ HandlerMapping 初始化 ”相同的处理方式,这里不做赘述。那么接下来,就看看 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

@Deprecated
public 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,是不是很眼熟,就是我们上篇 “ HandlerMapping 初始化 ”时创建的。具体的 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 中所有的方法。

    之后就用上篇 HandlerMapping初始化 时创建的 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 的适配以及调用逻辑,这些逻辑将作为后续章节介绍一次具体请求流程的铺垫。

转载于:https://my.oschina.net/marvelcode/blog/1838329

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值