Spring加载消息转换器的流程以及自定义类型转换器的优先级


目录

一、加载转换器的流程,通过下面详细的时序图可知

  

 原图见百度云

二、自定义转换器的加载逻辑

1、总共有三种方式

a、@bean;

b、实现WebMvcConfigurer类的方法configureMessageConverters 或者 extendMessageConverters方法;

c、继承WebMvcConfigurationSupport类,覆盖方法configureMessageConverters 或者 extendMessageConverters方法;(不推荐使用,因为会导致spring、springboot默认的转换器失效,原因下面会说)

如果b和c同时定义,只会生效c的方式;

下面讲讲a、b同时使用和 a、c同时使用的优先级区别

2、a 和 c同时使用

  1. @bean

  2. 继承WebMvcConfigurationSupport类,覆盖方法configureMessageConverters

  3. 继承WebMvcConfigurationSupport类,覆盖方法extendMessageConverters

如下图:

 结论:这种方法,只会有JavaSerializationConverter1、JavaSerializationConverter2两个转换器在处理json请求时生效,并且JavaSerializationConverter2在JavaSerializationConverter1前面。

加载流程如下:

spring启动时,在org.springframework.context.support.AbstractApplicationContext#refresh方法中,会初始化所有的bean,如图:

 调用栈如下:

preInstantiateSingletons:737, DefaultListableBeanFactory (org.springframework.beans.factory.support)
finishBeanFactoryInitialization:867, AbstractApplicationContext (org.springframework.context.support)
refresh:548, AbstractApplicationContext (org.springframework.context.support)

最后会调用org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons

 在图①中,遍历500多个bean,进行非延迟加载的bean创建,这里会先创建我们自定义的转换器JavaSerializationConverter,然后会创建requestMappingHandlerAdapter

创建requestMappingHandlerAdapter的过程:

这个对象是由org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#requestMappingHandlerAdapter这个bean实例化方法创建的,因为我们自定义MyConverterConfig2继承了WebMvcConfigurationSupport对象,所以这一系列方法实际是自定义对象触发的如图:

 图中①会给org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#messageConverters属性赋值四个默认转换器如下:

 赋值后回到上面图②:会调用MyConverterConfig2的父类WebMvcConfigurationSupport#getMessageConverters的方法,如下:

  ①当前WebMvcConfigurationSupport#messageConverters属性为空;

  ②执行MyConverterConfig2#configureMessageConverters方法加入一个转换器;

  ③这时WebMvcConfigurationSupport#messageConverters属性不为空;

  ④不会执行,也就不会记载spring默认八个转换器;

  ⑤执行MyConverterConfig2#extendMessageConverters方法加入一个转换器;

  方法退出后,WebMvcConfigurationSupport#messageConverters就有两个我们自定义的转换器:

  RequestMappingHandlerAdapter#messageConverters中也会添加返回的两个自定义转换器;

之后会执行RequestMappingHandlerAdapter#afterPropertiesSet初始化方法,如下图:

 ①中会调用RequestMappingHandlerAdapter#getDefaultArgumentResolvers获取一系列默认参数解析器列表:

重点关注图中RequestResponseBodyMethodProcessor参数解析器,是对@RequestBody、@ResponseBody方法json入参和出参做处理的解析器,构造该解析器时调用了RequestMappingHandlerAdapter#getMessageConverters将之前RequestMappingHandlerAdapter中的两个自定义解析器填充到了AbstractMessageConverterMethodArgumentResolver#messageConverters

中,也就是RequestResponseBodyMethodProcessor的父类中;

结论:从这里可以看出一旦通过这种方式配置的自定义转换器,就会覆盖默认的spring、springboot的各种转换器,处理json就只会用自定义的转换器了;

后面org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#addDefaultHandlerExceptionResolvers创建异常处理器时也是执行MyConverterConfig2#getMessageConverters方法,此时属性messageConverters已经存在了两个自定义转换器,所以直接返回填充到异常处理器中;

后续会继续执行springboot 自动配置类中bean的初始化,其中就有HttpMessageConverters转换器的初始化,如下:

创建过程细化流程见第一个标题画的时序图,这里不详细列出,大概流程如下: 

如下图:

图中是创建时,获取默认转换器的方法

①中匿名创建了WebMvcConfigurationSupport的子类,添加了defaultMessageConverters方法,改子类没有重写父类任何方法;

②中调用了父类的getMessageConverters方法,如下图:

 这个时候因为是匿名创建的子类,没有给父类任何属性赋值,也就意味着图中:

①处父类的messageConverters是空的,所以会执行②

②是空方法;

③这时属性还是空的;

④这里会获取spirng的八个默认转换器;

⑤是空方法;

所以这里返回了八个spring的默认转换器;

  • 返回spring加载八个转换器后,并排序,xml转换器排到最后面;

  • spring 八个转换器和springboot 默认三个转换器组合,并赋值给HttpMessageConverters#converters(这个对象在springboot包中,也就是springboot的转换器所属对象),并没有赋值给WebMvcConfigurationSupport#messageConverters,所以WebMvcConfigurationSupport#messageConverters(这个对象在spring包中,也就是spring中生效的还是我们自定义的两个转换器)中还是上面我们自定义类MyConverterConfig2中方法添加的两个转换器;

springboot三个自动配置转换器如下图:

两个默认的和一个自定义的@bean转换器

这里可以得出结论:

springboot 自动化配置默认的转换器加载的时候会一起加载@bean自定义的转换器,但是@bean自定义的转换器会在第一个,优先级高于springboot 自动化配置默认转换器

扩展:

自定义配置类MyConverterConfig2中两个重写方法为:converters.add(0,new JavaSerializationConverter1());

这是通过list的add重载方法,加入到了第一个,第一个参数可以指定list的顺序,所以这样写会影响最终转换器list中的顺序,因为由图可看出代码执行顺序优先级中extendMessageConverters < configureMessageConverters,但是extendMessageConverters中代码converters.add(0,new JavaSerializationConverter1());是放到list第一个,所以extendMessageConverters后执行,反而配置的转换器会在list的第一个;

正常一般都不会用到三种方式,只会用到一种,如果用到了extendMessageConverters或者configureMessageConverters任意一种,想让自定义的转换器在第一个(spring 接收请求的时候是遍历转换器list,一旦找到能够处理请求类型的转换器就不会往下找了),所以converters.add(0,new JavaSerializationConverter1());就能放到第一个,能保证优先使用自定义的转换器

3、a 和 b同时使用

  1. @bean

  2. 实现WebMvcConfigurer类的方法configureMessageConverters

    添加到list末尾converters.add(new JavaSerializationConverter());

  3. 实现WebMvcConfigurer类的方法extendMessageConverters

    添加到list末尾converters.add(new JavaSerializationConverter());

 如下图:

加载流程如下:

  1. 前面流程和之前一样,只不过创建RequestMappingHandlerAdapter的对象不再是自定义的(WebMvcConfigurationSupport的子类MyConverterConfig2),而是spring自己的WebMvcConfigurationSupport对象,所以调用WebMvcConfigurationSupport#getMessageConverters

    方法逻辑变了如下:

    ①当前WebMvcConfigurationSupport#messageConverters属性为空;

    ②执行WebMvcConfigurationSupport#configureMessageConverters方法,

    该方法会调用DelegatingWebMvcConfiguration#configureMessageConverters方法,最终会调用WebMvcConfigurerComposite#configureMessageConverters方法:

     这里会遍历三个WebMvcConfigurer,包括我们自定义的MyConverterConfig也实现了WebMvcConfigurer接口,如下:

     先去遍历springboot自动配置,也就是会创建HttpMessageConverters,跟之前一样,如下图:

    大致流程是:

    • 获得spring八个默认转换器,排序,xml转换器放最后;

    • 组合springboot自动配置转换器和@bean自定义转换器,@bean自定义转换器会放在第一个,一起和spring转换器进行匹配,类型相同的放在一起,匹配不到额外的springboot转换器会都放在转换器列表的最前面,最终转换器列表如下:

      ①是@bean自定义转换器;

      ②、③都是spirngboot和spring重复的转换器;

    • 这11个转换器会填充HttpMessageConverters#converters只读属性,并返回填充到WebMvcConfigurationSupport#messageConverters属性中

 

    然后会遍历第二个WebMvcConfigurer,也就是我们的MyConverterConfig,就会调用我们自定义MyConverterConfig中的configureMessageConverters方法添加转换器,由于我们方法中是converters.add(new JavaSerializationConverter1());,并不是converters.add(0, new JavaSerializationConverter1());所以转换器JavaSerializationConverter1会加入到WebMvcConfigurationSupport#messageConverters列表最后一个。

   ③这时WebMvcConfigurationSupport#messageConverters属性不为空,有12个转换器;

   ④不会执行,也就不会再次加载spring默认八个转换器;

   ⑤执行spring默认的WebMvcConfigurationSupport#extendMessageConverters方法,继续会最终调用WebMvcConfigurerComposite#extendMessageConverters,如下图:

 跟之前一样,不过是方法名改为了extendMessageConverters,继续遍历WebMvcConfigurer,包括我们自定义的MyConverterConfig,如下:

 当遍历到我们自定义类的时候MyConverterConfig#extendMessageConverters,会添加自定义转换器JavaSerializationConverter2,这时有13个转换器了:

 继续遍历第三个SpringDataWebConfiguration的时候,源码如下:

public void extendMessageConverters(List> converters) {
    if (ClassUtils.isPresent("com.jayway.jsonpath.DocumentContext", this.context.getClassLoader()) && ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", this.context.getClassLoader())) {  
      // 如果存在DocumentContext和ObjectMapper类型
        ObjectMapper mapper = (ObjectMapper)getUniqueBean(ObjectMapper.class, this.context, ObjectMapper::new);
        ProjectingJackson2HttpMessageConverter converter = new ProjectingJackson2HttpMessageConverter(mapper);
        converter.setBeanFactory(this.context);
        this.forwardBeanClassLoader(converter);
      // ProjectingJackson2HttpMessageConverter加入到转换器第一位
        converters.add(0, converter);
    }

    if (ClassUtils.isPresent("org.xmlbeam.XBProjector", this.context.getClassLoader())) {
        converters.add(0, this.xmlBeamHttpMessageConverter.orElseGet(() -> {
            return new XmlBeamHttpMessageConverter();
        }));
    }

}

从这里可以看出只要存在DocumentContext和ObjectMapper类型,就会将ProjectingJackson2HttpMessageConverter加入到转换器第一位加入到转换器列表第一位,而ProjectingJackson2HttpMessageConverter从创建的逻辑来看是支持json格式的,如下:

 所以目前转换器列表第一位是ProjectingJackson2HttpMessageConverter,也就是spring默认采用的jackson的json转换器,转换器列表此时如下:

方法退出后,WebMvcConfigurationSupport#messageConverters就有这14个转换器了;同时

RequestMappingHandlerAdapter#messageConverters也会赋值这14个转换器

之后会执行RequestMappingHandlerAdapter#afterPropertiesSet初始化方法,进而会将14个转换器赋值给

AbstractMessageConverterMethodArgumentResolver#messageConverters,这也就是真正处理json 时用到的转换器列表,这里第一个虽然是ProjectingJackson2HttpMessageConverter(spring data提供的)但是该转换器只能处理被ProjectedPayload注解注释的接口类型的json对象。普通的json对象不会被该转换器处理。

 

参考:https://segmentfault.com/a/1190000012659486

        https://segmentfault.com/a/1190000012658289

TRANSLATE with x English
Arabic Hebrew Polish
Bulgarian Hindi Portuguese
Catalan Hmong Daw Romanian
Chinese Simplified Hungarian Russian
Chinese Traditional Indonesian Slovak
Czech Italian Slovenian
Danish Japanese Spanish
Dutch Klingon Swedish
English Korean Thai
Estonian Latvian Turkish
Finnish Lithuanian Ukrainian
French Malay Urdu
German Maltese Vietnamese
Greek Norwegian Welsh
Haitian Creole Persian  
Bing Webmaster Portal Back