第四十一讲-自动配置原理

第四十一讲-自动配置原理

1. 自动配置类原理

假设在我们编写了一些配置类(第三方提供的配置类),那么我们在我们自己的项目中怎么将这些第三方配置类整合进来呢?如下面的代码:

public class A41Application {

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("config", Config.class);
        context.registerBean(ConfigurationClassPostProcessor.class);    // 解析@Bean和2Configuration注解
        context.refresh();

        // 打印出容器中的bean对象
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }
    
    @Configuration  // 本项目的配置类-->需要按导入其它的配置类
    @Import({AutoConfiguration1.class, AutoConfiguration2.class})   // 导入其它的配置类
    static class Config{

    }


    // 第三方配置类
    @Configuration
    static class AutoConfiguration1 {
        @Bean
        public Bean1 bean1(){
            return new Bean1();
        }
    }

    // 第三方配置类
    @Configuration
    static class AutoConfiguration2 {
        @Bean
        public Bean2 bean2(){
            return new Bean2();
        }
    }

    @Component
    static class Bean1{ }

    @Component
    static class Bean2{ }
}

测试结果如下:

config
org.springframework.context.annotation.ConfigurationClassPostProcessor
com.cherry.a41.A41Application$AutoConfiguration1
bean1
com.cherry.a41.A41Application$AutoConfiguration2
bean2

我们发现,通过@Import注解导入的第三方配置类都被成功的加入到了容器中,且第三方配置类引用的Bean对象也被加入到了容器中。

但是呢?这里有一个问题,那就是将来第三方配置类有可能会非常的多,而且我们希望第三方的配置类名不要写死在Java代码中,而是写在配置文件中,那么这该怎么做到呢?

我们可以先写一个专门引入第三方配置类的工具类:

static class MyImportSelector implements ImportSelector {

    // 方法返回值就是将来要导入的配置类名
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{AutoConfiguration1.class.getName(), AutoConfiguration2.class.getName()};
    }
}

接着将我们项目中的配置类从@Import导入这个工具类,让这个工具类返回需要导入的第三方配置类:

@Configuration  // 本项目的配置类-->需要按导入其它的配置类
//@Import({AutoConfiguration1.class, AutoConfiguration2.class})   // 导入其它的配置类
@Import(MyImportSelector.class)
static class Config {

}

我们再次测试一下:

config
org.springframework.context.annotation.ConfigurationClassPostProcessor
com.cherry.a41.A41Application$AutoConfiguration1
bean1
com.cherry.a41.A41Application$AutoConfiguration2
bean2

我们发现第三方配置类依然导入成功了,且第三方引入的其它Bean也导入成功了!

我们紧接着再做一些改进:因为我们的类名还是写死在Java代码中的,我们希望这写配置类写在外面配置文件里,而且这个配置文件的位置也很有讲究,要写在**META-INF文件夹下的spring.factories这个文件里! 我们将自动配置类类名作为key,第三方配置类类名作为value, ** 如下面的目录结构:

image-20240903183556645

com.cherry.a41.A41Application$MyImportSelector=\
com.cherry.a41.A41Application.AutoConfiguration1,\
com.cherry.a41.A41Application.AutoConfiguration2

我们使用Spring提供的额工具类从spring.factories文件中读取第三方配置类:

static class MyImportSelector implements ImportSelector {

    // 方法返回值就是将来要导入的配置类名
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 从配置文件中读取配置类
        List<String> names = SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, null);
        return names.toArray(new String[]);
    }
}

运行结果如下:

config
org.springframework.context.annotation.ConfigurationClassPostProcessor
com.cherry.a41.A41Application$AutoConfiguration1
bean1
com.cherry.a41.A41Application$AutoConfiguration2
bean2

我们写的配置类成功引入了第三方的配置类,且也将第三方引入的Bean对象加入到了容器中。

至此,我们模拟了自动配置类是如何被@Import导入的。Spring Boot自带了一些自动配置类,Spring Boot会自动的从每个jar包下的META-INFO下的 spring.factories文件。

image-20240903185936307

这里呢,我们看以下spring boot自带的AutoConfiguration:

image-20240903190317251

我们发现,Spring Boot的自定配置类的key就是EnableAutoConfiguration,该自动配置类引入了许多第三方自动配置类,这里呢,我们来使用代码获取一下EnableAutoConfiguration这个自动配置类中究竟引入了哪些外部配置类:

@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
    // 从配置文件中读取配置类
    List<String> names = SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, null);
    System.out.println("==========================>");
    for (String name : SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, null)) {
        System.out.println(name);
    }
    System.out.println("==========================>");

    return names.toArray(new String[0]);
}
}

image-20240903190941321

image-20240903191006229

我们发现,Spring Boot的自动配置类引入了很多第三方自动配置类,且有很多自动配置类是我们比较熟悉的,例如:DruidDataSourceAutoConfiguration,MybatisAutoConfiguration,DataSourceAutoConfiguration,JacksonAutoConfiguartion等等。

接下来我们研究一个问题,如果我们自己的配置类和第三方的配置类有些Bean的定义冲突了会怎么样?如下面的代码:

public class A41Application {

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("config", Config.class);
        context.registerBean(ConfigurationClassPostProcessor.class);    // 解析@Bean和2Configuration注解
        context.refresh();

        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }

        System.out.println("++++++++++++++++++>");
        // 获取bean1
        System.out.println(context.getBean("bean1"));
    }

    @Configuration  // 本项目的配置类-->需要按导入其它的配置类
    //@Import({AutoConfiguration1.class, AutoConfiguration2.class})   // 导入其它的配置类
    @Import(MyImportSelector.class)
    static class Config {

        @Bean
        public Bean1 bean1(){
            return new Bean1("本项目");
        }
    }


    static class MyImportSelector implements ImportSelector {

        // 方法返回值就是将来要导入的配置类名
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            // 从配置文件中读取配置类
            List<String> names = SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, null);
            return names.toArray(new String[0]);
        }
    }


    // 第三方配置类
    @Configuration
    static class AutoConfiguration1 {
        @Bean
        public Bean1 bean1() {
            return new Bean1("第三方");
        }
    }

    @Component
    static class Bean1 {
        private String name;

        public Bean1() {
        }

        public Bean1(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "Bean1{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
}

运行结果如下:

Bean1{name='本项目'}

我们发现本项目的配置类和第三方的配置类发生冲突的时候,使用的是本项目中的配置类!主要原因有两点:

  1. @Import导入第三方Bean时和我们在配置类中自己定义Bean的时候,它们的解析时机是不一样的:@Import注解导入的Bean会先被解析,也就是说先解析了第三方的Bean的定义,最后才会解析本项目中定义的Bean。
  2. 第二点是因为Spring中的BeanFactory默认是可以将后注册的Bean覆盖掉前面注册的Bean。也就是说后注册的本项目中的Bean会覆盖掉前面导入的第三方同名的Bean。当然,这在SpringBoot中是不允许出现后面的Bean覆盖点前面出现的同名Bean的。

基于上面的第二点,我们设置不允许覆盖选项来查看一下:

public class A41Application {

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        // 设置后面的Bean不允许覆盖与前面同名的Bean(默认就是false)
        context.getDefaultListableBeanFactory().setAllowBeanDefinitionOverriding(false);
        context.registerBean("config", Config.class);
        context.registerBean(ConfigurationClassPostProcessor.class);    // 解析@Bean和2Configuration注解
        context.refresh();

        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }

        System.out.println("++++++++++++++++++>");
        // 获取bean1
        System.out.println(context.getBean("bean1"));
    }

    @Configuration
    @Import(MyImportSelector.class)
    static class Config {

        @Bean
        public Bean1 bean1(){
            return new Bean1("本项目");
        }
    }


    static class MyImportSelector implements ImportSelector {

        // 方法返回值就是将来要导入的配置类名
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            // 从配置文件中读取配置类
            List<String> names = SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, null);
            return names.toArray(new String[0]);
        }
    }


    @Component
    static class Bean1 {
        private String name;

        public Bean1() {
        }

        public Bean1(String name) {
            this.name = name;
        }
    }
}

运行结果如下:

19:31:31.379 [main] WARN org.springframework.context.support.GenericApplicationContext -- Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'bean1' defined in com.cherry.a41.A41Application$Config: Cannot register bean definition

不能够注册一个BeanDefinition,因为已经有一个同名的BeanDefinition注册过了

对于这种情况,大家可以思考一下,这种方案合理吗?我们本项目中的同名Bean优先级高,还是第三方的同名Bean优先级高呢?按照道理来讲,其实是我们本项目中的同名Bean优先级更高才对!这里举个例子:Spring Boot集成了数据源配置,但是我不想用Spring提供的数据源,我就是想用自己的数据源,我希望自己的数据源可以覆盖掉Spring提供的数据源。

因此呢,我们希望我们自己的一些配置类来取代Spring Boot提供的一些配置类,这该怎么做呢?

我们首先要改变配置类的解析时机要变得不一样,应该先让Spring Boot解析本项目中的配置类,再解析第三方的配置类,刚才默认是先解析第三方的配置类,再解析本项目中的配置类。这该怎么做呢?

我们可以将 MyImportSelector 实现的接口 ImportSelector 变为 DeferredImportSelector(推迟导入选择器),这个接口的解析规则是先解析本项目中的配置类,再去解析第三方的配置类!

此外,我们还可以加一个注解@ConditionalOnMissingBean,该注解的含义是当我们项目中有该自动配置Bean的时候,会忽略掉第三方同名的自动配置Bean:

// 第三方配置类
@Configuration
static class AutoConfiguration1 {
    @Bean
    @ConditionalOnMissingBean
    public Bean1 bean1() {
        return new Bean1("第三方");
    }
}

这里呢,我们测试一下:

  1. 当本项目中存在与第三方同名自动配置Bean的时候:
public class A41Application {

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        // 设置后面的Bean不允许覆盖与前面同名的Bean(默认就是false)
        context.getDefaultListableBeanFactory().setAllowBeanDefinitionOverriding(false);
        context.registerBean("config", Config.class);
        context.registerBean(ConfigurationClassPostProcessor.class);    // 解析@Bean和2Configuration注解
        context.refresh();
        System.out.println("++++++++++++++++++>");
        // 获取bean1
        System.out.println(context.getBean("bean1"));
    }

    @Configuration  // 本项目的配置类-->需要按导入其它的配置类
    //@Import({AutoConfiguration1.class, AutoConfiguration2.class})   // 导入其它的配置类
    @Import(MyImportSelector.class)
    static class Config {

        @Bean
        public Bean1 bean1(){
            return new Bean1("本项目");
        }
    }


    static class MyImportSelector implements DeferredImportSelector {

        // 方法返回值就是将来要导入的配置类名
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            // 从配置文件中读取配置类
            List<String> names = SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, null);
            return names.toArray(new String[0]);
        }
    }


    // 第三方配置类
    @Configuration
    static class AutoConfiguration1 {
        @Bean
        @ConditionalOnMissingBean
        public Bean1 bean1() {
            return new Bean1("第三方");
        }
    }

    @Component
    static class Bean1 {
        private String name;

        public Bean1() {
        }

        public Bean1(String name) {
            this.name = name;
        }
    }
}
Bean1{name='本项目'}

本项目中有自动配置Bean的时候,会使用本项目中的自动配置Bean。

  1. 当本项目中不存在与第三方同名自动配置Bean的时候:
public class A41Application {

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        // 设置后面的Bean不允许覆盖与前面同名的Bean(默认就是false)
        context.getDefaultListableBeanFactory().setAllowBeanDefinitionOverriding(false);
        context.registerBean("config", Config.class);
        context.registerBean(ConfigurationClassPostProcessor.class);    // 解析@Bean和2Configuration注解
        context.refresh();
        System.out.println("++++++++++++++++++>");
        // 获取bean1
        System.out.println(context.getBean("bean1"));
    }

    @Configuration  // 本项目的配置类-->需要按导入其它的配置类
    //@Import({AutoConfiguration1.class, AutoConfiguration2.class})   // 导入其它的配置类
    @Import(MyImportSelector.class)
    static class Config {
        
    }


    static class MyImportSelector implements DeferredImportSelector {

        // 方法返回值就是将来要导入的配置类名
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            // 从配置文件中读取配置类
            List<String> names = SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, null);
            return names.toArray(new String[0]);
        }
    }


    // 第三方配置类
    @Configuration
    static class AutoConfiguration1 {
        @Bean
        @ConditionalOnMissingBean
        public Bean1 bean1() {
            return new Bean1("第三方");
        }
    }

    @Component
    static class Bean1 {
        private String name;

        public Bean1() {
        }

        public Bean1(String name) {
            this.name = name;
        }
    }
}
Bean1{name='第三方'}

本项目中没有自动配置Bean的时候,会使用第三方中的自动配置Bean。

Spring Boot就是这样做的!

2. 常见的自动配置实现

2.1 AopAutoConfiguration自动配置实现

如下面的测试代码,将Aop自动配置类显式的加入到Spring容器中:



public class AopAutoTest {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        // 使用Spring提供的工具类往容器中加入一些常见的Bean后处理器
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
        context.registerBean(Config.class);
        context.refresh();
        for (String beanName : context.getBeanDefinitionNames()) {
            System.out.println(beanName);
        }
    }

    @Configuration
    @Import(MyImportSelector.class)
    static class Config{ }


    static class MyImportSelector implements DeferredImportSelector {
        // 加入AOP的自动配置类
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return new String[]{AopAutoConfiguration.class.getName()};
        }
    }
}

部分运行结果如下:

com.cherry.a41.AopAutoTest$Config
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$ClassProxyingConfiguration
forceAutoProxyCreatorToUseClassProxying
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.aop.config.internalAutoProxyCreator

我们可以从结果中可以看到,当我们把AopAutoConfiguration手动加入到容器中的时候,Aop自动配置类会自动的加入了4个Bean对象。这些Bean是怎么来的,分别有什么用处,我们接下来研究一下。

我们首先点进AopAutoConfiguration这个类里面查看一下源代码:

@AutoConfiguration
@ConditionalOnProperty(		
    prefix = "spring.aop",
    name = {"auto"},
    havingValue = "true",
    matchIfMissing = true
)
public class AopAutoConfiguration {
    public AopAutoConfiguration() {
    }

    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnMissingClass({"org.aspectj.weaver.Advice"})
    @ConditionalOnProperty(
        prefix = "spring.aop",
        name = {"proxy-target-class"},
        havingValue = "true",
        matchIfMissing = true
    )
    static class ClassProxyingConfiguration {
        ClassProxyingConfiguration() {
        }

        @Bean
        static BeanFactoryPostProcessor forceAutoProxyCreatorToUseClassProxying() {
            return (beanFactory) -> {
                if (beanFactory instanceof BeanDefinitionRegistry registry) {
                    AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
                    AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
                }

            };
        }
    }

    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnClass({Advice.class})
    static class AspectJAutoProxyingConfiguration {
        AspectJAutoProxyingConfiguration() {
        }

        @Configuration(
            proxyBeanMethods = false
        )
        @EnableAspectJAutoProxy(
            proxyTargetClass = true
        )
        @ConditionalOnProperty(
            prefix = "spring.aop",
            name = {"proxy-target-class"},
            havingValue = "true",
            matchIfMissing = true
        )
        static class CglibAutoProxyConfiguration {
            CglibAutoProxyConfiguration() {
            }
        }

        @Configuration(
            proxyBeanMethods = false
        )
        @EnableAspectJAutoProxy(
            proxyTargetClass = false
        )
        @ConditionalOnProperty(
            prefix = "spring.aop",
            name = {"proxy-target-class"},
            havingValue = "false"
        )
        static class JdkDynamicAutoProxyConfiguration {
            JdkDynamicAutoProxyConfiguration() {
            }
        }
    }
}

我们发现,对于AopAutoConfiguration这个类来讲:

只有spring.factories中的spring.aop.auto=true或者这个配置没有手动指定的时候会导入AopAutoConfiguration这个自动配置,除非手动设置spring.aop.auto=false!

对于AspectJAutoProxyingConfigurationAspectJ自动代理配置来讲激活的条件是类路径下是否存在Advice这个类

对于ClassProxyingConfiguration这个自动配置类来讲,激活的条件是类路径下缺失org.aspectj.weaver.Advice这个类。

上述两者是相互互补的条件。

对于CglibAutoProxyConfiguration自动配置类来讲,其激活的条件是配置文件中是否有spring.aop.proxy-target-class=true`这一项。

对于JdkDynamicAutoProxyConfiguration自动配置类来讲,其激活的条件是配置文件中是否有spring.aop.proxy-target-class=false这一项。

对于上面两者,同样是互斥的关系,如果spring.aop.proxy-target-class=true,则使用CglibAutoProxyConfiguration;如果spring.aop.proxy-target-class=false,则使用JdkDynamicAutoProxyConfiguration

前面我们讲过,Spring究竟什么时候JDK动态代理,什么时候使用Cglib动态代理,会有一个约定,会根据其中的一个属性proxyTargetClass进行判断:

  1. targetProxyClass=false时, 如果目标类实现了接口,就会优先使用JDK的动态代理;如果目标类没有实现接口,会优先使用Cglib的动态代理
  2. targetProxyClass=true时,不管有没有实现接口,都会使用Cglib的动态代理

这里需要提一下,以后在Spring容器中,凡是看到@Enablexxxxx打头的注解,其实内部使用的都是@Import注解导入对应的自动配置类,这里特别强调!

如下面的@EnableAspectJAutoProxy的内部就是:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy{
    // ...
}

@Import注解我们提及到了,就是根据Bean的类型,将Bean对象加入到容器中,当然也会将该Bean引用的对象所对应的Bean对象加入到容器中!

2.2 DataSourceAutoConfiguration自动配置实现

如下面的代码:

public class DatasourceAutoTest {
    @SuppressWarnings("all")
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        StandardEnvironment env = new StandardEnvironment();
        env.getPropertySources().addLast(new SimpleCommandLinePropertySource(
                "--spring.datasource.url=jdbc:mysql://localhost:3306/test",
                "--spring.datasource.username=root",
                "--spring.datasource.password=root"
        ));
        context.setEnvironment(env);
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
        context.registerBean(Config.class);

        String packageName = DatasourceAutoTest.class.getPackageName();
        System.out.println("当前包名:" + packageName);
        AutoConfigurationPackages.register(context.getDefaultListableBeanFactory(),
                packageName);

        context.refresh();
        for (String name : context.getBeanDefinitionNames()) {
            String resourceDescription = context.getBeanDefinition(name).getResourceDescription();
            if (resourceDescription != null)
                System.out.println(name + " 来源:" + resourceDescription);
        }
    }

    @Configuration
    @Import(MyImportSelector.class)
    static class Config {

    }

    static class MyImportSelector implements DeferredImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return new String[]{
                    DataSourceAutoConfiguration.class.getName(),
                    MybatisAutoConfiguration.class.getName(),
                    DataSourceTransactionManagerAutoConfiguration.class.getName(),
                    TransactionAutoConfiguration.class.getName()
            };
        }
    }
}

运行结果如下:

当前包名:com.cherry.a41
16:10:49.724 [main] WARN org.mybatis.spring.mapper.ClassPathMapperScanner -- No MyBatis mapper was found in '[com.cherry.a41]' package. Please check your configuration.
jdbcConnectionDetailsHikariBeanPostProcessor 来源:class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]
dataSource 来源:class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]
jdbcConnectionDetails 来源:class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration$PooledDataSourceConfiguration.class]
hikariPoolDataSourceMetadataProvider 来源:class path resource [org/springframework/boot/autoconfigure/jdbc/metadata/DataSourcePoolMetadataProvidersConfiguration$HikariPoolDataSourceMetadataProviderConfiguration.class]
sqlSessionFactory 来源:class path resource [org/mybatis/spring/boot/autoconfigure/MybatisAutoConfiguration.class]
sqlSessionTemplate 来源:class path resource [org/mybatis/spring/boot/autoconfigure/MybatisAutoConfiguration.class]
transactionManager 来源:class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceTransactionManagerAutoConfiguration$JdbcTransactionManagerConfiguration.class]
org.springframework.transaction.config.internalTransactionAdvisor 来源:class path resource [org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.class]
transactionAttributeSource 来源:class path resource [org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.class]
transactionInterceptor 来源:class path resource [org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.class]
org.springframework.transaction.config.internalTransactionalEventListenerFactory 来源:class path resource [org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.class]
transactionTemplate 来源:class path resource [org/springframework/boot/autoconfigure/transaction/TransactionAutoConfiguration$TransactionTemplateConfiguration.class]

我们发现有很多关于自动配置类,Datasource来源于org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class

spring boot默认提供的数据源。 我们可以进入到DatasourceAutoConfiguration中去看看,其中的部分代码如下:

@AutoConfiguration(
    before = {SqlInitializationAutoConfiguration.class}
)
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@ConditionalOnMissingBean(
    type = {"io.r2dbc.spi.ConnectionFactory"}
)
@EnableConfigurationProperties({DataSourceProperties.class})
@Import({DataSourcePoolMetadataProvidersConfiguration.class, DataSourceCheckpointRestoreConfiguration.class})
public class DataSourceAutoConfiguration {	
    // 当前的应用是不是带有基于连接池的数据源,这里spring boot默认提供了Hikari数据源,一旦成立之后,又会进一步导入@Import中的配置类
    @Configuration(
        proxyBeanMethods = false
    )
    @Conditional({PooledDataSourceCondition.class})
    @ConditionalOnMissingBean({DataSource.class, XADataSource.class})
    @Import({DataSourceConfiguration.Hikari.class, 	// Hikari数据源 
             DataSourceConfiguration.Tomcat.class, 	// Tomcat提供的数据源
             DataSourceConfiguration.Dbcp2.class, 	// Dbcp2数据源
             DataSourceConfiguration.OracleUcp.class,	// OracleUcp数据源 
             DataSourceConfiguration.Generic.class, // Generic数据源
             DataSourceJmxConfiguration.class})
    protected static class PooledDataSourceConfiguration {
        protected PooledDataSourceConfiguration() {
        }

        @Bean
        @ConditionalOnMissingBean({JdbcConnectionDetails.class})
        PropertiesJdbcConnectionDetails jdbcConnectionDetails(DataSourceProperties properties) {
            return new PropertiesJdbcConnectionDetails(properties);
        }
    }

    
    // 当前项目是否支持内嵌数据源, 例如h2这样的内嵌数据库
    @Configuration(
        proxyBeanMethods = false
    )
    @Conditional({EmbeddedDatabaseCondition.class})
    @ConditionalOnMissingBean({DataSource.class, XADataSource.class})
    @Import({EmbeddedDataSourceConfiguration.class})
    protected static class EmbeddedDatabaseConfiguration {
        protected EmbeddedDatabaseConfiguration() {
        }
    }
}

此外还有一个比较重要的类,就是DataSourceProperties这个类,这个类的作用就是将spring环境中以 spring.datasource 打头的键值和DataSourceProperties对象做一个绑定,如下面的图:

image-20240904163010330

对于创建对应类型的Datasource时,需要一个DatasourceProperties这个对象作为参数将连接数据库的信息收集起来用于创建Datasource对象时使用。

2.3 MyBatisAutoConfiguration自动配置实现

接下来我们看一下sqlSessionFactorysqlSessionTemplate,它们两个都来源于MybatisAutoConfiguration,也就是Mybatis的自动配置类,我们首先来看一下MybatisAutoConfiguration这个类:

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})	// 
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
public class MybatisAutoConfiguration implements InitializingBean {
    
}

@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class}):SqlSessionFactory和SqlSessionFactoryBean这两个类必须在类路径下能找到才会生效这个自动配置类。

@ConditionalOnSingleCandidate(DataSource.class):现在的容器中有且仅有一个数据源才可以,如果有两个数据源,Mybatis就并不知道到底使用那个数据源创建SqlSession工厂,因此必须保证DataSource只能有一个!

@EnableConfigurationProperties({MybatisProperties.class}):将来会创建一个MybatisProperties属性对象,该对象会跟我们环境中的键值信息进行绑定,如下面该类的定义,所有以mybatis开头定义的前缀信息都会跟mybatisProperties对象进行绑定,绑定之后,在后续创建SqlSessionFactory中会收集起来使用上。

@ConfigurationProperties(
    prefix = "mybatis"
)
public class MybatisProperties {
    public static final String MYBATIS_PREFIX = "mybatis";
	//...
}

@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})是用来控制多个自动控制类的解析顺序的,这里是对MybatisLanguageDriverAutoConfiguration的解析应该放在DataSourceAutoConfiguration解析的后面的!

@Bean
@ConditionalOnMissingBean	// 当我们的配置中没有自己指定SqlSessionFactory时,
							// 该条件才会成立,会使用配置中自动提供的额SqlSessionFactory;
							// 如果我们自己提供了SqlSessionFactory时,
							// 那么就不会使用自动配置类中的SqlSessionFactory了
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {

}

@Bean
@ConditionalOnMissingBean	// 其解释和上面的SqlSessionFactory解释类似
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    ExecutorType executorType = this.properties.getExecutorType();
    return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);
}

2.4 TransactionManagerAutoConfiguration自动配置实现

接下来我么来看一下有关于事务管理的自动配置类。

@Bean
@ConditionalOnMissingBean({TransactionManager.class})
DataSourceTransactionManager transactionManager(Environment environment, DataSource dataSource, ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
    DataSourceTransactionManager transactionManager = this.createTransactionManager(environment, dataSource);
    transactionManagerCustomizers.ifAvailable((customizers) -> {
        customizers.customize(transactionManager);
    });
    return transactionManager;
}

当缺少TransactionManager这个类的时候该事务自动配置类就会生效。

@Configuration(
    proxyBeanMethods = false
)
@Role(2)
@ImportRuntimeHints({TransactionRuntimeHints.class})
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
    public ProxyTransactionManagementConfiguration() {
    }

    @Bean(
        name = {"org.springframework.transaction.config.internalTransactionAdvisor"}
    )
    @Role(2)
    public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {
        BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
        advisor.setTransactionAttributeSource(transactionAttributeSource);
        advisor.setAdvice(transactionInterceptor);
        if (this.enableTx != null) {
            advisor.setOrder((Integer)this.enableTx.getNumber("order"));
        }

        return advisor;
    }

    @Bean
    @Role(2)
    public TransactionAttributeSource transactionAttributeSource() {
        return new AnnotationTransactionAttributeSource(false);
    }

    @Bean
    @Role(2)
    public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) {
        TransactionInterceptor interceptor = new TransactionInterceptor();
        interceptor.setTransactionAttributeSource(transactionAttributeSource);
        if (this.txManager != null) {
            interceptor.setTransactionManager(this.txManager);
        }

        return interceptor;
    }
}

我们发现@Transactional注解的自动配置类的底层其实就是一个低级切面类,其中TransactionAttributeSource是切点,TransactionInterceptor是通知。

我们都知道@Transactional真正匹配的话首先看方法上有没有该注解,如果方法上没有该注解,再看这个方法所对应的类上或者接口上有没有该注解。

2.5 WebMVCAutoConfiguration自动配置实现

接下来我们来看一下跟WEB MVC相关的自动配置类。如下面的代码:

public class TestMvcAuto {
    @SuppressWarnings("all")
    public static void main(String[] args) {
        AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext();
        context.registerBean(Config.class);
        context.refresh();
        for (String name : context.getBeanDefinitionNames()) {
            String source = context.getBeanDefinition(name).getResourceDescription();
            if (source != null) {
                System.out.println(name + " 来源:" + source);
            }
        }
        context.close();

    }

    @Configuration
    @Import(MyImportSelector.class)
    static class Config {

    }

    static class MyImportSelector implements DeferredImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return new String[]{
                    ServletWebServerFactoryAutoConfiguration.class.getName(),	// 配置内嵌tomcat服务器工厂
                    DispatcherServletAutoConfiguration.class.getName(),			// 配置dispatcherServlet
                    WebMvcAutoConfiguration.class.getName(),					// 配置dispatcherServlet在运行时所需要的各种组件 
                    ErrorMvcAutoConfiguration.class.getName()					// 配置对MVC中错误的处理
            };
        }
    }
}

部分运行如下:

tomcatServletWebServerFactory 来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryConfiguration$EmbeddedTomcat.class]
servletWebServerFactoryCustomizer 来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryAutoConfiguration.class]
tomcatServletWebServerFactoryCustomizer 来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryAutoConfiguration.class]
dispatcherServlet 来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration$DispatcherServletConfiguration.class]
dispatcherServletRegistration 来源:class path resource [org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration.class]

我们发现Spring容器总引入了许多外部Bean,这里呢我们只挑一些重点的类说明一下:

首先是ServletWebServerFactoryConfiguration:

@Configuration(
    proxyBeanMethods = false
)
class ServletWebServerFactoryConfiguration {
	

    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnClass({Servlet.class, Server.class, Loader.class, WebAppContext.class})
    @ConditionalOnMissingBean(
        value = {ServletWebServerFactory.class},
        search = SearchStrategy.CURRENT
    )
    static class EmbeddedJetty {
        EmbeddedJetty() {
        }

        @Bean
        JettyServletWebServerFactory jettyServletWebServerFactory(ObjectProvider<JettyServerCustomizer> serverCustomizers) {
            JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
            factory.getServerCustomizers().addAll(serverCustomizers.orderedStream().toList());
            return factory;
        }
    }

    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})
    @ConditionalOnMissingBean(
        value = {ServletWebServerFactory.class},
        search = SearchStrategy.CURRENT
    )
    static class EmbeddedTomcat {
        EmbeddedTomcat() {
        }

        @Bean
        TomcatServletWebServerFactory tomcatServletWebServerFactory(ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers, ObjectProvider<TomcatContextCustomizer> contextCustomizers, ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
            TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
            factory.getTomcatConnectorCustomizers().addAll(connectorCustomizers.orderedStream().toList());
            factory.getTomcatContextCustomizers().addAll(contextCustomizers.orderedStream().toList());
            factory.getTomcatProtocolHandlerCustomizers().addAll(protocolHandlerCustomizers.orderedStream().toList());
            return factory;
        }
    }
    
    static class EmbeddedUndertow {
        EmbeddedUndertow() {
        }

        @Bean
        UndertowServletWebServerFactory undertowServletWebServerFactory(ObjectProvider<UndertowDeploymentInfoCustomizer> deploymentInfoCustomizers, ObjectProvider<UndertowBuilderCustomizer> builderCustomizers) {
            UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
            factory.getDeploymentInfoCustomizers().addAll(deploymentInfoCustomizers.orderedStream().toList());
            factory.getBuilderCustomizers().addAll(builderCustomizers.orderedStream().toList());
            return factory;
        }

        @Bean
        UndertowServletWebServerFactoryCustomizer undertowServletWebServerFactoryCustomizer(ServerProperties serverProperties) {
            return new UndertowServletWebServerFactoryCustomizer(serverProperties);
        }
    }
}

该类支持多种内嵌服务器,而不仅仅是Tomcat内嵌服务器,还支持Jetty内潜服务器,Undertow内嵌服务器。当然了,在选择哪个内嵌服务器作为默认的内嵌服务器还需要根据条件来进行判断。根据类路径下有哪种内嵌服务器的关键类的时候(如Tomcat.class, Loader.class, Undertow.class)就选择哪种内嵌服务器作为默认内嵌服务器。

接下来的servletWebServerFactoryCustomizertomcatServletWebServerFactoryCustomizer分别是针对通用服务器进行扩展和Tomcat服务器进行扩展的。此处就不详细介绍了。后面的也就不一一介绍了,大家有兴趣的话可以自己去了解一下。

学到这里我们发会发现:越是用起来非常方便的框架,其底层就越是这么的复杂啊!

3. 自定义自动配置类

这一讲,我们拿来做一个有意思的事情:如何将我们自己写的自动配置类变成能够让Spring Boot识别的自动配置类,虽然我们刚才讲了原理,但是Spring Boot目前还是不能识别我们写的配置类。

思路就是如下:

我们可以在ImportSelector中找到我们自己定义的配置类key,将其key改造为Spring Boot提供的EnableAutoConfiguration就可以了!就是这么的简单!

如下面我们将之前写的spring.factories中的内容中的key替换为EnableAutoConfiguration:

com.cherry.a41.A41Application$MyImportSelector=\
com.cherry.a41.A41Application.AutoConfiguration1,\
com.cherry.a41.A41Application.AutoConfiguration2

# 交给spring boot来识别管理
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.cherry.a41.A41Application.AutoConfiguration1,\
com.cherry.a41.A41Application.AutoConfiguration2

接着将我们自己写的MyImportSelector替换为Spring的AutoConfigurationImportSelector即可,完整的代码如下:

public class A41_2 {

    @SuppressWarnings("all")
    public static void main(String[] args) throws IOException {
        AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext();
        StandardEnvironment env = new StandardEnvironment();
        env.getPropertySources().addLast(new SimpleCommandLinePropertySource(
                "--spring.datasource.url=jdbc:mysql://localhost:3306/test",
                "--spring.datasource.username=root",
                "--spring.datasource.password=root"
        ));
        context.setEnvironment(env);
        context.registerBean("config", Config.class);
        context.refresh();

        for (String name : context.getBeanDefinitionNames()) {
            String resourceDescription = context.getBeanDefinition(name).getResourceDescription();
            if (resourceDescription != null)
                System.out.println(name + " 来源:" + resourceDescription);
        }
        context.close();
    }

    @Configuration // 本项目的配置类
//    @Import(MyImportSelector.class)
	@Import(AutoConfigurationImportSelector.class)
    @EnableAutoConfiguration
    static class Config {
        @Bean
        public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
            return new TomcatServletWebServerFactory();
        }
    }

    static class MyImportSelector implements DeferredImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, null).toArray(new String[0]);
        }
    }

    @Configuration // 第三方的配置类
    static class AutoConfiguration1 {
        @Bean
        public Bean1 bean1() {
            return new Bean1();
        }
    }

    @Configuration // 第三方的配置类
    static class AutoConfiguration2 {
        @Bean
        public Bean2 bean2() {
            return new Bean2();
        }
    }

    static class Bean1 {

    }
    static class Bean2 {

    }
}

由于测试结果太多,我们就不输出测试结果了。

posted @   LilyFlower  阅读(6)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示