文章作者:Tyan
博客:[noahsnail.com](http://noahsnail.com
3.3 Bean概述
Spring IoC容器管理一个或多个beans。这些beans由提供给容器的配置元数据生成,例如,XML形式的
定义。
在容器本身内部,这些bean定义被表示成BeanDefinition
对象,含有以下元数据:
- 包限定的类名:通常是被定义的bean的实现类。
- bean行为配置元素,规定了bean在容器中的行为(作用范围、生命周期回调函数)等等。
- bean工作需要的引用的其它bean,这些引用也被称为协作者或依赖。
- 其它的配置在新创建的对象中设置,例如,bean中使用的连接数量控制着一个连接池,或连接池的大小限制。
元数据转化为一系列的属性,这些属性构成了每个bean的定义。
表3.1. bean定义
Property | Explained in… |
---|---|
class | Section 3.3.2, “Instantiating beans” |
name | Section 3.3.1, “Naming beans” |
scope | Section 3.5, “Bean scopes” |
constructor arguments | Section 3.4.1, “Dependency Injection” |
properties | Section 3.4.1, “Dependency Injection” |
autowiring mode | Section 3.4.5, “Autowiring collaborators” |
lazy-initialization mode | Section 3.4.4, “Lazy-initialized beans” |
initialization method | the section called “Initialization callbacks” |
destruction method | the section called “Destruction callbacks” |
除了bean定义中包含怎么创建一个指定的bean的信息之外,ApplicationContext
实现也允许用户注册容器之外创建的现有对象。这是通过调用ApplicationContext’s BeanFactory的getBeanFactory()方法完成的,这个方法会返回BeanFactory的实现类DefaultListableBeanFactory
。DefaultListableBeanFactory
支持通过registerSingleton(..)
和registerBeanDefinition(..)
方法来注册。不管怎样,标准应用仅使用通过元数据bean定义定义的beans。
> bean元数据和人工提供的单例需要尽可能早的进行注册,为了使容器在自动注入及其它的内省步骤时能恰当的推理它们。虽然在一定程度上是支持覆盖现有的元数据和单例的,但运行时新beans的注册(并发实时访问工厂)是不被正式支持的,可能会引起并发访问异常,在容器中的与/或状态不一致。
3.3.1 beans命名
每个bean都有一个或多个标识符。这些托管bean的标识符在容器中必须是唯一的。一个bean通常只有一个标识符,但如果一个bean需要不止一个标识符,其它的标识符会被当成别名。
在基于XML的配置元数据中,你可以使用id
和/或name
属性指定bean标识符。id
属性允许你指定一个确定的id。按照惯例这些名字是字母数字的('myBean', 'fooService'等等),但也可能包含指定字符。如果你想引入bean其它的别名,你可以在name
属性中指定别名,用逗号 (,
),分号(;
),或空格分开。作为一个历史注解,在之前的Spring 3.1版本,id
属性被定义为一种xsd:ID
类型,可以通过合理字符来约束(XML控制id
唯一性)。从Spring 3.1开始,它被定义为xsd:string
类型。注意bean id
的唯一性仍然是容器强制的,虽然不再通过XML解析器来控制(容器控制id
唯一性)。
Bean命名规范
当命名bean时,采用的规范是标准Java实例字段命名规范。bean名称以小写字母开头,采用驼峰式的命名规则。这种命名方式的例子(不带引号)有'accountManager', 'accountService', 'userDao', 'loginController'等等。
一致的命名beans可以使人更容易读懂和理解你的配置,如果你正在使用Spring AOP,使用一致性来命名一系列bean名称是非常有帮助的。
在classpath中进行组件扫描,Spring会根据上面的规则为未命名组件产生bean名称,本质上来说,是采用简单的类名并将其首字母改成小写。然而在特殊情况下(不平常的),当类名有不止一个字母且第一二个字母都是大写的情况下,会保留最初始的状态。与
java.beans.Introspector.decapitalize
定义中的规则是相同的(Spring也采用这个规则)。
为bean定义别名
在定义bean时,通过与id
属性指定的名称相结合,你可以为bean提供不止一个名字,在name
属性中定义任何数量的其它名字。这些名字是同一个bean的等价别名,在一些情况下是非常有用的,例如允许应用中的每个组件通过bean名称引用一个共通的依赖,这个依赖为每个组件本身指定了一个名称。
然而在bean实际定义的地方指定所有别名并不总是适当的。有时会要求引入一个在别的地方定义的bean的别名。这通常是在大的系统中而配置被分割在每个子系统中,每个子系统有它知道对象定义集合。在基于XML配置元数据中,你可以使用
来完成别名的定义。
在这种情况下,在同一个容器中的bean被命名为fromName
,也可能是在别名定义使用之后,被作为toName
引用。
例如,子系统A的配置元数据可能通过名称subsystemA-dataSource
引用数据源。子系统B的配置元数据可能通过名称subsystemB-dataSource
引用数据源。当构成主应用的时,主应用使用这些子系统并通过名称myApp-dataSource
引用数据源。为了使这三个名称引用同一个对象,你可以将如下的别名定义添加到MyApp配置元数据中:
现在主应用和每个组件都能通过名称引用数据源,这个名称是唯一的且能保证不与任何其它的定义相冲突(有效的创建了一个命名空间),但它们引用了同一个bean。
Java配置
如果你正在使用Java配置,
@Bean
注解可以用来提供别名,更多细节请看3.12.3小节, “使用@Bean注解”。
3.3.2 beans实例化
bean定义本质上来说是创建一个或多个对象的方法。当问及一个命名bean时,容器会查看这个方法并使用bean定义中封装的配置元数据创建(或取得)一个实际的对象。
如果你使用基于XML的配置元数据,你可以指定对象的类型(或类),它将在
元素中的class
属性中进行实例化。class
属性,在BeanDefinition
实例的内部是Class
性质,通常是必需的。(例外的情况,请看"使用实例工厂方法进行实例化"小节和3.7小节,"bean定义继承")。你可以通过以下两种方式中的一种使用Class
属性:
通常情况下,指定要构造的bean类,容器本身通过反射调用bean的构造方法直接创建bean,这与Java代码中使用
new
操作符是等价的。在不常见的情况下,指定包含静态工厂方法的实际类,调用静态工厂方法创建对象,容器在类上调用静态工厂方法创建bean。静态工厂方法调用返回的对象类型可能是同一个类,也可能完全是另一个类。
内部类命名 如果你想为静态嵌套类配置bean定义,你必须使用嵌套类的二进制名字。
例如,如果你在
com.example
包中有个类叫Foo
,Foo
类中有一个静态嵌套类叫Bar
,'class'
属性在bean定义中的值为
com.example.Foo$Bar
注意名字中
$
符号的使用是为了将外部类名与嵌套类名分隔开。
使用构造函数实例化
当你使用构造方法创建bean时,所有的正常类都可以被Spring使用和兼容。也就是说,正在进行开发的类不需要实现任何特定的接口或以特定的方式进行编码。简单的指定bean类就足够了。然而,根据你为指定的bean所使用的IoC类型,你可能需要一个默认的(空的)构造函数。
事实上,Spring的IoC容器可以管理任何你想让它管理的类;它不受限于管理真实的JavaBeans。大多数Spring用户更喜欢实际的JavaBeans,在容器中它仅有一个默认(无参)的构造函数,并且属性之后有合适的setters,getters方法。在容器中你也可以有更多外来的非bean类型的类。例如,如果你需要使用遗留的连接池,这绝对不符合JavaBean规范,但Spring也可以管理它。
基于XML的配置元数据你可以用如下方式指定你的bean的类:
更多关于为构造函数提供参数(如果有必要的话)的机制和构造对象之后设置对象实例属性的细节,请看依赖注入。
通过静态工厂方法进行实例化
当定义的bean用静态工厂方法创建时,你可以使用class
属性指定包含静态工厂方法的类,用factory-method
属性指定工厂方法本身的名字。你应该能调用这个方法(用后面描述的可选参数)并且返回一个实时对象,随后对这个对象进行处理,就好像这个对象是通过构造函数创建的一样。这种bean定义的一个用法是在遗留代码(旧代码)中调用静态工厂方法。
下面的bean定义指定了一个通过调用工厂方法创建的bean。定义没有指定返回对象的类型只有包含工厂方法的类。在这个例子中,createInstance()
必须是一个静态方法。
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
更多关于为工厂方法提供(可选)参数的原理和从工厂方法返回对象后设置对象实例属性的信息,请看"依赖和详细配置"。
通过实例工厂方法进行实例化
与通过静态工厂方法进行实例化类似,通过实例化工厂方法进行实例化,要从容器中调用现有bean非静态方法创建一个新的bean。使用这种机制,要让class
属性为空,在factory-bean
属性中,在包含实例化方法的当前容器(或父/祖先)中指定bean的名字,通过调用实例化方法来创建对象。通过factory-method
属性设置工厂方法本身的名字。
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private DefaultServiceLocator() {}
public ClientService createClientServiceInstance() {
return clientService;
}
}
一个工厂类可以拥有多个工厂方法,如下所示:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private static AccountService accountService = new AccountServiceImpl();
private DefaultServiceLocator() {}
public ClientService createClientServiceInstance() {
return clientService;
}
public AccountService createAccountServiceInstance() {
return accountService;
}
}
这个方法展示了工厂bean本身可以通过依赖注入(DI)来管理和配置。更多细节请看"依赖和配置"。
在Spring文档中,工厂bean引用了配置在Spring容器中的bean,Spring容器将通过实例或静态工厂方法来创建对象。相比之下,
FactoryBean
(注意大写)引用了Spring特定的FactoryBean
。