红茶的个人站点

  • 首页
  • 专栏
  • 开发工具
  • 其它
  • 隐私政策
Awalon
Talk is cheap,show me the code.
  1. 首页
  2. 专栏
  3. Spring Boot 学习笔记
  4. 正文

Spring 源码学习 17:自动配置

2025年7月9日 14点热度 0人点赞 0条评论

原理

假设有两个外部的第三方配置类:

static class OuterBean1{}
static class OuterBean2{}
/**
 * 外部配置类1
 */
@Configuration
static class OuterConfig1{
    @Bean
    public OuterBean1 outerBean1(){
        return new OuterBean1();
    }
}
​
/**
 * 外部配置类2
 */
@Configuration
static class OuterConfig2{
    @Bean
    public OuterBean2 outerBean2(){
        return new OuterBean2();
    }
}

需要在项目中导入这两个配置类,使用它们定义的 Bean。最简单的方式是:

/**
 * 本项目的配置类
 */
@Configuration
@Import({OuterConfig1.class, OuterConfig2.class})
static class Config{}

ImportSelector

如果需要导入的外部配置类很多,这样做就不现实,可以使用一个ImportSelector类指定需要导入的外部类:

static class MyImportSelector implements ImportSelector {
​
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 返回需要导入的类的名称
        return new String[]{
            OuterConfig1.class.getName(),
            OuterConfig2.class.getName()
        };
    }
}

在配置类上直接导入 ImportSelector 类:

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

从配置读取

之前的 ImportSelector 是以硬编码的方式将要导入的类名返回,实际处理中需要使用配置文件保存要导入的类,配置文件的名称和位置都是固定的:

image-20250708140518819

配置文件的内容:

com.example.demo.ImportTests3$MyImportSelector=com.example.demo.ImportTests3.OuterConfig1,com.example.demo.ImportTests3.OuterConfig2

属性名称是类名,值使用,分隔。

为了方便阅读,可以使用\换行:

com.example.demo.ImportTests3$MyImportSelector=\
  com.example.demo.ImportTests3.OuterConfig1,\
  com.example.demo.ImportTests3.OuterConfig2

注意,这里的MyImportSelector是一个内部类,因此是$MyImportSelector而非.MyImportSelector。

修改ImportSelector,从配置文件中读取要导入的类名:

static class MyImportSelector implements ImportSelector {
​
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 从配置文件读取需要导入的类
        List<String> classNames = SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, this.getClass().getClassLoader());
        // 返回需要导入的类的名称
        return classNames.toArray(new String[0]);
    }
}

Bean 定义覆盖

有这么一个类型:

@AllArgsConstructor
@ToString
static class SameBean{
    private String source;
}

外部第三方配置类和项目自己的配置类都添加了该类型的 Bean:

@Configuration
static class OuterConfig1 {
	//...
    @Bean
    public SameBean sameBean(){
        return new SameBean("第三方");
    }
}

@Configuration
@Import(MyImportSelector.class)
static class Config {
    @Bean
    public SameBean sameBean(){
        return new SameBean("本项目");
    }
}

最终容器中产生的是 Bean 使用的是本项目的 Bean 定义。原因是容器在加载本地配置类Config的时候,先通过@Import(MyImportSelector.class)加载外部配置类中的 Bean 定义,再通过本地配置类的 Bean 方法添加 Bean 定义。而对于同一个类型的 Bean,后添加的 Bean 定义覆盖了先添加的。

容器可以设置是否允许 Bean 定义覆盖,Spring Boot 不允许:

ctx.setAllowBeanDefinitionOverriding(false);

此时会报错:

image-20250708152302240

报错信息显示同类型的 Bean 定义已经被外部配置类 OulterConfig1 绑定过了,不能重复定义。

现在类定义不能覆盖了,就需要修改类定义的加载顺序,必须优先加载本项目的类定义:

static class MyImportSelector implements DeferredImportSelector {
	// ...
}

改为使用ImportSelector的子接口即可,它将延迟加载通过@Import方式导入的外部配置类。

为了解决报错问题,可以在外部配置类的 Bean 方法上添加条件,只有相应的 Bean 定义不存在时才添加:

@Configuration
public static class OuterConfig1 {
	// ...

    @Bean
    @ConditionalOnMissingBean
    public SameBean sameBean(){
        return new SameBean("第三方");
    }
}

常见的自动配置类

AopAutoConfiguration

加载外部配置类AopAutoConfiguration:

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

static class MyImportSelector implements DeferredImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{AopAutoConfiguration.class.getName()};
    }
}

测试:

GenericApplicationContext ctx = new GenericApplicationContext();
ctx.registerBean(Config.class);
AnnotationConfigUtils.registerAnnotationConfigProcessors(ctx);
ctx.refresh();
for (String beanDefinitionName : ctx.getBeanDefinitionNames()) {
    System.out.println(beanDefinitionName);
}
ctx.close();

从打印可以看到,通过AopAutoConfiguration加载了这么几个 Bean 定义:

org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$ClassProxyingConfiguration
forceAutoProxyCreatorToUseClassProxying
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.aop.config.internalAutoProxyCreator

查看类定义:

@AutoConfiguration
@ConditionalOnBooleanProperty(name = "spring.aop.auto", matchIfMissing = true)
public class AopAutoConfiguration {
	// ...
}

@ConditionalOnBooleanProperty注解的用途是当存在属性spring.aop.auto,且值为 true,或者属性不存在时配置类生效。

通过编程的方式为容器的环境添加属性:

GenericApplicationContext ctx = new GenericApplicationContext();
StandardEnvironment environment = new StandardEnvironment();
environment.getPropertySources().addLast(new MapPropertySource("mapPropertySource",
        Map.of("spring.aop.auto","false")));
ctx.setEnvironment(environment);

再次执行就发现已经没有 AopAutoConfiguration 添加的类定义了。

AopAutoConfiguration中有两个内部类:

image-20250708161004696

@ConditionalOnClass注解表示当(类路径)存在Advice类时,加载AspectJAutoProxyingConfiguration,@ConditionalOnMissingClass注解表示当不存在Advice类时,加载ClassProxyingConfiguration。

可以通过依赖引入 Advice:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

此时加载AspectJAutoProxyingConfiguration,它里边又包含两个内部类:

image-20250708161403391

当存在属性spring.aop.proxy-target-class且值为false时,JdkDynamicAutoProxyConfiguration生效,否则CglibAutoProxyConfiguration生效。

无论哪个内部类生效,其目的都是加载@EnableAspectJAutoProxy注解。该注解的用途是导入AspectJAutoProxyRegistrar:

@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

这个类的用途是用编程的方式添加 Bean 定义:

image-20250708161943976

查看源码就会发现,最终会添加一个org.springframework.aop.config.internalAutoProxyCreator类型的 Bean 定义。

完整示例见这里。

DataSourceAutoConfiguration

自动配置类DataSourceAutoConfiguration的用途是添加数据源,通过配置类加载:

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

static class MyImportSelector implements DeferredImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{
                DataSourceAutoConfiguration.class.getName()
        };
    }
}

测试:

GenericApplicationContext context = new GenericApplicationContext();
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
context.registerBean(Config.class);
context.refresh();
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
    System.out.println(beanDefinitionName);
}
context.close();

没有任何数据源自动配置相关的 Bean 定义输出,自动配置没有生效,原因是这个配置类有一个条件注解:

@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })

EmbeddedDatabaseType类是 JDBC 的,因此需要为项目添加 JDBC 依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>

这也符合一般常识,数据源是建立在 JDBC 之上的。

Spring 的自动配置类支持这几种数据源:

image-20250708172256811

当缺少自定义DataSource Bean,且引入了相应数据源依赖,就会触发相应的自动配置:

image-20250708172406644

这里使用 Hikari,添加其依赖:

<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>6.3.0</version>
</dependency>

再次运行,报错,表示缺少数据库驱动信息,需要添加数据库驱动依赖:

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>

DataSourceAutoConfiguration还有一个@EnableConfigurationProperties(DataSourceProperties.class)注解,该注解的用途是创建DataSourceProperties Bean,并从环境信息中读取数据源(数据库)相关的信息作为其属性。

创建数据源时通过DataSourceProperties 获取数据库信息:

image-20250708173203966

因此还需要在容器环境中加入数据库相关属性:

GenericApplicationContext context = new GenericApplicationContext();
ConfigurableEnvironment environment = context.getEnvironment();
environment.getPropertySources().addLast(new MapPropertySource("dataSource", Map.of(
        "spring.datasource.url", "jdbc:mysql://localhost:3306/test",
        "spring.datasource.username", "root",
        "spring.datasource.password", "mysql"
)));

MybatisAutoConfiguration

添加 Mybatis 依赖:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.4</version>
</dependency>

Mybatis 内包含 Hikari 数据源依赖。

导入 Mybatis 的自动配置类:

static class MyImportSelector implements DeferredImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{
                DataSourceAutoConfiguration.class.getName(),
                MybatisAutoConfiguration.class.getName()
        };
    }
}

MybatisAutoConfiguration定义:

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

注解的作用:

  • @ConditionalOnSingleCandidate,当前容器中存在单个DataSource Bean 作为候选时可触发。

  • @EnableConfigurationProperties,在容器中添加 Bean MybatisProperties,并且绑定属性。

  • @AutoConfigureAfter,在DataSourceAutoConfiguration自动配置类之后生效

其中比较重要的 Bean:

@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
	// ...
}

@ConditionalOnMissingBean注解决定了,如果容器中没有其他的SqlSessionFactory Bean 定义,就会添加自动配置类中的 SqlSessionFactory Bean 定义。

还有一个:

@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
	// ...
}

同样使用了SqlSessionTemplate,在缺少 Bean 定义时添加。

其中还包含一个内部配置类:

@Import({AutoConfiguredMapperScannerRegistrar.class})
@ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
	// ...
}

会在缺少MapperFactoryBean和MapperScannerConfigurer类型的 Bean 时通过@Import加载默认的 Mapper 扫描实现AutoConfiguredMapperScannerRegistrar。

默认的包扫描实现使用的包名需要设置:

AutoConfigurationPackages.register(context, this.getClass().getPackageName());
context.refresh();

之所以普通的 Spring Boot 项目不需要设置,是因为入口类的@SpringBootApplication注解包含了@EnableAutoConfiguration注解,而该注解又包含了@AutoConfigurationPackage注解,@AutoConfigurationPackage 注解的用途就是将入口类所在的包注册为自动配置类可以使用的基础包名。

DataSourceTransactionManagerAutoConfiguration

自动配置类DataSourceTransactionManagerAutoConfiguration会在缺少TransactionManager时创建:

image-20250708193319823

MVC 自动配置

添加 MVC 相关自动配置类:

static class MyImportSelector implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{
                ServletWebServerFactoryAutoConfiguration.class.getName(),
                DispatcherServletAutoConfiguration.class.getName(),
                WebMvcAutoConfiguration.class.getName(),
                ErrorMvcAutoConfiguration.class.getName()
        };
    }
}

这些自动配置类的用途:

  • ServletWebServerFactoryAutoConfiguration,添加 ServletWebServerFactory

  • DispatcherServletAutoConfiguration,添加 DispatcherServlet 和 DispatcherServletRegistrationBean

  • WebMvcAutoConfiguration,添加各种 HandlerMapping 和 HandlerAdapter

  • ErrorMvcAutoConfiguration,添加错误处理的相关 bean

Spring Boot 自动配置

image-20250708145854551

新版的 Spring Boot 的 spring.factories中并不包含自动配置类,而是通过这两个文件加载:

image-20250708150010034

需要加载的自动配置类信息保存在AutoCOnfiguration.imports文件中:

org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration

是否加载的条件在spring-autoconfigure-metadata.properties文件中说明:

org.springframework.boot.autoconfigure.AutoConfiguration=
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration=
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration
org.springframework.boot.autoconfigure.amqp.RabbitAnnotationDrivenConfiguration=
org.springframework.boot.autoconfigure.amqp.RabbitAnnotationDrivenConfiguration.ConditionalOnClass=org.springframework.amqp.rabbit.annotation.EnableRabbit
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration=

比如RabbitAnnotationDrivenConfiguration.ConditionalOnClass=org...EnableRabbit,就意味着只有加载了EnableRabbit这个类(注解),才会加载自动配置类RabbitAnnotationDrivenConfiguration。起作用等同于在自动配置类上使用@ConditionOnClass注解。

这样做的好处是,传统使用spring.factories文件加载自动配置类,需要先加载所有自动配置类的字节码,再通过自动配置类上的条件注解判断需要加载哪个不需要加载哪个,现在将加载条件由文件描述,直接读取文件就可以只加载需要的类,避免了对无效类的字节码的不必要加载。

自定义自动配置类

实际上 Spring Boot 的自动配置生效是因为@EnableAutoConfiguration注解:

image-20250708202953187

该注解导入了一个ImportSelector:

image-20250708203035051

这个 ImportSelector 从配置文件中读取自动配置类:

image-20250708203154740

底层方法会从WEB-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports读取自动配置类。换言之,我们只需要将希望被 SpringBoot 加载的自动配置类添加到这个文件中就可以了。

image-20250708203550255

com.example.demo.EnableAutoConfigTests$OuterConfig1
com.example.demo.EnableAutoConfigTests$OuterConfig2

此外,还可以使用依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure-processor</artifactId>
    <optional>true</optional>
</dependency>

在编译时自动生成spring-autoconfigure-metadata.properties以优化自动配置类的加载。

本文完整示例可以从这里获取。

The End.

参考资料

  • 黑马程序员Spring视频教程,深度讲解spring5底层原理

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: spring 自动配置
最后更新:2025年7月9日

魔芋红茶

加一点PHP,加一点Go,加一点Python......

点赞
< 上一篇

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

COPYRIGHT © 2021 icexmoon.cn. ALL RIGHTS RESERVED.
本网站由提供CDN加速/云存储服务

Theme Kratos Made By Seaton Jiang

宁ICP备2021001508号

宁公网安备64040202000141号