Skip to content

Spring Boot 自动配置原理

1. 自动配置概述

1.1 什么是自动配置

Spring Boot 的自动配置机制会根据项目中的依赖、类路径下的类、已定义的 Bean 等条件,自动配置 Spring 应用所需的各种 Bean。

1.2 自动配置的优势

优势说明
减少配置无需手动配置大量 Bean
约定优于配置遵循最佳实践
快速开发专注于业务逻辑
灵活定制可随时覆盖默认配置

2. @SpringBootApplication 注解

2.1 注解组成

java
@SpringBootApplication
public class MyappApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyappApplication.class, args);
    }
}

等价于:

java
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public class MyappApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyappApplication.class, args);
    }
}

2.2 注解详解

注解说明
@SpringBootConfiguration表示这是一个配置类
@EnableAutoConfiguration启用自动配置
@ComponentScan组件扫描

2.3 自定义扫描

java
@SpringBootApplication(scanBasePackages = "com.example")
public class MyappApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyappApplication.class, args);
    }
}

// 或排除特定自动配置
@SpringBootApplication(exclude = {
    DataSourceAutoConfiguration.class,
    HibernateJpaAutoConfiguration.class
})
public class MyappApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyappApplication.class, args);
    }
}

3. @EnableAutoConfiguration 原理

3.1 导入自动配置类

java
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    
    Class<?>[] exclude() default {};
    
    String[] excludeName() default {};
}

3.2 AutoConfigurationImportSelector

java
public class AutoConfigurationImportSelector implements DeferredImportSelector {
    
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        
        AutoConfigurationEntry autoConfigurationEntry = 
            getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(
            autoConfigurationEntry.getConfigurations()
        );
    }
    
    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);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }
}

3.3 自动配置文件

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

内容示例:

org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

4. 条件注解

4.1 条件注解分类

注解说明
@ConditionalOnClass类路径存在指定类时生效
@ConditionalOnMissingClass类路径不存在指定类时生效
@ConditionalOnBean容器中存在指定 Bean 时生效
@ConditionalOnMissingBean容器中不存在指定 Bean 时生效
@ConditionalOnProperty配置属性满足条件时生效
@ConditionalOnWebApplicationWeb 应用时生效
@ConditionalOnNotWebApplication非 Web 应用时生效
@ConditionalOnExpressionSpEL 表达式为 true 时生效
@ConditionalOnJavaJava 版本满足条件时生效
@ConditionalOnResource资源存在时生效
@ConditionalOnSingleCandidate容器中只有一个指定 Bean 时生效

4.2 @ConditionalOnClass

java
@Configuration
@ConditionalOnClass(DataSource.class)
public class DataSourceAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    public DataSource dataSource(DataSourceProperties properties) {
        return properties.initializeDataSourceBuilder().build();
    }
}

4.3 @ConditionalOnBean

java
@Configuration
@ConditionalOnClass(DataSource.class)
public class JdbcTemplateAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean(JdbcOperations.class)
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

4.4 @ConditionalOnProperty

java
@Configuration
@ConditionalOnClass(RedisClient.class)
@ConditionalOnProperty(name = "spring.data.redis.enabled", 
                       havingValue = "true", 
                       matchIfMissing = true)
public class RedisAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    public RedisTemplate<String, Object> redisTemplate(
            RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        return template;
    }
}

4.5 @ConditionalOnWebApplication

java
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
public class WebMvcAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    public DispatcherServlet dispatcherServlet() {
        return new DispatcherServlet();
    }
}

4.6 组合条件

java
@Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@ConditionalOnMissingBean({ SqlSessionFactory.class, SqlSessionTemplate.class })
public class MybatisAutoConfiguration {
    
    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        return factory.getObject();
    }
}

5. 自动配置类分析

5.1 DataSourceAutoConfiguration

java
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
          DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {
    
    @Configuration(proxyBeanMethods = false)
    @Conditional(EmbeddedDatabaseCondition.class)
    @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
    @Import(EmbeddedDataSourceConfiguration.class)
    protected static class EmbeddedDatabaseConfiguration {
    }
    
    @Configuration(proxyBeanMethods = false)
    @Conditional(PooledDataSourceCondition.class)
    @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
    @Import({ HikariConfiguration.class, Tomcat.class, Dbcp2.class,
              OracleUcp.class, Generic.class, DataSourceJmxConfiguration.class })
    protected static class PooledDataSourceConfiguration {
    }
}

5.2 WebMvcAutoConfiguration

java
@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 {
    
    @Bean
    @ConditionalOnMissingBean
    public InternalResourceViewResolver defaultViewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix(this.mvcProperties.getView().getPrefix());
        resolver.setSuffix(this.mvcProperties.getView().getSuffix());
        return resolver;
    }
    
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnEnabledResourceChain
    public ResourceHandlerRegistration resourceHandlerRegistration() {
        // ...
    }
}

5.3 RedisAutoConfiguration

java
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisClient.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean(RedisConnectionDetails.class)
    public PropertiesRedisConnectionDetails redisConnectionDetails(
            RedisProperties properties) {
        return new PropertiesRedisConnectionDetails(properties);
    }
    
    @Bean
    @ConditionalOnMissingBean(RedisTemplate.class)
    public RedisTemplate<Object, Object> redisTemplate(
            RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
    
    @Bean
    @ConditionalOnMissingBean
    public StringRedisTemplate stringRedisTemplate(
            RedisConnectionFactory redisConnectionFactory) {
        return new StringRedisTemplate(redisConnectionFactory);
    }
}

6. 配置属性绑定

6.1 @ConfigurationProperties

java
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties {
    
    private String url;
    private String username;
    private String password;
    private String driverClassName;
    private String type;
    private Hikari hikari = new Hikari();
    
    // getters and setters
    
    public static class Hikari {
        private String connectionTimeout;
        private String maximumPoolSize;
        private String minimumIdle;
        // getters and setters
    }
}

6.2 启用配置属性

java
@Configuration
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceConfig {
    
    @Bean
    @ConditionalOnMissingBean
    public DataSource dataSource(DataSourceProperties properties) {
        return properties.initializeDataSourceBuilder().build();
    }
}

6.3 配置文件

yaml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb
    username: root
    password: password
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      connection-timeout: 30000
      maximum-pool-size: 10
      minimum-idle: 5

7. 自定义 Starter

7.1 创建 Starter 项目

my-spring-boot-starter/
├── src/main/java/
│   └── com/example/starter/
│       ├── MyService.java
│       ├── MyProperties.java
│       └── MyAutoConfiguration.java
└── src/main/resources/
    └── META-INF/spring/
        └── org.springframework.boot.autoconfigure.AutoConfiguration.imports

7.2 自动配置类

java
@Configuration
@ConditionalOnClass(MyService.class)
@EnableConfigurationProperties(MyProperties.class)
public class MyAutoConfiguration {
    
    private final MyProperties properties;
    
    public MyAutoConfiguration(MyProperties properties) {
        this.properties = properties;
    }
    
    @Bean
    @ConditionalOnMissingBean
    public MyService myService() {
        return new MyService(properties);
    }
}

7.3 配置属性类

java
@ConfigurationProperties(prefix = "my.service")
public class MyProperties {
    
    private String name = "default";
    private boolean enabled = true;
    private int timeout = 30000;
    
    // getters and setters
}

7.4 注册自动配置

# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.starter.MyAutoConfiguration

7.5 条件注解顺序

java
@Configuration
@ConditionalOnClass(MyService.class)
@ConditionalOnProperty(prefix = "my.service", name = "enabled", 
                       havingValue = "true", matchIfMissing = true)
@AutoConfigureBefore(AnotherAutoConfiguration.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MyAutoConfiguration {
    // ...
}

8. 排除自动配置

8.1 注解排除

java
@SpringBootApplication(exclude = {
    DataSourceAutoConfiguration.class,
    HibernateJpaAutoConfiguration.class
})
public class MyappApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyappApplication.class, args);
    }
}

// 或排除类名
@SpringBootApplication(excludeName = {
    "org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration"
})
public class MyappApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyappApplication.class, args);
    }
}

8.2 配置文件排除

yaml
spring:
  autoconfigure:
    exclude:
      - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
      - org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration

8.3 条件排除

java
@Configuration
@ConditionalOnProperty(name = "my.feature.enabled", havingValue = "false")
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
public class ExcludeDataSourceConfiguration {
    // 禁用数据源自动配置
}

9. 调试自动配置

9.1 启动日志

yaml
debug: true

9.2 Actuator 端点

yaml
management:
  endpoints:
    web:
      exposure:
        include: conditions

访问 /actuator/conditions 查看自动配置报告。

9.3 查看匹配条件

java
@SpringBootApplication
public class MyappApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = 
            SpringApplication.run(MyappApplication.class, args);
        
        ConditionEvaluationReport report = 
            context.getBeanFactory().getBean(
                AutoConfigurationReport.class, 
                ConditionEvaluationReport.class
            );
        
        report.getConditionAndOutcomesBySource().forEach((source, outcomes) -> {
            System.out.println(source + ": " + outcomes);
        });
    }
}

10. 最佳实践

10.1 合理使用条件注解

java
@Configuration
@ConditionalOnClass(DataSource.class)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = "spring.datasource", name = "url")
    public DataSource dataSource(DataSourceProperties properties) {
        return properties.initializeDataSourceBuilder().build();
    }
}

10.2 使用 @AutoConfigureOrder

java
@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class FirstAutoConfiguration {
    // 最先加载
}

@Configuration
@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
public class LastAutoConfiguration {
    // 最后加载
}

10.3 提供默认值

java
@ConfigurationProperties(prefix = "my.service")
public class MyProperties {
    
    private String name = "default-name";
    private int port = 8080;
    private Duration timeout = Duration.ofSeconds(30);
    
    // getters and setters
}

11. 小结

本章学习了 Spring Boot 自动配置的核心原理:

内容要点
@SpringBootApplication组合注解、组件扫描
@EnableAutoConfiguration导入自动配置类
条件注解@ConditionalOnXxx
自动配置文件AutoConfiguration.imports
配置属性绑定@ConfigurationProperties
自定义 Starter创建、注册、条件配置
排除配置exclude、配置文件
调试配置debug、actuator

下一章将学习 Spring Boot 4 的 Web 开发。