Spring Bean
Spring容器管理了一个或者多个bean,这些bean是更具bean定义元数据进行创建的。
在Spring容器内部,bean定义元数据以BeanDefinition
对象呈现,包含以下信息:
- 类的全类名
- 定义bean在容器中的行为的配置元素,比如:bean作用域、生命周期回掉方法等
- 被当前bean所引用的bean
- 其他配置-比如:连接池的大小
这些元数据会翻译为一系列属性组成BeanDefinition
:
属性 | 官方说明链接 |
---|---|
Class | Instantiating Beans |
Name | Naming Beans |
Scope | Bean Scopes |
Constructor arguments | Dependency Injection |
Properties | Dependency Injection |
Autowiring mode | Autowiring Collaborators |
Lazy initialization mode | Lazy-initialized Beans |
Initialization method | Initialization Callbacks |
Destruction method | Destruction Callbacks |
BeanDefinition
包含了如何准确创建一个bean的信息,ApplicationContext
也允许用户注册在容器外部创建的对象。可以通过getBeanFactory()
来获取ApplicationContext内部聚合的BeanFactory来做到,其返回BeanFactory实现类为DefaultListableBeanFactory
,DefaultListableBeanFactory
支持通过registerSingleton(..)
和registerBeanDefinition(..)
方法进行注册
TIPS: 为了能让Spring容器在自动装盘时正确的理解bean的意图,Bean元数据和手动注册的单实例对象应该被尽可能早地执行。虽然在某种程度上支持覆盖现有Bean元数据和现有单例实例,但是在运行时(与对工厂的实时访问同时)对新bean的注册不被Spring支持,并且可能导致并发访问异常,bean容器中的状态不一致。
bean的命名
任何一个bean都有一个或多个标识符,这些标识符在持有这些bean的容器中必须唯一,通常一个bean只有一个标识符,如果要求有多个,可以考虑起别名。
使用XML进行配置时,我们使用id
和name
属性进行指定bean的标识符,id
能指定唯一的标识,这些都是由字母数字组成的(‘myBean’, ‘someService’等),也能包含特殊的字符,如果需要起一些别名,则使用name
属性进行定义,使用逗号,分号或空格进行分隔。但id
必须唯一。
如果没有指定id
和name
,容器会为bean自动生成一个唯一的标识符。
bean命名惯例:bean的名称默认使用首单词的首字母小写,其余的单词首字母都是大写,比如:accountManager,
accountService,
userDao,
loginController等。
bean的命名能够使得应用的配置更加简单和易读,如果使用Spring Aop,能够更方便的通过bean的名称进行的切面定义。
TIPS
:通过在类路径中进行组件扫描,Spring会按照前面描述的规则为未命名的组件生成Bean名称:从本质上讲,将采用简单的类名称并将其首字符转换为小写。 但是,在(不寻常的)特殊情况下,如果有多个字符且第一个和第二个字符均为大写字母,则会保留原始大小写。
在BeanDefinition之外给bean取别名
我们可以通过BeanDefinition给bean定义一个或多个名称,通过id
属性定义唯一标识符和name
属性定义多个别名。
但是,在实际定义bean的地方指定所有别名并不能满足需求。 有时需要为在别处定义的bean引入别名。 在大型系统中通常是这种情况,在大型系统中,配置在每个子系统之间分配,每个子系统都有自己的对象定义集。
<alias name="fromName" alias="toName"/>
使用XML的fromName
的bean起一个toName
的别名。
比如:A系统通过subsystemA-dataSource
来引用一个Datasource对象,B系统通过subsystemB-dataSource
来引用DataSource对象,当组合这2个子系统为一个系统时,主应用使用myApp-dataSource
来进行引用,这种情况可以使用以下配置:
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
实例化bean
一个beanDefinition可以创建一个或多个对象,容器在被查询时会查看命名bean的定义,并使用该bean定义封装的配置元数据来创建(或获取)实际对象。
如果使用XML定义Bean元数据,通常需要指定class
属性class
属性对应BeanDefinition
中的Class
字段。Class
字段有2种用法:
- 通常,在容器本身通过反射机制调用其构造函数直接创建Bean的情况下,指定要构造的Bean类型,这在某种程度上等同于使用
new
运算符的Java代码。 - 要指定包含用于创建对象的静态工厂方法的实际类,在不太常见的情况下,容器将在类上调用静态工厂方法以创建Bean。 从静态工厂方法的调用返回的对象类型可以是同一类,也可以是完全不同的另一类。
内部类
:如果需要配置类的内部静态类的bean定义,必须要使用类的组合名称,比如:在 com.example
包下有一个叫SomeThing
的类,在其内部有一个叫OtherThing
的内部静态类,则其beanDefinition的class
属性将会是com.example.SomeThing$OtherThing
,$
被用来作为内部类和外部类的分隔。
使用构造器初始化
当通过构造方法创建一个bean时,所有普通类都可以被Spring使用并兼容。 也就是说,正在开发的类不需要实现任何特定的接口或以特定的方式进行编码。 只需指定bean类就足够了。 但是,根据用于该特定bean的IoC类型,可能需要一个默认(空)构造函数。
Spring IoC容器几乎可以管理任何类。 它不仅限于管理真正的JavaBean。 大多数Spring用户更喜欢实际的JavaBean,它们仅具有默认(无参数)构造函数,并具有根据容器中的属性建模的适当的setter和getter。 还可以在容器中包含更多奇特的非bean样式类。 例如,如果需要使用绝对不符合JavaBean规范的旧式连接池,则Spring也可以对其进行管理。
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
使用静态工厂方法进行实例化
可以使用class
属性来指定包含静态工厂方法的的类,使用factory-method
属性来指定工厂方法的名字。
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
使用实例工厂方法
使用已存在的bean的非静态工厂方法创建新的bean:
<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<!-- the bean to be created via the factory bean -->
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
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"/>
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;
}
}
这种方式说明工厂bean本省可以被容器管理和通过DI配置。
决定bean的运行时类型
确定特定bean的运行时类型并非易事。 Bean元数据定义中的指定类只是初始类引用,可能与声明的工厂方法结合使用,或者是FactoryBean
类,这可能导致Bean的运行时类型不同,或者在实例的情况下根本不进行设置 工厂方法(通过指定的factory-bean名称解析)。 此外,AOP代理可以使用基于接口的代理包装Bean实例,而目标Bean的实际类型(仅是其实现的接口)的暴露程度有限。
建议使用BeanFactory.getType
获取运行时bean的类型。