Spring 教程

Spring Bean 简介

Spring IoC 容器管理一个或多个 bean,这些 bean 是使用您提供给容器的配置元数据创建的,例如以 XML <bean/> 形式定义的 bean。

在 IoC 容器中,这些 bean 定义使用 BeanDefinition 对象进行表示,其中包含以下元数据:

(1)包限定类名,通常是正在定义的 Bean 的实际实现类,如:com.hxstrive.spring5.ioc.UserServiceImpl。

(2)bean 行为配置元素,用来说明 bean 在容器中的行为方式,如:bean 的范围、bean 的生命周期回调等等。

(3)bean 对其他 bean 的引用,也称为依赖项。

(4)要在新创建的对象中设置的其他配置设置,如:线程池的大小限制、连接池的连接数等等。

除了包含如何创建特定 bean 的信息的 bean 定义之外,ApplicationContext 实现还允许注册在容器外部创建的现有对象。这是通过 getBeanFactory() 方法访问ApplicationContext 的 BeanFactory 来完成的,该方法返回 DefaultListableBeanFactory 实现。DefaultListableBeanFactory 通过 registerSingleton(..) 和registerBeanDefinition(..) 方法支持这种注册。但是,普通的应用程序只使用通过常规 bean 定义元数据定义的 bean。

提示:Bean 元数据和手动提供的单例实例需要尽早注册,以便容器在自动装配和其他自检步骤中正确地推断它们。虽然在某种程度上支持覆盖现有的元数据和现有的单例实例,但在运行时注册新 bean(同时对工厂进行实时访问)并不是官方支持的,并且可能导致并发访问异常、bean 容器中的不一致状态,或者两者兼而有之。

命名 Bean

每个 Bean 都有一个或多个标识符,这些标识符在托管 Bean 的容器中必须是唯一的。一个 Bean 通常只有一个标识符。但是,如果它需要多个别名,则可以将多余的名称视为别名。

在基于 XML 的配置元数据中,可以使用 id 属性或 name 属性来指定 Bean 标识符。id 属性允许您只指定一个 id。通常,这些名称是字母数字(“myBean”,“someService” 等),但它们也可以包含特殊字符。如果要为 Bean 引入其他别名,还可以在 name 属性中指定它们,用英文逗号(,)、分号(;)或空格分隔。

在 Spring 3.1 之前的版本中,id 属性被定义为 xsd:ID 类型,它约束了可能的字符。从 3.1 开始,它被定义为 xsd:string 类型。请注意,Bean ID 的唯一性仍由容器强制执行,不再由 XML 解析器强制执行。

注意:Bean 的 name 或 id 属性不是必填的。如果未显式提供 name 或 id 属性,容器将自动为该 Bean 生成唯一名称。如果要通过 ref 元素或通过 Service Locator 按名称引用该 Bean,则必须提供名称。

Bean 命名约定

在命名 Bean 时使用标准 Java 实例字段名约定。也就是说,Bean 名以小写字母开头,从那里开始驼峰大小写。例如:accountManager、accountService、userDao、loginController 等等。

以一致的方式命名 Bean 会让您的配置更容易阅读和理解。如果您使用 Spring AOP,那么在对一组名称相关的 Bean 应用 Advice 时,也会有很大帮助。注意,Advice 是 Spring AOP 中的概念,后续章节介绍。

提示:通过类路径中的组件扫描,Spring 按照前面描述的规则为未命名的组件生成 bean 名称。本质上,采用简单的类名并将其首字符转换为小写,如:UserManagerService 自动生成的 Bean 名称为 userManagerService。但是,在一些特殊情况下,当有多个字符并且第一个和第二个字符都是大写时,原始大小写将被保留,如:OSInfoService 自动生成的 Bean 名称为 oSInfoService。这些规则与 java.beans.Introspector.decapitalize 定义的规则相同。

在 Bean 定义之外为 Bean 设置别名

在 Bean 定义本身中,可以通过使用 id 属性指定的一个名称与 name 属性中任意数量的其他名称的组合,为 Bean 提供多个名称。这些名称可以是同一 Bean 的等效别名,并且在某些情况下很有用,例如,让应用程序中的每个组件通过使用特定于该组件本身的 Bean 名称来引用公共依赖项。

但是,指定实际定义 Bean 的所有别名并不总是足够的。有时需要为在其他地方定义的 Bean 引入别名。这在大型系统中很常见,其中配置在每个子系统之间拆分,每个子系统都有自己的一组对象定义。在基于 XML 的配置元数据中,可以使用该<alias/>元素来完成此操作。例如:

<alias name="fromName" alias="toName"/>

在这种情况下,在使用此别名定义后,名为 fromName 的 bean(在同一容器中)也可以称为 toName。

例如,子系统 A 的配置元数据可能通过子系统 A 数据源的名称引用数据源。子系统 B 的配置元数据可以通过子系统 B 数据源的名称来引用数据源。在编写使用这两个子系统的主应用程序时,主应用程序通过 myApp 数据源的名称引用数据源。要使所有三个名称都引用同一对象,可以将以下别名定义添加到配置元数据:

<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>

现在,每个组件和主应用程序都可以通过唯一的名称引用数据源,并保证不会与任何其他定义冲突(有效地创建命名空间),但它们引用的是同一个 bean。

实例化 Bean

Bean 定义本质上是创建一个或多个对象的配方。当系统询问时,容器会查看命名 Bean 的配方,并使用该 Bean 定义封装的配置元数据来创建(或获取)实际对象。

如果使用基于 XML 的配置元数据,则应在 <bean/> 元素的 class 属性中指定要实例化的对象类型(或类别)。这个 class 属性(在内部是 BeanDefinition 实例的 Class 属性)通常是必需的。您可以通过以下两种方式之一使用 Class 属性:

(1)通常,在容器本身通过反射调用其构造函数直接创建 Bean 的情况下指定要构造的 Bean 类,这有点类似使用 new 运算符的 Java 代码。

(2)在容器中调用一个类上的静态工厂方法来创建 Bean 这种不太常见的情况下,指定包含调用静态工厂方法来创建对象的实际类。调用静态工厂方法返回的对象类型可能是同一个类,也可能完全是另一个类。

嵌套类名

如果要为嵌套类配置 Bean 定义,可以使用嵌套类的二进制名称或源名称。

例如,如果你在 com.example 包中有一个名为 SomeThing 的类,并且这个 SomeThing 类有一个名为 OtherThing 的静态嵌套类,则可以用美元符号($)或点(.)分隔它们。因此,bean 定义中 class 属性的值将是 com.example.SomeThing$OtherThing 或 com.example.SomeThing.OtherThing。

使用构造函数实例化

当你通过构造函数方法创建一个 bean 时,所有普通类都可以由 Spring 使用并与之兼容。也就是说,正在开发的类不需要实现任何特定的接口或以特定的方式进行编码。只需指定 Bean 类就足够了。但是,根据用于该特定 Bean 的 IoC 类型,您可能需要一个默认(空)构造函数。

Spring IoC 容器几乎可以管理您希望它管理的任何类。它不仅限于管理真正的 JavaBeans。大多数 Spring 用户更喜欢实际的 JavaBeans,它只有一个默认的(无参数)构造函数和适当的 setter 和 getter,这些 setter 和 getter 方法是根据容器中的属性构建的。您还可以在容器中拥有更多奇特的非 Bean 样式类。例如,如果您需要使用绝对不符合 JavaBean 规范的传统连接池,Spring也可以管理它。

使用基于 XML 的配置元数据,您可以按如下方式指定 Bean 类:

<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>

使用静态工厂方法实例化

定义使用静态工厂方法创建的 Bean 时,请使用 class 属性指定包含静态工厂方法的类,并使用名为 factory-method 的属性指定工厂方法本身的名称。您应该能够调用此方法并返回一个实时对象,该对象随后被视为通过构造函数创建的。这种 Bean 定义的一个用途是在遗留代码中调用静态工厂。

以下 Bean 定义指定将通过调用工厂方法创建 Bean。该定义不指定返回对象的类型(类),而是指定包含工厂方法的类。在此示例中,createInstance() 方法必须是静态方法。例如:

<bean id="clientService"
   class="examples.ClientService"
   factory-method="createInstance"/>

examples.ClientService 代码如下:

public class ClientService {
   private static ClientService clientService = new ClientService();
   private ClientService() {}

   public static ClientService createInstance() {
       return clientService;
   }
}

使用实例工厂方法实例化

与通过静态工厂方法进行实例化类似,使用实例工厂方法进行实例化会从容器中调用现有 Bean 的非静态方法来创建新 Bean。若要使用此机制,请将 class 属性留空,并在工厂 Bean 属性中指定当前(或父容器或祖先)容器中 Bean 的名称,该容器包含要调用以创建对象的实例方法。使用 factory-method 属性设置工厂方法本身的名称。例如:

<!--工厂 bean,其中包含一个名为 createInstance() 的方法 -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
   <!-- inject any dependencies required by this locator bean -->
</bean>

<!--通过工厂 bean 创建的 bean -->
<bean id="clientService"
   factory-bean="serviceLocator"
   factory-method="createClientServiceInstance"/>

DefaultServiceLocator 代码如下:

public class DefaultServiceLocator {
   private static ClientService clientService = new ClientServiceImpl();

   // 实例工厂方法
   public ClientService createClientServiceInstance() {
       return clientService;
   }
}

一个工厂类还可以保存多个工厂方法,如以下示例所示:

<bean id="serviceLocator" class="examples.DefaultServiceLocator">
   <!-- inject any dependencies required by this locator bean -->
</bean>

<bean id="clientService"
   factory-bean="serviceLocator"
   factory-method="createClientServiceInstance"/>

<bean id="accountService"
   factory-bean="serviceLocator"
   factory-method="createAccountServiceInstance"/>

DefaultServiceLocator 代码如下:

public class DefaultServiceLocator {
   private static ClientService clientService = new ClientServiceImpl();
   private static AccountService accountService = new AccountServiceImpl();

   // ClientService 实例工厂方法
   public ClientService createClientServiceInstance() {
       return clientService;
   }

   // AccountService 实例工厂方法
   public AccountService createAccountServiceInstance() {
       return accountService;
   }
}

这种方法表明工厂 Bean 本身可以通过依赖关系注入(DI)进行管理和配置。

在 Spring 文档中,“factory bean” 是指在 Spring 容器中配置的 bean,它通过实例或静态工厂方法创建对象。相比之下,FactoryBean(注意大写)指的是特定于 Spring 的FactoryBean 实现类。

确定 Bean 的运行时类型

确定特定 bean 的运行时类型非常重要。bean 元数据定义中指定的类只是一个初始类引用,可能与声明的工厂方法结合在一起,或者作为一个 FactoryBean 类,这可能导致bean 的不同运行时类型,或者在实例级工厂方法的情况下根本不设置 (通过指定的工厂 bean 名称解析)。另外,AOP 代理可以用基于接口的代理来包装 bean 实例,该代理对目标 bean 的实际类型 (仅是其实现的接口) 的暴露有限。

要了解特定 Bean 的实际运行时类型,建议使用指定的 Bean 名称调用 BeanFactory.getType() 方法。它会考虑上述所有情况,并返回 BeanFactory.getBean() 调用返回的相同 Bean 名称的对象类型。

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号