Spring Bean 的继承与依赖:复用配置与控制实例化顺序
在 Spring 中,Bean 的继承和依赖是两种重要的配置机制。继承用于复用配置(减少重复代码),依赖用于控制 Bean 的实例化顺序。详细解析这两种机制的实现方式、使用场景及注意事项。
Bean 的继承:复用配置,减少冗余
Spring 中的 Bean 继承并非 Java 中的类继承(不涉及类的父子关系),而是配置的继承:子 Bean 可以继承父 Bean 的所有配置(属性、依赖等),并可覆盖父 Bean 的配置。这类似于 “模板模式”,父 Bean 定义通用配置,子 Bean 仅需定义差异化部分。
1. 基本使用:通过 parent 属性实现继承
(1)普通 Bean 作为父 Bean
父 Bean 可以是一个可实例化的普通 Bean,子 Bean 通过 parent 属性指定父 Bean 的 id,从而继承其配置。
示例:
1 | <!-- 父 Bean:定义通用配置 --> |
效果:sonPerson 会继承 parentPerson 的 car、cars 配置,仅 name 被覆盖为 “张飞”。
(2)抽象 Bean:专门用于被继承的 “模板”
如果一个 Bean 仅作为父 Bean 被继承,无需实例化(也不能实例化),可将其定义为抽象 Bean(abstract="true")。
特点:
- 抽象 Bean 不能被实例化(调用
getBean("抽象BeanId")会报错); - 抽象 Bean 的
class属性可选(若子 Bean 会指定class,父 Bean 可省略class); - 抽象 Bean 仅用于配置复用,本身不生成实例。
示例:
1 | <!-- 抽象 Bean:仅作为模板,不实例化 --> |
错误场景:若尝试实例化抽象 Bean,会抛出 BeanIsAbstractException:
1 | // 错误:尝试获取抽象 Bean |
2. 继承的规则与限制
- 配置覆盖:子 Bean 可覆盖父 Bean 的任何配置(属性、依赖、初始化方法等);
class继承:若父 Bean 指定了class,子 Bean 可省略class(继承父类类型);若子 Bean 指定class,需确保与父 Bean 类型兼容(如子类或同一类型);- 抽象性继承:子 Bean 不会继承父 Bean 的
abstract属性(需手动指定abstract="true"才会成为抽象 Bean); - 依赖继承:父 Bean 的
depends-on配置会被子 Bean 继承(子 Bean 可追加新依赖)。
3. 适用场景
- 通用配置复用:多个 Bean 共享大量相同配置(如数据库连接池的默认参数、服务类的通用依赖);
- 模板化配置:定义 “基础模板”,不同子 Bean 仅修改少量属性(如不同环境的配置差异)。
Bean 的依赖:控制实例化顺序
Spring 容器默认按配置顺序实例化 Bean,但有时需要确保某个 Bean 先于目标 Bean 实例化(如目标 Bean 依赖该 Bean 的初始化数据)。此时可通过 depends-on 属性强制指定依赖关系。
1. 基本使用:depends-on 强制依赖顺序
depends-on 用于声明:目标 Bean 依赖于指定 Bean,容器必须先实例化依赖的 Bean,再实例化目标 Bean。
示例:
1 | <!-- 被依赖的 Bean:必须先实例化 --> |
效果:容器会先实例化 leader,再实例化 follower。
2. 多依赖处理
depends-on 支持指定多个依赖 Bean(用逗号、空格或分号分隔),容器会按顺序实例化所有依赖 Bean,再实例化目标 Bean。
示例:
1 | <!-- 多个被依赖的 Bean --> |
3. 错误场景:依赖的 Bean 不存在
若 depends-on 指定的 Bean 不存在,容器初始化时会抛出 NoSuchBeanDefinitionException:
1 | <!-- 错误配置:依赖不存在的 beanX --> |
异常信息:
1 | org.springframework.beans.factory.BeanCreationException: |
4. 与自动装配的区别
depends-on:仅控制实例化顺序,不负责注入依赖(需手动通过ref或@Autowired注入);- 自动装配(
@Autowired/ref):负责注入依赖,且隐含依赖顺序(被注入的 Bean 会先实例化)。
示例:若 follower 通过 ref 注入 leader,则无需 depends-on(注入本身已保证顺序):
1 | <bean id="leader" class="com.zhanghe.study.spring4.beans.beantest.Person"/> |
5. 适用场景
- 非直接依赖的初始化顺序:当 Bean A 不直接依赖 Bean B,但 A 的初始化需要 B 已初始化(如 B 初始化时加载了 A 所需的全局配置);
- 资源释放顺序:
depends-on还会影响销毁顺序(目标 Bean 先销毁,依赖的 Bean 后销毁),适合资源释放有依赖的场景(如先销毁连接池使用者,再销毁连接池)。
总结:继承与依赖的最佳实践
- Bean 继承:
- 用于配置复用,减少重复代码;
- 抽象 Bean 作为 “模板”,仅被继承,不实例化;
- 子 Bean 可覆盖父 Bean 的配置,保持灵活性。
- Bean 依赖:
- 用于控制实例化 / 销毁顺序,解决非直接依赖的初始化问题;
- 优先通过注入(
ref/@Autowired)隐含依赖顺序,仅在必要时使用depends-on; - 避免循环依赖(如 A 依赖 B,B 依赖 A),会导致容器初始化失败