SpringBoot自动装配

1. 自动装配机制的设计

SpringBoot 的自动装配机制,核心是由 SpringFramework 模块装配 + 条件装配 + SPI 机制组合而成,最终落到 @SpringBootApplication 注解中予以最终实现。我们先简单回顾自动装配的底层支持。

1.1 模块装配

模块装配的核心是把一个场景中所需要的组件,一次性注册到应用的上下文中。模块装配强调的是整体,它通过定义一个特殊的注解,配合 @Import 注解,即可实现 4 种不同类型组件的导入,从而实现多个组件的一次性导入。

@Import 注解可以导入的组件类型有:

  • 普通类
  • 注解配置类
  • ImportSelector
  • ImportBeanDefinitionRegistrar

1.2 条件装配

条件装配的核心是根据不同场景分别导入不同的组件,SpringFramework 中包含基于 Profile 的条件装配,以及基于 Conditional 的条件装配。SpringBoot 中更多使用的是基于 Conditional 的条件装配,它相较于 Profile 来讲适用范围更广,判断条件更加灵活。SpringBoot 由 SpringFramework 的基础之上扩展了一组 @ConditionalOnxxx 注解,进一步简化了条件装配的使用。

1.3 SPI机制

Spring SPI 机制本身是依赖倒转原则的体现,它可以通过一个指定的接口 / 实现类 / 注解,寻找到预先配置好的实现类(并可以创建实现类的对象)。相较于 jdk 原生的 SPI 机制,SpringFramework 的 SPI 机制更加强大,所以 SpringBoot 直接借用了 SpringFramework 的 SPI 机制,并利用其可以支持注解作为索引的特性,实现了 SpringBoot 中最强大的特性之一:自动装配。

1.4 @SpringBootApplication注解

SpringBoot 的主启动类上通常都会标注 @SpringBootApplication 注解,用于启用自动装配和组件扫描。 

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication

@ComponentScan 注解用于扫描主启动类所在的包及子包下的所有组件,@SpringBootConfiguration 注解用于标注当前的类是一个配置类,这两个注解都相对简单,最复杂也是最重要的注解是 @EnableAutoConfiguration ,它承载着 SpringBoot 自动装配的灵魂。

总的来看,@EnableAutoConfiguration 注解的作用是:启用 SpringBoot 的自动装配,根据导入的依赖、上下文配置合理加载默认的自动配置

具体到源码中,它通过 @AutoConfigurationPackage 注解将当前主启动类的所在包记录下来,为第三方组件场景整合时提供支持;

@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage

导入的 AutoConfigurationImportSelector 本身是一个 ImportSelector ,它利用 SpringFramework 的 SPI 机制,将当前工程中所有的 spring.factories 文件中 @EnableAutoConfiguration 对应的自动配置类都取出并进行匹配,最终确定出可以被应用的自动配置类并注册。

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    // 加载注解属性配置
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 【加载自动配置类的真实动作】
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    // 自动配置类去重
    configurations = removeDuplicates(configurations);
    // 获取显式配置了要排除掉的自动配置类
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    // 排除动作
    configurations.removeAll(exclusions);
    configurations = getConfigurationClassFilter().filter(configurations);
    // 广播AutoConfigurationImportEvent事件
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
    // assert ......
    return configurations;
}

由上方 getAutoConfigurationEntry 方法往下执行,核心的 getCandidateConfigurations 方法即是加载自动配置类的核心方法,对应的源码实现如下:

// 存储SPI机制加载的类及其映射
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();
// SPI的文件名必须为spring.factories
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    String factoryTypeName = factoryType.getName();
    // 利用缓存机制提高加载速度
    return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    // 解析之前先检查缓存,有则直接返回
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null) {
        return result;
    }

    try {
        // 真正的加载动作,利用类加载器加载所有的spring.factories(多个),并逐个配置解析
        Enumeration<URL> urls = (classLoader != null ?
                classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();
        while (urls.hasMoreElements()) {
            // 取出每个spring.factories文件
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            // 以Properties的方式读取spring.factories
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                // 逐个配置项映射收集
                String factoryTypeName = ((String) entry.getKey()).trim();
                // 如果一个key配置了多个value,则用英文逗号隔开,此处会做切分
                for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                    result.add(factoryTypeName, factoryImplementationName.trim());
                }
            }
        }
        cache.put(classLoader, result);
        return result;
    } // catch ......
}

 

2. 自动配置类的设计

自动装配的核心是自动配置类,在 SpringBoot 加载自动配置类后,会一一解析这些自动配置类。下面以几个 SpringBoot 内置的自动配置类为例,提取自动配置类中的核心设计细节。

2.1 HttpEncodingAutoConfiguration

HttpEncodingAutoConfiguration 的核心作用是解决 WebMvc 的使用场景中请求编码的问题。做过原生 SSM 开发的小伙伴一定不会陌生,在 web.xml 中需要配置一个 CharacterEncodingFilter ,使用它就可以解决客户端请求的数据乱码问题。下面通过源码简单观察 HttpEncodingAutoConfiguration 的设计。

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ServerProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {

通过 HttpEncodingAutoConfiguration 类上标注的注解,可以得知 HttpEncodingAutoConfiguration 的生效条件如下:

  • 当前的 Web 类型是 Servlet ;
  • 当前工程的类路径下存在一个 CharacterEncodingFilter 
  • 当前全局配置文件中有声明一个 server.servlet.encoding.enabled 属性,且值不为 false (或没有声明该属性)。

当以上条件全部生效时,该自动配置类会做以下几个关键动作:

  • 开启 ServerProperties 对象与 SpringBoot 全局配置文件的属性绑定;
  • 向容器中注册一个 CharacterEncodingFilter 。

注意其中注册 CharacterEncodingFilter 的源码,创建的方法上方标注了 @ConditionalOnMissingBean 注解,意味着如果工程中没有显式注册该组件,则自动配置类会生效并注册,反之亦然。

    private final Encoding properties;

    public HttpEncodingAutoConfiguration(ServerProperties properties) {
        this.properties = properties.getServlet().getEncoding();
    }

    @Bean
    @ConditionalOnMissingBean
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
        return filter;
    }

由此可以总结出自动配置类的几个设计细节:

  • @ConditionalOnXXX 注解可以控制自动配置类是否生效;
  • 自动配置类生效后,可以激活当前配置类中需要使用的配置属性;
  • 自动配置类注册组件时遵循约定大于配置原则。

2.2 DataSourceAutoConfiguration

DataSourceAutoConfiguration 的核心作用是向应用上下文中注册数据源。当引入 spring-boot-starter-jdbc 依赖后,该配置类会默认向容器中注册一个 DataSource 数据源。

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
        DataSourceInitializationConfiguration.InitializationSpecificCredentialsDataSourceInitializationConfiguration.class,
        DataSourceInitializationConfiguration.SharedCredentialsDataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {

注意观察 DataSourceAutoConfiguration 类上的注解可以发现,除了有类似 HttpEncodingAutoConfiguration 中的 @ConditionalOnXXX 与 @EnableConfigurationProperties 注解之外,还有 @Import 注解导入其他的配置类。

由此可以知道,自动配置类的另一个设计细节:自动配置类可以导入其他的配置类(这些配置类也会参与条件装配的规则匹配)

除此之外,观察 DataSourceAutoConfiguration 内部的几个内部类,可以发现部分内部类本身也是注解配置类,它们也被 @Import 注解标注,导入一些其他的配置类。以下源码是导入数据库连接池的注解配置内部类 PooledDataSourceConfiguration 的定义。

public class DataSourceAutoConfiguration {

    @Configuration(proxyBeanMethods = false)
    @Conditional(PooledDataSourceCondition.class)
    @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
    @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
            DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
            DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
    protected static class PooledDataSourceConfiguration {

    }

由此可以再总结一个设计细节:自动配置类的内部可能会定义其他的注解配置类,这些类会通过内部类的方式定义

2.3 WebMvcAutoConfiguration

WebMvcAutoConfiguration 是基于 WebMvc 场景的核心自动装配,它会向应用上下文注册 WebMvc 相关的核心组件。观察 WebMvcAutoConfiguration 类上标注的注解,可以发现一些新的注解。

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {

通过上述源码可以发现,WebMvcAutoConfiguration 被两个具备排序意义的注解标注,分别是 @AutoConfigureOrder 与 @AutoConfigureAfter ,它们分别代表着绝对顺序与相对顺序。@AutoConfigureOrder 注解会传入一个 order 排序值,数值越小优先级越高;@AutoConfigureAfter 与 @AutoConfigureBefore 则可以指定当前自动配置类的顺序在指定配置类之后 / 之前。

由此可以再总结出一个设计细节:自动配置类可以指定被应用的顺序,来达到一些特殊的定制效果

  • 16
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值