Spring的 @Import注解 笔记251008
1. @Import 注解概述
@Import 是 Spring 框架中的一个重要注解,用于快速导入一个或多个配置类到当前配置类中。它是 Spring 模块化配置和条件化配置的核心注解之一。
基本语法
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
Class<?>[] value();
}
2. @Import 的主要用途
2.1 导入配置类
// 数据库配置类
@Configuration
public class DatabaseConfig {
@Bean
public DataSource dataSource() {
return new HikariDataSource();
}
}
// 事务配置类
@Configuration
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
// 主配置类 - 导入其他配置类
@Configuration
@Import({DatabaseConfig.class, TransactionConfig.class})
public class AppConfig {
// 主配置类逻辑
}
2.2 导入普通组件类
// 普通服务类(非配置类)
public class EmailService {
public void sendEmail(String message) {
System.out.println("Sending email: " + message);
}
}
// 配置类导入普通组件
@Configuration
@Import(EmailService.class)
public class ServiceConfig {
// EmailService 会被注册为 Spring Bean
}
3. @Import 的高级用法
3.1 使用 ImportSelector 接口
ImportSelector 允许根据条件动态选择要导入的配置类。
// 环境相关的配置选择器
public class EnvironmentImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
String environment = System.getProperty("app.environment", "dev");
if ("prod".equals(environment)) {
return new String[]{"com.example.config.ProdConfig"};
} else {
return new String[]{"com.example.config.DevConfig"};
}
}
}
// 使用 ImportSelector
@Configuration
@Import(EnvironmentImportSelector.class)
public class MainConfig {
// 根据环境动态导入配置
}
3.2 使用 ImportBeanDefinitionRegistrar 接口
ImportBeanDefinitionRegistrar 提供更细粒度的控制,可以直接操作 BeanDefinition。
// 自定义注册器
public class CustomBeanRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
// 动态注册 Bean
RootBeanDefinition beanDefinition = new RootBeanDefinition(CustomService.class);
beanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
registry.registerBeanDefinition("customService", beanDefinition);
// 根据条件注册不同的 Bean
if (isFeatureEnabled()) {
RootBeanDefinition featureBean = new RootBeanDefinition(FeatureService.class);
registry.registerBeanDefinition("featureService", featureBean);
}
}
private boolean isFeatureEnabled() {
// 检查特性是否启用
return true;
}
}
// 使用 ImportBeanDefinitionRegistrar
@Configuration
@Import(CustomBeanRegistrar.class)
public class FeatureConfig {
// 自定义 Bean 注册逻辑
}
3.3 结合 @Conditional 注解使用
// 条件化配置
@Configuration
@ConditionalOnClass(name = "com.example.SpecialService")
@Import(SpecialConfig.class)
public class ConditionalConfig {
// 只有当 SpecialService 类存在时才会导入 SpecialConfig
}
@Configuration
// 条件注解的评估顺序:从上到下
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class})
@ConditionalOnProperty(prefix = "app", name = "web.enabled", havingValue = "true", matchIfMissing = true)
@Import(WebMvcConfig.class)
public class WebApplicationConfig {
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "app.web", name = "cache.enabled", havingValue = "true")
public WebContentCache webContentCache() {
return new WebContentCache();
}
}
4. @Import 的多种使用场景
4.1 模块化配置
// 安全模块配置
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilter securityFilter() {
return new SecurityFilter();
}
}
// Web 模块配置
@Configuration
public class WebConfig {
@Bean
public WebInterceptor webInterceptor() {
return new WebInterceptor();
}
}
// 主应用配置 - 组合各个模块
@Configuration
@Import({SecurityConfig.class, WebConfig.class, DatabaseConfig.class})
public class ApplicationConfig {
@Bean
public ApplicationRunner applicationRunner() {
return new ApplicationRunner();
}
}
4.2 第三方库集成
// 第三方库配置类
public class ThirdPartyConfig {
@Bean
@ConditionalOnMissingBean
public ThirdPartyService thirdPartyService() {
return new DefaultThirdPartyService();
}
}
// 启用第三方库的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ThirdPartyConfig.class)
public @interface EnableThirdParty {
}
// 使用自定义启用注解
@Configuration
@EnableThirdParty
public class AppConfig {
// 自动配置第三方库
}
4.3 多环境配置
// 开发环境配置
@Profile("dev")
@Configuration
public class DevConfig {
@Bean
public DataSource devDataSource() {
// 开发环境数据源配置
return new EmbeddedDatabaseBuilder().build();
}
}
// 生产环境配置
@Profile("prod")
@Configuration
public class ProdConfig {
@Bean
public DataSource prodDataSource() {
// 生产环境数据源配置
return new HikariDataSource();
}
}
// 主配置根据环境导入
@Configuration
@Import({
DevConfig.class,
ProdConfig.class
})
public class EnvironmentConfig {
// 根据激活的 profile 自动选择配置
}
5. @Import 与其他注解的组合
5.1 与 @ComponentScan 配合使用
@Configuration
@ComponentScan("com.example.components")
@Import({DatabaseConfig.class, SecurityConfig.class})
public class ComprehensiveConfig {
// 既扫描组件,又导入配置类
}
5.2 与 @PropertySource 配合使用
@Configuration
@PropertySource("classpath:application.properties")
@Import(DataSourceConfig.class)
public class PropertyAndImportConfig {
@Autowired
private Environment env;
@Bean
public AppProperties appProperties() {
AppProperties props = new AppProperties();
props.setAppName(env.getProperty("app.name"));
return props;
}
}
6. 实际应用示例
6.1 完整的应用配置示例
// 数据源配置
@Configuration
public class DataSourceConfig {
@Value("${datasource.url}")
private String url;
@Bean
public DataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(url);
return dataSource;
}
}
// JPA 配置
@Configuration
@EnableJpaRepositories("com.example.repository")
public class JpaConfig {
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
DataSource dataSource) {
// JPA 配置逻辑
return new LocalContainerEntityManagerFactoryBean();
}
}
// 事务配置
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
// 主应用配置
@Configuration
@PropertySource("classpath:application.properties")
@ComponentScan("com.example.service")
@Import({
DataSourceConfig.class,
JpaConfig.class,
TransactionConfig.class
})
public class ApplicationConfig {
@Bean
public ApplicationRunner applicationRunner() {
return new ApplicationRunner();
}
}
7. 最佳实践和注意事项
7.1 最佳实践
- 模块化设计: 将相关配置分组到不同的配置类中
- 条件化导入: 使用
ImportSelector实现条件化配置 - 明确依赖: 确保导入的配置类之间的依赖关系清晰
- 避免循环导入: 注意配置类之间的循环依赖问题
7.2 注意事项
@Import的类会被 Spring 容器管理- 导入的顺序可能会影响 Bean 的初始化顺序
- 使用
ImportBeanDefinitionRegistrar时要注意 Bean 的依赖关系 - 在测试环境中可以使用
@Import快速导入测试配置
@Import 注解是 Spring 框架中实现配置模块化和条件化的重要工具,合理使用可以大大提高配置的灵活性和可维护性。
@Import 与 @ComponentScan
Spring @Import 与 @ComponentScan 详解
1. 核心概念对比
@ComponentScan
- 作用: 包扫描,自动发现并注册组件
- 机制: 基于包路径扫描,自动检测带有特定注解的类
- 使用场景: 注册应用程序自定义组件
@Import
- 作用: 显式导入配置类或组件
- 机制: 直接指定要导入的类,不进行包扫描
- 使用场景: 导入第三方库配置、条件化配置、模块化配置
2. 详细功能对比
| 特性 | @ComponentScan | @Import |
|---|---|---|
| 扫描方式 | 包路径扫描 | 直接类引用 |
| 自动发现 | ✅ 支持 | ❌ 不支持 |
| 条件过滤 | ✅ 支持 (include/exclude) | ✅ 支持 (通过 ImportSelector) |
| 性能影响 | 相对较慢 (需要扫描) | 快速 (直接引用) |
| 适用范围 | 应用内部组件 | 配置类、第三方库 |
| 灵活性 | 批量注册 | 精确控制 |
3. 使用示例对比
3.1 @ComponentScan 基础用法
// 扫描指定包下的组件
@Configuration
@ComponentScan(
basePackages = {"com.example.service", "com.example.repository"},
includeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = Service.class
),
excludeFilters = @ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
classes = {OldService.class, DeprecatedRepository.class}
)
)
public class ComponentScanConfig {
// 自动注册 com.example.service 和 com.example.repository 包下的
// 带有 @Component, @Service, @Repository, @Controller 注解的类
}
3.2 @Import 基础用法
// 显式导入配置类
@Configuration
@Import({
DatabaseConfig.class,
SecurityConfig.class,
CacheConfig.class,
EmailService.class // 也可以导入普通组件
})
public class ImportConfig {
// 精确导入指定的配置类和组件
}
4. 组合使用场景
4.1 典型的企业级配置
@Configuration
// 扫描应用内部组件
@ComponentScan(
basePackages = {
"com.example.application.service",
"com.example.application.controller",
"com.example.infrastructure.repository"
}
)
// 导入基础设施配置
@Import({
DataSourceConfig.class, // 数据源配置
SecurityConfig.class, // 安全配置
CacheConfig.class, // 缓存配置
MessageQueueConfig.class, // 消息队列配置
ThirdPartyIntegrationConfig.class // 第三方集成配置
})
@PropertySource("classpath:application.properties")
@EnableTransactionManagement
public class EnterpriseApplicationConfig {
@Bean
public ApplicationMonitor applicationMonitor() {
return new ApplicationMonitor();
}
}
4.2 模块化架构中的使用
// 领域模块配置
@Configuration
@ComponentScan("com.example.order.domain")
public class OrderDomainConfig {
// 扫描订单领域相关组件
}
// 基础设施模块配置
@Configuration
@Import({DatabaseConfig.class, CacheConfig.class})
public class InfrastructureConfig {
// 导入基础设施相关配置
}
// Web 模块配置
@Configuration
@ComponentScan("com.example.order.interfaces.web")
public class WebConfig {
// 扫描 Web 层组件
}
// 主应用配置 - 组合所有模块
@Configuration
@Import({
OrderDomainConfig.class,
InfrastructureConfig.class,
WebConfig.class
})
@ComponentScan("com.example.shared") // 扫描共享组件
public class ModularApplicationConfig {
// 组合各个模块的配置
}
5. 高级用法对比
5.1 @ComponentScan 的高级过滤
@Configuration
@ComponentScan(
basePackages = "com.example",
useDefaultFilters = false,
includeFilters = {
@ComponentScan.Filter(
type = FilterType.REGEX,
pattern = ".*Service.*"
),
@ComponentScan.Filter(
type = FilterType.ASPECTJ,
pattern = "com.example..*Repository+"
)
},
excludeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = {Deprecated.class}
)
)
public class AdvancedComponentScanConfig {
// 只注册名称包含 "Service" 的类
// 和 com.example 包及其子包下的 Repository 接口实现类
// 排除带有 @Deprecated 注解的类
}
5.2 @Import 的动态选择
// 基于环境的配置选择器
public class EnvironmentBasedImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
String env = System.getProperty("spring.profiles.active", "dev");
switch (env) {
case "prod":
return new String[]{
"com.example.config.ProdDataSourceConfig",
"com.example.config.ProdSecurityConfig"
};
case "staging":
return new String[]{
"com.example.config.StagingDataSourceConfig",
"com.example.config.StagingSecurityConfig"
};
default:
return new String[]{
"com.example.config.DevDataSourceConfig",
"com.example.config.DevSecurityConfig"
};
}
}
}
// 使用动态导入选择器
@Configuration
@Import(EnvironmentBasedImportSelector.class)
@ComponentScan("com.example.business")
public class DynamicImportConfig {
// 根据环境动态导入不同的配置
}
6. 性能考量
6.1 包扫描的性能影响
// 不推荐的宽泛扫描 - 性能较差
@Configuration
@ComponentScan("com") // 扫描范围过大
public class BroadScanConfig {
// 会扫描整个 com 包下的所有类,性能差
}
// 推荐的精确扫描 - 性能较好
@Configuration
@ComponentScan({
"com.example.app.service",
"com.example.app.repository",
"com.example.app.component"
})
public class PreciseScanConfig {
// 只扫描必要的包,性能好
}
6.2 优化的混合配置
@Configuration
// 精确扫描应用核心包
@ComponentScan({
"com.example.core.service",
"com.example.core.domain",
"com.example.core.component"
})
// 显式导入第三方配置
@Import({
DataSourceConfig.class, // 数据库配置
RedisConfig.class, // Redis 配置
SwaggerConfig.class, // 文档配置
ValidationConfig.class // 验证配置
})
public class OptimizedConfig {
// 结合精确扫描和显式导入,达到最佳性能
}
7. 实际项目中的最佳实践
7.1 分层架构中的配置策略
// 1. 领域层配置 - 使用组件扫描
@Configuration
@ComponentScan({
"com.example.order.domain.model",
"com.example.order.domain.service",
"com.example.order.domain.factory"
})
public class DomainLayerConfig {
// 扫描领域相关组件
}
// 2. 基础设施层配置 - 使用导入
@Configuration
@Import({
JpaConfig.class,
RedisConfig.class,
EmailConfig.class,
FileStorageConfig.class
})
public class InfrastructureLayerConfig {
// 导入基础设施配置
}
// 3. 应用层配置 - 混合使用
@Configuration
@ComponentScan("com.example.order.application.service")
@Import({
DomainLayerConfig.class,
InfrastructureLayerConfig.class,
UseCaseConfig.class
})
public class ApplicationLayerConfig {
// 组合领域层和基础设施层
}
// 4. 接口层配置 - 使用组件扫描
@Configuration
@ComponentScan({
"com.example.order.interfaces.rest",
"com.example.order.interfaces.dto",
"com.example.order.interfaces.assembler"
})
public class InterfaceLayerConfig {
// 扫描接口相关组件
}
// 5. 主配置 - 组合所有层
@Configuration
@Import({
ApplicationLayerConfig.class,
InterfaceLayerConfig.class
})
@EnableAsync
@EnableScheduling
public class MainApplicationConfig {
// 应用入口配置
}
7.2 微服务中的配置管理
// 共享配置模块
@Configuration
@Import({
CommonDatabaseConfig.class,
CommonSecurityConfig.class,
CommonLoggingConfig.class
})
public class CommonConfig {
// 微服务共享的基础配置
}
// 业务服务特定配置
@Configuration
@ComponentScan("com.example.userservice.business")
@Import(CommonConfig.class)
public class UserServiceConfig {
@Bean
public UserService userService(UserRepository repository) {
return new UserServiceImpl(repository);
}
}
// API 网关配置
@Configuration
@ComponentScan("com.example.gateway.route")
@Import(CommonConfig.class)
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("user_service", r -> r.path("/api/users/**")
.uri("lb://user-service"))
.build();
}
}
8. 常见问题与解决方案
8.1 循环依赖问题
// 错误的配置 - 可能导致循环依赖
@Configuration
@ComponentScan("com.example.circular")
public class CircularDependencyConfig {
// 如果扫描的包中存在循环依赖,启动会失败
}
// 解决方案 - 使用 @Lazy 或重构
@Configuration
@ComponentScan("com.example.circular")
public class FixedCircularConfig {
@Bean
@Lazy
public ServiceA serviceA(ServiceB serviceB) {
return new ServiceA(serviceB);
}
@Bean
@Lazy
public ServiceB serviceB(ServiceA serviceA) {
return new ServiceB(serviceA);
}
}
8.2 配置覆盖问题
// 基础配置
@Configuration
public class BaseConfig {
@Bean
@ConditionalOnMissingBean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder().build();
}
}
// 覆盖配置 - 使用 @Import 和 @Primary
@Configuration
@Import(BaseConfig.class)
public class OverrideConfig {
@Bean
@Primary // 标记为主要bean
public DataSource productionDataSource() {
return new HikariDataSource();
}
}
总结
- 使用 @ComponentScan: 当需要自动发现和注册应用程序自定义组件时
- 使用 @Import: 当需要精确控制配置类导入、集成第三方库或条件化配置时
- 组合使用: 在实际项目中,通常两者结合使用,
@ComponentScan用于应用内部组件,@Import用于基础设施和第三方配置
合理使用这两个注解可以让 Spring 应用的配置更加清晰、模块化和可维护。
@Import注解 与 ImportSelector接口
Spring @Import 注解与 ImportSelector 接口详解
1. 核心关系概述
@Import 注解和 ImportSelector 接口是 Spring 配置机制中紧密配合的两个组件:
- @Import: 用于声明式导入配置
- ImportSelector: 用于编程式、条件化导入配置
2. @Import 注解的三种导入方式
2.1 导入普通配置类
@Configuration
public class DatabaseConfig {
@Bean
public DataSource dataSource() {
return new HikariDataSource();
}
}
@Configuration
@Import(DatabaseConfig.class) // 直接导入配置类
public class AppConfig {
}
2.2 导入普通组件类
public class SimpleService {
public void doSomething() {
System.out.println("Doing something...");
}
}
@Configuration
@Import(SimpleService.class) // 导入普通类,Spring 会将其注册为 Bean
public class ServiceConfig {
}
2.3 导入 ImportSelector 实现
@Configuration
@Import(CustomImportSelector.class) // 导入 ImportSelector 实现
public class DynamicConfig {
// 由 ImportSelector 决定具体导入哪些配置
}
3. ImportSelector 接口详解
3.1 接口定义
public interface ImportSelector {
/**
* 选择要导入的配置类
* @param importingClassMetadata 导入类的元数据
* @return 要导入的配置类的全限定名数组
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
/**
* 可选的 Predicate,用于过滤候选类(默认实现返回 null)
*/
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
3.2 基础实现示例
public class SimpleImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 返回要导入的配置类的全限定名
return new String[]{
"com.example.config.DatabaseConfig",
"com.example.config.CacheConfig",
"com.example.config.SecurityConfig"
};
}
}
4. ImportSelector 的高级用法
4.1 基于条件的动态导入
public class EnvironmentAwareImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
String environment = getActiveEnvironment();
List<String> imports = new ArrayList<>();
// 基础配置
imports.add("com.example.config.CommonConfig");
// 环境特定配置
switch (environment) {
case "dev":
imports.add("com.example.config.DevDataSourceConfig");
imports.add("com.example.config.DevSecurityConfig");
break;
case "prod":
imports.add("com.example.config.ProdDataSourceConfig");
imports.add("com.example.config.ProdSecurityConfig");
imports.add("com.example.config.MonitoringConfig");
break;
case "test":
imports.add("com.example.config.TestDataSourceConfig");
imports.add("com.example.config.MockExternalServiceConfig");
break;
}
// 特性开关配置
if (isFeatureEnabled("redis-cache")) {
imports.add("com.example.config.RedisCacheConfig");
}
if (isFeatureEnabled("message-queue")) {
imports.add("com.example.config.RabbitMQConfig");
}
return imports.toArray(new String[0]);
}
private String getActiveEnvironment() {
return System.getProperty("spring.profiles.active", "dev");
}
private boolean isFeatureEnabled(String feature) {
// 从配置中心、环境变量等读取特性开关
return Boolean.parseBoolean(System.getProperty("feature." + feature, "false"));
}
}
4.2 基于注解属性的动态导入
// 自定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(AnnotationDrivenImportSelector.class)
public @interface EnableModule {
String[] modules() default {};
boolean enableCache() default false;
boolean enableSecurity() default true;
}
// 基于注解属性的 ImportSelector
public class AnnotationDrivenImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 获取注解属性
Map<String, Object> attributes = importingClassMetadata
.getAnnotationAttributes(EnableModule.class.getName());
if (attributes == null) {
return new String[0];
}
List<String> imports = new ArrayList<>();
// 根据注解属性决定导入哪些配置
String[] modules = (String[]) attributes.get("modules");
boolean enableCache = (Boolean) attributes.get("enableCache");
boolean enableSecurity = (Boolean) attributes.get("enableSecurity");
// 添加模块配置
for (String module : modules) {
imports.add("com.example.modules." + module + ".ModuleConfig");
}
// 添加功能配置
if (enableCache) {
imports.add("com.example.config.CacheConfig");
}
if (enableSecurity) {
imports.add("com.example.config.SecurityConfig");
}
return imports.toArray(new String[0]);
}
}
// 使用注解驱动导入
@Configuration
@EnableModule(
modules = {"user", "order", "payment"},
enableCache = true,
enableSecurity = true
)
public class ApplicationConfig {
}
5. DeferredImportSelector 接口
DeferredImportSelector 是 ImportSelector 的子接口,用于延迟导入处理,确保某些配置在其他配置之后处理。
5.1 DeferredImportSelector 示例
public class DeferredConfigurationSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 这些配置会在所有常规 @Import 处理完成后才处理
return new String[]{
"com.example.config.AutoConfigurationConfig",
"com.example.config.FeatureToggledConfig"
};
}
@Override
public Class<? extends Group> getImportGroup() {
return CustomImportGroup.class;
}
// 自定义分组逻辑
private static class CustomImportGroup implements Group {
private final List<Entry> imports = new ArrayList<>();
@Override
public void process(AnnotationMetadata metadata, DeferredImportSelector selector) {
// 处理逻辑,可以排序、过滤等
String[] selectedImports = selector.selectImports(metadata);
for (String importClassName : selectedImports) {
imports.add(new Entry(metadata, importClassName));
}
}
@Override
public Iterable<Entry> selectImports() {
// 可以对导入项进行排序
imports.sort(Comparator.comparing(Entry::getImportClassName));
return imports;
}
}
}
6. 实际应用场景
6.1 Spring Boot 自动配置模拟
// 模拟 Spring Boot 的 @EnableAutoConfiguration
public class AutoConfigurationImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 从 META-INF/spring.factories 读取自动配置类
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
EnableAutoConfiguration.class, getBeanClassLoader());
// 过滤和排序(模拟 Spring Boot 的自动配置逻辑)
configurations = filter(configurations, getAutoConfigurationMetadata());
configurations = sort(configurations);
return configurations.toArray(new String[0]);
}
private List<String> filter(List<String> configurations, AutoConfigurationMetadata metadata) {
// 根据条件过滤
return configurations.stream()
.filter(config -> shouldInclude(config, metadata))
.collect(Collectors.toList());
}
private List<String> sort(List<String> configurations) {
// 根据 @Order 注解排序
return configurations;
}
private boolean shouldInclude(String configuration, AutoConfigurationMetadata metadata) {
// 检查各种条件:类路径、Bean 条件等
return true;
}
}
6.2 多租户配置选择
public class TenantAwareImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
String tenantId = TenantContext.getCurrentTenant();
// 根据租户选择不同的配置
switch (tenantId) {
case "tenant-a":
return new String[]{
"com.example.tenants.tenantA.DatabaseConfig",
"com.example.tenants.tenantA.ThemeConfig"
};
case "tenant-b":
return new String[]{
"com.example.tenants.tenantB.DatabaseConfig",
"com.example.tenants.tenantB.ThemeConfig",
"com.example.tenants.tenantB.CustomFeatureConfig"
};
default:
return new String[]{
"com.example.tenants.default.DatabaseConfig"
};
}
}
}
// 租户上下文
public class TenantContext {
private static final ThreadLocal<String> currentTenant = new ThreadLocal<>();
public static String getCurrentTenant() {
return currentTenant.get();
}
public static void setCurrentTenant(String tenant) {
currentTenant.set(tenant);
}
}
6.3 特性开关配置
public class FeatureToggleImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
List<String> imports = new ArrayList<>();
// 基础配置
imports.add("com.example.config.CoreConfig");
// 根据特性开关动态导入
if (isFeatureEnabled("advanced-search")) {
imports.add("com.example.features.advancedsearch.ElasticsearchConfig");
}
if (isFeatureEnabled("real-time-notifications")) {
imports.add("com.example.features.notifications.WebSocketConfig");
imports.add("com.example.features.notifications.PushNotificationConfig");
}
if (isFeatureEnabled("analytics-dashboard")) {
imports.add("com.example.features.analytics.AnalyticsConfig");
imports.add("com.example.features.analytics.ReportingConfig");
}
return imports.toArray(new String[0]);
}
private boolean isFeatureEnabled(String feature) {
// 从特性管理服务、数据库或配置中心获取特性状态
// 这里简化为从系统属性读取
return Boolean.parseBoolean(System.getProperty("feature." + feature, "false"));
}
}
7. 最佳实践和注意事项
7.1 最佳实践
- 单一职责: 每个 ImportSelector 应该只负责一个明确的配置选择逻辑
- 错误处理: 在 selectImports 方法中妥善处理异常
- 性能考虑: 避免在 ImportSelector 中执行耗时操作
- 条件检查: 在导入前检查必要的条件,避免导入不必要的配置
7.2 错误处理示例
public class RobustImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
try {
// 执行配置选择逻辑
return doSelectImports(importingClassMetadata);
} catch (Exception e) {
// 记录错误并返回空数组或默认配置
System.err.println("Failed to select imports: " + e.getMessage());
return new String[]{"com.example.config.FallbackConfig"};
}
}
private String[] doSelectImports(AnnotationMetadata metadata) {
// 实际的配置选择逻辑
List<String> imports = new ArrayList<>();
// 条件检查
if (checkPrerequisites()) {
imports.add("com.example.config.MainConfig");
}
return imports.toArray(new String[0]);
}
private boolean checkPrerequisites() {
// 检查必要的条件
try {
Class.forName("com.example.RequiredClass");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
7.3 测试策略
// ImportSelector 的单元测试
class CustomImportSelectorTest {
@Test
void testSelectImportsInDevEnvironment() {
// 设置测试环境
System.setProperty("spring.profiles.active", "dev");
CustomImportSelector selector = new CustomImportSelector();
StandardAnnotationMetadata metadata = new StandardAnnotationMetadata(TestConfig.class);
String[] imports = selector.selectImports(metadata);
// 验证返回的配置类
assertThat(imports).contains(
"com.example.config.DevDataSourceConfig",
"com.example.config.DevSecurityConfig"
);
}
@Configuration
static class TestConfig {
}
}
8. 总结
@Import 注解和 ImportSelector 接口共同构成了 Spring 强大的配置机制:
- @Import: 提供声明式配置导入
- ImportSelector: 提供编程式、条件化配置导入
- DeferredImportSelector: 提供延迟导入能力,用于处理配置顺序依赖
通过合理使用这些机制,可以实现:
- 环境特定的配置
- 特性开关驱动的配置
- 多租户架构配置
- 自动配置和插件化架构
- 条件化 Bean 注册
这种组合为 Spring 应用提供了极高的灵活性和可扩展性,是理解 Spring Boot 自动配置等高级特性的基础。
@Import注解 与 ImportBeanDefinitionRegistrar 接口
Spring @Import 注解与 ImportBeanDefinitionRegistrar 接口详解
1. 核心关系概述
@Import 注解和 ImportBeanDefinitionRegistrar 接口共同提供了 Spring 容器中最灵活、最底层的 Bean 注册机制:
- @Import: 声明式导入机制
- ImportBeanDefinitionRegistrar: 编程式 Bean 定义注册器,直接操作 BeanDefinition
2. ImportBeanDefinitionRegistrar 接口定义
2.1 接口源码
public interface ImportBeanDefinitionRegistrar {
/**
* 注册 Bean 定义到容器中
* @param importingClassMetadata 导入类的注解元数据
* @param registry Bean 定义注册器
* @param importBeanNameGenerator Bean 名称生成器
*/
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
/**
* 旧版本方法,保持向后兼容
*/
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
// 默认空实现
}
}
3. 基础使用示例
3.1 简单的 BeanDefinition 注册
// 通过 @Import 使用 ImportBeanDefinitionRegistrar
@Configuration
@Import(CustomBeanDefinitionRegistrar.class)
public class AppConfig {
}
// 实现 ImportBeanDefinitionRegistrar
public class CustomBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
// 1. 创建 RootBeanDefinition
RootBeanDefinition beanDefinition = new RootBeanDefinition(CustomService.class);
// 2. 设置 Bean 属性
beanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
beanDefinition.setLazyInit(false);
beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
// 3. 设置构造器参数
ConstructorArgumentValues constructorArgs = new ConstructorArgumentValues();
constructorArgs.addIndexedArgumentValue(0, "default-config");
beanDefinition.setConstructorArgumentValues(constructorArgs);
// 4. 设置属性值
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("timeout", 5000);
propertyValues.add("maxRetries", 3);
beanDefinition.setPropertyValues(propertyValues);
// 5. 注册 Bean 定义
registry.registerBeanDefinition("customService", beanDefinition);
}
}
4. 高级用法
4.1 基于注解属性的动态注册
// 自定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(AnnotationDrivenBeanRegistrar.class)
public @interface EnableCustomFeatures {
String[] features() default {};
boolean enableCache() default false;
int timeout() default 3000;
}
// 注解驱动的 Bean 注册器
public class AnnotationDrivenBeanRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
// 获取注解属性
Map<String, Object> attributes = importingClassMetadata
.getAnnotationAttributes(EnableCustomFeatures.class.getName());
if (attributes == null) {
return;
}
String[] features = (String[]) attributes.get("features");
boolean enableCache = (Boolean) attributes.get("enableCache");
int timeout = (Integer) attributes.get("timeout");
// 根据注解属性注册 Bean
registerFeatureBeans(features, registry);
if (enableCache) {
registerCacheBean(timeout, registry);
}
}
private void registerFeatureBeans(String[] features, BeanDefinitionRegistry registry) {
for (String feature : features) {
switch (feature) {
case "validation":
registerValidationBeans(registry);
break;
case "monitoring":
registerMonitoringBeans(registry);
break;
case "security":
registerSecurityBeans(registry);
break;
}
}
}
private void registerValidationBeans(BeanDefinitionRegistry registry) {
RootBeanDefinition validator = new RootBeanDefinition(ValidationService.class);
registry.registerBeanDefinition("validationService", validator);
}
private void registerMonitoringBeans(BeanDefinitionRegistry registry) {
RootBeanDefinition monitor = new RootBeanDefinition(MonitoringService.class);
registry.registerBeanDefinition("monitoringService", monitor);
}
private void registerSecurityBeans(BeanDefinitionRegistry registry) {
RootBeanDefinition security = new RootBeanDefinition(SecurityService.class);
registry.registerBeanDefinition("securityService", security);
}
private void registerCacheBean(int timeout, BeanDefinitionRegistry registry) {
RootBeanDefinition cache = new RootBeanDefinition(CacheService.class);
MutablePropertyValues properties = new MutablePropertyValues();
properties.add("timeout", timeout);
cache.setPropertyValues(properties);
registry.registerBeanDefinition("cacheService", cache);
}
}
// 使用注解驱动配置
@Configuration
@EnableCustomFeatures(
features = {"validation", "monitoring"},
enableCache = true,
timeout = 5000
)
public class FeatureConfiguration {
}
4.2 扫描和注册类路径下的组件
public class PackageScanBeanRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
// 获取要扫描的包路径
String basePackage = getBasePackage(importingClassMetadata);
// 创建类路径扫描器
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false);
// 设置过滤器
scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class));
scanner.addIncludeFilter(new AnnotationTypeFilter(Component.class));
scanner.addExcludeFilter(new AnnotationTypeFilter(Deprecated.class));
// 执行扫描
int beanCount = scanner.scan(basePackage);
System.out.println("Registered " + beanCount + " beans from package: " + basePackage);
}
private String getBasePackage(AnnotationMetadata metadata) {
// 从注解属性获取包路径,或使用默认值
Map<String, Object> attributes = metadata.getAnnotationAttributes(EnableCustomScan.class.getName());
if (attributes != null && attributes.containsKey("basePackage")) {
return (String) attributes.get("basePackage");
}
// 默认为导入类所在的包
return metadata.getClassName().substring(0, metadata.getClassName().lastIndexOf('.'));
}
}
5. 实际应用场景
5.1 模拟 MyBatis @MapperScan 实现
// 自定义 Mapper 扫描注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MapperScannerRegistrar.class)
public @interface EnableMapperScan {
String[] value() default {};
Class<?>[] basePackageClasses() default {};
}
// Mapper 扫描注册器
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
// 获取注解属性
Map<String, Object> attributes = importingClassMetadata
.getAnnotationAttributes(EnableMapperScan.class.getName());
// 解析包路径
List<String> basePackages = getBasePackages(attributes, importingClassMetadata);
// 注册 MapperScannerConfigurer
registerMapperScannerConfigurer(registry, basePackages);
}
private List<String> getBasePackages(Map<String, Object> attributes, AnnotationMetadata metadata) {
List<String> basePackages = new ArrayList<>();
// 从 value 属性获取
if (attributes.containsKey("value")) {
Collections.addAll(basePackages, (String[]) attributes.get("value"));
}
// 从 basePackageClasses 获取
if (attributes.containsKey("basePackageClasses")) {
Class<?>[] classes = (Class<?>[]) attributes.get("basePackageClasses");
for (Class<?> clazz : classes) {
basePackages.add(clazz.getPackage().getName());
}
}
// 如果都没有指定,使用导入类所在的包
if (basePackages.isEmpty()) {
String className = metadata.getClassName();
basePackages.add(className.substring(0, className.lastIndexOf('.')));
}
return basePackages;
}
private void registerMapperScannerConfigurer(BeanDefinitionRegistry registry, List<String> basePackages) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(MapperScannerConfigurer.class);
MutablePropertyValues properties = new MutablePropertyValues();
properties.add("basePackage", String.join(",", basePackages));
properties.add("annotationClass", Mapper.class);
properties.add("sqlSessionFactoryBeanName", "sqlSessionFactory");
beanDefinition.setPropertyValues(properties);
registry.registerBeanDefinition("mapperScannerConfigurer", beanDefinition);
}
}
// 使用示例
@Configuration
@EnableMapperScan({"com.example.mapper", "com.example.dao"})
public class MyBatisConfig {
}
5.2 动态数据源注册
public class DynamicDataSourceRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
// 读取数据源配置
Map<String, DataSourceProperties> dataSourceConfigs = loadDataSourceConfigs();
// 注册各个数据源
for (Map.Entry<String, DataSourceProperties> entry : dataSourceConfigs.entrySet()) {
registerDataSource(registry, entry.getKey(), entry.getValue());
}
// 注册动态数据源路由
registerDynamicDataSource(registry, dataSourceConfigs.keySet());
}
private void registerDataSource(BeanDefinitionRegistry registry, String name,
DataSourceProperties properties) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(HikariDataSource.class);
MutablePropertyValues props = new MutablePropertyValues();
props.add("jdbcUrl", properties.getUrl());
props.add("username", properties.getUsername());
props.add("password", properties.getPassword());
props.add("driverClassName", properties.getDriverClassName());
props.add("maximumPoolSize", properties.getMaxPoolSize());
beanDefinition.setPropertyValues(props);
registry.registerBeanDefinition(name + "DataSource", beanDefinition);
}
private void registerDynamicDataSource(BeanDefinitionRegistry registry, Set<String> dataSourceNames) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(DynamicDataSource.class);
// 设置数据源映射
MutablePropertyValues props = new MutablePropertyValues();
Map<String, Object> targetDataSources = new HashMap<>();
for (String name : dataSourceNames) {
targetDataSources.put(name, new RuntimeBeanReference(name + "DataSource"));
}
props.add("targetDataSources", targetDataSources);
// 设置默认数据源
String defaultDataSource = dataSourceNames.iterator().next() + "DataSource";
props.add("defaultTargetDataSource", new RuntimeBeanReference(defaultDataSource));
beanDefinition.setPropertyValues(props);
registry.registerBeanDefinition("dataSource", beanDefinition);
}
private Map<String, DataSourceProperties> loadDataSourceConfigs() {
// 从配置文件中加载多数据源配置
// 这里简化为硬编码示例
Map<String, DataSourceProperties> configs = new HashMap<>();
configs.put("primary", new DataSourceProperties("jdbc:mysql://localhost:3306/primary", "user", "pass"));
configs.put("secondary", new DataSourceProperties("jdbc:mysql://localhost:3306/secondary", "user", "pass"));
return configs;
}
static class DataSourceProperties {
private String url;
private String username;
private String password;
private String driverClassName = "com.mysql.cj.jdbc.Driver";
private int maxPoolSize = 10;
// 构造器、getter、setter...
}
}
5.3 AOP 切面自动配置
public class AopAutoConfigurationRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
// 注册 AspectJ 表达式切点
registerAspectJExpressionPointcut(registry);
// 注册通知(Advice)
registerAdvices(registry);
// 注册 Advisor
registerAdvisors(registry);
// 注册自动代理创建器
registerAutoProxyCreator(registry);
}
private void registerAspectJExpressionPointcut(BeanDefinitionRegistry registry) {
RootBeanDefinition pointcut = new RootBeanDefinition(AspectJExpressionPointcut.class);
pointcut.setScope(BeanDefinition.SCOPE_PROTOTYPE);
MutablePropertyValues props = new MutablePropertyValues();
props.add("expression", "execution(* com.example.service.*.*(..))");
pointcut.setPropertyValues(props);
registry.registerBeanDefinition("servicePointcut", pointcut);
}
private void registerAdvices(BeanDefinitionRegistry registry) {
// 注册方法前置通知
RootBeanDefinition beforeAdvice = new RootBeanDefinition(MethodBeforeAdvice.class);
beforeAdvice.setBeanClass(LoggingBeforeAdvice.class);
registry.registerBeanDefinition("loggingBeforeAdvice", beforeAdvice);
// 注册方法后置通知
RootBeanDefinition afterAdvice = new RootBeanDefinition(AfterReturningAdvice.class);
afterAdvice.setBeanClass(LoggingAfterAdvice.class);
registry.registerBeanDefinition("loggingAfterAdvice", afterAdvice);
}
private void registerAdvisors(BeanDefinitionRegistry registry) {
// 创建 DefaultPointcutAdvisor
RootBeanDefinition advisor = new RootBeanDefinition(DefaultPointcutAdvisor.class);
MutablePropertyValues props = new MutablePropertyValues();
props.add("pointcut", new RuntimeBeanReference("servicePointcut"));
props.add("advice", new RuntimeBeanReference("loggingBeforeAdvice"));
advisor.setPropertyValues(props);
registry.registerBeanDefinition("loggingAdvisor", advisor);
}
private void registerAutoProxyCreator(BeanDefinitionRegistry registry) {
RootBeanDefinition autoProxyCreator = new RootBeanDefinition(AnnotationAwareAspectJAutoProxyCreator.class);
MutablePropertyValues props = new MutablePropertyValues();
props.add("proxyTargetClass", true);
autoProxyCreator.setPropertyValues(props);
registry.registerBeanDefinition("internalAutoProxyCreator", autoProxyCreator);
}
}
// 使用示例
@Configuration
@Import(AopAutoConfigurationRegistrar.class)
public class AopConfig {
}
6. 与 ImportSelector 的对比
6.1 功能对比
| 特性 | ImportSelector | ImportBeanDefinitionRegistrar |
|---|---|---|
| 控制粒度 | 类级别 | Bean 定义级别 |
| 灵活性 | 中等 | 极高 |
| 使用场景 | 条件化导入配置类 | 精细控制 Bean 注册 |
| 性能 | 较好 | 需要更多资源 |
| 复杂度 | 相对简单 | 相对复杂 |
6.2 组合使用示例
// 组合使用 ImportSelector 和 ImportBeanDefinitionRegistrar
public class ComprehensiveConfiguration implements ImportSelector, ImportBeanDefinitionRegistrar {
// ImportSelector 方法 - 选择要导入的配置类
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
List<String> imports = new ArrayList<>();
// 根据条件导入配置类
if (isCacheEnabled()) {
imports.add("com.example.config.CacheConfig");
}
if (isSecurityEnabled()) {
imports.add("com.example.config.SecurityConfig");
}
return imports.toArray(new String[0]);
}
// ImportBeanDefinitionRegistrar 方法 - 直接注册 Bean 定义
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
// 直接注册一些特殊的 Bean
registerInfrastructureBeans(registry);
registerCustomBeans(registry);
}
private void registerInfrastructureBeans(BeanDefinitionRegistry registry) {
// 注册基础设施 Bean
RootBeanDefinition propertiesBean = new RootBeanDefinition(PropertiesFactoryBean.class);
MutablePropertyValues props = new MutablePropertyValues();
props.add("location", "classpath:application.properties");
propertiesBean.setPropertyValues(props);
registry.registerBeanDefinition("appProperties", propertiesBean);
}
private void registerCustomBeans(BeanDefinitionRegistry registry) {
// 注册自定义 Bean
RootBeanDefinition customBean = new RootBeanDefinition(CustomComponent.class);
customBean.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
registry.registerBeanDefinition("customComponent", customBean);
}
private boolean isCacheEnabled() {
return Boolean.parseBoolean(System.getProperty("app.cache.enabled", "true"));
}
private boolean isSecurityEnabled() {
return Boolean.parseBoolean(System.getProperty("app.security.enabled", "false"));
}
}
// 使用组合配置
@Configuration
@Import(ComprehensiveConfiguration.class)
public class MainConfig {
}
7. 最佳实践和注意事项
7.1 最佳实践
- 明确的 Bean 命名: 为注册的 Bean 使用清晰的名称
- 条件检查: 在注册前检查必要的条件
- 避免重复注册: 检查 Bean 是否已存在
- 合理的 Bean 作用域: 根据需求设置合适的 Bean 作用域
7.2 健壮的实现示例
public class RobustBeanRegistrar implements ImportBeanDefinitionRegistrar {
private static final Logger logger = LoggerFactory.getLogger(RobustBeanRegistrar.class);
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
try {
// 1. 检查前提条件
if (!checkPrerequisites()) {
logger.warn("Prerequisites not met, skipping bean registration");
return;
}
// 2. 检查 Bean 是否已存在
if (registry.containsBeanDefinition("customService")) {
logger.info("CustomService already registered, skipping");
return;
}
// 3. 安全地创建和注册 Bean 定义
BeanDefinition beanDefinition = createBeanDefinitionSafely();
if (beanDefinition != null) {
registry.registerBeanDefinition("customService", beanDefinition);
logger.info("Successfully registered CustomService");
}
} catch (Exception e) {
logger.error("Failed to register beans", e);
// 可以选择注册回退 Bean
registerFallbackBean(registry);
}
}
private boolean checkPrerequisites() {
// 检查必要的类是否存在
try {
Class.forName("com.example.RequiredDependency");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
private BeanDefinition createBeanDefinitionSafely() {
try {
RootBeanDefinition definition = new RootBeanDefinition(CustomService.class);
definition.setScope(BeanDefinition.SCOPE_SINGLETON);
definition.setLazyInit(true);
// 设置初始化/销毁方法
definition.setInitMethodName("initialize");
definition.setDestroyMethodName("cleanup");
return definition;
} catch (Exception e) {
logger.error("Failed to create bean definition", e);
return null;
}
}
private void registerFallbackBean(BeanDefinitionRegistry registry) {
try {
RootBeanDefinition fallback = new RootBeanDefinition(FallbackService.class);
registry.registerBeanDefinition("fallbackService", fallback);
} catch (Exception e) {
logger.error("Failed to register fallback bean", e);
}
}
}
8. 测试策略
8.1 单元测试示例
class CustomBeanDefinitionRegistrarTest {
@Test
void testRegisterBeanDefinitions() {
// 创建模拟对象
AnnotationMetadata metadata = mock(AnnotationMetadata.class);
BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();
// 设置注解属性
Map<String, Object> attributes = new HashMap<>();
attributes.put("features", new String[]{"validation", "monitoring"});
when(metadata.getAnnotationAttributes(anyString())).thenReturn(attributes);
// 执行注册
CustomBeanDefinitionRegistrar registrar = new CustomBeanDefinitionRegistrar();
registrar.registerBeanDefinitions(metadata, registry);
// 验证结果
assertTrue(registry.containsBeanDefinition("validationService"));
assertTrue(registry.containsBeanDefinition("monitoringService"));
assertEquals(2, registry.getBeanDefinitionCount());
}
@Test
void testBeanDefinitionProperties() {
BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();
CustomBeanDefinitionRegistrar registrar = new CustomBeanDefinitionRegistrar();
registrar.registerBeanDefinitions(mock(AnnotationMetadata.class), registry);
BeanDefinition beanDefinition = registry.getBeanDefinition("customService");
assertNotNull(beanDefinition);
assertEquals(BeanDefinition.SCOPE_SINGLETON, beanDefinition.getScope());
assertFalse(beanDefinition.isLazyInit());
}
}
@Import 与 @Conditional
Spring @Import 与 @Conditional 注解详解
1. 核心概念与关系
@Import 和 @Conditional 是 Spring 条件化配置的两个核心机制,它们共同实现了 Spring 应用的灵活配置和自动装配:
- @Import: 控制配置类的导入
- @Conditional: 控制 Bean 或配置类的创建条件
2. @Conditional 注解家族
2.1 核心 @Conditional 注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
2.2 Spring Boot 的条件注解 Spring Boot 提供了更具体的条件注解:
| 注解 | 作用 |
|---|---|
@ConditionalOnClass | 类路径下存在指定类时生效 |
@ConditionalOnMissingClass | 类路径下不存在指定类时生效 |
@ConditionalOnBean | 容器中存在指定 Bean 时生效 |
@ConditionalOnMissingBean | 容器中不存在指定 Bean 时生效 |
@ConditionalOnProperty | 配置属性满足条件时生效 |
@ConditionalOnResource | 资源文件存在时生效 |
@ConditionalOnWebApplication | Web 应用环境下生效 |
@ConditionalOnNotWebApplication | 非 Web 应用环境下生效 |
@ConditionalOnExpression | SpEL 表达式为 true 时生效 |
3. @Import 与 @Conditional 的基础组合
3.1 条件化导入配置类
// 开发环境配置
@Configuration
@ConditionalOnProperty(name = "app.env", havingValue = "dev")
public class DevConfig {
@Bean
public DataSource devDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
@Bean
public Logger devLogger() {
return LoggerFactory.getLogger("DEV-LOGGER");
}
}
// 生产环境配置
@Configuration
@ConditionalOnProperty(name = "app.env", havingValue = "prod")
public class ProdConfig {
@Bean
public DataSource prodDataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/prod");
dataSource.setUsername("prod-user");
dataSource.setPassword("prod-pass");
return dataSource;
}
@Bean
@ConditionalOnMissingBean
public MonitoringService monitoringService() {
return new ProductionMonitoringService();
}
}
// 主配置类 - 条件化导入
@Configuration
@Import({DevConfig.class, ProdConfig.class})
public class EnvironmentConfig {
// 根据 app.env 属性决定激活哪个配置
}
3.2 基于类存在的条件导入
@Configuration
@ConditionalOnClass(name = "com.fasterxml.jackson.databind.ObjectMapper")
@Import(JacksonConfig.class)
public class JsonSupportConfig {
// 只有当 Jackson 在类路径中时才导入 Jackson 配置
}
@Configuration
@ConditionalOnClass(name = "com.google.gson.Gson")
@Import(GsonConfig.class)
public class GsonSupportConfig {
// 只有当 Gson 在类路径中时才导入 Gson 配置
}
@Configuration
@Import({JsonSupportConfig.class, GsonSupportConfig.class})
public class SerializationConfig {
// 自动根据类路径中的 JSON 库导入相应配置
}
4. 自定义条件注解
4.1 实现 Condition 接口
// 自定义条件:检查特性开关
public class FeatureToggleCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获取注解属性
Map<String, Object> attributes = metadata
.getAnnotationAttributes(ConditionalOnFeature.class.getName());
if (attributes == null) {
return false;
}
String featureName = (String) attributes.get("value");
boolean expectedState = (Boolean) attributes.get("enabled");
// 从环境变量或配置中检查特性状态
Environment env = context.getEnvironment();
String featureKey = "feature." + featureName;
boolean actualState = env.getProperty(featureKey, Boolean.class, false);
return expectedState == actualState;
}
}
// 自定义条件注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(FeatureToggleCondition.class)
public @interface ConditionalOnFeature {
String value();
boolean enabled() default true;
}
4.2 使用自定义条件注解
@Configuration
@ConditionalOnFeature("advanced-search")
@Import(ElasticsearchConfig.class)
public class AdvancedSearchConfig {
// 只有当 feature.advanced-search=true 时才生效
}
@Configuration
@ConditionalOnFeature(value = "real-time-analytics", enabled = true)
@Import({KafkaConfig.class, AnalyticsConfig.class})
public class RealTimeAnalyticsConfig {
// 特性开关控制的实时分析配置
}
5. 与 ImportSelector 的条件化组合
5.1 条件化 ImportSelector
public class ConditionalImportSelector implements ImportSelector, EnvironmentAware {
private Environment environment;
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
List<String> imports = new ArrayList<>();
// 根据条件选择导入的配置
if (isDevEnvironment()) {
imports.add("com.example.config.DevToolsConfig");
imports.add("com.example.config.HotReloadConfig");
}
if (isCacheEnabled()) {
imports.add("com.example.config.RedisConfig");
}
if (isMetricsEnabled()) {
imports.add("com.example.config.MetricsConfig");
imports.add("com.example.config.HealthCheckConfig");
}
if (isApiDocumentationEnabled()) {
imports.add("com.example.config.SwaggerConfig");
}
return imports.toArray(new String[0]);
}
private boolean isDevEnvironment() {
return Arrays.stream(environment.getActiveProfiles())
.anyMatch(profile -> profile.equals("dev"));
}
private boolean isCacheEnabled() {
return environment.getProperty("app.cache.enabled", Boolean.class, false);
}
private boolean isMetricsEnabled() {
return environment.getProperty("management.endpoints.web.exposure.include")
.contains("metrics");
}
private boolean isApiDocumentationEnabled() {
return environment.getProperty("app.api.docs.enabled", Boolean.class, true);
}
}
// 使用条件化 ImportSelector
@Configuration
@Import(ConditionalImportSelector.class)
public class AutoImportConfig {
// 自动根据环境和配置导入相应的配置类
}
5.2 基于注解属性的条件选择器
// 自定义启用注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ModuleBasedImportSelector.class)
public @interface EnableModule {
Module[] value() default {};
boolean enableSecurity() default true;
}
enum Module {
CACHE, MESSAGING, SCHEDULING, VALIDATION, MONITORING
}
// 模块化的条件选择器
public class ModuleBasedImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
Map<String, Object> attributes = importingClassMetadata
.getAnnotationAttributes(EnableModule.class.getName());
if (attributes == null) {
return new String[0];
}
Module[] modules = (Module[]) attributes.get("value");
boolean enableSecurity = (Boolean) attributes.get("enableSecurity");
List<String> imports = new ArrayList<>();
// 根据模块选择配置
for (Module module : modules) {
switch (module) {
case CACHE:
imports.add("com.example.modules.cache.CacheConfig");
break;
case MESSAGING:
imports.add("com.example.modules.messaging.RabbitMQConfig");
break;
case SCHEDULING:
imports.add("com.example.modules.scheduling.SchedulerConfig");
break;
case VALIDATION:
imports.add("com.example.modules.validation.ValidationConfig");
break;
case MONITORING:
imports.add("com.example.modules.monitoring.MonitoringConfig");
break;
}
}
// 安全配置
if (enableSecurity) {
imports.add("com.example.modules.security.SecurityConfig");
}
return imports.toArray(new String[0]);
}
}
// 使用模块化配置
@Configuration
@EnableModule({
Module.CACHE,
Module.MESSAGING,
Module.MONITORING
})
public class ApplicationConfig {
// 只启用缓存、消息和监控模块
}
6. 与 ImportBeanDefinitionRegistrar 的条件化组合
6.1 条件化 Bean 定义注册
public class ConditionalBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private Environment environment;
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
// 条件化注册数据源
if (shouldRegisterPrimaryDataSource()) {
registerPrimaryDataSource(registry);
}
// 条件化注册备用数据源
if (shouldRegisterSecondaryDataSource()) {
registerSecondaryDataSource(registry);
}
// 条件化注册缓存
if (shouldRegisterCache()) {
registerCacheBeans(registry);
}
}
private boolean shouldRegisterPrimaryDataSource() {
return environment.containsProperty("spring.datasource.primary.url") &&
!registry.containsBeanDefinition("primaryDataSource");
}
private boolean shouldRegisterSecondaryDataSource() {
return environment.containsProperty("spring.datasource.secondary.url") &&
environment.getProperty("app.datasource.secondary.enabled", Boolean.class, false);
}
private boolean shouldRegisterCache() {
return environment.getProperty("app.cache.type", "none").equals("redis");
}
private void registerPrimaryDataSource(BeanDefinitionRegistry registry) {
RootBeanDefinition dataSource = new RootBeanDefinition(HikariDataSource.class);
MutablePropertyValues properties = new MutablePropertyValues();
properties.add("jdbcUrl", environment.getProperty("spring.datasource.primary.url"));
properties.add("username", environment.getProperty("spring.datasource.primary.username"));
properties.add("password", environment.getProperty("spring.datasource.primary.password"));
dataSource.setPropertyValues(properties);
registry.registerBeanDefinition("primaryDataSource", dataSource);
}
private void registerSecondaryDataSource(BeanDefinitionRegistry registry) {
RootBeanDefinition dataSource = new RootBeanDefinition(HikariDataSource.class);
MutablePropertyValues properties = new MutablePropertyValues();
properties.add("jdbcUrl", environment.getProperty("spring.datasource.secondary.url"));
properties.add("username", environment.getProperty("spring.datasource.secondary.username"));
properties.add("password", environment.getProperty("spring.datasource.secondary.password"));
dataSource.setPropertyValues(properties);
registry.registerBeanDefinition("secondaryDataSource", dataSource);
}
private void registerCacheBeans(BeanDefinitionRegistry registry) {
// 注册 Redis 连接工厂
RootBeanDefinition redisConnectionFactory = new RootBeanDefinition(LettuceConnectionFactory.class);
MutablePropertyValues redisProps = new MutablePropertyValues();
redisProps.add("hostName", environment.getProperty("spring.redis.host", "localhost"));
redisProps.add("port", environment.getProperty("spring.redis.port", Integer.class, 6379));
redisConnectionFactory.setPropertyValues(redisProps);
registry.registerBeanDefinition("redisConnectionFactory", redisConnectionFactory);
// 注册 Redis Template
RootBeanDefinition redisTemplate = new RootBeanDefinition(RedisTemplate.class);
redisTemplate.setPropertyValues(new MutablePropertyValues());
registry.registerBeanDefinition("redisTemplate", redisTemplate);
}
}
7. 实际应用场景
7.1 多环境配置管理
// 开发环境特定配置
@Configuration
@Profile("dev")
@ConditionalOnProperty(name = "app.feature.hot-reload", havingValue = "true")
@Import({DevToolsConfig.class, LiveReloadConfig.class})
public class DevelopmentFeaturesConfig {
@Bean
@ConditionalOnMissingBean
public DevelopmentWatcher developmentWatcher() {
return new DevelopmentWatcher();
}
}
// 测试环境配置
@Configuration
@Profile("test")
@ConditionalOnClass(name = "org.testcontainers.Testcontainers")
@Import(TestContainersConfig.class)
public class TestEnvironmentConfig {
@Bean
@ConditionalOnProperty(name = "app.test.mock-external-services", havingValue = "true")
public MockExternalService mockExternalService() {
return new MockExternalService();
}
}
// 生产环境优化配置
@Configuration
@Profile("prod")
@ConditionalOnProperty(name = "app.performance.optimize", havingValue = "true")
@Import({PerformanceConfig.class, MonitoringConfig.class, SecurityHardeningConfig.class})
public class ProductionOptimizationConfig {
@Bean
@ConditionalOnWebApplication
public PerformanceFilter performanceFilter() {
return new PerformanceFilter();
}
}
// 主配置类
@Configuration
@Import({
DevelopmentFeaturesConfig.class,
TestEnvironmentConfig.class,
ProductionOptimizationConfig.class
})
public class EnvironmentAwareConfig {
// 根据环境和条件自动激活相应的配置
}
7.2 特性开关配置
// 特性开关配置
@Configuration
@ConditionalOnExpression("${app.feature.payment.enabled:false}")
@Import({PaymentGatewayConfig.class, BillingConfig.class})
public class PaymentFeatureConfig {
@Bean
@ConditionalOnProperty(name = "app.feature.payment.provider", havingValue = "stripe")
public PaymentProvider stripePaymentProvider() {
return new StripePaymentProvider();
}
@Bean
@ConditionalOnProperty(name = "app.feature.payment.provider", havingValue = "paypal")
public PaymentProvider paypalPaymentProvider() {
return new PayPalPaymentProvider();
}
}
@Configuration
@ConditionalOnExpression("${app.feature.notification.enabled:true}")
@Import({EmailConfig.class, SmsConfig.class, PushNotificationConfig.class})
public class NotificationFeatureConfig {
@Bean
@ConditionalOnProperty(prefix = "app.feature.notification", name = "type", havingValue = "async")
public NotificationService asyncNotificationService() {
return new AsyncNotificationService();
}
@Bean
@ConditionalOnProperty(prefix = "app.feature.notification", name = "type", havingValue = "sync")
public NotificationService syncNotificationService() {
return new SyncNotificationService();
}
}
@Configuration
@ConditionalOnProperty(name = "app.feature.cache.enabled", havingValue = "true")
@Import(CacheConfig.class)
public class CacheFeatureConfig {
@Bean
@ConditionalOnClass(name = "redis.clients.jedis.Jedis")
public CacheManager redisCacheManager() {
return new RedisCacheManager();
}
@Bean
@ConditionalOnMissingClass("redis.clients.jedis.Jedis")
public CacheManager localCacheManager() {
return new ConcurrentMapCacheManager();
}
}
7.3 第三方库自动配置
// Jackson 自动配置
@Configuration
@ConditionalOnClass(ObjectMapper.class)
@ConditionalOnProperty(name = "app.json.provider", havingValue = "jackson", matchIfMissing = true)
@Import({JacksonConfiguration.class})
public class JacksonAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
}
}
// Gson 自动配置
@Configuration
@ConditionalOnClass(Gson.class)
@ConditionalOnProperty(name = "app.json.provider", havingValue = "gson")
@Import({GsonConfiguration.class})
public class GsonAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public Gson gson() {
return new GsonBuilder().setPrettyPrinting().create();
}
}
// 序列化配置选择器
public class SerializationAutoConfiguration implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
List<String> imports = new ArrayList<>();
// 检查类路径和配置决定使用哪种 JSON 库
if (isJacksonAvailableAndPreferred()) {
imports.add("com.example.config.JacksonAutoConfiguration");
} else if (isGsonAvailable()) {
imports.add("com.example.config.GsonAutoConfiguration");
}
return imports.toArray(new String[0]);
}
private boolean isJacksonAvailableAndPreferred() {
try {
Class.forName("com.fasterxml.jackson.databind.ObjectMapper");
// 还可以检查配置属性
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
private boolean isGsonAvailable() {
try {
Class.forName("com.google.gson.Gson");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
8. 最佳实践和注意事项
8.1 条件注解的优先级和组合
@Configuration
// 条件注解的评估顺序:从上到下
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class})
@ConditionalOnProperty(prefix = "app", name = "web.enabled", havingValue = "true", matchIfMissing = true)
@Import(WebMvcConfig.class)
public class WebApplicationConfig {
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "app.web", name = "cache.enabled", havingValue = "true")
public WebContentCache webContentCache() {
return new WebContentCache();
}
}
8.2 条件配置的测试策略
// 条件配置的测试
@SpringBootTest
@TestPropertySource(properties = {
"app.env=test",
"app.feature.cache.enabled=true",
"app.feature.payment.enabled=false"
})
class ConditionalImportTest {
@Autowired(required = false)
private CacheManager cacheManager;
@Autowired(required = false)
private PaymentProvider paymentProvider;
@Test
void testCacheFeatureEnabled() {
assertNotNull(cacheManager, "CacheManager should be configured when cache feature is enabled");
}
@Test
void testPaymentFeatureDisabled() {
assertNull(paymentProvider, "PaymentProvider should not be configured when payment feature is disabled");
}
}
// 条件评估的单元测试
class FeatureToggleConditionTest {
@Test
void testFeatureEnabled() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("feature.advanced-search", "true");
ConditionContext context = mock(ConditionContext.class);
when(context.getEnvironment()).thenReturn(environment);
FeatureToggleCondition condition = new FeatureToggleCondition();
boolean matches = condition.matches(context, mock(AnnotatedTypeMetadata.class));
assertTrue(matches);
}
}
8.3 条件配置的调试和日志
@Slf4j
public class LoggingImportSelector implements ImportSelector, EnvironmentAware {
private Environment environment;
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
List<String> imports = new ArrayList<>();
log.debug("Starting conditional import selection for: {}",
importingClassMetadata.getClassName());
// 记录条件评估过程
if (isCacheEnabled()) {
log.info("Cache feature enabled, importing cache configuration");
imports.add("com.example.config.CacheConfig");
} else {
log.debug("Cache feature disabled, skipping cache configuration");
}
if (isMetricsEnabled()) {
log.info("Metrics feature enabled, importing metrics configuration");
imports.add("com.example.config.MetricsConfig");
}
log.debug("Selected {} configurations to import", imports.size());
return imports.toArray(new String[0]);
}
private boolean isCacheEnabled() {
boolean enabled = environment.getProperty("app.cache.enabled", Boolean.class, false);
log.debug("Cache enabled: {}", enabled);
return enabled;
}
private boolean isMetricsEnabled() {
boolean enabled = environment.getProperty("management.metrics.export.enabled", Boolean.class, false);
log.debug("Metrics enabled: {}", enabled);
return enabled;
}
}
@Import 与 @Profile
Spring @Import 与 @Profile 注解详解
1. 核心概念与关系
@Import 和 @Profile 是 Spring 中用于环境隔离和配置管理的两个重要注解:
- @Import: 控制配置类的导入和组合
- @Profile: 控制配置类或 Bean 在特定环境下的激活
2. @Profile 注解基础
2.1 @Profile 注解定义
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
String[] value();
}
2.2 Profile 激活方式
# application.properties
spring.profiles.active=dev,debug
# 或通过命令行
java -jar app.jar --spring.profiles.active=prod
# 或环境变量
export SPRING_PROFILES_ACTIVE=staging
3. @Import 与 @Profile 的基础组合
3.1 环境特定的配置类导入
// 开发环境配置
@Configuration
@Profile("dev")
public class DevConfig {
@Bean
public DataSource devDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:dev-schema.sql")
.addScript("classpath:dev-data.sql")
.build();
}
@Bean
public Logger devLogger() {
return LoggerFactory.getLogger("DEV-LOGGER");
}
}
// 生产环境配置
@Configuration
@Profile("prod")
public class ProdConfig {
@Bean
public DataSource prodDataSource() {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl("jdbc:mysql://prod-db:3306/app");
ds.setUsername("prod-user");
ds.setPassword("prod-password");
ds.setMaximumPoolSize(20);
return ds;
}
@Bean
public MonitoringService monitoringService() {
return new NewRelicMonitoringService();
}
}
// 测试环境配置
@Configuration
@Profile("test")
public class TestConfig {
@Bean
public DataSource testDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.setName("testdb")
.build();
}
@Bean
@Primary // 测试环境中优先使用 Mock 服务
public ExternalService mockExternalService() {
return new MockExternalService();
}
}
// 主配置类 - 导入所有环境配置
@Configuration
@Import({DevConfig.class, ProdConfig.class, TestConfig.class})
public class EnvironmentConfig {
// 根据激活的 Profile 自动选择对应的配置
}
3.2 多 Profile 组合
// 开发和测试环境共享的配置
@Configuration
@Profile({"dev", "test"})
public class NonProductionConfig {
@Bean
public DevelopmentTools developmentTools() {
return new DevelopmentTools();
}
@Bean
public HotReloadService hotReloadService() {
return new HotReloadService();
}
}
// 仅生产环境的配置
@Configuration
@Profile("prod")
public class ProductionOnlyConfig {
@Bean
public SecurityHardeningService securityHardening() {
return new SecurityHardeningService();
}
@Bean
public PerformanceMonitoring performanceMonitoring() {
return new PerformanceMonitoring();
}
}
// 云环境配置(可以是多个云环境的组合)
@Configuration
@Profile({"aws", "azure", "gcp"})
public class CloudConfig {
@Bean
@Profile("aws")
public CloudService awsCloudService() {
return new AwsCloudService();
}
@Bean
@Profile("azure")
public CloudService azureCloudService() {
return new AzureCloudService();
}
@Bean
@Profile("gcp")
public CloudService gcpCloudService() {
return new GcpCloudService();
}
}
4. 高级用法:Profile 与 Import 的复杂组合
4.1 Profile 驱动的 ImportSelector
public class ProfileBasedImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 获取当前激活的 Profiles
Environment environment = getEnvironment();
String[] activeProfiles = environment.getActiveProfiles();
List<String> imports = new ArrayList<>();
// 根据 Profile 选择导入的配置
if (containsProfile(activeProfiles, "dev")) {
imports.add("com.example.config.dev.DevToolsConfig");
imports.add("com.example.config.dev.HotReloadConfig");
}
if (containsProfile(activeProfiles, "prod")) {
imports.add("com.example.config.prod.MonitoringConfig");
imports.add("com.example.config.prod.SecurityConfig");
imports.add("com.example.config.prod.PerformanceConfig");
}
if (containsProfile(activeProfiles, "cloud")) {
imports.add("com.example.config.cloud.CloudInfrastructureConfig");
}
if (containsProfile(activeProfiles, "multi-tenant")) {
imports.add("com.example.config.tenant.TenantConfig");
imports.add("com.example.config.tenant.IsolationConfig");
}
// 默认配置
imports.add("com.example.config.common.CoreConfig");
return imports.toArray(new String[0]);
}
private boolean containsProfile(String[] activeProfiles, String profile) {
return Arrays.stream(activeProfiles).anyMatch(p -> p.equals(profile));
}
private Environment getEnvironment() {
// 在实际应用中,可以通过 ApplicationContext 获取 Environment
return SpringApplication.run().getEnvironment();
}
}
// 使用 Profile 驱动的导入
@Configuration
@Import(ProfileBasedImportSelector.class)
public class DynamicProfileConfig {
// 根据激活的 Profile 动态导入配置
}
4.2 Profile 条件化的 ImportBeanDefinitionRegistrar
public class ProfileAwareBeanRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private Environment environment;
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
String[] activeProfiles = environment.getActiveProfiles();
// 根据 Profile 注册不同的 Bean
if (isProfileActive(activeProfiles, "dev")) {
registerDevelopmentBeans(registry);
}
if (isProfileActive(activeProfiles, "prod")) {
registerProductionBeans(registry);
}
if (isProfileActive(activeProfiles, "cloud")) {
registerCloudBeans(registry);
}
// 始终注册的通用 Bean
registerCommonBeans(registry);
}
private void registerDevelopmentBeans(BeanDefinitionRegistry registry) {
RootBeanDefinition devTools = new RootBeanDefinition(DevelopmentTools.class);
registry.registerBeanDefinition("developmentTools", devTools);
RootBeanDefinition mockServices = new RootBeanDefinition(MockServiceFactory.class);
registry.registerBeanDefinition("mockServiceFactory", mockServices);
}
private void registerProductionBeans(BeanDefinitionRegistry registry) {
RootBeanDefinition monitoring = new RootBeanDefinition(ProductionMonitoring.class);
registry.registerBeanDefinition("productionMonitoring", monitoring);
RootBeanDefinition security = new RootBeanDefinition(SecurityEnhancement.class);
registry.registerBeanDefinition("securityEnhancement", security);
}
private void registerCloudBeans(BeanDefinitionRegistry registry) {
RootBeanDefinition cloudConfig = new RootBeanDefinition(CloudConfiguration.class);
registry.registerBeanDefinition("cloudConfiguration", cloudConfig);
}
private void registerCommonBeans(BeanDefinitionRegistry registry) {
RootBeanDefinition commonService = new RootBeanDefinition(CommonService.class);
registry.registerBeanDefinition("commonService", commonService);
}
private boolean isProfileActive(String[] activeProfiles, String profile) {
return Arrays.stream(activeProfiles).anyMatch(profile::equals);
}
}
5. 实际应用场景
5.1 多环境数据库配置
// 数据库通用配置
@Configuration
public abstract class AbstractDatabaseConfig {
protected abstract DataSource createDataSource();
@Bean
public DataSource dataSource() {
return createDataSource();
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
// 开发环境数据库配置
@Configuration
@Profile("dev")
@Import(DevDatabaseConfig.class)
public class DevDatabaseConfig extends AbstractDatabaseConfig {
@Override
protected DataSource createDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.setName("devdb")
.addScript("classpath:schema-dev.sql")
.addScript("classpath:data-dev.sql")
.build();
}
@Bean
@Profile("dev")
public DatabaseConsole databaseConsole() {
return new H2Console();
}
}
// 生产环境数据库配置
@Configuration
@Profile("prod")
@Import(ProdDatabaseConfig.class)
public class ProdDatabaseConfig extends AbstractDatabaseConfig {
@Value("${datasource.url}")
private String url;
@Value("${datasource.username}")
private String username;
@Value("${datasource.password}")
private String password;
@Override
protected DataSource createDataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setMaximumPoolSize(20);
dataSource.setMinimumIdle(5);
return dataSource;
}
@Bean
@Profile("prod")
public DatabaseMonitor databaseMonitor() {
return new ProductionDatabaseMonitor();
}
}
// 测试环境数据库配置
@Configuration
@Profile("test")
@Import(TestDatabaseConfig.class)
public class TestDatabaseConfig extends AbstractDatabaseConfig {
@Override
protected DataSource createDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.setName("testdb")
.addScript("classpath:schema-test.sql")
.build();
}
@Bean
@Profile("test")
@Primary // 测试环境中使用内存数据库
public DataSource testDataSource() {
return createDataSource();
}
}
5.2 微服务环境配置
// 本地开发环境(单机模式)
@Configuration
@Profile("local")
@Import({LocalServiceDiscovery.class, LocalConfigServer.class, LocalMessageQueue.class})
public class LocalDevelopmentConfig {
@Bean
@Profile("local")
public ServiceRegistry localServiceRegistry() {
return new InMemoryServiceRegistry();
}
@Bean
@Profile("local")
public MessageQueue localMessageQueue() {
return new EmbeddedActiveMQ();
}
}
// Docker 开发环境
@Configuration
@Profile("docker")
@Import({DockerServiceDiscovery.class, DockerNetworkConfig.class})
public class DockerDevelopmentConfig {
@Bean
@Profile("docker")
public ServiceRegistry dockerServiceRegistry() {
return new ConsulServiceRegistry();
}
@Bean
@Profile("docker")
public NetworkConfiguration dockerNetwork() {
return new DockerNetworkConfiguration();
}
}
// Kubernetes 生产环境
@Configuration
@Profile("k8s")
@Import({KubernetesDiscovery.class, K8sConfigMap.class, K8sSecrets.class})
public class KubernetesProductionConfig {
@Bean
@Profile("k8s")
public ServiceRegistry kubernetesServiceRegistry() {
return new KubernetesServiceRegistry();
}
@Bean
@Profile("k8s")
public ConfigSource k8sConfigSource() {
return new KubernetesConfigMapSource();
}
}
// 主配置 - 组合所有环境
@Configuration
@Import({
LocalDevelopmentConfig.class,
DockerDevelopmentConfig.class,
KubernetesProductionConfig.class
})
public class MicroserviceEnvironmentConfig {
// 根据 Profile 自动选择部署环境配置
}
5.3 功能模块的 Profile 控制
// 缓存模块配置
@Configuration
@Profile("cache-redis")
@Import(RedisCacheConfig.class)
public class RedisCacheConfiguration {
@Bean
@Profile("cache-redis")
public CacheManager redisCacheManager() {
return new RedisCacheManager(redisConnectionFactory());
}
@Bean
@Profile("cache-redis")
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory();
}
}
@Configuration
@Profile("cache-hazelcast")
@Import(HazelcastCacheConfig.class)
public class HazelcastCacheConfiguration {
@Bean
@Profile("cache-hazelcast")
public CacheManager hazelcastCacheManager() {
return new HazelcastCacheManager(hazelcastInstance());
}
@Bean
@Profile("cache-hazelcast")
public HazelcastInstance hazelcastInstance() {
return Hazelcast.newHazelcastInstance();
}
}
// 消息队列模块配置
@Configuration
@Profile("mq-rabbit")
@Import(RabbitMQConfig.class)
public class RabbitMQConfiguration {
@Bean
@Profile("mq-rabbit")
public ConnectionFactory rabbitConnectionFactory() {
CachingConnectionFactory factory = new CachingConnectionFactory();
factory.setHost("localhost");
return factory;
}
}
@Configuration
@Profile("mq-kafka")
@Import(KafkaConfig.class)
public class KafkaConfiguration {
@Bean
@Profile("mq-kafka")
public ProducerFactory<String, String> producerFactory() {
return new DefaultKafkaProducerFactory<>(producerConfigs());
}
}
// 安全模块配置
@Configuration
@Profile("security-jwt")
@Import(JwtSecurityConfig.class)
public class JwtSecurityConfiguration {
@Bean
@Profile("security-jwt")
public TokenProvider jwtTokenProvider() {
return new JwtTokenProvider();
}
}
@Configuration
@Profile("security-oauth2")
@Import(OAuth2SecurityConfig.class)
public class OAuth2SecurityConfiguration {
@Bean
@Profile("security-oauth2")
public OAuth2AuthorizedClientService authorizedClientService() {
return new InMemoryOAuth2AuthorizedClientService();
}
}
6. Profile 表达式和复杂条件
6.1 Profile 表达式
// 非生产环境
@Configuration
@Profile("!prod")
@Import(DevelopmentFeaturesConfig.class)
public class NonProductionConfiguration {
@Bean
@Profile("!prod")
public DevelopmentTools developmentTools() {
return new DevelopmentTools();
}
}
// 多个 Profile 的组合
@Configuration
@Profile({"dev & local", "test & embedded"})
@Import(EmbeddedServicesConfig.class)
public class EmbeddedEnvironmentConfiguration {
@Bean
@Profile("dev & local")
public LocalDevelopmentService localDevService() {
return new LocalDevelopmentService();
}
}
// Profile 表达式
@Configuration
@Profile("cloud & (aws | azure)")
@Import(CloudConfig.class)
public class CloudProviderConfiguration {
@Bean
@Profile("cloud & aws")
public CloudService awsCloudService() {
return new AwsCloudService();
}
@Bean
@Profile("cloud & azure")
public CloudService azureCloudService() {
return new AzureCloudService();
}
}
6.2 自定义 Profile 条件
// 自定义 Profile 条件注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(ClusterProfileCondition.class)
public @interface ConditionalOnClusterProfile {
String value();
}
// 自定义条件实现
public class ClusterProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(
ConditionalOnClusterProfile.class.getName());
if (attributes == null) {
return false;
}
String expectedProfile = (String) attributes.get("value");
Environment environment = context.getEnvironment();
// 检查集群特定的 Profile
String clusterProfile = environment.getProperty("app.cluster.profile");
return expectedProfile.equals(clusterProfile);
}
}
// 使用自定义 Profile 条件
@Configuration
@ConditionalOnClusterProfile("primary")
@Import(PrimaryClusterConfig.class)
public class PrimaryClusterConfiguration {
@Bean
@ConditionalOnClusterProfile("primary")
public ClusterService primaryClusterService() {
return new PrimaryClusterService();
}
}
@Configuration
@ConditionalOnClusterProfile("secondary")
@Import(SecondaryClusterConfig.class)
public class SecondaryClusterConfiguration {
@Bean
@ConditionalOnClusterProfile("secondary")
public ClusterService secondaryClusterService() {
return new SecondaryClusterService();
}
}
7. 最佳实践和注意事项
7.1 Profile 命名规范
// 清晰的 Profile 命名
public class ProfileConstants {
public static final String LOCAL_DEVELOPMENT = "local";
public static final String DOCKER_DEVELOPMENT = "docker";
public static final String CI_TEST = "ci-test";
public static final String STAGING = "staging";
public static final String PRODUCTION = "prod";
public static final String AWS_CLOUD = "aws";
public static final String AZURE_CLOUD = "azure";
}
// 使用常量提高可维护性
@Configuration
@Profile(ProfileConstants.LOCAL_DEVELOPMENT)
@Import(LocalConfig.class)
public class LocalDevelopmentConfiguration {
// 本地开发配置
}
@Configuration
@Profile(ProfileConstants.PRODUCTION)
@Import(ProductionConfig.class)
public class ProductionConfiguration {
// 生产环境配置
}
7.2 Profile 的继承和组合
// 基础 Profile 配置
@Configuration
@Profile("database-mysql")
public abstract class AbstractMySqlConfig {
protected abstract String getDatabaseUrl();
@Bean
public DataSource dataSource() {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl(getDatabaseUrl());
// 公共配置
return ds;
}
}
// 具体环境的 MySQL 配置
@Configuration
@Profile("dev & database-mysql")
public class DevMySqlConfig extends AbstractMySqlConfig {
@Override
protected String getDatabaseUrl() {
return "jdbc:mysql://localhost:3306/devdb";
}
}
@Configuration
@Profile("prod & database-mysql")
public class ProdMySqlConfig extends AbstractMySqlConfig {
@Value("${mysql.prod.url}")
private String databaseUrl;
@Override
protected String getDatabaseUrl() {
return databaseUrl;
}
}
7.3 Profile 配置的测试
@SpringBootTest
// 测试不同的 Profile 组合
@TestPropertySource(properties = "spring.profiles.active=test,embedded")
class ProfileConfigurationTest {
@Autowired(required = false)
private EmbeddedDatabase embeddedDatabase;
@Autowired(required = false)
private ProductionDatabase productionDatabase;
@Test
void testEmbeddedDatabaseInTestProfile() {
assertNotNull(embeddedDatabase, "Embedded database should be available in test profile");
assertNull(productionDatabase, "Production database should not be available in test profile");
}
}
// Profile 条件的单元测试
class ProfileConditionTest {
@Test
void testProfileConditionMatches() {
MockEnvironment environment = new MockEnvironment();
environment.setActiveProfiles("dev", "local");
ConditionContext context = mock(ConditionContext.class);
when(context.getEnvironment()).thenReturn(environment);
ProfileCondition condition = new ProfileCondition();
// 测试匹配逻辑
AnnotatedTypeMetadata metadata = mock(AnnotatedTypeMetadata.class);
when(metadata.getAnnotationAttributes(Profile.class.getName()))
.thenReturn(Collections.singletonMap("value", new String[]{"dev", "local"}));
assertTrue(condition.matches(context, metadata));
}
}
总结
@Import 和 @Profile 的组合为 Spring 应用提供了强大的环境隔离和配置管理能力:
核心优势:
- 环境隔离: 不同环境使用不同的配置
- 配置组合: 通过 Profile 组合实现灵活的配置策略
- 部署适配: 适应各种部署环境(本地、Docker、Kubernetes、云平台)
- 功能开关: 通过 Profile 控制功能模块的启用
使用模式:
- 环境特定配置:
@Profile("env")+@Import - 功能模块控制: Profile 控制特定功能模块的导入
- 条件化注册: Profile 与 ImportBeanDefinitionRegistrar 结合
- 动态选择: Profile 驱动的 ImportSelector
最佳实践:
- 使用清晰的 Profile 命名规范
- 避免 Profile 冲突和矛盾组合
- 提供合理的默认配置
- 验证 Profile 配置的合理性
- 完善的日志和调试支持
这种机制使得 Spring 应用能够轻松适应从开发到生产的各种环境,是现代云原生应用开发的重要基础。