Spring Bean 注册与条件装配
1. Bean 注册与依赖注入
注册:将 Bean 定义交给 Spring,由容器负责创建和管理对象。
注入:将容器管理的依赖对象,提供给需要它的对象。
例如:@Import 负责把类注册到容器,不是直接把对象注入到写了 @Import 的类中。
2. Bean 注册的常见方式
2.1 XML 配置
<bean id="userService"
class="com.example.service.UserService"
scope="singleton"/>id:Bean 名称。class:全限定类名,即包名加类名。scope:作用域,默认是singleton。
2.2 Java 配置
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserServiceImpl();
}
}@Configuration本身带有@Component,可以被组件扫描发现。@Bean方法用于创建容器管理的对象,默认 Bean 名是方法名。声明返回接口、实际返回实现类,是 Java 多态,与代理没有必然关系。
2.3 导入 Java 类与 XML
@Configuration
@Import({UserServiceImpl.class, OtherConfig.class})
@ImportResource("classpath:applicationContext.xml")
public class AppConfig {
}@Import可以注册没有@Component的普通类。导入包含
@Bean方法的类时,也会解析这些方法。被导入的类不一定需要
@Configuration;没有时属于轻量配置,直接调用其他@Bean方法不会经过配置类代理拦截。写了这些注解的类,必须被 Spring 发现并处理才会生效。
Java 类用
@Import,XML 用@ImportResource。
3. proxyBeanMethods = true / false
控制直接调用配置类中的 @Bean 方法时,是否经过 Spring 配置类代理,不控制 Bean 的作用域。
@Configuration(proxyBeanMethods = true)
public class AppConfig {
@Bean
public UserRepository userRepository() {
return new UserRepository();
}
@Bean
public UserService userService() {
// 重点:直接调用另一个 @Bean 方法
return new UserService(userRepository());
}
}3.1 两种设置的区别
原理简述:
true:Spring 通过 CGLIB 生成配置类子类,拦截相关方法调用,并交给容器获取 Bean;容器真正创建 Bean 时,再执行原方法体。false:不进行这种代理增强,直接调用方法就执行其中的代码。本例会执行new UserRepository(),额外创建一个对象。
额外创建的对象不会仅因为方法带有 @Bean,就自动成为容器管理的 Bean。
3.2 为什么通常会复用同一个 Bean?
因为 scope 默认是 singleton(单例)。
Spring 获取单例 Bean 时:
已经创建:从单例缓存返回同一个对象。
尚未创建:创建、初始化并缓存,再返回。
“Bean 池子”更准确的叫法是单例缓存,不是借出、归还对象的对象池。
proxyBeanMethods = true让方法调用经过容器;scope = singleton让容器复用同一个对象。
注意:
true不代表永远返回同一个对象,还要看作用域。false不代表容器中的 Bean 变成多例。单例不表示“一个类型只能有一个 Bean”。
自己
new AppConfig(),不会得到 Spring 的配置类代理能力。
3.3 推荐:通过方法参数注入依赖
@Configuration(proxyBeanMethods = false)
public class AppConfig {
@Bean
public UserRepository userRepository() {
return new UserRepository();
}
@Bean
public UserService userService(UserRepository userRepository) {
// 依赖由 Spring 从容器中获取并传入
return new UserService(userRepository);
}
}这样不需要直接调用其他 @Bean 方法,依赖关系更清楚,也不依赖配置类代理。
无论 proxyBeanMethods 是什么值,通过依赖注入或 context.getBean() 获取 Bean,都会遵守其作用域。
注意:将多例 Bean 注入单例 Bean 时,通常只在注入时获取一次,不会在每次使用时自动换一个对象。需要每次获取新对象时,可以使用 ObjectProvider。
版本说明:proxyBeanMethods 从 Spring Framework 5.2 开始提供,对应 Spring Boot 2.2 起可使用。
4. AnnotationConfigApplicationContext
它是支持注解配置的 Spring 容器,属于创建容器并加载配置的入口。
try (AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext()) {
context.register(AppConfig.class);
context.refresh();
UserService service = context.getBean(UserService.class);
}也可以直接传入配置类,自动注册并初始化:
try (AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class)) {
UserService service = context.getBean(UserService.class);
}Spring Boot 通常由启动流程完成容器初始化,业务代码一般不需要另建容器。
5. 条件装配
根据条件,决定是否注册某个配置或 Bean。
Spring 提供 @Conditional,Spring Boot 在此基础上扩展了常用条件注解。
5.1 类存在,不等于 Bean 存在
@ConditionalOnClass(DataSource.class)只说明 DataSource 类型可用,不代表容器已经存在数据源,也不代表数据库连接成功。
@ConditionalOnMissingBean(DataSource.class)表示不存在符合条件的数据源 Bean 时才注册,常用于自动配置的退让机制:
用户自己提供了 Bean,自动配置就不再提供默认 Bean。
5.2 条件判断的原理
Spring 解析配置或注册 Bean 定义时,会调用对应的条件判断逻辑;条件不满足,就跳过相应配置或 Bean 定义。
需要自定义条件时,可以实现 Condition 接口,再通过 @Conditional 使用。
这些方式并非完全独立:@ConditionalOnClass 等注解,本身也是基于 @Conditional 扩展出来的。
注意:Bean 条件依据当时已处理的 Bean 定义判断,需要注意配置处理顺序,尤其适合在自动配置中使用。
5.3 JDBC 驱动示例
可以判断某个 JDBC 驱动类是否存在,存在才启用相关配置。
但驱动存在只是前提之一,还需要连接地址等配置;不能把完整的数据源自动配置理解成“发现驱动就一定能创建并连接成功”。
6. 易错点整理
7. 重点记忆
注册到容器,不等于注入到某个类。
Java 类用
@Import,XML 用@ImportResource。proxyBeanMethods管方法调用,scope管实例复用。scope默认单例,单例实例由容器通过单例缓存复用。类存在和 Bean 存在,是两种不同条件。
条件装配决定是否注册,不保证对象创建或业务执行成功。
