专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

SpringMVC源码分析六、组件二HandlerAdapter源码分析

简单的回顾一下前面笔者对HandlerMapping源码的分析, 着重分析了以方法作为Handler这一派系的体系结构, SpringMVC通过监听器的回调来触发九大策略的初始化流程, 其实九大策略的初始化流程是差不多的, 首先判断是否应 该检测容器中所有的该策略实现类, 如果是则获取这些实现类的对象并保存到DispatcherServlet中的局部变量中, 如 果不是则取第一个, 那如果容器中没有对应的实现类对象的话, 则会通过读取配置文件来获取对应的默认策略(可能有 多个), 对于RequestMappingHandlerMapping, 是我们日常使用中最为频繁的, SpringMVC通过扫描整个Spring容器 获取所有的对象, 然后对这些对象进行扫描, 仅仅过滤@Controller或者@RequestMapping注解标注的对象, 对这些 对象的方法进行遍历, 每一个被@RequestMapping标注的方法都会被封装成一个handler, 用泛型T表示, 之所以用泛型 的原因笔者就不进行介绍了, 大家有兴趣的可以去了解下(因为这是一个RequestCondition), 在我们分析中, 或者说 正常情况下只要将这个泛型T当成RequestMappingInfo就好了, RequestMappingInfo就是一个@RequestMapping注 解的对象表示形式, 一个RequestMappingInfo对象对应一个HandlerMethod, HandlerMethod是对该注解标注的方法 的封装, 保存了Method, bean对象的引用, 这样的一种关系被放在了AbstractHandlerMethodMapping对象中的属性 mappingRegistry中的mappingLookup中, SpringMVC还将url到RequestMappingInfo的关系保存到了 mappingRegistry中的urlLookup中, 可能有点绕, 简单的说, AbstractHandlerMethodMapping不直接保存url、 RequestMappingInfo、HandlerMethod的引用关系, 而是通过一个MappingRegistry类来保存, 而在请求过来的时候 就是去这个类中的两个map找到对应的HandlerMethod的, 这个HandlerMethod也被称为handler

HandlerAdapter引入

那简单的回顾了一下HandlerMapping的源码执行流程, 我们知道, 当SpringMVC启动完成的时候, url、
RequestMappingInfo、HandlerMethod的引用关系就已经保存到了AbstractHandlerMethodMapping中了, 请求过来
的时候, 就会从中取到对应的HandlerMethod, 从而开始处理请求, 由于我们仅仅说了其中一种HandlerMapping的情
况, 但是SpringMVC中可是有很多个HandlerMapping类型的, 其他的HandlerMapping类型就不是通过HandlerMethod
来处理请求了, 比如直接实现HttpServlet接口的就通过调用该接口的doXXX方法来执行的, DispatcherServlet中为
了能够统一这些调用方式, 比如在处理请求时, 不管获取到的handler是什么类型的, 都通过调用其handle方法去处理

然而不可能直接去修改源代码, 于是就引入了适配器模式, 下面我们先来看看HandlerAdapter接口的定义吧

public interface HandlerAdapter {
  boolean supports(Object handler);

  ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)

  long getLastModified(HttpServletRequest request, Object handler);
}

简简单单的三个方法, 第三个方法我们不进行分析, 跟http协议相关, 首先来看看第一个方法, 传入一个Object类型
的handler, 判断当前HandlerAdapter是否能够处理该handler, 其实很简单, 对于HandlerMethod类型的handler
来说, 仅仅需要判断其是不是HandlerMethod类型就好了, 对于HttpServlet类型的来说, 就判断是不是HttpServlet
类型

第二个方法, 就是我们前面提到的, 需要统一所有handler的调用方式, 其实在SpringMVC中, 就是通过找到一个
handler对应的HandlerAdapter, 然后调用HandlerAdapter的hanle方法进行处理请求的, 所以不同的hanler仅仅
需要提供不同的适配器就好了, 举个例子, HanlerMethod类型的hanler, 仅仅需要在这个handle方法中调用
HandlerMethod中Method对象的invoke方法就好了, 其他的也是类似, 但是对于HandlerMethod却没有想象中那么简
单

HandlerAdapter继承体系分析

如下图所示, HandlerAdapter一共有5个实现类, 其中最难, 最重要的就是AbstractHandlerMethodAdapter这一派
系, 我们也会着重的讲解这个Adapter是如何处理HandlerMethod的, 剩下三个实现类就简单多了, 我们以
HttpRequestHandlerAdapter为例子看看其如何实现HandlerAdater接口的吧:

public class HttpRequestHandlerAdapter implements HandlerAdapter {
    public boolean supports(Object handler) {
        return (handler instanceof HttpRequestHandler);
    }

    public ModelAndView handle(HttpServletRequest request, 
                                                HttpServletResponse response, Object handler) {
        ((HttpRequestHandler) handler).handleRequest(request, response);
        return null;
    }

    public long getLastModified(HttpServletRequest request, Object handler) {
        if (handler instanceof LastModified) {
            return ((LastModified) handler).getLastModified(request);
        }
        return -1L;
    }
}

很简单, 就是调用了HttpRequestHandler类的handleRequest方法而已, 我们可以通过实现一个XXXController, 并
且这个Controller继承HttpRequestHandler, 然后注册到容器中就好了, 与此同时我们需要将
HttpRequestHandlerAdapter放到容器中, 然而该适配器同时也是默认策略中的其中一个, 所以我们也不用将这个适
配器手动放入

简单的看了下继承结构, 接下来我们就要详细的分析AbstractHandlerMethodAdapter这一派系的内容了

64_1.png

AbstractHandlerMethodAdapter源码

下面仅仅将该类的主要实现放出来, 其实现的Ordered接口, 以及WebContentGenerator就不进行解释了, 因为这两个
接口意义都不太大, 我们的核心是这个适配器如何处理HandlerMethod类型的handler的
public abstract class AbstractHandlerMethodAdapter implements HandlerAdapter {
    public final boolean supports(Object handler) {
        return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
    }

    protected abstract boolean supportsInternal(HandlerMethod handlerMethod);

    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;
}

分析:
    那可以看到, AbstractHandlerMethodAdapter实现的HandlerAdapter的方法中, supports方法实现很简单,
    就是判断是否是HttpMethod类型而已, 同时增加了一个supportsInternal的判断, 然而这个判断在子类
    RequestMappingHandlerAdapter中是直接返回true的, 所以我们可以认为, 当一个Handler是HandlerMethod
    类型的时候, 就会被这个适配器处理

    再来看看handle方法, 实现也很简单, 调用了方法handleInternal, 将handler强转为HandlerMethod类型
    就ok了, 子类RequestMappingHandlerAdapter对这个handleInternal进行了实现, 所以我们的重点就出来了

RequestMappingHandlerAdapter整体结构

一、属性分析

  • ArgumentResolver
private List<HandlerMethodArgumentResolver> customArgumentResolvers;
private HandlerMethodArgumentResolverComposite argumentResolvers;

分析: 
    ArgumentResolvers, 用于参数解析, 当我们在调用HandlerMethod的时候, 必然是先取到其里面保存的Method
    对象, 以及bean对象的, 然后利用反射: method.invoke(bean, args) 来完成调用, 并且可以联想到, 我们日
    常使用的时候, 可能会有以下几种参数的使用方式:
        @RequestMapping( "/test" )
        public String test (@RequestBody User user, @RequestParam("name") String name,
        HttpServletRequest httpServletRequest, HttpSession httpSession) {
            return "index";
        }

    而参数解析器就是在这个时候启作用的, 不同的参数解析器解析不同类型的参数, 比如
    RequestResponseBodyMethodProcessor参数解析器就是用来解析@RequestBody注解的参数的, 在SpringMVC
    中, 反射调用HandlerMethod之前, 会利用参数解析器先取得对应的参数, 然后才反射调用, 这些参数的来源之后
    我们再进行分析, 总之通过参数解析器能够获得对应的参数就够了, 再来看看参数解析器接口的定义:

    public interface HandlerMethodArgumentResolver {
        boolean supportsParameter(MethodParameter parameter);

        Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                            NativeWebRequest webRequest, WebDataBinderFactory binderFactory)
    }  

    可以联想到, supportsParameter方法就是用来判断一个参数是否能用该参数解析器来解析, resolveArgument
    就是用来真正解析这些参数的, 而参数值其实就是从该方法的四个参数中获取而已, 我们先不用管这四个参数是什
    么......

再来说说customArgumentResolvers和argumentResolvers的区别吧, 程序员能够手动的注入不同的参数解析器, 或
者SpringMVC提供了默认的参数解析器, 一开始都会放置在customArgumentResolvers中, 当初始化完成
RequestMappingHandlerAdapter的时候, 就会将customArgumentResolvers中的所有参数解析器整合到
argumentResolvers中, argumentResolvers是一个HandlerMethodArgumentResolverComposite类型, Composite
的是整合/综合的意思, 那很好理解了, SpringMVC利用这个类来完成了参数解析器的查找与应用, 因为如果将查找与
应用的代码全部写在HandlerAdapter的话, 就会呈现耦合的情况, 将这些对参数解析器的操作全部抽离出来放到一个
额外的类中, HandlerAdapter持有该类对象的引用就可以进行解耦了, HandlerMethodArgumentResolverComposite
中就是维护了一个HandlerMethodArgumentResolver的集合而已

  • ReturnValueHandler
private List<HandlerMethodReturnValueHandler> customReturnValueHandlers;

private HandlerMethodReturnValueHandlerComposite returnValueHandlers;

分析:
    ReturnValueHandler跟上面的参数解析器很相似, 这个是用来解析HandlerMethod调用的返回值的, 同样的, 也
    分为很多类型的ReturnValueHandler, 比如我们@ResponseBody就是用RequestResponseBodyMethodProcessor
    这个类来完成解析的, 这个时候, 大家就会发现, 这个类好像似曾相识!!因为@RequestBody也是它来解析的!!!

    与此同时, 上面两个变量的意思我就不进行解释了, 跟ArgumentResovler是一模一样的处理情况

  • HttpMessageConverter
private List<HttpMessageConverter<?>> messageConverters;

分析:
    这个messageConverters在我们SpringMVC中起着极其重要的作用, 我们在处理返回值的时候, @ResponseBody
    注解标注的方法中, 返回值会被当成JSON处理, 然而我们返回的是一个对象, 竟然变成了一个JSON, 就是这个
    消息转换器起的作用, 举个简单的例子, 如果用@ResponseBody标注了, 那么返回的时候就会利用Jackson将这个
    对象利用ObjectMapper转为JSON, 这个大家应该就很熟悉了

  • SessionAttributes
private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore();

private final Map<Class<?>, SessionAttributesHandler> sessionAttributesHandlerCache 
                                                                = new ConcurrentHashMap<>(64);

分析:
    先来看看SessionAttributeStore, 这个类是一个工具类, 他是帮助我们将key-value放到session中的, 其只
    有一个子类DefaultSessionAttributeStore, 该子类实现了SessionAttributeStore接口的三个方法, 实现
    很简单, 如下:
    public class DefaultSessionAttributeStore implements SessionAttributeStore {
        public void storeAttribute(WebRequest request, String attributeName, Object attributeValue) {
            String storeAttributeName = getAttributeNameInSession(request, attributeName);
            request.setAttribute(storeAttributeName, attributeValue, WebRequest.SCOPE_SESSION);
        }

        public Object retrieveAttribute(WebRequest request, String attributeName) {
            String storeAttributeName = getAttributeNameInSession(request, attributeName);
            return request.getAttribute(storeAttributeName, WebRequest.SCOPE_SESSION);
        }

        public void cleanupAttribute(WebRequest request, String attributeName) {
            String storeAttributeName = getAttributeNameInSession(request, attributeName);
            request.removeAttribute(storeAttributeName, WebRequest.SCOPE_SESSION);
        }
    }

    ok, 可以看到, 以storeAttribute为例子, 利用WebRequest对象将属性设置到了session中, 这个方法有三个
    参数, 属性名、属性值、作用域, 作用域就两个选择, 一个是session作用域, 一个是request作用域, 如果作用
    域选择前者, 那么就把key-value设置到session中, 如果是后者则将key-value设置到request中, 很简单吧..

    再来看看sessionAttributesHandlerCache这个Map, key为Class对象, 值为SessionAttributesHandler, 
    SessionAttributesHandler是@SessionAttributes这个注解的对象表示形式, 根据前一篇文章可以得知, 
    @SessionAttributes作用域是类, 所以这个map表示的是类名到该类所在@SessionAttributes的映射

    public class SessionAttributesHandler {
        private final Set<String> attributeNames = new HashSet<>();
        private final Set<Class<?>> attributeTypes = new HashSet<>();
        private final Set<String> knownAttributeNames = Collections.newSetFromMap(new ConcurrentHashMap<>(4));

        private final SessionAttributeStore sessionAttributeStore;
    }

    我们可以在@SessionAttributes注解上配置name、type, 对应到SessionAttributesHandler中的
    attributeNames, attributeTypes, 为了能够将这两个统一, 方便直接利用name进行查找, SpringMVC引入了
    knownAttributeNames, 因为我们配置了type的情况下, 一个请求结束后, 这个请求中的Model对象中, 如果是
    这个type属性中的一个, 那么这个Model中该key-value就会被放到session中, 与此同时, 之后为了能够直接用
    name从session中获取, 又将其保存到了knownAttributeNames中, 即整合了attributeNames和
    attributeTypes, 同时SessionAttributesHandler中内置了SessionAttributeStore, 理由很简单, 
    SessionAttributeStore对象是用来帮助设置key-value到session以及从session中获取的, 那么内置一个该
    对象就能直接在SessionAttributesHandler中操作了, 这个SessionAttributeStore真正被创建是在
    RequestMappingHandlerAdapter中的, 因为只要有一份该工具就好了, SessionAttributesHandler中仅仅是
    持有该工具的引用而已, 通过构造方法传进来的

  • InitBinder && ModelAttribute
private final Map<Class<?>, Set<Method>> initBinderCache = new ConcurrentHashMap<>(64);

private final Map<ControllerAdviceBean, Set<Method>> initBinderAdviceCache 
                                                                    = new LinkedHashMap<>();

private final Map<Class<?>, Set<Method>> modelAttributeCache = new ConcurrentHashMap<>(64);

private final Map<ControllerAdviceBean, Set<Method>> modelAttributeAdviceCache 
                                                                    = new LinkedHashMap<>();

private HandlerMethodArgumentResolverComposite initBinderArgumentResolvers;

分析:
    经过前一篇文章的分析, 我们清楚的了解了@InitBinder和@ModelAttribute的作用, 那么对于这两个注解标注
    的方法来说, 就是分别保存到了initBinderCache和modelAttributeCache, 可以看到, key为Controller对象

    可以看到, 一个Controller是允许多个该注解标注的方法的, 那么如果是全局的呢?即@ControllerAdvice中的,
    就会被分别放入到initBinderAdviceCache和modelAttributeAdviceCache中

    因为我们调用@InitBinder标注的方法的时候, 是可以传入参数的, 参数的解析就是用
    initBinderArgumentResolvers, 可以看到其类型是HandlerMethodArgumentResolverComposite, 即多个参
    数解析器会被放到这个类中, 但是我们正常的handler的参数解析确是用的上面提到的argumentsResolvers, 这
    点需要注意

二、构造器分析

讲解完属性相关的, 就该来讲一下构造器了, 同时也是RequestMappingHandlerAdapter源码分析的第一步, 使用该
对象必须得先创建该对象:

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

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

可以看到, 构造器中就是创建了一系列的HttpMessageConverter, 这里我举个例子, 我们日常使用的@ResponseBody
来说: 
@Controller
public class TestController1 {
    @GetMapping( "/request1" )
    @ResponseBody
    public List<String> request1 () {
        return Arrays.asList( "A", "B" );
    }
}

分析:
    在请求/request1的时候, 被@ResponseBody标注的返回值会被之前我们所说的ReturnValueHandler进行处理,
    真正是用RequestResponseBodyMethodProcessor处理的, 首先转JSON, SpringMVC用的是jackson包提供的
    序列化JSON的功能, 所以我们要引入这个jackson包, 否则是请求执行过来会报错的, 其次, 在本小结讲解的
    RequestMappingHandlerAdapter的构造器中, 就将用这个jackson序列化JSON的类放入进去了, 我们来看看
        this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
    先来看看AllEncompassingFormHttpMessageConverter这个类中的其中一部分吧:
    public class AllEncompassingFormHttpMessageConverter extends FormHttpMessageConverter {
        private static final boolean jackson2Present =
            ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", 
                            AllEncompassingFormHttpMessageConverter.class.getClassLoader()) &&
            ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", 
                            AllEncompassingFormHttpMessageConverter.class.getClassLoader());

        public AllEncompassingFormHttpMessageConverter() {
            if (jackson2Present) {
                addPartConverter(new MappingJackson2HttpMessageConverter());
            }
        }
    }

    ClassUtils.isPresent方法很简单, 就是try...catch, try里面是直接利用Class.forName反射获取传入的字
    符串, 如果反射创建失败, 说明不存在这个类, 于是catch中捕捉到异常, 但是什么也不做, 这个类被初始化的
    时候, 就判断是否存在ObjectMapper这个类了, 一个与判断, 如果为true, 则构造方法中就将这个
    MappingJackson2HttpMessageConverter的消息转换器放入到自身内部保存的partConverters属性中

    而MappingJackson2HttpMessageConverter中就持有一个ObjectMapper对象, 判断一个消息转换器是否适合
    当前返回值是这样的判断的, 以上面的请求例子进行分析, 返回的是一个List<String>, 当返回的时候, 如果请
    求该url时携带了对应的mediaType, 比如"application/json", 那么就会利用这个消息转换器进行返回值的处
    理, 笔者之前就在这里被坑过....直接用浏览器get请求发过来, 发现返回值是一个xml.....因为判断到底用哪个
    消息转换器肯定是通过遍历的方式一个个判断的, 由于请求过来的时候没有明确指定是content-type, 所以还没
    遍历到MappingJackson2HttpMessageConverter就已经找到转换器了

三、afterPropertiesSet进一步初始化

public void afterPropertiesSet() {
    initControllerAdviceCache();

    if (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);
    }
}

分析:
    初始化很简单, 第一步initControllerAdviceCache的做法就是初始化全局的ControllerAdvice中定义的
    @ModelAttribute以及@InitBinder, 将这些全局的方法找出来放入到对应的cache中, 前面已经详细分析过了相
    关的cache了, 同时找到被@ControllerAdvice注解标注的类同时实现了RequestBodyAdvice或者
    ResponseBodyAdvice接口的, 将他们放入到对应的属性中, 这个RequestBodyAdvice或者
    ResponseBodyAdvice接口大家有兴趣可以了解下, 这里就不进行展开了, 后者就是对请求返回的数据的一种扩展
    处理而已

    之后三步, 获取到所有的默认的ArgumentResolvers、InitBinderArgumentResolvers以及
    ReturnValueHandlers, 然后放到复合类HandlerMethodArgumentResolverComposite中, 也很简单, 就是直
    接new对应的组件而已, 我们可以用getDefaultArgumentResolvers的一部分代码来进行演示:

    private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
        List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
        resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),
                                                                this.requestResponseBodyAdvice));
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
        if (getCustomArgumentResolvers() != null) {
            resolvers.addAll(getCustomArgumentResolvers());
        }

        return resolvers;
    }

    可以看到, 就是直接new而已, 如果程序员手动提供的不为空, 也会放入到里面, 最后将这些返回

RequestMappingHandlerAdapter处理HandlerMethod

handleInternal方法入口

protected ModelAndView handleInternal(HttpServletRequest request,
        HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ModelAndView mav;
    checkRequest(request);

    mav = invokeHandlerMethod(request, response, handlerMethod);

    if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
        if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
            applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
        }
        else {
            prepareResponse(response);
        }
    }

    return mav;
}

分析:
    在这个方法中, 笔者删除了对session同步的判断, 这里大家不用纠结, 没啥意义.....为了让代码简单易懂, 就
    进行了这样的处理, 可以看到, 整个handleInternal方法仅仅做了三件事而已, 最后返回了一个ModelAndView,
    注意了, ModelAndView的意思是Model和View的结合体, 结合上一篇文章的分析, 我们知道了Model这个对象是
    贯穿了整个请求的执行过程的, 其就是一个Map而已, view是视图, 是我们执行完请求后要返回给前端的东西, 视
    图怎么解析的我们下一章节再详细分析

    checkRequest方法就是做了一些检查, 判断当前请求方式是否被支持, 通常情况下是所有请求方式都会支持的,
    我们所说的@GetMapping以及@PostMapping这样规定请求方式不是在这里判断的, 因为此时还处于外层, 还没开
    始处理HandlerMethod, 其次对session进行了判断, 如果session是必须的, 但是当前请求中又不存在的
    session的话就抛出一个异常

    其次就是核心方法invokeHandlerMethod, 这里传入了HttpServletRequest, HttpServletResponse, 以及
    HandlerMethod进去, 我们之后再详细分析这里面的执行流程

    最后是对request缓存的处理, 如果response中没有响应头Cache-Control的话, 就进行一定的处理, 这里就不
    进行分析了......大家有兴趣可以进去看下, 可能有一个判断跟SessionAttributeHandler有关, 如果看完了
    后面对invokeHandlerMethod方法的分析的话, 再来看这个跟SessionAttributeHandler有关的判断就会清晰很
    多

invokeHandlerMethod

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);

        ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
        if (this.argumentResolvers != null) {
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }
        if (this.returnValueHandlers != null) {
            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);

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

分析:
    先贴上代码, 首先说明一点, 笔者将所有跟异步请求相关的代码都删除了, 这一块我也不是很熟悉, 并且没有用
    到过, 而且笔者文章的整体目的是将通常情况下SpringMVC的执行流程给分析出来, 对于一些边边角角又不太常用
    的知识点就不进行分析的

    首先来看看ServletWebRequest, 在前面分析Adapter的SessionAttributes属性相关的时候, 我们是不是传入
    了一个WebRequest对象, 利用这个对象的setAttribute并提供一个作用域来决定将key-value设置到request中
    还是session中, 而那个WebRequest对象就是在这里创建的, 它的子类ServletWebRequest......将request
    和response放入进ServletWebRequest中, 之后可以通过这个对象获取request和response相关的信息, 以及设
    置key-value到request或者session中

    接下来我们一个个分析这些代码中涉及到的组件, 相信有了前面一章节以及本章节前面对Adapter属性的分析, 后
    面对这些组件分析大家会听起来看起来很顺畅

WebDataBinderFactory

将这个组件的名称可以理解为两部分, WebDataBinder以及Factory, 顾名思义, 就是WebDataBinder对象工厂, 所以
核心就是这个WebDataBinder了, 之前也分析过@InitBinder可以对WebDataBinder进行初始化, 同时@InitBinder有
两种形式存在, 一个是只作用于当前Controller的, 一种是放置在@ControllerAdvice标注的类上全局的

private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
    Class<?> handlerType = handlerMethod.getBeanType();
    Set<Method> methods = this.initBinderCache.get(handlerType);
    if (methods == null) {
        methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
        this.initBinderCache.put(handlerType, methods);
    }
    List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();
    // Global methods first
    this.initBinderAdviceCache.forEach((clazz, methodSet) -> {
        if (clazz.isApplicableToBeanType(handlerType)) {
            Object bean = clazz.resolveBean();
            for (Method method : methodSet) {
                initBinderMethods.add(createInitBinderMethod(bean, method));
            }
        }
    });
    for (Method method : methods) {
        Object bean = handlerMethod.getBean();
        initBinderMethods.add(createInitBinderMethod(bean, method));
    }
    return createDataBinderFactory(initBinderMethods);
}

分析:
    代码其实很简单,首先是从initBinderCache中获取到当前HanlderMethod所在的Controller类中的所有被
    @InitBinder标注的方法, 如果没有获取到, 就利用MethodIntrospector来对该类进行扫描, 这个大家应该很
    熟悉了, 前面对HandlerMapping也是用的MethodIntrospector类进行扫描的, 找到后将其放置到缓存中

    所以第一部分就是找到当前Controller中的所有@InitBinder标注的方法而已, 然后初始化了一个List, 里面存
    储的是InvocableHandlerMethod, 我们之后再分析什么是InvocableHandlerMethod

    第二部分代码就是对initBinderAdviceCache这个全局缓存进行遍历了, 因为之前Adapter初始化的时候就已经
    将容器中所有被@ControllerAdvice标注的类找出来了, 并且将全局的@InitBinder标注的方法存储到了
    initBinderAdviceCache中, 这时候就要从这个缓存中取出能够用在当前Controller的, 前面笔者也有提到,
    @ControllerAdvice是可以配置作用在哪些包, 哪些类的, 这一步就是筛选出这些方法

    第三部分遍历所有的当前Controller中的methods, 将他们变成一个个的InvocableHandlerMethod, 并放入到
    initBinderMethods这个集合中

    最后, initBinderMethods这个集合中保存了当前Controller中以及满足条件的被@ControllerAdvice标注的
    类中的所有@InitBinder标注的方法, 并且这些方法都是封装成了InvocableHandlerMethod的

InvocableHandlerMethod

看这个名字, 就知道是可调用的HandlerMethod, 没错, 这个类继承了HandlerMethod, 为什么说是可调用的呢?
HandlerMethod中封装了bean对象、Method对象以及参数信息, 但是不能直接调用, 因为没有参数值, 并且调用后的
返回值不一样, 怎么对参数值和返回值进行处理, 就是InvocableHandlerMethod需要做的事情了, 
InvocableHandlerMethod继承了HandlerMethod, 同时提供了几个属性, 看下面的类定义就能够很清楚了:
    public class InvocableHandlerMethod extends HandlerMethod {
        private WebDataBinderFactory dataBinderFactory;

        private HandlerMethodArgumentResolverComposite argumentResolvers 
                                                = new HandlerMethodArgumentResolverComposite();

        private ParameterNameDiscoverer parameterNameDiscoverer 
                                                = new DefaultParameterNameDiscoverer();
    }

可以看到, 就是保存了参数解析器以及WebDataBinderFactor以及一个参数名称查找器而已, 这个参数名称查找器我们
不进行深入分析.....

InvocableHandlerMethod有一个直接子类: ServletInvocableHandlerMethod, 该直接子类增加了一个额外的属性

public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
    private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
}

那到此为止, InvocableHandlerMethod的作用就很明显了, 在HandlerMethod的基础上增加了参数值、返回值的处理

再回到上一段落, 将这些@InitBinder标注的方法找出来后, 封装成InvocableHandlerMethod, 这样就能够很好的调
用并处理参数了, 最后调用createDataBinderFactory方法, 以InvocableHandlerMethod的集合作为参数, 其实就
是new了一个ServletRequestDataBinderFactory, 需要注意的是, 这里放入的是InvocableHandlerMethod类对象,
而不是ServletInvocableHandlerMethod, 究其原因是@InitBinder是不允许有返回值的, 不信你试试.....所以
压根没必要用ServletInvocableHandlerMethod

小小的总结

ServletWebRequest保存了HttpServletRequest以及HttpServletResponse, 同时提供了对键值对放入到session
或者request作用域的功能, WebDataBinderFactory就WebDataBinder的工厂方法, 将所有的@InitBinder方法找出
来后放入到了这个工厂方法中, 在每次请求来的时候创建好WebDataBinder后就先执行所有的@InitBinder方法对
WebDataBinder进行初始化, 比如增加自定义的属性编辑器?

ModelFactory

ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

接下来我们要分析的就是这个ModelFactory了, 前面已经处理完了@InitBinder注解, 将他们放到了
WebInitBinderFactory中, 此时这个ModelFactory就是用来处理@ModelAttribute注解的, 因为我们之前说过, 每
个请求执行的时候都会执行@InitBinder以及@ModelAttribute注解标注的方法

private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
    SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
    Class<?> handlerType = handlerMethod.getBeanType();
    Set<Method> methods = this.modelAttributeCache.get(handlerType);
    if (methods == null) {
        methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
        this.modelAttributeCache.put(handlerType, methods);
    }
    List<InvocableHandlerMethod> attrMethods = new ArrayList<>();
    // Global methods first
    this.modelAttributeAdviceCache.forEach((clazz, methodSet) -> {
        if (clazz.isApplicableToBeanType(handlerType)) {
            Object bean = clazz.resolveBean();
            for (Method method : methodSet) {
                attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
            }
        }
    });
    for (Method method : methods) {
        Object bean = handlerMethod.getBean();
        attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
    }
    return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
}

分析:
    代码结构大家应该会感觉很熟悉, 跟@InitBinder的处理是类似的, 都是先处理全局的, 然后才处理局部的, 最后
    将每一个扫描到的方法都封装成InvocableHandlerMethod, 同时赋予参数解析器, 这一步骤是在
    createModelAttributeMethod方法中完成的

    最后, 生成了一个ModelFactory对象, 在这个构造方法中, 将InvocableHandlerMethod又进行了一次包装, 
    变成了ModelMethod, 包装的结果是为了处理@ModelAttribute标注的方法同时其参数也有@ModelAttribute注
    解的情况的, 大家有兴趣跟进去看一下就明白了, 很简单....

    最后需要注意的是, 在整个方法的最开头调用了一个方法getSessionAttributesHandler, 同时将这个方法的返
    回值放入到了ModelFactory中, 首先说下SessionAttributesHandler吧, 前面有专门讲这个组件, 他的作用就
    是@SessionAttributes的类表示形式, 里面有四个参数, 一个是attributeNames, 对应注解中的name, 一个是
    attributeTypes, 对应注解中的type, 而knownAttributeNames这个参数就是将前两个整合起来了而已, 最后
    一个参数是SessionAttributeStore, 提供对session中key-value的操作, 并且我们知道
    SessionAttributesHandler是以类为作用域的, 每个类都会有一个该对象, 为什么这么说呢? 这就是
    getSessionAttributesHandler方法实现的

private SessionAttributesHandler getSessionAttributesHandler(HandlerMethod handlerMethod) {
    Class<?> handlerType = handlerMethod.getBeanType();
    SessionAttributesHandler sessionAttrHandler 
                                        = this.sessionAttributesHandlerCache.get(handlerType);
    if (sessionAttrHandler == null) {
        synchronized (this.sessionAttributesHandlerCache) {
            sessionAttrHandler = this.sessionAttributesHandlerCache.get(handlerType);
            if (sessionAttrHandler == null) {
                sessionAttrHandler 
                            = new SessionAttributesHandler(handlerType, sessionAttributeStore);
                this.sessionAttributesHandlerCache.put(handlerType, sessionAttrHandler);
            }
        }
    }
    return sessionAttrHandler;
}

分析:
    首先根据这个handler方法获取到其所在的Controller的类型, 因为是类作用域, 然后从cache中获取, 一开始
    肯定是没有的, 所以就进入了synchronized关键字, 可以看到, 直接创建了一个该对象, 并且将
    sessionAttributeStore传了进去, 我们之前说过, sessionAttributeStore仅仅是一个用来操作session中
    key-value的工具而已, 所以这一块直接传进去没关系, 大家共享同一个工具就好了...

    最后将创建好的SessionAttributesHandler放到缓存中, 所以说, 即使一个类没有被@SessionAttributes标注
    同样也会创建一个SessionAttributesHandler对象, 只是其里面的那些属性中三个集合都为刚初始化完成的空
    集合而已

ServletInvocableHandlerMethod

ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
    invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
    invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

接下来是创建ServletInvocableHandlerMethod对象, 这个类是InvocableHandlerMethod的子类, 在其父类的功能
下提供了返回值的解析器, 即returnValueHandlers, 所以其适应于处理真正的handler方法, 可以看到, 将复合的
参数解析器(里面放入了所有初始化好的参数解析器)以及复合的返回值解析器(里面放入了所有初始化好的返回值解析
器)设置到了invocableMethod中, 这时候如果去调用handler方法, 就能很好的解析参数和返回值了, 但是还不够,
我们参数绑定的时候还需要一个WebDataBinder呀, 数据从哪里来我们之后再说, 绑定参数必须要有WebDataBinder
吧, 于是invocableMethod又放入了之前我们创建好的WebDataBinderFactory(创建WebDataBinder的工厂, 同时能
初始化WebDataBinder, 利用@InitBinder注解)

ModelAndViewContainer

ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

分析:
    通过之前的操作, 我们已经成功的将WebDataBinder、@ModelAttribute以及需要调用的handler准备好了, 此时
    还缺的是数据, 这个数据就放到了ModelAndViewContainer中, 这个容器中存放了所有需要绑定的数据, 以及在
    hanlder方法执行完毕后返回的视图名称

    我们先不分析这个InputFlashMap是干嘛的, 之后还会遇到一个OutputFlashMap, 先不去管他, 笔者之后会专门
    分析这个组件, 它的作用一般用于重定向这样的请求中数据的传递

    所以这几句代码中最重要的就是initModel了, 它的参数是一个webRequest(可以从session中获取当前类的
    SessionAttributesHandler定义的key所对应的值), 一个mavContainer(用来存储所有的数据)

public void initModel(NativeWebRequest request, ModelAndViewContainer container,
            HandlerMethod handlerMethod) throws Exception {
    Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
    container.mergeAttributes(sessionAttributes);
    invokeModelAttributeMethods(request, container);

    for (String name : findSessionAttributeArguments(handlerMethod)) {
        if (!container.containsAttribute(name)) {
            Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
            if (value == null) {
                throw new HttpSessionRequiredException("Expected session attribute '" + name + "'", name);
            }
            container.addAttribute(name, value);
        }
    }
}

分析:
    此时已经到了ModelFactory中了, 结合之前的分析, 我们知道ModelFactory中有该类对应的
    sessionAttributesHandler, 所以此时直接调用该sessionAttributesHandler的retrieveAttributes方法,
    结果是利用该sessionAttributesHandler中的knownAttributeNames, 找到他们在session中对应的值, 然后
    以一个Map返回而已

    我们之前说ModelAndViewContainer有一个作用是用来保存可以用来绑定的参数信息的, 这里就能看到了, 
    container.mergeAttributes的调用其实就是将sessionAttributes这个map的信息转移到了container中对应
    的Model中, 其实就是一个Map, 里面代码很简单, 就不贴出来了, 大家有兴趣看看

    container中除了存放session信息外, 还应该存放@ModelAttribute修饰的方法返回的key-value, 而
    invokeModelAttributeMethods方法的调用就是完成这个功能的, 在该方法中, 首先取出我们之前放入到
    ModelFactory中的所有被@ModelAttribute标注的方法, 在ModelFactory中被放入到了modelMethod属性中,
    然后一个个遍历, 同时调用该方法, 获取返回值, 最后利用一定的名称生成规则生成key(如果注解中有指定name,
    则用指定的name作为key), 最后将这个key-value放入到container中

    到此为止, 我们基本就明白了, initModel方法的功能很简单, 就是初始化ModelAndViewContainer中的Model
    对象(是一个map)中的数据, 就是将满足条件的session数据和@ModelAttribute数据放入到Model中而已....

    接下来就是一个for循环, findSessionAttributeArguments方法的作用是找到当前这个handler中的所有参数
    中被@ModelAttribute标注的参数, 并且这个参数也在sessionAttributesHandler中定义的, 比如:
        @Controller
        @SessionAttributes( value = "customer")
        public class TestController1 {
            @RequestMapping( value="/request1" )
            @ResponseBody
            public Customer request1 (@ModelAttribute( "customer" ) Customer customer) {
                return new Customer();
            }
        }

    比如上面这个例子中的customer这个key, 同时存在于request1这个handler中被@ModelAttribute标注的参数
    中, 也存在于@SessionAttributes标注中

    然后一个个遍历, 如果container中没有, 说明经过前面的初始化还没有找到这个数据, 此时就抛出一个异常, 因
    为我request1方法需要绑定customer这个key对应的属性, 但是没找到, 抛出个异常....

小小的总结

那到此为止, 我们为了能够执行这个handler方法的准备工作终于做完了, 涉及的组件非常的多, 先来小小的总结一下
这些组件的功能吧:

WebDataBinderFactory: 保存了@InitBinder相关的信息
ModelFactory: 保存了@ModelAttribute以及@SessionAttributes相关信息, 前者的表现形式是一个个的
                InvocableHandlerMethod, 在ModelFactory对象中被包装成ModelMethod, 后者的表现形式是
                SessionAttributesHandler
ServletInvocableHandlerMethod: 真正用来触发调用handler方法的对象, 除了存储了HandlerMethod外, 还存储
                                了需要用到参数解析器、返回值解析器等
ModelAndViewContainer: 调用handler方法, 一定会涉及到绑定的参数, 这些参数被放到了container中, 而参数
                        的来源就是@ModelAttribute以及@SessionAttributes, 即ModelFactory中, 触发点
                        是initModel方法, 数据真正被存放在container中的defaultModel中, 是一个Map, 同
                        时container还会存储handler方法调用完成后的view视图信息, 之后会看到....                      

开始调用HandlerMethod

  • invokeAndHandle为HandlerMethod调用入口
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);
    this.returnValueHandlers.handleReturnValue(
            returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}

分析:
    先来说一下mavContainer中的requestHandled属性吧, true表示请求执行完毕, false表示请求还没有执行完毕

    其实整个方法中最核心的代码就两行, 一个是调用HandlerMethod方法, 一个是处理返回值, 我们着重分析这两个
    方法

  • invokeForRequest开始调用HandlerMethod
public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
                                            Object... providedArgs) throws Exception {

    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    Object returnValue = doInvoke(args);
    return returnValue;
}

分析:
    可以看到, 首先是获取参数, 之后就是利用这个参数, 以及HandlerMethod中保存的bean直接反射调用方法了,
    doInvoke中就是反射调用

    getMethodArgumentValues获取参数其实也很简单, 我们之前不是往这个ServletInvocableHandlerMethod对
    象中放入了一系列的参数解析器吗, 这个时候就是遍历这些参数解析器以及遍历这些参数了, 即第一步找到当前
    遍历的参数对应的参数解析器, 比如@RequestBody就是用的RequestResponseBodyMethodProcessor这个解析器
    来进行解析的, 判断一个参数解析器是否适用于当前参数, 用的是参数解析器通用接口
    HandlerMethodArgumentResolver的supportsParameter方法, 而解析参数用的是resolveArgument方法, 比
    如判断RequestResponseBodyMethodProcessor参数解析器是这样判断的:
        public boolean supportsParameter(MethodParameter parameter) {
            return parameter.hasParameterAnnotation(RequestBody.class);
        }

    而解析参数也很简单, 前端传的json, 我需要的对象, 自然就是用Jackson的ObjectMapper了, 大家有兴趣可以
    看下这个类的resolveArgument方法, 他会获取到一个MessageConverter消息转换器, 其实用的是
    MappingJackson2HttpMessageConverter这个转换器, 里面就是用ObjectMapper来反序列化的

  • handleReturnValue处理返回值
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
        ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

分析:
    handleReturnValue跟处理参数类似, 处理参数是找到合适的参数解析器, 处理返回值自然就找到合适的返回值
    解析器了, 而对于@ResponseBody来说, 其返回值解析器跟@RequestBody这个参数解析器是同一个....一个用来
    将json转对象, 一个用来对象转json

总结

在上面的分析中, 我们先通过之前的两篇文章引入了一些相关联的知识点, 在了解这些知识点的情况下再去看源码才不
会有盲点, 随后我们通过对HandlerAdapter的体系进行了分析, 引出了目前开发最流行的@RequestMapping对应的
适配器RequestMappingHandlerAdapter, 先对该适配器对象的属性进行了分析, 指明了各个属性的作用, 随后对该
对象在Spring容器下的初始化进行了分析, 初始化过程中完成了各种组件的初始化, 消息转换器、参数解析器、返回值
解析器、initBinder、modelAttribute等等, 当对整体有了了解后, 我们开始深入的分析handleInternal方法, 在
这个方法中, 完成了对HandlerMethod的调用, 其中又涉及到了WebDataBinderFactory、ModelFactory、
ServletInvocableHandlerMethod、ModelAndViewContainer四个组件, 并进行了详细的分析, 最后带着大家看了
下参数解析器和返回值解析器的处理, 到此为止, 作为SpringMVC中最复杂的HandlerAdapter组件就分析完成了....

文章永久链接:https://tech.souyunku.com/36497

未经允许不得转载:搜云库技术团队 » SpringMVC源码分析六、组件二HandlerAdapter源码分析

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们