Spring Boot 3基于Spring Framework 6和Jakarta EE 9+,自动装配机制通过条件化注解实现了模块按需加载。理解@Conditional系列注解的工作原理和自动装配的加载流程,对于构建可扩展的微服务架构至关重要。本文从自动装配源码分析入手,结合微服务实战场景,讲解自定义starter开发和条件化配置策略。
Spring Boot自动装配加载流程与SPI机制
Spring Boot启动时通过SpringBootApplication注解触发自动装配。EnableAutoConfiguration通过AutoConfigurationImportSelector加载META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中声明的配置类:
// SpringBootApplication注解结构
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class)
})
public @interface SpringBootApplication {}
// AutoConfigurationImportSelector核心逻辑
public class AutoConfigurationImportSelector {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
// 读取AutoConfiguration.imports文件
List<String> configurations = getCandidateConfigurations(metadata, attributes);
// 去重
configurations = removeDuplicates(configurations);
// 应用排除规则
Set<String> exclusions = getExclusions(metadata, attributes);
configurations.removeAll(exclusions);
// 过滤不满足条件的配置类
configurations = filter(configurations, autoConfigurationMetadata);
return configurations.toArray(new String[0]);
}
}
Spring Boot 3.x不再使用spring.factories文件注册自动配置类,改为AutoConfiguration.imports文件。每个自动配置类通过@Conditional系列注解控制是否生效。
@Conditional条件注解体系与匹配逻辑
@Conditional注解接受一个Condition实现类,通过matches方法返回是否满足条件。Spring Boot在此基础上扩展了一系列条件注解:
// 核心条件注解
@ConditionalOnClass(DataSource.class) // classpath存在指定类
@ConditionalOnMissingBean(DataSource.class) // 容器中不存在指定Bean
@ConditionalOnBean(DataSource.class) // 容器中存在指定Bean
@ConditionalOnProperty(name = "app.cache.enabled", havingValue = "true")
@ConditionalOnWebApplication // Web应用环境
@ConditionalOnNotWebApplication // 非Web应用环境
@ConditionalOnExpression("#{systemProperties['app.feature.enabled']}")
@ConditionalOnJava(JavaVersion.SEVENTEEN) // Java版本
// 自定义条件注解示例
public class OnRedisAvailableCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 检查Redis连接是否可用
String redisHost = context.getEnvironment().getProperty("spring.redis.host");
String redisPort = context.getEnvironment().getProperty("spring.redis.port");
if (redisHost == null || redisPort == null) return false;
try (var socket = new Socket()) {
socket.connect(new InetSocketAddress(redisHost,
Integer.parseInt(redisPort)), 1000);
return true;
} catch (Exception e) {
return false;
}
}
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(OnRedisAvailableCondition.class)
public @interface ConditionalOnRedisAvailable {}
自定义Starter开发与自动配置实战
以开发一个分布式限流Starter为例,展示完整的自动配置编写流程。Starter模块结构:
// pom.xml依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
// 配置属性类
@ConfigurationProperties(prefix = "rate-limiter")
public class RateLimiterProperties {
private boolean enabled = true;
private int defaultLimit = 100;
private int defaultPeriod = 60;
private String type = "redis"; // redis | memory
// getter/setter省略
}
// 自动配置类
@AutoConfiguration
@ConditionalOnProperty(prefix = "rate-limiter", name = "enabled", havingValue = "true")
@EnableConfigurationProperties(RateLimiterProperties.class)
public class RateLimiterAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "rate-limiter", name = "type", havingValue = "memory")
public RateLimiter memoryRateLimiter(RateLimiterProperties properties) {
return new MemoryRateLimiter(properties.getDefaultLimit(),
properties.getDefaultPeriod());
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "rate-limiter", name = "type", havingValue = "redis")
@ConditionalOnClass(name = "org.springframework.data.redis.core.StringRedisTemplate")
public RateLimiter redisRateLimiter(RateLimiterProperties properties,
StringRedisTemplate redisTemplate) {
return new RedisRateLimiter(redisTemplate,
properties.getDefaultLimit(),
properties.getDefaultPeriod());
}
@Bean
@ConditionalOnMissingBean
public RateLimiterAspect rateLimiterAspect(RateLimiter rateLimiter) {
return new RateLimiterAspect(rateLimiter);
}
}
// 注册自动配置
// META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
// com.example.ratelimiter.RateLimiterAutoConfiguration
// 限流切面实现
@Aspect
public class RateLimiterAspect {
private final RateLimiter rateLimiter;
@Around("@annotation(rateLimit)")
public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit)
throws Throwable {
String key = generateKey(joinPoint, rateLimit);
if (!rateLimiter.tryAcquire(key, rateLimit.limit(), rateLimit.period())) {
throw new RateLimitExceededException(
"请求频率超限: " + rateLimit.limit() + "/" + rateLimit.period() + "s");
}
return joinPoint.proceed();
}
private String generateKey(ProceedingJoinPoint joinPoint, RateLimit rateLimit) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
return signature.getDeclaringType().getName() + "." + signature.getName();
}
}
// 注解定义
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
int limit() default 100;
int period() default 60;
}
Spring Boot 3 Jakarta EE迁移要点
Spring Boot 3将javax.*包名迁移到jakarta.*,影响所有Servlet API、JPA、Validation等依赖。迁移时需注意依赖坐标变更:
<!-- Spring Boot 2.x (javax) -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<!-- Spring Boot 3.x (jakarta) -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
</dependency>
<!-- JPA迁移 -->
<!-- Spring Boot 2.x -->
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>javax.persistence-api</artifactId>
</dependency>
<!-- Spring Boot 3.x -->
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>3.1.0</version>
</dependency>
// 代码中的包名变更
// Spring Boot 2.x
import javax.servlet.http.HttpServletRequest;
import javax.persistence.Entity;
import javax.validation.constraints.NotNull;
// Spring Boot 3.x
import jakarta.servlet.http.HttpServletRequest;
import jakarta.persistence.Entity;
import jakarta.validation.constraints.NotNull;
// application.yml无需变更配置
// 但部分第三方库可能尚未迁移,需检查兼容性
微服务配置中心与条件化加载策略
在微服务架构中,不同环境需要加载不同的Bean配置。通过Profile和@Conditional的组合实现环境感知的自动装配:
@Configuration
public class MultiEnvironmentConfig {
@Bean
@Profile("dev")
@ConditionalOnMissingBean
public DataSource devDataSource() {
return DataSourceBuilder.create()
.url("jdbc:h2:mem:devdb")
.username("sa")
.password("")
.build();
}
@Bean
@Profile("prod")
@ConditionalOnMissingBean
public DataSource prodDataSource(
@Value("${db.pool.size:20}") int poolSize,
@Value("${db.url}") String url,
@Value("${db.username}") String username,
@Value("${db.password}") String password) {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(url);
config.setUsername(username);
config.setPassword(password);
config.setMaximumPoolSize(poolSize);
config.setConnectionTimeout(30000);
config.setIdleTimeout(600000);
config.setMaxLifetime(1800000);
return new HikariDataSource(config);
}
}
条件装配顺序通过@AutoConfigureBefore和@AutoConfigureAfter控制。当多个自动配置类存在依赖关系时,显式声明加载顺序避免循环依赖:
@AutoConfiguration(after = DataSourceAutoConfiguration.class)
@ConditionalOnBean(DataSource.class)
public class MyServiceAutoConfiguration {
// 确保DataSource已配置完成后再加载
}
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot3-wei-fu-wu-jia-gou-shi-zhan-yu-zi-dong-zhuang/