很多开发者在使用 Spring 时会疑惑:为什么@Bean 方法要写在@Configuration 类中?不写行不行?今天通过实际代码来彻底搞清楚这个问题。
先看一个诡异的现象
java
import org.springframework.stereotype.Component;
import org.springframework.context.annotation.Bean;
import lombok.extern.slf4j.Slf4j;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.jdbc.core.JdbcTemplate;
// 场景1:@Bean在普通类中
@Component
@Slf4j
public class AppConfig {
@Bean
public DataSource dataSource() {
log.info("Creating DataSource instance");
return new HikariDataSource();
}
@Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource());
}
}
运行后你会发现日志打印了两次"Creating DataSource instance"!这意味着创建了两个 DataSource 实例。
java
// 场景2:@Bean在@Configuration类中
@Configuration
@Slf4j
public class AppConfig {
@Bean
public DataSource dataSource() {
log.info("Creating DataSource instance");
return new HikariDataSource();
}
@Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource());
}
}
这次只打印了一次,符合我们的预期。
背后的原理
区别的根源在于 Spring 对这两种类的处理方式完全不同:

1. @Configuration 类的 CGLIB 代理机制
Spring 会为@Configuration 类创建 CGLIB 代理,我们可以验证这一点:
java
@Configuration
@Slf4j
public class DatabaseConfig {
@PostConstruct
public void showActualClass() {
// 打印实际的类名,展示代理效果
log.info("Actual class: {}", this.getClass().getName());
// 输出类似:DatabaseConfig$$EnhancerBySpringCGLIB$$xxxxx
}
@Bean(name = "primaryDataSource",
initMethod = "init",
destroyMethod = "close")
@Primary
@DependsOn("configProperties")
public DataSource dataSource() {
try {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/test");
config.setUsername("root");
config.setPassword("******"); // 不记录密码
config.setMaximumPoolSize(10);
// 安全的日志记录,不暴露敏感信息
log.debug("DataSource configuration - URL: {}, MaxPoolSize: {}",
config.getJdbcUrl(), config.getMaximumPoolSize());
return new HikariDataSource(config);
} catch (Exception e) {
log.error("Failed to create DataSource", e);
throw new BeanCreationException("DataSource creation failed", e);
}
}
@Bean
public JdbcTemplate jdbcTemplate() {
// 这里调用dataSource()方法会被代理拦截
// 返回的是容器中的单例Bean,而不是新创建的
return new JdbcTemplate(dataSource());
}
@Bean
public PlatformTransactionManager transactionManager() {
// 同样,这里也会返回容器中的同一个dataSource实例
return new DataSourceTransactionManager(dataSource());
}
}
2. @Bean 方法参数注入机制
这是一个重要但经常被忽视的特性:
java
@Configuration
@Slf4j
public class BeanMethodParameterInjection {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserService userService(
UserRepository repository, // 自动注入容器中的Bean
PasswordEncoder encoder, // 注入上面定义的Bean
@Value("${user.default.role}") String defaultRole) { // 注入配置值
log.info("Creating UserService with injected dependencies");
return new UserService(repository, encoder, defaultRole);
}
@Bean
@ConditionalOnProperty("app.cache.enabled")
public CacheManager cacheManager(
@Autowired(required = false) RedisConnectionFactory redisFactory) {
if (redisFactory != null) {
log.info("Using Redis cache manager");
return RedisCacheManager.builder(redisFactory).build();
} else {
log.info("Using simple cache manager");
return new ConcurrentMapCacheManager();
}
}
}
3. 实际测试验证
java
@SpringBootTest
@Slf4j
public class BeanCreationVerificationTest {
@Autowired
private ApplicationContext context;
@Test
public void verifyBeanSingleton() {
// 从容器获取bean
DataSource ds1 = context.getBean("dataSource", DataSource.class);
JdbcTemplate jdbc = context.getBean(JdbcTemplate.class);
TransactionManager tm = context.getBean(TransactionManager.class);
// 验证是否是同一实例
assertThat(jdbc.getDataSource()).isSameAs(ds1);
assertThat(((DataSourceTransactionManager)tm).getDataSource()).isSameAs(ds1);
log.info("Verification passed: All components use the same DataSource instance");
// 验证CGLIB代理
Object configBean = context.getBean("databaseConfig");
assertThat(configBean.getClass().getName()).contains("CGLIB");
log.info("Configuration class is proxied: {}", configBean.getClass().getName());
}
@Test
public void testDataSourceCreation(@Autowired DataSource dataSource) {
assertThat(dataSource).isNotNull();
assertThat(dataSource).isInstanceOf(HikariDataSource.class);
HikariDataSource hikari = (HikariDataSource) dataSource;
assertThat(hikari.getMaximumPoolSize()).isEqualTo(10);
}
}
4. Spring 5.2+的新特性:proxyBeanMethods
Spring 5.2 引入了更灵活的配置方式:
java
// Full模式(默认)- 创建CGLIB代理
@Configuration // 等同于 @Configuration(proxyBeanMethods = true)
public class FullModeConfig {
// 支持内部方法调用,保证单例
}
// Lite模式 - 不创建代理,性能更好
@Configuration(proxyBeanMethods = false)
public class LiteModeConfig {
// 适用于没有内部方法调用的场景
}
@Import 机制的深入理解
1. 基本的@Import 使用
java
// @Import的使用场景
@Configuration
@Import({DataSourceConfig.class, SecurityConfig.class})
@Slf4j
public class MainConfiguration {
// 通过@Import导入其他配置类
}
2. 使用 ImportSelector 动态导入
java
@Configuration
@Import(EnvironmentImportSelector.class)
public class DynamicImportConfig {
public static class EnvironmentImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 根据环境动态选择要导入的配置类
String profile = System.getProperty("spring.profiles.active", "dev");
log.info("Active profile: {}, selecting appropriate configuration", profile);
if ("prod".equals(profile)) {
return new String[]{
ProductionDataSourceConfig.class.getName(),
ProductionSecurityConfig.class.getName()
};
}
return new String[]{
DevelopmentDataSourceConfig.class.getName(),
DevelopmentSecurityConfig.class.getName()
};
}
}
}
3. 使用 ImportBeanDefinitionRegistrar
java
@Configuration
@Import(DynamicBeanRegistrar.class)
public class DynamicBeanConfig {
public static class DynamicBeanRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 动态注册Bean定义
Map<String, Object> attrs = metadata.getAnnotationAttributes(
EnableDynamicBeans.class.getName());
if (attrs != null) {
String[] beanNames = (String[]) attrs.get("value");
for (String beanName : beanNames) {
GenericBeanDefinition beanDef = new GenericBeanDefinition();
beanDef.setBeanClass(DynamicService.class);
beanDef.setScope(BeanDefinition.SCOPE_SINGLETON);
beanDef.getConstructorArgumentValues()
.addGenericArgumentValue(beanName);
registry.registerBeanDefinition(beanName, beanDef);
log.info("Dynamically registered bean: {}", beanName);
}
}
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(DynamicBeanRegistrar.class)
public @interface EnableDynamicBeans {
String[] value() default {};
}
}
@Slf4j
public class DynamicService {
private final String serviceName;
public DynamicService(String serviceName) {
this.serviceName = serviceName;
log.info("Creating DynamicService: {}", serviceName);
}
public String getServiceName() {
return serviceName;
}
public void performOperation() {
log.info("Service {} performing operation", serviceName);
}
}
@Configuration 类的继承特性
java
// 基础配置类
@Configuration
public abstract class BaseDataSourceConfig {
@Value("${db.driver:com.mysql.cj.jdbc.Driver}")
protected String driverClassName;
@Autowired
protected PasswordDecryptor passwordDecryptor;
protected HikariConfig createBaseConfig() {
HikariConfig config = new HikariConfig();
config.setDriverClassName(driverClassName);
config.setConnectionTimeout(30000);
config.setIdleTimeout(600000);
config.setMaxLifetime(1800000);
return config;
}
// 子类必须实现
public abstract DataSource dataSource();
}
// 开发环境配置
@Configuration
@Profile("development")
public class DevDataSourceConfig extends BaseDataSourceConfig {
@Bean
@Override
public DataSource dataSource() {
HikariConfig config = createBaseConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/dev_db");
config.setUsername("dev_user");
config.setPassword("dev_pass");
config.setMaximumPoolSize(5);
return new HikariDataSource(config);
}
}
// 生产环境配置
@Configuration
@Profile("production")
public class ProdDataSourceConfig extends BaseDataSourceConfig {
@Value("${datasource.password.encrypted}")
private String encryptedPassword;
@Bean
@Override
public DataSource dataSource() {
HikariConfig config = createBaseConfig();
config.setJdbcUrl("jdbc:mysql://prod-db:3306/prod_db");
config.setUsername("prod_user");
config.setPassword(passwordDecryptor.decrypt(encryptedPassword));
config.setMaximumPoolSize(50);
config.setLeakDetectionThreshold(60000);
return new HikariDataSource(config);
}
}
// 密码解密组件
@Component
@Slf4j
public class PasswordDecryptor {
@Value("${encryption.key}")
private String encryptionKey;
public String decrypt(String encrypted) {
try {
// 使用AES解密
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec secretKey = new SecretKeySpec(
encryptionKey.getBytes(StandardCharsets.UTF_8), "AES");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encrypted));
return new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
log.error("Password decryption failed", e);
throw new IllegalStateException("Cannot decrypt password", e);
}
}
}
常见配置错误和解决方案
java
@Configuration
@Slf4j
public class CommonConfigurationMistakes {
// ❌ 错误:在@Configuration中使用@Autowired字段
@Autowired
private SomeService someService; // 不推荐
// ✅ 正确:通过方法参数注入
@Bean
public AnotherService anotherService(SomeService someService) {
return new AnotherService(someService);
}
// ❌ 错误:创建原型Bean时的内部调用
@Bean
@Scope("prototype")
public RequestHandler requestHandler() {
return new DefaultRequestHandler();
}
@Bean
public RequestProcessor processor() {
// 错误:每次都返回同一个实例,即使requestHandler是prototype
return new RequestProcessor(requestHandler());
}
// ✅ 正确:使用Provider或ObjectFactory
@Bean
public RequestProcessor correctProcessor(Provider<RequestHandler> handlerProvider) {
return new RequestProcessor(handlerProvider);
}
// 或者使用ObjectFactory
@Bean
public RequestProcessor processorWithFactory(ObjectFactory<RequestHandler> handlerFactory) {
return new RequestProcessor(() -> handlerFactory.getObject());
}
}
public interface RequestHandler {
void handle(Request request);
}
@Component
@Scope("prototype")
@Slf4j
public class DefaultRequestHandler implements RequestHandler {
private final String handlerId = UUID.randomUUID().toString();
@Override
public void handle(Request request) {
log.info("Handler {} processing request: {}", handlerId, request.getId());
}
}
public class RequestProcessor {
private final Provider<RequestHandler> handlerProvider;
public RequestProcessor(Provider<RequestHandler> handlerProvider) {
this.handlerProvider = handlerProvider;
}
public RequestProcessor(Supplier<RequestHandler> handlerSupplier) {
this.handlerProvider = handlerSupplier::get;
}
public void processRequest(Request request) {
// 每次获取新的handler实例(如果是prototype作用域)
RequestHandler handler = handlerProvider.get();
handler.handle(request);
}
}
@Data
@AllArgsConstructor
public class Request {
private String id;
private String content;
private LocalDateTime timestamp;
}
BeanFactoryPostProcessor 的高级用法
java
@Configuration
@Slf4j
public class AdvancedBeanCustomization {
@Bean
public static BeanFactoryPostProcessor beanFactoryPostProcessor() {
return beanFactory -> {
// 修改所有DataSource的Bean定义
Arrays.stream(beanFactory.getBeanDefinitionNames())
.filter(name -> {
try {
BeanDefinition bd = beanFactory.getBeanDefinition(name);
String className = bd.getBeanClassName();
return className != null &&
Class.forName(className).isAssignableFrom(DataSource.class);
} catch (Exception e) {
return false;
}
})
.forEach(name -> {
BeanDefinition bd = beanFactory.getBeanDefinition(name);
// 设置为非懒加载,确保数据源立即初始化
bd.setLazyInit(false);
// 添加属性
MutablePropertyValues props = bd.getPropertyValues();
props.add("loginTimeout", 30);
log.info("Customized DataSource bean: {}", name);
});
};
}
@Bean
public static BeanDefinitionRegistryPostProcessor customRegistryProcessor() {
return new BeanDefinitionRegistryPostProcessor() {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// 在这里可以添加、修改或删除Bean定义
if (!registry.containsBeanDefinition("auditDataSource")) {
GenericBeanDefinition beanDef = new GenericBeanDefinition();
beanDef.setBeanClass(HikariDataSource.class);
beanDef.setLazyInit(true);
// 使用构造函数注入
ConstructorArgumentValues constructorArgs = new ConstructorArgumentValues();
constructorArgs.addGenericArgumentValue(createAuditDataSourceConfig());
beanDef.setConstructorArgumentValues(constructorArgs);
registry.registerBeanDefinition("auditDataSource", beanDef);
log.info("Registered audit DataSource");
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// 进一步处理已注册的Bean定义
}
private HikariConfig createAuditDataSourceConfig() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:h2:mem:audit");
config.setMaximumPoolSize(2);
return config;
}
};
}
}
复杂的条件装配场景
java
@Configuration
@Slf4j
public class ComplexConditionalConfig {
// 多条件组合
@Bean
@ConditionalOnExpression("${app.feature.enabled:false} && ${app.env:dev} == 'prod'")
@ConditionalOnClass(name = "com.example.AdvancedFeature")
@ConditionalOnMissingBean(name = "customFeatureService")
public FeatureService advancedFeatureService() {
log.info("Creating advanced feature service");
return new AdvancedFeatureService();
}
// 自定义条件
@Bean
@Conditional(OnDatabaseTypeCondition.class)
public DatabaseOptimizer databaseOptimizer() {
return new DatabaseOptimizer();
}
// 自定义条件实现
public static class OnDatabaseTypeCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String dbUrl = context.getEnvironment().getProperty("spring.datasource.url", "");
boolean matches = dbUrl.contains("mysql") || dbUrl.contains("postgresql");
if (matches) {
log.debug("Database type condition matched for URL: {}", dbUrl);
}
return matches;
}
}
// Profile和条件的组合使用
@Bean
@Profile("!test")
@ConditionalOnProperty(
prefix = "app.cache",
name = "type",
havingValue = "redis",
matchIfMissing = false
)
public CacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(60))
.disableCachingNullValues()
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.transactionAware()
.build();
}
}
高级错误处理和恢复机制
java
@Configuration
@Slf4j
public class FaultTolerantConfiguration {
@Bean
public DataSource faultTolerantDataSource(
@Value("${datasource.urls}") List<String> dbUrls,
@Value("${datasource.retry.max:3}") int maxRetries,
@Value("${datasource.retry.delay:1000}") long retryDelay) {
List<DataSource> dataSources = createDataSources(dbUrls);
RoutingDataSource routingDataSource = new RoutingDataSource(dataSources, maxRetries, retryDelay);
Map<Object, Object> targetDataSources = new HashMap<>();
for (int i = 0; i < dataSources.size(); i++) {
targetDataSources.put(i, dataSources.get(i));
}
routingDataSource.setTargetDataSources(targetDataSources);
routingDataSource.setDefaultTargetDataSource(dataSources.get(0));
routingDataSource.afterPropertiesSet(); // 重要:初始化路由
return routingDataSource;
}
private List<DataSource> createDataSources(List<String> urls) {
return urls.stream()
.map(url -> {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(url);
config.setMaximumPoolSize(5);
config.setConnectionTimeout(5000);
config.setPoolName("FaultTolerant-" + urls.indexOf(url));
return new HikariDataSource(config);
})
.collect(Collectors.toList());
}
// 独立的路由数据源实现
public static class RoutingDataSource extends AbstractRoutingDataSource {
private final AtomicInteger currentIndex = new AtomicInteger(0);
private final List<DataSource> dataSources;
private final int maxRetries;
private final long retryDelay;
public RoutingDataSource(List<DataSource> dataSources, int maxRetries, long retryDelay) {
this.dataSources = dataSources;
this.maxRetries = maxRetries;
this.retryDelay = retryDelay;
}
@Override
protected Object determineCurrentLookupKey() {
return currentIndex.get() % dataSources.size();
}
@Override
public Connection getConnection() throws SQLException {
SQLException lastException = null;
for (int attempt = 0; attempt < dataSources.size() * maxRetries; attempt++) {
try {
int index = currentIndex.get() % dataSources.size();
Connection conn = dataSources.get(index).getConnection();
// 验证连接
if (conn.isValid(2)) {
return conn;
}
} catch (SQLException e) {
lastException = e;
log.warn("Failed to get connection from DataSource {}, attempt {}",
currentIndex.get() % dataSources.size(), attempt + 1, e);
// 切换到下一个数据源
currentIndex.incrementAndGet();
// 延迟重试
if (attempt < dataSources.size() * maxRetries - 1) {
try {
Thread.sleep(retryDelay);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new SQLException("Interrupted during retry", ie);
}
}
}
}
throw new SQLException("All DataSources failed after " +
dataSources.size() * maxRetries + " attempts", lastException);
}
}
}
配置验证框架
java
@Component
@Slf4j
public class ConfigurationValidationFramework {
@Autowired
private ApplicationContext context;
@EventListener
public void validateOnStartup(ApplicationStartedEvent event) {
List<ConfigurationIssue> issues = new ArrayList<>();
// 验证DataSource配置
validateDataSources(issues);
// 验证循环依赖
validateCircularDependencies(issues);
// 验证Bean作用域
validateBeanScopes(issues);
// 生成报告
generateValidationReport(issues);
}
private void validateDataSources(List<ConfigurationIssue> issues) {
Map<String, DataSource> dataSources = context.getBeansOfType(DataSource.class);
dataSources.forEach((name, ds) -> {
try (Connection conn = ds.getConnection()) {
DatabaseMetaData metadata = conn.getMetaData();
log.debug("DataSource '{}' connected to: {} {}",
name, metadata.getDatabaseProductName(),
metadata.getDatabaseProductVersion());
} catch (SQLException e) {
issues.add(new ConfigurationIssue(
Severity.ERROR,
String.format("DataSource '%s' validation failed: %s", name, e.getMessage())
));
}
});
}
private void validateCircularDependencies(List<ConfigurationIssue> issues) {
// 检测循环依赖
ConfigurableListableBeanFactory beanFactory =
(ConfigurableListableBeanFactory) context.getAutowireCapableBeanFactory();
Set<String> beansInCreation = new HashSet<>();
Arrays.stream(beanFactory.getBeanDefinitionNames()).forEach(beanName -> {
if (beanFactory.isCurrentlyInCreation(beanName)) {
beansInCreation.add(beanName);
}
});
if (!beansInCreation.isEmpty()) {
issues.add(new ConfigurationIssue(
Severity.WARNING,
"Potential circular dependencies detected in beans: " + beansInCreation
));
}
}
private void validateBeanScopes(List<ConfigurationIssue> issues) {
ConfigurableListableBeanFactory beanFactory =
(ConfigurableListableBeanFactory) context.getAutowireCapableBeanFactory();
// 检查prototype bean是否被singleton bean引用
Arrays.stream(context.getBeanDefinitionNames()).forEach(beanName -> {
try {
BeanDefinition bd = beanFactory.getBeanDefinition(beanName);
if (bd.isSingleton()) {
// 检查依赖
String[] dependencies = beanFactory.getDependenciesForBean(beanName);
for (String dep : dependencies) {
if (beanFactory.containsBeanDefinition(dep)) {
BeanDefinition depBd = beanFactory.getBeanDefinition(dep);
if (depBd.isPrototype()) {
issues.add(new ConfigurationIssue(
Severity.WARNING,
String.format("Singleton bean '%s' depends on prototype bean '%s'",
beanName, dep)
));
}
}
}
}
} catch (Exception e) {
log.debug("Error checking bean scope for: {}", beanName, e);
}
});
}
private void generateValidationReport(List<ConfigurationIssue> issues) {
if (issues.isEmpty()) {
log.info("Configuration validation passed - no issues found");
return;
}
Map<Severity, List<ConfigurationIssue>> groupedIssues =
issues.stream().collect(Collectors.groupingBy(ConfigurationIssue::getSeverity));
log.warn("=== Configuration Validation Report ===");
groupedIssues.forEach((severity, severityIssues) -> {
log.warn("{}: {} issues", severity, severityIssues.size());
severityIssues.forEach(issue -> log.warn(" - {}", issue.getMessage()));
});
}
@Data
@AllArgsConstructor
static class ConfigurationIssue {
private Severity severity;
private String message;
}
enum Severity {
INFO, WARNING, ERROR
}
}
完整的多数据源配置示例
java
@Configuration
@EnableTransactionManagement
@Slf4j
public class MultiDataSourceConfiguration {
@Bean
@Primary
@ConfigurationProperties("spring.datasource.primary")
public DataSource primaryDataSource() {
log.info("Creating primary DataSource");
return DataSourceBuilder.create()
.type(HikariDataSource.class)
.build();
}
@Bean
@ConfigurationProperties("spring.datasource.secondary")
public DataSource secondaryDataSource() {
log.info("Creating secondary DataSource");
return DataSourceBuilder.create()
.type(HikariDataSource.class)
.build();
}
@Bean
@Primary
public JdbcTemplate primaryJdbcTemplate(@Qualifier("primaryDataSource") DataSource ds) {
return new JdbcTemplate(ds);
}
@Bean
public JdbcTemplate secondaryJdbcTemplate(@Qualifier("secondaryDataSource") DataSource ds) {
return new JdbcTemplate(ds);
}
@Bean
@Primary
public PlatformTransactionManager primaryTransactionManager(
@Qualifier("primaryDataSource") DataSource ds) {
return new DataSourceTransactionManager(ds);
}
@Bean
public PlatformTransactionManager secondaryTransactionManager(
@Qualifier("secondaryDataSource") DataSource ds) {
return new DataSourceTransactionManager(ds);
}
}
增强的监控和诊断
java
@Configuration
@EnableMBeanExport
@Slf4j
@ConditionalOnProperty(name = "app.monitoring.enabled", havingValue = "true")
public class EnhancedMonitoringConfig {
@Autowired
private ApplicationContext applicationContext;
@Bean
@ConditionalOnClass(MeterRegistry.class)
public MeterBinder configurationMetrics() {
return registry -> {
// 监控配置加载时间
Timer.Sample sample = Timer.start(registry);
// 注册配置相关的指标
registry.gauge("configuration.beans.total",
applicationContext, ctx -> ctx.getBeanDefinitionCount());
registry.gauge("configuration.beans.lazy",
applicationContext, ctx -> {
ConfigurableListableBeanFactory factory =
(ConfigurableListableBeanFactory) ctx.getAutowireCapableBeanFactory();
return Arrays.stream(factory.getBeanDefinitionNames())
.map(name -> {
try {
return factory.getBeanDefinition(name);
} catch (Exception e) {
return null;
}
})
.filter(Objects::nonNull)
.filter(BeanDefinition::isLazyInit)
.count();
});
registry.gauge("configuration.beans.singleton",
applicationContext, ctx -> {
ConfigurableListableBeanFactory factory =
(ConfigurableListableBeanFactory) ctx.getAutowireCapableBeanFactory();
return Arrays.stream(factory.getBeanDefinitionNames())
.map(name -> {
try {
return factory.getBeanDefinition(name);
} catch (Exception e) {
return null;
}
})
.filter(Objects::nonNull)
.filter(BeanDefinition::isSingleton)
.count();
});
sample.stop(Timer.builder("configuration.load.time")
.description("Configuration loading time")
.register(registry));
};
}
// JMX监控
@ManagedResource(description = "Configuration Statistics")
@Component
public static class ConfigurationStatistics {
@Autowired
private ApplicationContext context;
@ManagedAttribute(description = "Total number of beans")
public int getTotalBeans() {
return context.getBeanDefinitionCount();
}
@ManagedAttribute(description = "Configuration classes")
public List<String> getConfigurationClasses() {
return context.getBeansWithAnnotation(Configuration.class)
.keySet().stream()
.collect(Collectors.toList());
}
@ManagedOperation(description = "Check if bean exists")
@ManagedOperationParameters({
@ManagedOperationParameter(name = "beanName", description = "Bean name to check")
})
public boolean beanExists(String beanName) {
return context.containsBean(beanName);
}
}
}
快速判断使用哪种模式
使用 @Configuration:
- 配置类中有方法相互调用
- 需要确保 Bean 单例
- 复杂的依赖配置
- Spring Boot 自动配置类
使用 @Configuration(proxyBeanMethods = false):
- 配置类中没有内部方法调用
- 追求更好的启动性能
- Spring 5.2+版本
使用普通 @Component:
- 简单的 Bean 定义
- 无内部方法调用
- 明确需要每次创建新实例
故障排除
Bean 创建失败
- 检查@Configuration 注解是否存在
- 验证@Bean 方法的参数是否都能被注入
- 确认没有循环依赖
- 检查@Conditional 条件是否满足
- 查看是否有必需的依赖未配置
性能问题
- 考虑使用@Lazy 延迟初始化
- 检查是否可以使用 Lite 模式
- 分析 Bean 的创建顺序和依赖关系
- 监控 Bean 初始化时间
- 评估是否需要所有 Bean 都在启动时创建
调试技巧
properties
# 1. 查看Bean创建顺序
logging.level.org.springframework.beans.factory.support=DEBUG
# 2. 查看哪些配置生效了,哪些没生效
debug=true
# 3. 查看代理类信息
logging.level.org.springframework.aop=DEBUG
# 4. 监控配置类加载
logging.level.org.springframework.context.annotation=DEBUG
版本兼容性
Spring 版本 | @Configuration 特性 | 推荐使用方式 |
---|---|---|
3.0 - 5.1 | 仅支持 Full 模式 | @Configuration |
5.2+ | 支持 proxyBeanMethods | 根据需求选择 |
6.0+ | 性能优化 | 优先考虑 Lite 模式 |
FAQ
Q: 为什么我的@Bean 方法被调用了多次?
A: 检查是否使用了@Configuration 注解,以及是否在 Spring 5.2+版本中设置了 proxyBeanMethods=false。
Q: @Configuration 和@Component 的性能差异有多大?
A: 在典型应用中,差异通常在启动时间的 1-5%之间。对于有数百个 bean 的大型应用,可能会更明显。
Q: 如何调试 Configuration 相关问题?
A: 使用上面提供的 ConfigurationValidationFramework 工具类,或开启 Spring 的调试日志。
Q: 生产环境应该使用哪种模式?
A: 如果没有内部方法调用,建议使用 Lite 模式(proxyBeanMethods=false)以获得更好的性能。
Q: 如何处理复杂的 Bean 依赖关系?
A: 使用@DependsOn 明确指定依赖顺序,或者重构代码减少依赖复杂度。
总结
特性 | @Configuration(Full 模式) | @Configuration(proxyBeanMethods=false) | 普通类(@Component 等) |
---|---|---|---|
CGLIB 代理 | ✓ 自动创建代理类 | ✗ 不创建代理 | ✗ 普通 Java 类 |
内部方法调用 | 返回容器中的 Bean | 创建新实例 | 创建新实例 |
单例保证 | ✓ 自动保证 | ✗ 需要手动处理 | ✗ 需要手动处理 |
启动性能 | 较慢(代理开销) | 快 | 最快 |
内存占用 | 较高(代理类) | 低 | 最低 |
Spring 版本要求 | 3.0+ | 5.2+ | 2.5+ |
适用场景 | 复杂配置、内部调用 | 简单配置、无内部调用 | 简单 Bean 定义 |
核心原则:如果你的配置类中存在@Bean 方法之间的调用,务必使用@Configuration 注解(Full 模式),否则可能会创建意外的多个实例。对于没有内部调用的简单配置,可以考虑使用 Lite 模式来提升性能。在生产环境中,要特别注意日志安全、资源管理、错误处理和性能监控。