HttpMessageConverters 自动配置类源码解析 -HttpMessageConvertersAutoConfiguration

HttpMessageConverters 自动配置类源码解析 -HttpMessageConvertersAutoConfiguration

源码版本:spring-boot-autoconfigure-2.7.5.jar

源码分析

HttpMessageConverters

HttpMessageConverters 的作用是管理 Spring Boot 应用程序中使用的 HttpMessageConverters 的 Bean(不限于 Web 场景)。提供了一种方便的方法来添加和合并用户自定义的 HttpMessageConverter 到 web 应用程序中。

我们只需要声明 HttpMessageConverter 类型的 bean(有需要的话可以通过 @Order 进行排序),就可以添加自定义的 HttpMessageConverter,而在 SpringMVC 中,还需要重写 configureMessageConvertersextendMessageConverters 方法。SpringMVC 配置 HttpMessageConverter 的方式请看《SpringMVC- 第十篇:基于注解配置 SpringMVC.md》的 configureMessageConverters & extendMessageConverters 小节

我们可以在创建 HttpMessageConverters 实例的时候指定额外的、用户自定义的 HttpMessageConverter,否则只会使用默认的 HttpMessageConverter

实际上,默认的 HttpMessageConverterWebMvcConfigurationSupport#addDefaultHttpMessageConverters 方法返回的 HttpMessageConverter,获取之后,会稍微调一下顺序,将处理 XMLHttpMessageConverter 放到列表的最后面。

也就是说,默认的 HttpMessageConverter,SpringBoot 跟 SpringMVC 一致,只是如果存在处理 XMLHttpMessageConverter 的话,默认的 HttpMessageConverter 的顺序可能稍微不一样。

源码中的核心点是 HttpMessageConverters 的构造函数,而在构造函数中,有两个方法比较核心,

public class HttpMessageConverters implements Iterable<HttpMessageConverter<?>> {

    // 初始化一个用户自定义添加的、在与系统默认的HttpMessageConverter合并时,不调整顺序(优先级)的 HttpMessageConverter,即 TypeConstrainedMappingJackson2HttpMessageConverter
    private static final List<Class<?>> NON_REPLACING_CONVERTERS;
    static {
        List<Class<?>> nonReplacingConverters = new ArrayList<>();
        addClassIfExists(nonReplacingConverters, "org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter");
        NON_REPLACING_CONVERTERS = Collections.unmodifiableList(nonReplacingConverters);
    }
    // addClassIfExists
    ...

    // 初始化一个系统默认添加的、在与用户自定义添加的HttpMessageConverter合并时,视为相同替换对象的 HttpMessageConverter,
    // 即 系统默认添加了 MappingJackson2HttpMessageConverter ,但是用户没有自定义添加 MappingJackson2HttpMessageConverter 类型或MappingJackson2HttpMessageConverter的子类型的 HttpMessageConverter ,用户自定义添加的是 GsonHttpMessageConverter 或其子类,也要调整优先级 ,将用户自定义添加的 GsonHttpMessageConverter 放到前面去
    private static final Map<Class<?>, Class<?>> EQUIVALENT_CONVERTERS;
    static {
        Map<Class<?>, Class<?>> equivalentConverters = new HashMap<>();
        putIfExists(equivalentConverters, "org.springframework.http.converter.json.MappingJackson2HttpMessageConverter", "org.springframework.http.converter.json.GsonHttpMessageConverter");
        EQUIVALENT_CONVERTERS = Collections.unmodifiableMap(equivalentConverters);
    }
    // putIfExists
    ...

    // 核心字段,保存所有的 HttpMessageConverter
    private final List<HttpMessageConverter<?>> converters;

    // converters 的 getter 方法 和  converters的迭代器实现
    ......

    // 参数缺省的构造函数
    ......

    // 核心构造函数
    public HttpMessageConverters(boolean addDefaultConverters, Collection<HttpMessageConverter<?>> converters) {
        // 将用户自定义添加的 HttpMessageConverter 列表和 系统默认添加的 HttpMessageConverter 列表合并
        List<HttpMessageConverter<?>> combined = getCombinedConverters(converters, addDefaultConverters ? getDefaultConverters() : Collections.emptyList());
        // 对合并结果进行后置处理,postProcessConverters方法目前为空方法,是个钩子,方便子类重写,自定义业务逻辑
        combined = postProcessConverters(combined);
        // 赋值到 converters 属性
        this.converters = Collections.unmodifiableList(combined);
    }

    // 获取系统默认添加的 HttpMessageConverter
    private List<HttpMessageConverter<?>> getDefaultConverters() {
        List<HttpMessageConverter<?>> converters = new ArrayList<>();
        if (ClassUtils.isPresent("org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport", null)) {
            // WebMvcConfigurationSupport 存在,表示是SpringMVC环境
            // 这是匿名内部类的写法
            converters.addAll(new WebMvcConfigurationSupport() {
                // 匿名内部类添加 defaultMessageConverters 方法
                public List<HttpMessageConverter<?>> defaultMessageConverters() {
                    // 匿名内部类实例的 defaultMessageConverters 方法实际上是委托给父类也就是 WebMvcConfigurationSupport的getMessageConverters方法
                    // 实际上在 WebMvcConfigurationSupport 中因为 configureMessageConverters 和 extendMessageConverters 都是空方法,
                    // 所以getMessageConverters方法最终返回的 其实就是 addDefaultHttpMessageConverters 方法。
                    return super.getMessageConverters();
                }

            }.defaultMessageConverters());
        } else {
            // 如果不是SpringMVC框架,那么就是添加 RestTemplate 中的 HttpMessageConverter
            converters.addAll(new RestTemplate().getMessageConverters());
        }
        // 将 xml类的 messageConverter放到列表的末尾
        reorderXmlConvertersToEnd(converters);
        return converters;
    }

    private List<HttpMessageConverter<?>> getCombinedConverters(Collection<HttpMessageConverter<?>> converters, List<HttpMessageConverter<?>> defaultConverters) {
        // 最终返回的 HttpMessageConverter 集合
        List<HttpMessageConverter<?>> combined = new ArrayList<>();
        // 用户自定义的,用来进行循环处理的 HttpMessageConverter 的集合
        // 注意,下文中的迭代器中的 iterator.remove(); 操作,是会修改 processing 列表的值的
        List<HttpMessageConverter<?>> processing = new ArrayList<>(converters);
        for (HttpMessageConverter<?> defaultConverter : defaultConverters) {
            Iterator<HttpMessageConverter<?>> iterator = processing.iterator();
            while (iterator.hasNext()) {
                HttpMessageConverter<?> candidate = iterator.next();
                // 如果需要替换,则将用户自定义添加的HttpMessageConverter先添加到结果中,即优先级高于默认添加的的HttpMessageConverter
                // isReplacement 方法的本质是判断是否进行顺序(优先级)的调整
                if (isReplacement(defaultConverter, candidate)) {
                    combined.add(candidate);
                    iterator.remove();
                }
            }
            // 将用户自定义的同类型或为子类的HttpMessageConverter添加完毕之后,再添加默认的HttpMessageConverter
            // 即自定义的同类型或为子类的HttpMessageConverter比默认的HttpMessageConverter的优先级更高。
            combined.add(defaultConverter);
            if (defaultConverter instanceof AllEncompassingFormHttpMessageConverter) {
                // AllEncompassingFormHttpMessageConverter 中也包含很多 HttpMessageConverter,也需要跟用户自定义添加的HttpMessageConverter进行混合(调序)
                configurePartConverters((AllEncompassingFormHttpMessageConverter) defaultConverter, converters);
            }
        }
        // 将剩下的自定义HttpMessageConverter放到最终结果列表的最前面。
        combined.addAll(0, processing);
        // 对HttpMessageConverter进行调序,本质上是调整优先级
        return combined;
    }

    private boolean isReplacement(HttpMessageConverter<?> defaultConverter, HttpMessageConverter<?> candidate) {
        // 如果用户自定义添加的 HttpMessageConverter 是 NON_REPLACING_CONVERTERS 中指定的类型,则不进行顺序(优先级)调整
        for (Class<?> nonReplacingConverter : NON_REPLACING_CONVERTERS) {
            if (nonReplacingConverter.isInstance(candidate)) {
                return false;
            }
        }
        // 如果自定义添加的 HttpMessageConverter 和 默认添加的HttpMessageConverter 是同一个类型,或者为其子类,则进行顺序(优先级)调整
        Class<?> converterClass = defaultConverter.getClass();
        if (ClassUtils.isAssignableValue(converterClass, candidate)) {
            return true;
        }
        // 如果自定义添加的 HttpMessageConverter 和 默认添加的HttpMessageConverter 既不是同一个类型,也不为其子类,那就检查跟默认添加的 HttpMessageConverter 的类型等效的类型
        // 如果 自定义添加的 HttpMessageConverter 是等效类型或其子类,则依然进行顺序(优先级)调整
        Class<?> equivalentClass = EQUIVALENT_CONVERTERS.get(converterClass);
        return equivalentClass != null && ClassUtils.isAssignableValue(equivalentClass, candidate);
    }

    private void configurePartConverters(AllEncompassingFormHttpMessageConverter formConverter,
            Collection<HttpMessageConverter<?>> converters) {
        List<HttpMessageConverter<?>> partConverters = formConverter.getPartConverters();
        List<HttpMessageConverter<?>> combinedConverters = getCombinedConverters(converters, partConverters);
        combinedConverters = postProcessPartConverters(combinedConverters);
        formConverter.setPartConverters(combinedConverters);
    }

    // reorderXmlConvertersToEnd
    ......

    // postProcessConverters 和 postProcessPartConverters ,都是 空方法
    ......


}

简单调试

合并结果的实践,其实主要就是实验 HttpMessageConverters#isReplacement 方法

注册三个 HttpMessageConverter 类型的 bean:MyMessageConverter0MyMessageConverter1MyResourceHttpMessageConverter

@Component
@Order(2)
public class MyMessageConverter0 implements HttpMessageConverter {
    @Override
    public boolean canRead(Class clazz, MediaType mediaType) {
        return false;
    }

    @Override
    public boolean canWrite(Class clazz, MediaType mediaType) {
        return false;
    }

    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return null;
    }

    @Override
    public Object read(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }

    @Override
    public void write(Object o, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {

    }
}
@Component
@Order(1)
public class MyMessageConverter1 implements HttpMessageConverter {
    @Override
    public boolean canRead(Class clazz, MediaType mediaType) {
        return false;
    }

    @Override
    public boolean canWrite(Class clazz, MediaType mediaType) {
        return false;
    }

    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return null;
    }

    @Override
    public Object read(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }

    @Override
    public void write(Object o, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {

    }
}
@Component
@Order(3)
public class MyResourceHttpMessageConverter extends ResourceHttpMessageConverter {

}

启动项目,在 HttpMessageConverters#getCombinedConverters 方法上打断点,可以看到:

用户自定义添加的 HttpMessageConverter 列表

可以看到除了我们自定义的三个 HttpMessageConverter 类型的 bean 之外,还有两个 HttpMessageConverter 类型的 bean,StringHttpMessageConverterMappingJackson2HttpMessageConverter

关于这两个 bean 的添加,请看 HttpMessageConvertersAutoConfiguration 小节

其中 MyMessageConverter1MyMessageConverter0MyResourceHttpMessageConverter 三个 HttpMessageConverter 类型的 bean 的顺序由 @Order 注解控制,其实 StringHttpMessageConverterMappingJackson2HttpMessageConverter 这两个 bean 的顺序也是 @Order 注解控制的,因为这两个 bean 没有 @Order 注解修饰,所以默认放到组后面

关于 HttpMessageConverter 类型的 bean 的顺序,具体请看 HttpMessageConvertersAutoConfiguration 小节

系统默认添加的 HttpMessageConverter 列表

可以注意到会有三个默认添加的 HttpMessageConverter 是自定义添加的 HttpMessageConverter 的父类或者同类型

我们可以注意到,MappingJackson2HttpMessageConverter 不仅默认添加了,在自定义添加的 HttpMessageConverter(大部分都是 IOC 容器中 HttpMessageConverter 类型的 bean)中也存在,于是导致了最终的 HttpMessageConverter 列表中有两个 MappingJackson2HttpMessageConverter。具体原因看 HttpMessageConvertersAutoConfiguration 小节

这三个默认添加的 HttpMessageConverter 将和自定义添加的对应 HttpMessageConverter 交换顺序。

最终合并结果,默认添加 8 个,自定义添加 5 个,总共 13 个。

MyMessageConverter1MyMessageConverter0 因为没有默认添加的 HttpMessageConverter 是其父类或者跟其类型相同,因此成为漏网之鱼,在 HttpMessageConverters#getCombinedConverters 方法的最后将其添加到了最终列表的最前面,自定义/额外添加的 MyResourceHttpMessageConverterStringHttpMessageConverterMappingJackson2HttpMessageConverter 与默认添加的、ResourceHttpMessageConverterStringHttpMessageConverterMappingJackson2HttpMessageConverter 对应,分别调整到对应 HttpMessageConverter 的前面。

HttpMessageConvertersAutoConfiguration

HttpMessageConvertersAutoConfiguration 的作用是自动配置 HttpMessageConverter

HttpMessageConvertersAutoConfiguration 在《SpringBoot 基础篇 -2- 自动配置.md》中都提到过。

SpringMVC 配置 HttpMessageConverter 的方式请看《SpringMVC- 第十篇:基于注解配置 SpringMVC.md》的 configureMessageConverters & extendMessageConverters 小节

为什么到了 SpringBoot 之后,要将其改成通过 Bean 注册的方式来配置 HttpMessageConverter,我猜是因为 HttpMessageConverter 应用比较广泛,不仅限于 Web 场景,因此将其提取出来作为 bean 进行管理。

接下来进行简单的源码分析:

HttpMessageConvertersAutoConfiguration 本身注册了一个 HttpMessageConverters 类型的 bean,同时内部还有一个内部配置类 HttpMessageConvertersAutoConfiguration$StringHttpMessageConverterConfiguration,注册了 StringHttpMessageConverter 类型的 bean。注意,这两个 bean 都配置了 @ConditionalOnMissingBean,如果用户手动注册了此类型的 bean,那么自动配置类就不注册了

首先看 HttpMessageConverters 类型的 bean,首先会获取 IOC 中所有的 HttpMessageConverter 类型 bean,按照 Bean 声明时 @Order 注解声明的顺序会总成一个 list,这个 list 作为用户额外添加的手动配置的 HttpMessageConverter 与系统默认添加的 HttpMessageConverter 一起来实例化化 HttpMessageConverters

ObjectProvider#orderedStream 方法的分析和实践请看《SpringBoot-IOC-ObjectProvider 详解.md

HttpMessageConverters 的构造函数请看《SpringBoot-HttpMessageConverters 自动配置类源码解析 -HttpMessageConvertersAutoConfiguration.md》的 HttpMessageConverters 小节

这个 HttpMessageConverters 类型的 bean,会在 WebMvcAutoConfigurationAdapter 中重写 configureMessageConverters 的时候使用。《SpringBoot-Web 相关自动配置类源码解析 -WebMvcAutoConfiguration.md》的 configureMessageConverters 小节。

再看 StringHttpMessageConverter 类型的 bean,也很简单,自动配置 StringHttpMessageConverter 的主要目的修改 StringHttpMessageConverter 默认的编码格式(StandardCharsets.ISO_8859_1)为配置项 server.servlet.encoding 的配置。如果没有在配置文件中配置此配置项也没关系,因为 encoding.getCharset() 返回的,也就是 Encoding 的默认编码就是 StandardCharsets.UTF_8。这样当我们的控制器方法配置了 @ResponseBody 且返回值为 String 的时候,返回给客户端的字符串的编码就是 StandardCharsets.UTF_8,中文就不会乱码了。

其实在 SpringMVC 的时代,我们就做过类似的事情,那时为了实现这个效果,只能手动注册一个 StringHttpMessageConverter,具体请看 《SpringMVC-ContentNegotiation 内容协商.md》的 简单分析RequestResponseBodyMethodProcessor 小节

有关 StringHttpMessageConverter 本身的源码分析,请看 《SpringMVC-ContentNegotiation 内容协商.md》的 StringHttpMessageConverter 小节

// 加载顺序的配置
@AutoConfiguration(after = {GsonAutoConfiguration.class, JacksonAutoConfiguration.class, JsonbAutoConfiguration.class })
// 类路径下必须存在 HttpMessageConverter ,此自动配置类才会生效,也对,HttpMessageConverter 都不存在,HttpMessageConvertersAutoConfiguration还配置个啥呢
@ConditionalOnClass(HttpMessageConverter.class)
// 只有在非 Reactive 的wen应用中才生效,目前就只有 Servlet web应用
@Conditional(NotReactiveWebApplicationCondition.class)
// 注意,这个自动配置类,还引入了 JacksonHttpMessageConvertersConfiguration、GsonHttpMessageConvertersConfiguration、JsonbHttpMessageConvertersConfiguration 这三个配置类
@Import({ JacksonHttpMessageConvertersConfiguration.class, GsonHttpMessageConvertersConfiguration.class, JsonbHttpMessageConvertersConfiguration.class })
public class HttpMessageConvertersAutoConfiguration {

    static final String PREFERRED_MAPPER_PROPERTY = "spring.mvc.converters.preferred-json-mapper";

    // 注册 HttpMessageConverters 类型的 bean
    @Bean
    // 如果用户手动注册了此类型的bean,那么自动配置类就不注册了
    @ConditionalOnMissingBean
    public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
        // 获取IOC中所有的 HttpMessageConverter 类型bean,按照Bean声明时@Order注解声明的顺序会总成一个list,
        // 这个list作为用户额外添加的手动配置的HttpMessageConverter 与 系统默认添加的HttpMessageConverter一起初始化 HttpMessageConverters
        // ObjectProvider#orderedStream 方法的分析和实践请看《SpringBoot-IOC-ObjectProvider详解.md》
        // HttpMessageConverters的构造函数请看《SpringBoot-HttpMessageConverters自动配置类源码解析-HttpMessageConvertersAutoConfiguration.md》 的 HttpMessageConverters 小节
        return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
    }

    // StringHttpMessageConverter的自动配置类
    @Configuration(proxyBeanMethods = false)
    // 类路径下必须存在 StringHttpMessageConverter ,此自动配置类才会生效
    @ConditionalOnClass(StringHttpMessageConverter.class)
    protected static class StringHttpMessageConverterConfiguration {

        // 注册 StringHttpMessageConverter 类型的 bean
        @Bean
        // 如果用户手动注册了此类型的bean,那么自动配置类就不注册了
        @ConditionalOnMissingBean
        public StringHttpMessageConverter stringHttpMessageConverter(Environment environment) {
            // 自动配置 StringHttpMessageConverter 的主要目的修改StringHttpMessageConverter默认的编码格式(StandardCharsets.ISO_8859_1)为 配置项 server.servlet.encoding 的配置
            // 如果没有在配置文件中配置此配置项也没关系,因为 encoding.getCharset() 返回的,也就是 Encoding 的默认编码就是 StandardCharsets.UTF_8。
            // 这样当我们的控制器方法配置了 @ResponseBody 且返回值为 String的时候,返回给客户端的字符串的编码就是  StandardCharsets.UTF_8 ,中文就不会乱码了。
            // 其实在SpringMVC的时代,我们就做过类似的事情,那时为了实现这个效果,只能手动注册一个 StringHttpMessageConverter,具体请看 《SpringMVC-ContentNegotiation内容协商.md》 的 简单分析RequestResponseBodyMethodProcessor 小节
            // 有关 StringHttpMessageConverter 本身的源码分析,请看 《SpringMVC-ContentNegotiation内容协商.md》 的 StringHttpMessageConverter 小节
            Encoding encoding = Binder.get(environment).bindOrCreate("server.servlet.encoding", Encoding.class);
            StringHttpMessageConverter converter = new StringHttpMessageConverter(encoding.getCharset());
            converter.setWriteAcceptCharset(false);
            return converter;
        }

    }

    // NotReactiveWebApplicationCondition
    ......

}

除了注册这两个 bean,HttpMessageConvertersAutoConfiguration 还引入了 JacksonHttpMessageConvertersConfigurationGsonHttpMessageConvertersConfigurationJsonbHttpMessageConvertersConfiguration 这三个配置类,这三个配置类中同样会注册了一些 HttpMessageConverter 类型的 bean。这些注册的 HttpMessageConverter 类型的 bean 会跟用户手动创建的 HttpMessageConverter 类型的 bean 一起作为默认添加的 HttpMessageConverter 之外的用户自定义的 HttpMessageConverter。用户自定义添加的 HttpMessageConverter 列表会跟默认添加的 HttpMessageConverter 列表进行合并,根据类型将用户自定义添加的与默认添加的类型相同或为其子类的 HttpMessageConverter 放到最终 HttpMessageConverter 列表的前面

这里就不进行源码分析了,简单看看即可:

实际上,MappingJackson2HttpMessageConverterMappingJackson2XmlHttpMessageConverterGsonHttpMessageConverterJsonbHttpMessageConverterWebMvcConfigurationSupport#addDefaultHttpMessageConverters 默认添加的 HttpMessageConverter 中也会存在,也就是说,如果条件满足,在 SpringBoot 中,这些 HttpMessageConverter 会在最终的 HttpMessageConverter 列表中存在两次。

最终的 HttpMessageConverter 列表(即 HttpMessageConverter#converters 的值)会是什么样子,我们直接采用本文中 简单调试 小节的截图。

简单总结

SpringBoot 中对 HttpMessageConverter 的管理跟 SpringMVC 框架中确实不一样。专门增加了 HttpMessageConverters 类型的 bean 来管理所有的 HttpMessageConverter,而且还专门额外创建了 HttpMessageConvertersAutoConfiguration 自动配置类来配置 HttpMessageConverters

系统除了会默认添加 HttpMessageConverter 之外,还会添加容器中用户手动写代码注册的 HttpMessageConverter 类型的 bean,还会有自动配置类自动注册常用的 HttpMessageConverter 类型的 bean 到 IOC 容器中,辅助、减少用户自己手动写代码注册 HttpMessageConverter 类型的 bean 的工作量,非常方便。

SpringMVC 配置 HttpMessageConverter 的方式请看《SpringMVC- 第十篇:基于注解配置 SpringMVC.md》的 configureMessageConverters & extendMessageConverters 小节