美文网首页
03-SpringMVC初始化HandlerAdapters过程

03-SpringMVC初始化HandlerAdapters过程

作者: AcientFish | 来源:发表于2020-02-13 12:24 被阅读0次

    上篇文章中我们看到了Spring官方对DispatcherServlet的几个特殊Bean的介绍,其中第二个就是HandlerAdapter,它是DispatcherServlet调用handler的关键,因为它会对DispatcherServlet屏蔽细节实现,让DispatcherServlet只关心调用结果而不需要去关心调用细节(例如参数类型转换及封装等)。下面我们来分析HandlerAdapter的初始化过程
    我们已经知道DispatcherServlet有一个默认配置文件,里面配置了各种策略。对于HandlerAdapter来说存在3个默认配置,他们分别是

    • HttpRequestHandlerAdapter 这个处理器是用来处理静态资源的
    • SimpleControllerHandlerAdapter 这个处理器是用来处理在实现了Controller接口或者AbstractController等Spring的默认实现类的子类
    • RequestMappingHandlerAdapter 这个处理器用来处理@RequestMapping注解的处理器,也是最常用,用的最多的一个,我们今天会着重分析这个
      DispatcherServlet在初始化几个策略时遇到使用默认策略均会调用createDefaultStrategy方法,最终通过IOC容器创建对象并执行回调。通过RequestMappingHandlerAdapter的类关系图我们可以看出RequestMappingHandlerAdapter同样实现了InitializingBean接口,因此我们关注的重点一个是在实例化阶段,一个是在回调InitializingBean接口方法阶段。
      首先我们来看实例化阶段
    // 祖先类中会设置allowHeader为除了Trace外所有的HttpMethod但并未设置supportedMethods
    public WebContentGenerator(boolean restrictDefaultSupportedMethods) {
            if (restrictDefaultSupportedMethods) {
                this.supportedMethods = new LinkedHashSet<>(4);
                this.supportedMethods.add(METHOD_GET);
                this.supportedMethods.add(METHOD_HEAD);
                this.supportedMethods.add(METHOD_POST);
            }
            initAllowHeader();
    }
    
    private void initAllowHeader() {
            Collection<String> allowedMethods;
            // 这里配置allowedMethods
            if (this.supportedMethods == null) {
                allowedMethods = new ArrayList<>(HttpMethod.values().length - 1);
                for (HttpMethod method : HttpMethod.values()) {
                    if (method != HttpMethod.TRACE) {
                        allowedMethods.add(method.name());
                    }
                }
            }
            else if (this.supportedMethods.contains(HttpMethod.OPTIONS.name())) {
                allowedMethods = this.supportedMethods;
            }
            else {
                allowedMethods = new ArrayList<>(this.supportedMethods);
                allowedMethods.add(HttpMethod.OPTIONS.name());
    
            }
            this.allowHeader = StringUtils.collectionToCommaDelimitedString(allowedMethods);
        }
    
    // 在自身的构造器中添加了StringHttpMessageConverter、ByteArrayHttpMessageConverter、
    // SourceHttpMessageConverter、AllEncompassingFormHttpMessageConverter
    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);
            try {
                this.messageConverters.add(new SourceHttpMessageConverter<>());
            }
            catch (Error err) {
                // Ignore when no TransformerFactory implementation is available
            }
            this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
        }
    

    上述代码中初始化了4种消息转换器,针对不同的content-type,设置不同的消息转换器

    • StringHttpMessageConverter 支持text/plain或所有/的请求类型
    • ByteArrayHttpMessageConverter 支持application/octet-stream或所有/的请求类型
    • SourceHttpMessageConverter 支持text/xml、application/xml、application/*-xml这三种类型的请求
    • AllEncompassingFormHttpMessageConverter 支持application/x-www-form-urlencoded的读写(file upload)和multipart/form-data格式的写操作(RestTemplate或WebClient中发送数据)。同时会根据classpath是否包含class文件来决定是否添加对xml和json的处理。
        /**
         * jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
         * jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
                            ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
         * jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
         * jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
         * gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
         * jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
         */
        public AllEncompassingFormHttpMessageConverter() {
            try {
                addPartConverter(new SourceHttpMessageConverter<>());
            }
            catch (Error err) {
                // Ignore when no TransformerFactory implementation is available
            }
    
            if (jaxb2Present && !jackson2XmlPresent) {
                addPartConverter(new Jaxb2RootElementHttpMessageConverter());
            }
    
            if (jackson2Present) {
                addPartConverter(new MappingJackson2HttpMessageConverter());
            }
            else if (gsonPresent) {
                addPartConverter(new GsonHttpMessageConverter());
            }
            else if (jsonbPresent) {
                addPartConverter(new JsonbHttpMessageConverter());
            }
    
            if (jackson2XmlPresent) {
                addPartConverter(new MappingJackson2XmlHttpMessageConverter());
            }
    
            if (jackson2SmilePresent) {
                addPartConverter(new MappingJackson2SmileHttpMessageConverter());
            }
        }
    

    实例化阶段主要就做了这些操作,后面在分析DispatcherServlet处理过程时详细分析各转换器的作用。
    下面我们再来关注初始化后的回调,开始之前我们先来看两张图


    RequestMethod.png ReturnValue.png

    以上两张图来自Spring官方文档,介绍了SpringMVC的Controller中支持的请求参数类型和返回值类型。其中我们眼熟的@RequestParam、@PathVariable、@RequestBody、Map、Model、ServletRequest、ServletResponse、@ResponseBody、ModelAndView等等。下面就会针对上述的类型注册相关解析器或处理器。

        @Override
        public void afterPropertiesSet() {
            // Do this first, it may add ResponseBody advice beans
            // 在这个方法内会获取当前IOC容器中所有注有@ControllerAdvice的Bean,将他们包装成ControllerAdviceBean。
            // 并将他们分条件缓存。条件包括注有@InitBinder的方法,注有@ModelAttribute但没有@RequestMapping的方法。
            // 还会判断ControllerAdviceBean上是否注有RequestBodyAdvice或ResponseBodyAdvice,若存在也会缓存
            initControllerAdviceCache();
            
            // 这里会初始化参数解析器(包括内建解析器和通过setCustomArgumentResolvers方法自定义的解析器)
            if (this.argumentResolvers == null) {
                List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
                this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
            }
            // 这里会初始化一系列解析@InitBinder的解析器
            if (this.initBinderArgumentResolvers == null) {
                List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
                this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
            }
            // 这里会初始化返回值处理器()包括内建处理器和通过setReturnValueHandlers自定义的处理器)
            if (this.returnValueHandlers == null) {
                List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
                this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
            }
        }
    

    至此初始化过程基本结束,后面的异常处理、视图解析、本地化、样式、文件上传和请求转发处理我们以后在分析,下一篇我们会开始分析DispatcherServlet的处理过程。
    在查看官方文档的时候发现Spring现在推荐使用MVC Config作为最佳入口,因为它申明了必须的Special Bean同时提供要给高等级的自定义配置回调API。


    WebMVCConfig.png

    只有无法找到MVC Config时才会使用DispatcherServlet.properties进行默认配置。后面有机会我们会重新分析MVC Config方式下的操作流程。

    相关文章

      网友评论

          本文标题:03-SpringMVC初始化HandlerAdapters过程

          本文链接:https://www.haomeiwen.com/subject/oumkfhtx.html