原文链接:docs.spring.io/spring-fram…
1.3 Bean概述
Spring IoC容器管理一个或多个bean。这些bean是使用您提供的配置元数据创建的(例如,以XML 定义的)。
在容器本身内,这些bean定义表示为BeanDefinition对象,其中包含(在其他信息中)以下元数据:
- 类的全限定名:通常是定义的bean的实际实现类
- Bean行为配置元素,声明Bean在容器中应该行为(范围、生命周期回调,等等)。
- 对其他必要bean的引用关系:这些引用也称为依赖
- 对象的配置值:例如,池的大小限制或管理连接池要使用的连接数。
元数据转换为每个bean定义的属性。下表描述了这些属性:
| Property | Explained in… |
|---|---|
| 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 |
除了包含关于如何创建特定bean的信息的bean定义外,ApplicationContext实现还允许注册(由用户)在容器外部创建的现有对象。通过getBeanFactory()方法访问ApplicationContext的BeanFactory来完成的,该方法返回BeanFactory的实现类 DefaultListableBeanFactory。DefaultListableBeanFactory通过registerSingleton(..)和registerBeanDefinition(..)方法支持注册。但是,典型的应用程序只使用通过常规元数据定义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元素或Service Locator样式查找按名称引用该bean,则必须提供名称。不提供名称的动机与 using inner beans和autowiring collaborators.有关。
约定是在命名bean时使用标准Java约定。也就是说,bean名称以小写字母开头,采用驼峰命名法。如:accountManager、accountService、userDao、loginController等。
一致地命名bean可以使您的配置更易于阅读和理解。另外,如果您使用Spring AOP,那么将通知应用到名称相关的一组bean时,它会有很大帮助。
通过在类路径中扫描组件,Spring为未命名组件生成bean名,遵循前面描述的规则:本质上,采用简单的类名并将其初始字符转换为小写。但是,在(不常见的)特殊情况下,当有多个字符且第一个和第二个字符都是大写时,保留原来的大小写。这些规则与java.beans.Introspector.decapitalize (Spring在这里使用的)定义的规则相同。
在Bean定义之外对Bean进行别名设置
在bean定义本身中,通过使用唯一的id指定最多一个名称和可以指定任意个值的name属性结合使用,您可以为bean提供多个名称,这些名称是该bean的等效别名。
但是,在定义bean时就指定所有别名并不总是适用的,有时需要为在其他地方定义的bean引入别名。在大型系统中,配置在每个子系统之间分割,每个子系统都有自己的对象定义集。在基于xml的配置元数据中,可以使用元素来实现这一点。下面的例子展示了如何做到这一点:
<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"/>
现在,子系统和主应用程序都可以通过一个唯一的名称来引用dataSource,这个名称保证不会与任何其他定义冲突,但它们引用的是同一个bean。
如果使用java configuration,可以使用@Bean注释提供别名。详细信息请参见使用@Bean注释。
1.3.2 实例化Bean
Bean定义本质上是创建一个或多个对象的方法。容器在被询问时查看命名bean定义创建(或获取)实际对象。
如果使用基于xml的配置元数据,则需要指定要在元素的class属性中指明实例化对象的类类型。这个属性(在内部是BeanDefinition实例上的class属性)通常是强制性的(对于异常情况,参见使用实例工厂方法和Bean定义继承进行对象实例化)。你可以用以下两种方式之一来使用Class属性:
-
通常,在容器本身通过反射调用构造函数直接创建bean的情况下,指定要构造的bean类,这在某种程度上相当于带有new操作符的Java代码。
-
指定包含用于创建对象的静态工厂方法的实际类,容器调用类上的静态工厂方法来创建bean,这种情况不太常见。调用静态工厂方法返回的对象类型可以是与工厂类相同类,也可以是另一个类。
如果您想为内部类配置bean定义,您可以使用内部类的二进制名称或源名称。
例如,如果你在com.example中有一个叫做SomeThing的类。这个SomeThing类有一个名为OtherThing的静态内部类,它们可以用美元符号(OtherThing或com.example.SomeThing.OtherThing。
通过构造函数实例化bean
当您通过构造函数方法创建一个bean时,所有普通的类都可以被Spring使用,并且与Spring兼容。也就是说,托管给Spring的类不需要实现任何特定的接口,也不需要以特定的方式编码,只需指定bean类名就足够了。但是,需要一个默认(空)构造函数。
Spring IoC容器实际上可以管理您希望它管理的任何类。它并不局限于管理真正的javabean。大多数Spring用户更喜欢只有默认(无参数)构造函数和根据容器中的属性具备setter和getter的javabean类。您还可以在容器中添加更多非javabean风格的类。例如,如果您需要使用绝对不遵守JavaBean规范的连接池,Spring也可以管理它。
使用基于xml的配置元数据,你可以如下指定你的bean类:
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
有关向构造函数提供参数和在对象构造后设置对象实例属性的机制的详细信息,请参见依赖注入
通过静态工厂方法实例化bean
在定义使用静态工厂方法创建的bean时,使用class属性指定包含静态工厂方法的类,并使用名为factory-method的属性指定工厂方法本身的名称。您应该能够调用这个方法(带有可选参数)返回一个活动对象,该对象随后被视为通过构造函数创建的。
下面的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;
}
}
关于向工厂方法提供(可选)参数和在从工厂返回对象后设置对象实例属性的机制的详细信息,请参考依赖与配置详解docs.spring.io/spring-fram…
通过实例工厂方法进行实例化
与通过静态工厂方法进行实例化类似,使用实例工厂方法的实例化从容器调用现有bean的非静态方法来创建新bean。要使用这种机制,请将class属性保留为空,并在factory-bean属性中,指定当前(或父或祖先)容器中bean的名称,该容器包含要被调用来创建对象的实例方法。使用factory-method属性设置工厂方法本身的名称。下面的例子展示了如何配置这样一个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;
}
}
在Spring文档中,“factory bean”指的是在Spring容器中通过实例或静态工厂方法创建对象。相反,FactoryBean(注意大写)指的是FactoryBean实现类。
确定Bean的运行时类型
确定特定bean的运行时类型是非常重要的。bean元数据定义中的指定类只是一个初始类引用,可能是使用静态工厂方法或者实例工厂方法实例化对象,这可能导致bean的不同运行时类型。此外,AOP代理可以用一个基于接口的代理来包装一个bean实例,并有限地暴露目标bean的实际类型(仅仅是实现的接口)。
要了解特定bean的实际运行时类型,推荐使用BeanFactory的getType方法指定bean名获取。