Skip to content

Spring 配置类详解:@Configuration 与 @Bean

还记得 XML 配置时代吗?

xml
<bean id="userService" class="com.example.UserServiceImpl">
    <property name="userDao" ref="userDao"/>
    <property name="cacheManager" ref="cacheManager"/>
</bean>

写了半天,还容易出错。

Spring 3.0 引入了 Java Config,用注解和代码代替 XML 配置。代码即配置,配置即代码。

今天,彻底搞懂 Spring 的 Java 配置。

@Configuration 的本质

什么是配置类?

java
@Configuration
public class AppConfig {
    // 这里定义 Bean
}

@Configuration 标注的类,就像 XML 里的 <beans> 标签。类里的 @Bean 方法,就像 XML 里的 <bean> 标签。

配置类的等价 XML

java
@Configuration
public class AppConfig {

    @Bean
    public UserService userService() {
        UserServiceImpl service = new UserServiceImpl();
        service.setUserDao(userDao());  // 调用另一个 @Bean 方法
        return service;
    }

    @Bean
    public UserDao userDao() {
        return new UserDaoImpl();
    }
}

等价于:

xml
<beans>
    <bean id="userDao" class="com.example.UserDaoImpl"/>
    <bean id="userService" class="com.example.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
    </bean>
</beans>

@Bean 注解详解

基本用法

java
@Configuration
public class DataSourceConfig {

    // 默认为方法名作为 Bean 名称
    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        config.setUsername("root");
        config.setPassword("password");
        return new HikariDataSource(config);
    }

    // 指定 Bean 名称
    @Bean(name = "myDataSource")
    public DataSource dataSource() {
        // ...
    }

    // 多个名称
    @Bean(name = {"dataSource", "ds"})
    public DataSource dataSource() {
        // ...
    }
}

@Bean 的属性

java
@Configuration
public class ComplexConfig {

    @Bean(
        name = "customBean",        // Bean 名称
        initMethod = "init",        // 初始化方法
        destroyMethod = "cleanup"    // 销毁方法
    )
    public MyService myService() {
        return new MyServiceImpl();
    }
}

Bean 依赖注入

java
@Configuration
public class ServiceConfig {

    // 方式一:直接调用 @Bean 方法
    @Bean
    public UserService userService() {
        return new UserServiceImpl(userDao());  // 自动注入
    }

    @Bean
    public UserDao userDao() {
        return new UserDaoImpl();
    }

    // 方式二:作为方法参数注入
    @Bean
    public OrderService orderService(UserDao userDao) {
        // Spring 会自动注入 userDao
        return new OrderServiceImpl(userDao);
    }

    // 方式三:@Autowired 注入
    @Bean
    public PaymentService paymentService() {
        PaymentServiceImpl service = new PaymentServiceImpl();
        // 需要添加 @Autowired 才能注入
        return service;
    }
}

配置类进阶

@Configuration 的 proxyBeanMethods

这是理解 Spring 配置类的关键。

java
@Configuration(proxyBeanMethods = true)  // 默认值
public class AppConfig {
    @Bean
    public A a() {
        return new A(b());  // 每次调用 b(),返回的是同一个 Bean
    }

    @Bean
    public B b() {
        return new B();
    }
}
java
@Configuration(proxyBeanMethods = false)
public class AppConfig {
    @Bean
    public A a() {
        return new A(b());  // 每次调用 b(),创建新的 B 实例!
    }

    @Bean
    public B b() {
        return new B();
    }
}
配置作用
proxyBeanMethods = true确保多次调用 @Bean 方法返回同一个 Bean
proxyBeanMethods = false每次调用 @Bean 方法创建新实例(轻量级)

什么时候用 proxyBeanMethods = false

java
// 性能优化场景:配置类只是用来注册 Bean,不涉及 Bean 间依赖检查
@Configuration(proxyBeanMethods = false)
public class MyLiteConfig {
    @Bean
    public MyService myService() {
        return new MyService();
    }
}

@Scope 与 @Lazy

java
@Configuration
public class ScopedConfig {

    // 单例(默认)
    @Bean
    public SingletonService singletonService() {
        return new SingletonService();
    }

    // 原型:每次获取创建新实例
    @Bean
    @Scope("prototype")
    public PrototypeService prototypeService() {
        return new PrototypeService();
    }

    // 延迟初始化:第一次使用时才创建
    @Bean
    @Lazy
    public LazyService lazyService() {
        return new LazyService();
    }
}

@Primary 和 @Profile

java
@Configuration
public class DataSourceConfig {

    // 默认使用这个
    @Bean
    @Primary
    public DataSource primaryDataSource() {
        return new HikariDataSource();
    }

    @Bean
    @Profile("dev")
    public DataSource devDataSource() {
        // 开发环境数据源
        return new HikariDataSource();
    }

    @Bean
    @Profile("prod")
    public DataSource prodDataSource() {
        // 生产环境数据源
        return new HikariDataSource();
    }
}

条件装配

@Conditional

根据条件决定是否创建 Bean:

java
@Configuration
public class ConditionalConfig {

    // 只有当类路径中存在某个类时才创建
    @Bean
    @Conditional(OnClassCondition.class)
    public MyService myService() {
        return new MyServiceImpl();
    }
}

// 自定义条件
public class OnClassCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return ClassUtils.isPresent(
            "com.example.OptionalClass", 
            context.getClassLoader()
        );
    }
}

Spring Boot 提供了很多内置的 @Conditional 变体:

java
@Configuration
public class AutoConfig {

    // 只有存在某个配置属性时才创建
    @Bean
    @ConditionalOnProperty(name = "app.feature.enabled", havingValue = "true")
    public FeatureService featureService() {
        return new FeatureService();
    }

    // 只有类路径中存在某个类时才创建
    @Bean
    @ConditionalOnClass(RedisTemplate.class)
    public CacheService cacheService() {
        return new RedisCacheService();
    }

    // 只有类路径中不存在某个类时才创建
    @Bean
    @ConditionalOnMissingBean(CacheService.class)
    public CacheService defaultCacheService() {
        return new SimpleCacheService();
    }

    // 只有某个 Bean 不存在时才创建
    @Bean
    @ConditionalOnMissingBean
    public MyService defaultMyService() {
        return new DefaultMyService();
    }

    // 只有应用是 Web 应用时才创建
    @Bean
    @ConditionalOnWebApplication(type = Type.SERVLET)
    public WebService webService() {
        return new WebService();
    }
}

配置类组件依赖

@Import 导入配置类

java
// 方式一:导入普通类
@Configuration
@Import({DataSourceConfig.class, ServiceConfig.class})
public class AppConfig {
}

// 方式二:导入 @Configuration 类
// DataSourceConfig 和 ServiceConfig 也是 @Configuration 类
@Configuration
@Import(DataSourceConfig.class)
public class AppConfig {
}

@Import 与 ImportSelector

java
// 自定义选择器
public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 根据条件动态决定导入哪些类
        Map<String, Object> attributes = 
            importingClassMetadata.getAnnotationAttributes(EnableMyFeature.class.getName());
        
        if (attributes != null) {
            boolean enabled = (boolean) attributes.get("enabled");
            if (enabled) {
                return new String[] {
                    "com.example.MyFeatureAutoConfiguration"
                };
            }
        }
        return new String[0];
    }
}

// 使用
@Configuration
@Import(MyImportSelector.class)
public @interface EnableMyFeature {
    boolean enabled() default true;
}

@Import 与 DeferredImportSelector

延迟导入,在所有 @Configuration 处理完后执行:

java
public class MyDeferredImportSelector implements DeferredImportSelector {
    @Override
    public Class<? extends DeferredImportSelector.Group> getImportGroup() {
        return MyDeferredImportGroup.class;
    }

    public static class MyDeferredImportGroup implements Group {
        private final List<Entry> entries = new ArrayList<>();

        @Override
        public void addImport(String importClassName) {
            entries.add(new Entry(getOrder(), importClassName));
        }

        @Override
        public Iterator<Entry> iterator() {
            return entries.iterator();
        }

        @Override
        public int getOrder() {
            return Ordered.LOWEST_PRECEDENCE;
        }
    }
}

配置类的加载时机

┌─────────────────────────────────────────────────────────────────────────┐
│                      Spring 配置类加载时机                                │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  1. @ComponentScan 扫描到 @Configuration 类                             │
│         │                                                               │
│         ▼                                                               │
│  2. @Import 导入配置类                                                  │
│         │                                                               │
│         ▼                                                               │
│  3. @Configuration + @EnableXXX 导入                                     │
│         │                                                               │
│         ▼                                                               │
│  4. Spring Boot 的 META-INF/spring.factories                            │
│         │                                                               │
│         ▼                                                               │
│  5. Spring Boot 的 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

最佳实践

1. 配置类分层

java
// 数据源层
@Configuration
public class DataSourceConfiguration {
    @Bean
    public DataSource dataSource() { ... }
}

// 服务层
@Configuration
public class ServiceConfiguration {
    @Bean
    public UserService userService(DataSource dataSource) { ... }
}

// Web 层
@Configuration
public class WebConfiguration {
    @Bean
    public WebMvcConfigurer webMvcConfigurer() { ... }
}

2. 配置属性绑定

java
@Configuration
@ConfigurationProperties(prefix = "app")
public class AppProperties {
    private String name;
    private int maxSize;
    private List<String> allowedOrigins;

    // getter/setter
}

@Configuration
@EnableConfigurationProperties(AppProperties.class)
public class AppConfig {
    // AppProperties 会自动注册为 Bean
}

3. 配置类替换 XML

java
// XML 配置
/*
<beans>
    <context:property-placeholder location="classpath:app.properties"/>
    <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
    </bean>
</beans>
*/

// Java Config
@Configuration
@PropertySource("classpath:app.properties")
public class DataSourceConfig {
    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(url);
        config.setUsername(username);
        return new HikariDataSource(config);
    }
}

面试核心问题

Q1:@Configuration 和 @Component 的区别?

特性@Configuration@Component
用途定义配置类定义组件
Bean 管理所有 @Bean 方法会被代理直接实例化
@Bean 调用多次调用返回同一 Bean每次调用创建新实例
性能略有开销(代理)更轻量

Q2:为什么 @Bean 方法调用要谨慎?

proxyBeanMethods = true 时,Spring 会创建代理对象,确保 @Bean 方法被调用时返回的是容器中的单例 Bean。

proxyBeanMethods = false 时,方法调用会创建新实例,可能导致非预期结果。

Q3:@Import 的执行顺序?

@Import 的执行在 @ComponentScan 之前。这让你可以先导入配置类,再被扫描。


下节预告组件扫描详解 —— 深入理解 @ComponentScan 的扫描规则和过滤条件。

基于 VitePress 构建