02.01 Spring Boot|自动配置机制深度解析
导读
- 目标:深入理解Spring Boot的自动配置机制,包括@EnableAutoConfiguration、条件注解、spring.factories等核心机制,掌握自动配置的设计思想和实现原理。
- 适用场景:Spring Boot学习、自定义自动配置、框架扩展、面试准备。
- 前置知识:Spring IoC容器、条件注解、@Configuration
一、自动配置核心概念
1.1 什么是自动配置?
**自动配置(Auto Configuration)**是Spring Boot的核心特性之一,它能够根据类路径中的依赖和配置,自动配置Spring应用所需的Bean。
传统Spring配置 vs Spring Boot自动配置:
java
// 传统Spring配置(繁琐)
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/test");
config.setUsername("root");
config.setPassword("root");
config.setDriverClassName("com.mysql.cj.jdbc.Driver");
return new HikariDataSource(config);
}
}
// Spring Boot自动配置(简单)
// 只需在application.yml中配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
// Spring Boot会自动创建DataSource Bean
自动配置的优势:
- 零配置:开箱即用,减少配置代码
- 约定优于配置:遵循最佳实践
- 可覆盖:可以自定义配置覆盖默认配置
- 条件化:根据条件自动启用或禁用
1.2 @SpringBootApplication注解解析
@SpringBootApplication组成:
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration // = @Configuration
@EnableAutoConfiguration // 启用自动配置(核心)
@ComponentScan // 组件扫描
public @interface SpringBootApplication {
// 排除自动配置类
Class<?>[] exclude() default {};
String[] excludeName() default {};
// 组件扫描配置
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
}
@SpringBootApplication = 三个注解的组合:
java
// 等价于
@Configuration // 配置类
@EnableAutoConfiguration // 启用自动配置
@ComponentScan // 组件扫描
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
1.3 @EnableAutoConfiguration核心机制
@EnableAutoConfiguration注解:
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class) // 核心:导入选择器
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
// 排除自动配置类
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
AutoConfigurationImportSelector工作原理:
java
public class AutoConfigurationImportSelector implements
DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 1. 检查是否启用自动配置
if (!isEnabled(annotationMetadata)) {
return new String[0];
}
// 2. 获取自动配置类列表
AutoConfigurationEntry autoConfigurationEntry =
getAutoConfigurationEntry(annotationMetadata);
// 3. 返回自动配置类全限定名数组
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(
AnnotationMetadata annotationMetadata) {
// 1. 获取所有自动配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 2. 去重
configurations = removeDuplicates(configurations);
// 3. 排除指定的自动配置类
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
configurations.removeAll(exclusions);
// 4. 过滤(条件注解检查)
configurations = getConfigurationClassFilter().filter(configurations);
// 5. 触发自动配置导入事件
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
protected List<String> getCandidateConfigurations(
AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 从META-INF/spring.factories加载自动配置类
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
return configurations;
}
}
关键点:
- 扫描spring.factories :从
META-INF/spring.factories文件加载自动配置类 - 条件过滤:使用条件注解过滤不满足条件的配置类
- 去重和排除:处理重复和排除的配置类
二、spring.factories机制
2.1 spring.factories文件格式
文件位置 :META-INF/spring.factories
文件格式:
properties
# 自动配置类列表
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.autoconfigure.DataSourceAutoConfiguration,\
com.example.autoconfigure.RedisAutoConfiguration,\
com.example.autoconfigure.KafkaAutoConfiguration
# 其他工厂类
org.springframework.boot.ApplicationContextInitializer=\
com.example.initializer.MyInitializer
org.springframework.boot.ApplicationListener=\
com.example.listener.MyListener
Spring Boot内置自动配置类示例:
properties
# spring-boot-autoconfigure-2.7.0.jar/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
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,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchClientAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jpa.JpaBaseConfiguration,\
org.springframework.boot.autoconfigure.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transport.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.transport.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration
2.2 SpringFactoriesLoader加载机制
SpringFactoriesLoader源码分析:
java
public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
// 缓存已加载的工厂类
private static final Map<ClassLoader, Map<String, List<String>>> cache = new ConcurrentReferenceHashMap<>();
public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
// 1. 获取工厂类名称列表
List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoader);
// 2. 实例化工厂类
List<T> result = new ArrayList<>(factoryImplementationNames.size());
for (String factoryImplementationName : factoryImplementationNames) {
result.add(instantiateFactory(factoryImplementationName, factoryType, classLoader));
}
// 3. 排序(实现Ordered接口或@Order注解)
AnnotationAwareOrderComparator.sort(result);
return result;
}
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) {
// 1. 从缓存获取
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
// 2. 加载所有spring.factories文件
result = new HashMap<>();
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
// 3. 解析properties文件
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, k -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// 4. 去重并排序
result.replaceAll((k, v) -> v.stream().distinct().collect(Collectors.toList()));
cache.put(classLoader, result);
} catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}
}
关键点:
- 多文件支持:可以加载多个jar包中的spring.factories文件
- 缓存机制:使用ConcurrentReferenceHashMap缓存,提高性能
- 排序支持:支持Ordered接口和@Order注解排序
- 去重处理:自动处理重复的配置类
三、条件注解详解
3.1 条件注解概述
**条件注解(Conditional Annotations)**是Spring Boot自动配置的核心机制,用于控制配置类或Bean的创建条件。
常用条件注解:
| 注解 | 说明 | 示例 |
|---|---|---|
| @ConditionalOnClass | 类路径存在指定类时生效 | @ConditionalOnClass(DataSource.class) |
| @ConditionalOnMissingClass | 类路径不存在指定类时生效 | @ConditionalOnMissingClass("com.example.Class") |
| @ConditionalOnBean | 容器中存在指定Bean时生效 | @ConditionalOnBean(DataSource.class) |
| @ConditionalOnMissingBean | 容器中不存在指定Bean时生效 | @ConditionalOnMissingBean(DataSource.class) |
| @ConditionalOnProperty | 配置属性满足条件时生效 | @ConditionalOnProperty(name="enabled", havingValue="true") |
| @ConditionalOnResource | 资源文件存在时生效 | @ConditionalOnResource(resources="classpath:config.xml") |
| @ConditionalOnWebApplication | Web应用时生效 | @ConditionalOnWebApplication |
| @ConditionalOnNotWebApplication | 非Web应用时生效 | @ConditionalOnNotWebApplication |
| @ConditionalOnExpression | SpEL表达式为true时生效 | @ConditionalOnExpression("${feature.enabled:true}") |
3.2 条件注解源码分析
@ConditionalOnClass实现:
java
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class) // 条件判断类
public @interface ConditionalOnClass {
Class<?>[] value() default {};
String[] name() default {};
}
// OnClassCondition实现
@Order(Ordered.HIGHEST_PRECEDENCE)
class OnClassCondition extends FilteringSpringBootCondition {
@Override
protected final ConditionOutcome[] getOutcomes(
AnnotatedTypeMetadata metadata, ConditionContext context) {
// 获取@ConditionalOnClass注解
MultiValueMap<String, Object> onClasses =
getAttributesForAll(metadata, ConditionalOnClass.class);
List<String> missingClasses = new ArrayList<>();
for (Object value : onClasses.values()) {
if (value instanceof String) {
if (!ClassUtils.isPresent((String) value, context.getClassLoader())) {
missingClasses.add((String) value);
}
} else if (value instanceof Class) {
if (!ClassUtils.isPresent(((Class<?>) value).getName(), context.getClassLoader())) {
missingClasses.add(((Class<?>) value).getName());
}
}
}
if (!missingClasses.isEmpty()) {
return new ConditionOutcome[] {
ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
.didNotFind("required class", "required classes").items(Style.QUOTE, missingClasses))
};
}
return new ConditionOutcome[] { ConditionOutcome.match() };
}
}
@ConditionalOnProperty实现:
java
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
String[] value() default {};
String prefix() default "";
String[] name() default {};
String havingValue() default "";
boolean matchIfMissing() default false;
}
// OnPropertyCondition实现
@Order(Ordered.HIGHEST_PRECEDENCE + 40)
class OnPropertyCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
List<AnnotationAttributes> allAnnotationAttributes =
annotationAttributesFromMultiValueMap(
metadata.getAllAnnotationAttributes(ConditionalOnProperty.class.getName()));
List<ConditionMessage> noMatch = new ArrayList<>();
List<ConditionMessage> match = new ArrayList<>();
for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
ConditionOutcome outcome = determineOutcome(annotationAttributes,
context.getEnvironment());
(outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());
}
if (!noMatch.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));
}
return ConditionOutcome.match(ConditionMessage.of(match));
}
private ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes,
Environment environment) {
Spec spec = new Spec(annotationAttributes);
List<String> missingProperties = new ArrayList<>();
List<String> nonMatchingProperties = new ArrayList<>();
spec.collectProperties(environment, missingProperties, nonMatchingProperties);
if (!nonMatchingProperties.isEmpty()) {
return ConditionOutcome.noMatch(
ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
.found("different value in property", "different values in properties")
.items(Style.QUOTE, nonMatchingProperties));
}
if (!missingProperties.isEmpty()) {
return ConditionOutcome.noMatch(
ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
.didNotFind("property", "properties").items(Style.QUOTE, missingProperties));
}
return ConditionOutcome.match(
ConditionMessage.forCondition(ConditionalOnProperty.class, spec).because("matched"));
}
}
3.3 条件注解实战案例
案例1:DataSource自动配置
java
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "javax.sql.DataSource")
@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,
EmbeddedDataSourceJmxConfiguration.class })
protected static class EmbeddedDatabaseConfiguration {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HikariDataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type",
havingValue = "com.zaxxer.hikari.HikariDataSource",
matchIfMissing = true)
static class Hikari {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
HikariDataSource dataSource(DataSourceProperties properties) {
HikariDataSource dataSource = createDataSource(properties,
HikariDataSource.class);
if (StringUtils.hasText(properties.getName())) {
dataSource.setPoolName(properties.getName());
}
return dataSource;
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type",
havingValue = "org.apache.tomcat.jdbc.pool.DataSource",
matchIfMissing = false)
static class Tomcat {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.tomcat")
org.apache.tomcat.jdbc.pool.DataSource dataSource(DataSourceProperties properties) {
return createDataSource(properties, org.apache.tomcat.jdbc.pool.DataSource.class);
}
}
}
条件注解执行顺序:
@ConditionalOnClass:检查类路径是否存在DataSource类@ConditionalOnMissingBean:检查容器中是否已存在DataSource Bean@ConditionalOnProperty:检查配置属性是否满足条件- 如果所有条件都满足,才创建DataSource Bean
案例2:Redis自动配置
java
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
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) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
四、自动配置实战案例
4.1 自定义自动配置类
完整示例:短信服务自动配置
java
// 1. 配置属性类
@ConfigurationProperties(prefix = "sms")
public class SmsProperties {
private String accessKeyId;
private String accessKeySecret;
private String signName;
private String templateCode;
private boolean enabled = true;
// getters and setters
public String getAccessKeyId() { return accessKeyId; }
public void setAccessKeyId(String accessKeyId) { this.accessKeyId = accessKeyId; }
// ... 其他getter/setter
}
// 2. 服务接口
public interface SmsService {
void sendSms(String phone, String message);
}
// 3. 服务实现
public class AliyunSmsService implements SmsService {
private final SmsProperties properties;
public AliyunSmsService(SmsProperties properties) {
this.properties = properties;
}
@Override
public void sendSms(String phone, String message) {
// 阿里云短信发送逻辑
System.out.println("发送短信到: " + phone + ", 内容: " + message);
}
}
// 4. 自动配置类
@Configuration
@ConditionalOnClass(SmsService.class)
@ConditionalOnProperty(prefix = "sms", name = "enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(SmsProperties.class)
public class SmsAutoConfiguration {
@Bean
@ConditionalOnMissingBean(SmsService.class)
public SmsService smsService(SmsProperties properties) {
return new AliyunSmsService(properties);
}
}
// 5. spring.factories
# META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.autoconfigure.SmsAutoConfiguration
// 6. 配置文件
# application.yml
sms:
enabled: true
access-key-id: your-access-key-id
access-key-secret: your-access-key-secret
sign-name: 你的签名
template-code: SMS_123456789
// 7. 使用
@Service
public class UserService {
@Autowired
private SmsService smsService; // 自动注入
public void sendVerificationCode(String phone) {
smsService.sendSms(phone, "您的验证码是:123456");
}
}
4.2 自动配置最佳实践
1. 使用条件注解控制配置
java
@Configuration
@ConditionalOnClass(DataSource.class)
@ConditionalOnProperty(prefix = "my.datasource", name = "enabled", havingValue = "true")
public class MyDataSourceAutoConfiguration {
// 只在满足条件时创建
}
2. 提供默认配置
java
@Configuration
@EnableConfigurationProperties(MyProperties.class)
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService(MyProperties properties) {
// 提供默认实现
MyProperties defaultProps = new MyProperties();
defaultProps.setTimeout(1000);
defaultProps.setRetries(3);
// 合并用户配置
if (properties.getTimeout() != null) {
defaultProps.setTimeout(properties.getTimeout());
}
return new MyService(defaultProps);
}
}
3. 支持多实现选择
java
@Configuration
@ConditionalOnClass(MyService.class)
public class MyAutoConfiguration {
@Bean
@ConditionalOnProperty(prefix = "my.service", name = "type", havingValue = "aliyun")
@ConditionalOnMissingBean
public MyService aliyunService(MyProperties properties) {
return new AliyunService(properties);
}
@Bean
@ConditionalOnProperty(prefix = "my.service", name = "type", havingValue = "tencent")
@ConditionalOnMissingBean
public MyService tencentService(MyProperties properties) {
return new TencentService(properties);
}
}
4. 提供配置元数据
json
// META-INF/spring-configuration-metadata.json
{
"properties": [
{
"name": "sms.enabled",
"type": "java.lang.Boolean",
"defaultValue": true,
"description": "是否启用短信服务"
},
{
"name": "sms.access-key-id",
"type": "java.lang.String",
"description": "短信服务AccessKeyId"
}
]
}
五、禁用和排除自动配置
5.1 禁用自动配置
方式1:使用@SpringBootApplication的exclude属性
java
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class,
JpaRepositoriesAutoConfiguration.class
})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
方式2:使用配置文件
yaml
# application.yml
spring:
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
- org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
方式3:使用条件属性
yaml
# application.yml
spring:
datasource:
type: none # 禁用DataSource自动配置
5.2 条件排除
使用@ConditionalOnProperty控制
java
@Configuration
@ConditionalOnProperty(
prefix = "feature",
name = "enabled",
havingValue = "true",
matchIfMissing = false // 默认不启用
)
public class FeatureAutoConfiguration {
// 只有配置feature.enabled=true时才启用
}
高频面试问答(深度解析)
1. Spring Boot自动配置原理?
标准答案:
- @EnableAutoConfiguration:启用自动配置
- AutoConfigurationImportSelector:选择自动配置类
- spring.factories:定义自动配置类列表
- 条件注解:控制自动配置生效条件
深入追问:
Q: 自动配置的执行顺序?
java
// 1. 扫描所有META-INF/spring.factories文件
// 2. 加载EnableAutoConfiguration对应的配置类列表
// 3. 过滤(排除指定的配置类)
// 4. 条件注解检查(@ConditionalOnClass等)
// 5. 去重和排序
// 6. 导入配置类到Spring容器
Q: 如何调试自动配置?
bash
# 1. 启用调试日志
logging.level.org.springframework.boot.autoconfigure=DEBUG
# 2. 查看自动配置报告
# 访问 /actuator/conditions(需要Actuator)
# 3. 查看已启用的自动配置
# 访问 /actuator/configprops
2. 条件注解的执行时机?
标准答案 :
条件注解在Bean定义阶段执行,而不是Bean实例化阶段。
执行流程:
1. 加载配置类
2. 解析@Conditional注解
3. 执行Condition.matches()方法
4. 如果条件满足,注册Bean定义
5. 如果条件不满足,跳过Bean定义
延伸阅读
- Spring Boot官方文档:https://spring.io/projects/spring-boot
- 《Spring Boot实战》--- 汪云飞
- Spring Boot源码:https://github.com/spring-projects/spring-boot