@Bean 在@Configuration 中和普通类中的本质区别

很多开发者在使用 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 模式来提升性能。在生产环境中,要特别注意日志安全、资源管理、错误处理和性能监控。

相关推荐
米粉030516 分钟前
深入剖析Nginx:从入门到高并发架构实战
java·运维·nginx·架构
简诚19 分钟前
HttpURLConnection实现
java
androidwork1 小时前
Android LinearLayout、FrameLayout、RelativeLayout、ConstraintLayout大混战
android·java·kotlin·androidx
每次的天空1 小时前
Android第十三次面试总结基础
android·面试·职场和发展
周末程序猿1 小时前
Linux高性能网络编程十谈|C++11实现22种高并发模型
后端·面试
忠于明白1 小时前
Spring AI 核心工作流
人工智能·spring·大模型应用开发·spring ai·ai 应用商业化
陈小桔1 小时前
限流算法java实现
java
黑客老李1 小时前
JavaSec | SpringAOP 链学习分析
java·运维·服务器·开发语言·学习·apache·memcached
憨憨睡不醒啊2 小时前
如何让LLM智能体开发助力求职之路——构建属于你的智能体开发知识体系📚📚📚
面试·程序员·llm
勤奋的知更鸟2 小时前
Java编程之原型模式
java·开发语言·原型模式