Skip to content

Bean 定义继承 (Bean Definition Inheritance)

Bean 定义中可以包含大量的配置信息,包括构造函数参数、属性值以及容器特定的信息(如初始化方法、静态工厂方法名称等)。子 Bean 定义 (Child bean definition) 从父定义继承配置数据。子定义可以根据需要覆盖某些值或添加其他值。使用父子 Bean 定义可以减少大量的重复输入。实际上,这是一种模板形式。

如果你以编程方式操作 ApplicationContext 接口,子 Bean 定义由 ChildBeanDefinition 类表示。大多数用户不会在这个层面上操作,而是通过 XML 等声明性方式配置。在基于 XML 的配置元数据中,你可以通过使用 parent 属性来指明子 Bean 定义,并将父 Bean 指定为该属性的值。以下示例显示了如何操作:

xml
<bean id="inheritedTestBean" abstract="true"
		class="org.springframework.beans.TestBean">
	<property name="name" value="parent"/>
	<property name="age" value="1"/>
</bean>

<bean id="inheritsWithDifferentClass"
		class="org.springframework.beans.DerivedTestBean"
		parent="inheritedTestBean" init-method="initialize">  <!-- (1) -->
	<property name="name" value="override"/>
	<!-- age 属性值 1 将从父 Bean 继承 -->
</bean>
  1. 注意 parent 属性。

如果子 Bean 定义没有指定 Bean 类,则使用父定义的 Bean 类,但也可以覆盖它。在后一种情况下,子 Bean 类必须与父类兼容(即必须能够接受父类的属性值)。

子 Bean 定义从父类继承作用域、构造函数参数值、属性值和方法覆盖,并可以选择添加新值。你指定的任何作用域、初始化方法、销毁方法或静态工厂方法设置都会覆盖相应的父类设置。

其余设置始终取自子定义:depends-on、自动装配模式 (autowire mode)、依赖检查 (dependency check)、单例 (singleton) 和延迟初始化 (lazy init)。

前面的示例通过使用 abstract 属性显式地将父 Bean 定义标记为抽象。如果父定义没有指定类,则必须显式地将父 Bean 定义标记为 abstract,如下例所示:

xml
<bean id="inheritedTestBeanWithoutClass" abstract="true">
	<property name="name" value="parent"/>
	<property name="age" value="1"/>
</bean>

<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
		parent="inheritedTestBeanWithoutClass" init-method="initialize">
	<property name="name" value="override"/>
	<!-- age 将从父 Bean 定义中继承值 1 -->
</bean>

父 Bean 本身不能被实例化,因为它是不完整的,并且已被明确标记为 abstract。当一个定义是 abstract 时,它仅作为纯模板 Bean 定义使用,充当子定义的父定义。尝试单独使用这样一个 abstract 父 Bean(例如将其作为另一个 Bean 的 ref 属性,或显式调用 getBean())会返回错误。同样,容器内部的 preInstantiateSingletons() 方法会忽略定义为抽象的 Bean。

注意

ApplicationContext 默认会预实例化所有单例。因此,如果你有一个(父)Bean 定义只想将其用作模板,且该定义指定了一个类,请务必将 abstract 属性设置为 true,否则应用上下文将尝试预实例化该抽象 Bean。


补充教学 —— 配置的“模版工程”

1. Bean 继承 vs Java 继承 这是新人最容易混淆的地方。

  • Java 继承:是代码逻辑的延续(extends),是 类与类 的关系。
  • Bean 继承:是配置信息的拷贝与覆盖,是 实例配置与配置 的关系。
  • 联系:子 Bean 的 class 通常是父 Bean class 的子类,但也 不一定。只要子 Bean 的类拥有父 Bean 中定义的那些属性(Setter),它们就能通过 Bean 继承来复用配置。

2. 典型的应用场景:公共属性抽取 假设你有多个 DataSource 或多个 Service,它们有一部分配置是完全一样的(比如超时时间、重试策略)。 你可以定义一个 abstract="true" 的父 Bean 专门存放这些公共配置,然后让所有业务 Bean 继承它。这样修改公共参数时,只需要改动一处。

3. 为什么 abstract="true" 很重要? 在 Spring XML 配置文件中,如果你定义了一个 Bean 但没有写 class 属性,Spring 就不知道怎么实例化它。如果你还没把它标为 abstract="true",Spring 容器启动时尝试创建这个 Bean 就会 报错。 即使你写了 class,很多时候父 Bean 也只是个“配置模版”,它不具备完整的业务功能。通过 abstract 显式禁用其实例化,是一种健壮的防御性编程。

4. 现代开发中的“平替” 在基于 Java Config (@Configuration) 的现代开发中,Bean 继承的使用率大大降低了。

  • 替代方案:通常使用 Java 语言本身的继承特性,或者通过公共的方法/组件来提取共有配置。
  • 例如
    java
    @Bean
    public MyService serviceA() {
        MyService service = new MyService();
        applyCommonConfig(service); // 手动复用配置逻辑
        return service;
    }

虽然如此,在维护老旧的 XML 项目或者处理复杂的嵌套配置时,理解 Bean 继承依然是必修课。

Based on Spring Framework.