1.3 Bean概述 #
Spring IoC容器管理着一个或多个bean。这些bean是基于您提供给容器的配置元数据创建的(例如,以XML<bean/>
定义的形式)。
在容器本身中,这些bean定义表示为BeanDefinition
对象,其中包含(除其他信息外)以下元数据:
-
包限定的类名:通常是定义的bean的实际实现类。
-
bean行为的配置元素,它规定bean在容器中的行为(作用域、生命周期回调等)。
-
bean执行其工作时所需引用的其他bean。这些引用也称为协作者或依赖项。
-
要在新创建的对象中设置的其他配置环境 — 例如,池的大小限制或管理连接池的bean中要使用的连接数。
此元数据转换为组成每个bean定义的一组属性。下表介绍了这些属性:
表1. bean定义
属性 | 解释… |
---|---|
类 | 实例化bean |
名称 | 命名bean |
作用域 | bean的作用域 |
构造函数参数 | 依赖注入 |
属性 | 依赖注入 |
自动装配模式 | 自动装配协作者 |
延迟初始化模式 | 延迟初始化bean |
初始化方法 | 初始化时回调 |
销毁方法 | 销毁时回调 |
除了通过控制bean定义的信息来创建特定的bean之外,ApplicationContext
实现还允许注册(由用户)在容器外部创建的现有对象。这是通过getBeanFactory()
方法访问ApplicationContext的BeanFactory来完成的,该方法返回BeanFactory DefaultListableBeanFactory
实现。DefaultListableBeanFactory
支持通过registerSingleton(..)
和registerBeanDefinition(..)
方法注册。但是,普通的应用程序只使用通过常规bean定义元数据定义的bean。
bean元数据和手动提供的单例实例需要尽早注册,以便容器在自动装配和其他自省步骤中正确地对它们进行推理。虽然在某种程度上支持覆盖现有元数据和现有单例实例,但官方不支持在运行时注册新bean(与对工厂的实时访问同时进行),这可能导致并发访问异常、bean容器中的状态不一致,或两者兼有。
1.3.1 命名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
元素或服务定位器方式按名称引用该bean,则必须提供名称。不提供名称的内在原因与使用内部bean和自动装配协作者有关。
bean命名约定 #
约定在命名bean时使用标准Java约定作为实例字段名。也就是说,bean名称以小写字母开头,然后用驼峰大小写。此类名称的示例包括
accountManager
、accountService
、userDao
、loginController
等。命名bean会使您的配置更易于阅读和理解。此外,如果您使用Spring AOP,那么在将通知应用于一组名称相关的bean时,它会有很大帮助。
通过类路径中的组件扫描,Spring为未命名的组件生成bean名称,遵循前面描述的规则:本质上,采用简单的类名并将其首个字符转换为小写。但是,在(不常见的)特殊情况下,当有多个字符且第一个和第二个字符都是大写时,原始大小写将被保留。这些规则与java.beans.Introspector.decapitalize
(Spring在这里使用)定义的规则相同。
在bean定义之外给bean添加别名 #
在bean定义本身中,可以为bean提供多个名称,通过使用id
属性指定的最多一个名称和使用name
属性指定任意数量的其他名称。这些名称可以是同一bean的等效别名,这在某些情况下非常有用,例如,可以通过使用为组件本身指定bean名称,让应用程序中的每个组件引用公共依赖项。
但是,在实际定义bean的地方指定所有别名并不总是足够的。有时需要为在别处定义的bean引入别名。在大型系统中,配置通常在每个子系统之间分割,每个子系统都有自己的对象定义集。在基于XML的配置元数据中,您可以使用<alias/>
元素来实现这一点。以下示例展示了如何执行此操作:
<alias name="fromName" alias="toName"/>
在这个例子中,使用此别名定义后,名为fromName
的bean(在同一容器中)也可以通过toName
引用。
例如,子系统A的配置元数据可能以subsystemA-dataSource
的名称引用数据源。子系统B的配置元数据可以通过subsystemB-dataSource
的名称引用数据源。在编写使用这两个子系统的主应用程序时,主应用程序以myApp-dataSource
的名称引用数据源。要使所有三个名称都引用同一对象,可以将以下别名定义添加到配置元数据中:
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
现在,每个组件和主应用程序都可以通过一个唯一的名称引用数据源,并保证不会与任何其他定义冲突(有效地创建命名空间),但它们引用的是同一个bean。
Java 配置 #
如果使用的是Java配置,可以用
@Bean
注解来设置别名。有关详细信息,请参见[@Bean
注解使用]。
1.3.2 实例化bean #
bean定义本质上是创建一个或多个对象的创建方法。容器在被请求某个名字的bean时查找到该创建方法,并使用该bean定义封装的配置元数据创建(或获取)实际对象。
如果使用基于XML的配置元数据,那么可以在<bean/>
元素的class
属性中指定要实例化的对象的类型(或类)。该class
属性(在内部是BeanDefinition
实例上的Class
属性)通常是必需的(有关例外情况,请参阅通过使用实例工厂方法实例化和Bean定义继承。)您可以通过以下两种方式之一使用Class
属性:
- 通常,指定要构造的bean类,然后容器本身通过反射调用其构造函数直接创建bean的场景,这在某种程度上相当于使用
new
操作符的Java代码。 - 指定包含静态工厂方法(通过调用该方法以创建对象)的实际类,在不常见的情况下,容器调用类上的静态工厂方法以创建bean。调用静态工厂方法返回的对象类型可以是同一个类,也可以是另一个类。
内部类命名
如果要为静态嵌套类配置bean定义,则必须使用嵌套类的二进制名称。
例如,如果在
com.example
包中有一个名为SomeThing
的类,而这个SomeThing
类中有一个名为OtherThing
的静态嵌套类,那么bean定义上的class
属性的值将是com.example.SomeThing$OtherThing
。请注意,名称中使用
$
字符将嵌套类名与外部类名分开。
使用构造函数进行实例化 #
当您通过构造函数方法创建bean时,所有普通类都可以由Spring使用,并且与Spring兼容。也就是说,正在开发的类不需要实现任何特定的接口,也不需要以特定的方式进行编码。只需指定bean类就足够了。但是,根据您对特定bean使用的IoC类型,您可能需要一个默认(空)构造函数。
Spring IoC容器实际上可以管理您希望它管理的任何类。它不仅限于管理真正的JavaBean。大多数Spring用户更喜欢真正的JavaBean,它只有一个默认(无参数)构造函数,并根据容器中的属性建模适当的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"/>
以下示例展示了一个使用前面的bean定义的类:
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
class ClientService private constructor() {
companion object {
private val clientService = ClientService()
fun createInstance() = clientService
}
}
有关为工厂方法提供(可选)参数和从工厂返回对象后设置对象实例属性机制的详细信息,请参阅依赖项和配置。
使用实例工厂方法进行实例化 #
与通过静态工厂方法的实例化类似,使用实例工厂方法的实例化是从容器中调用现有bean的非静态方法来创建新bean。要使用此机制,请将class
属性设置为空,并在factory-bean
属性中指定当前(或父级或祖先级)容器中的bean名称,该容器包含要调用以创建对象的实例方法。使用factory-method
属性设置工厂方法本身的名称。下面的示例演示了如何配置这样的bean:
<!-- 工厂bean,包含一个名为createInstance()的方法(注,此处的createInstance()指“创建实例”的方法) -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- 注入此定位器bean所需的任何依赖项 -->
</bean>
<!-- 要通过工厂bean创建的bean -->
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
以下示例展示了相应的类:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
class DefaultServiceLocator {
companion object {
private val clientService = ClientServiceImpl()
}
fun createClientServiceInstance(): ClientService {
return clientService
}
}
一个工厂类还可以包含多个工厂方法,如下例所示:
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- 注入此定位器bean所需的任何依赖项 -->
</bean>
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
<bean id="accountService"
factory-bean="serviceLocator"
factory-method="createAccountServiceInstance"/>
以下示例展示了相应的类:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private static AccountService accountService = new AccountServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
public AccountService createAccountServiceInstance() {
return accountService;
}
}
class DefaultServiceLocator {
companion object {
private val clientService = ClientServiceImpl()
private val accountService = AccountServiceImpl()
}
fun createClientServiceInstance(): ClientService {
return clientService
}
fun createAccountServiceInstance(): AccountService {
return accountService
}
}
这种方法表明,工厂bean本身可以通过依赖注入(DI)进行管理和配置。详情请查看依赖项和配置。
在Spring文档中,“工厂bean”(“factory bean”)是指在Spring容器中配置的bean,它通过实例或静态工厂方法创建对象。相比之下,FactoryBean
(注意大写)指的是Spring特定的FactoryBean
。