Spring 注入内部 Bean 详解
一、什么是内部 Bean?
内部 Bean(Inner Bean)是指定义在另一个 Bean 内部的 Bean 。它不需要单独的 id,也不能被外部容器直接引用,只服务于其所在的宿主 Bean。
xml
<!-- 外部 Bean -->
<bean id="userService" class="com.example.UserService">
<property name="userDao">
<!-- 内部 Bean:没有 id,只属于 userService -->
<bean class="com.example.UserDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
</property>
</bean>
UserDaoImpl 就是一个内部 Bean。它没有 id,外部无法通过 getBean() 获取它。它的生命周期完全绑定在 userService 上。
二、XML 中的内部 Bean
2.1 基本用法
内部 Bean 通过 <bean> 标签嵌套在 <property> 或 <constructor-arg> 中定义。
xml
<bean id="orderService" class="com.example.OrderService">
<property name="paymentService">
<bean class="com.example.AliPayService">
<property name="appId" value="2021001"/>
<property name="privateKey" value="xxxxx"/>
</bean>
</property>
</bean>
特点:
- 不需要
id属性,即使写了也会被忽略 - 不能通过
ref引用 - 不能被其他 Bean 引用
- 每次注入时都会创建新实例(即使宿主 Bean 是单例)
2.2 构造器参数中的内部 Bean
xml
<bean id="userService" class="com.example.UserService">
<constructor-arg>
<bean class="com.example.UserDaoImpl"/>
</constructor-arg>
</bean>
2.3 内部 Bean 的匿名性
内部 Bean 本质上是一个匿名 Bean。Spring 会为它生成一个内部名称,但开发者无法预知也无法引用。它只能被它的宿主 Bean 使用。
三、Java 配置中的内部 Bean
在 Java 配置中,内部 Bean 通常通过 @Bean 方法实现,或者直接在方法体内 new 出对象。
3.1 直接 new 对象
java
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
// 内部 Bean:直接 new,不注册到容器
UserDao userDao = new UserDaoImpl();
return new UserService(userDao);
}
}
这种方式创建的对象不是 Spring Bean,不受容器管理,不享受依赖注入和 AOP 增强。
3.2 通过 @Bean 方法调用(推荐)
java
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
// 调用 userDao() 方法,由于 CGLIB 代理,返回的是容器中的单例
return new UserService(userDao());
}
@Bean
public UserDao userDao() {
return new UserDaoImpl();
}
}
@Configuration 类会被 CGLIB 增强,userDao() 方法调用返回的是容器中的 Bean,而不是新创建的实例。
3.3 真正意义上的内部 Bean
Java 配置中没有严格意义上的"内部 Bean"。如果希望对象完全私有,不被容器管理,可以这样写:
java
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
UserDaoImpl userDao = new UserDaoImpl();
userDao.setDataSource(dataSource()); // 手动注入依赖
return new UserService(userDao);
}
@Bean
public DataSource dataSource() {
return new HikariDataSource();
}
}
userDao 是一个纯 Java 对象,不是 Spring Bean,但它的依赖 dataSource 来自容器。
四、内部 Bean 与外部 Bean 的对比
| 对比维度 | 内部 Bean | 外部 Bean |
|---|---|---|
| 定义位置 | 嵌套在宿主 Bean 内部 | 独立定义 |
| 是否有 id | 没有(匿名) | 有唯一 id |
| 是否可被引用 | 不能被其他 Bean 引用 | 可以被任意引用 |
| 生命周期 | 随宿主 Bean 创建/销毁 | 独立管理 |
| 作用域 | 与宿主 Bean 绑定 | 可配置多种作用域 |
| 适用场景 | 仅宿主 Bean 使用的私有依赖 | 多个 Bean 共享的公共依赖 |
五、内部 Bean 的生命周期
内部 Bean 的生命周期与宿主 Bean 紧密绑定:
- 创建:宿主 Bean 被创建时,内部 Bean 同时被创建
- 销毁:宿主 Bean 被销毁时,内部 Bean 同时被销毁
- 作用域:内部 Bean 不支持自定义作用域,始终与宿主 Bean 同生命周期
- AOP 代理:内部 Bean 可以被 AOP 代理,但代理对象无法被外部引用
六、内部 Bean 的典型使用场景
6.1 私有依赖
当某个依赖只被一个 Bean 使用时,用内部 Bean 可以避免污染容器。
xml
<bean id="reportService" class="com.example.ReportService">
<property name="dataSource">
<bean class="com.example.ReportDataSource">
<property name="url" value="jdbc:mysql://report-db:3306/report"/>
</bean>
</property>
</bean>
ReportDataSource 只被 reportService 使用,不需要暴露给容器。
6.2 多实例配置
同一个类需要不同的配置实例时,用内部 Bean 可以避免创建多个有 id 的 Bean。
xml
<bean id="emailService" class="com.example.EmailService">
<property name="mailSender">
<bean class="com.example.SmtpMailSender">
<property name="host" value="smtp.company.com"/>
</bean>
</property>
</bean>
<bean id="alertService" class="com.example.AlertService">
<property name="mailSender">
<bean class="com.example.SmtpMailSender">
<property name="host" value="smtp.alert.com"/>
</bean>
</property>
</bean>
两个 SmtpMailSender 实例使用不同的 SMTP 服务器,互不干扰。
6.3 一次性对象
某些对象只在某个 Bean 初始化时使用一次,不需要长期存在于容器中。
七、内部 Bean 的注意事项
1. 内部 Bean 不能使用 @Autowired
内部 Bean 如果是通过 XML 定义的匿名 Bean,无法被 @Autowired 注入到其他类中,因为它没有可引用的 id。
2. 内部 Bean 的 id 被忽略
xml
<bean id="userService" class="com.example.UserService">
<property name="userDao">
<bean id="innerDao" class="com.example.UserDaoImpl"/>
<!-- 这个 id 会被忽略,无法通过 getBean("innerDao") 获取 -->
</property>
</bean>
3. 内部 Bean 的循环引用
内部 Bean 无法引用外部 Bean,因为它没有 id。但外部 Bean 可以引用内部 Bean(通过嵌套定义)。
4. 每次注入创建新实例
如果宿主 Bean 是单例,内部 Bean 也是单例(与宿主绑定)。如果宿主 Bean 是 prototype,每次创建宿主时都会创建新的内部 Bean 实例。
5. Java 配置中 new 出来的对象不是 Bean
java
@Bean
public UserService userService() {
UserDao userDao = new UserDaoImpl(); // 这不是 Spring Bean
return new UserService(userDao);
}
这个 userDao 不受容器管理,依赖注入、AOP、生命周期回调都不生效。
八、最佳实践
优先使用外部 Bean。 除非确实只需要宿主 Bean 使用,否则尽量定义为外部 Bean。外部 Bean 更灵活,可以被测试直接获取,也可以被其他 Bean 复用。
合理使用 Java 配置。 在 Java 配置中,通过 @Bean 方法调用获取依赖,而不是直接 new。这样依赖仍然是 Spring Bean,享受容器管理。
避免过度嵌套。 内部 Bean 嵌套层级过深会导致 XML 难以阅读和维护。如果嵌套超过两层,考虑提取为外部 Bean。
明确内部 Bean 的定位。 内部 Bean 适合"私有、一次性、不共享"的依赖。如果需要复用或测试,应该定义为外部 Bean。
九、总结
| 维度 | 核心要点 |
|---|---|
| 定义 | 嵌套在宿主 Bean 内部,没有独立 id |
| 生命周期 | 与宿主 Bean 绑定,同生共死 |
| 引用 | 不能被外部 Bean 引用,只能被宿主使用 |
| XML 写法 | <bean> 嵌套在 <property> 或 <constructor-arg> 中 |
| Java 写法 | 通过 @Bean 方法调用或直接 new |
| 适用场景 | 私有依赖、多实例配置、一次性对象 |
| 注意事项 | 无法被 @Autowired、id 被忽略、每次创建新实例 |
内部 Bean 是 Spring 提供的一种"私有 Bean"机制,适合那些不需要暴露给容器、只服务于特定宿主 Bean 的依赖。合理使用内部 Bean,可以让容器中的 Bean 定义更清晰,避免不必要的 Bean 污染。在 Java 配置为主流的今天,内部 Bean 的使用频率已经降低,但在一些需要为同一个类创建多个配置实例的场景下,它仍然是一个有用的工具。