Spring IoC容器管理一个或多个bean。这些bean是使用提供给容器的配置元数据创建的(例如,以XML 定义的形式)。 在容器本身中,这些bean定义被表示为BeanDefinition对象,其中包含以下元数据(以及其他信息):
- 包限定类名:通常是定义的bean的实际实现类。
- Bean行为配置元素,它声明Bean在容器中的行为(作用域、生命周期回调等等)。
- 对bean完成其工作所需的其他bean的引用。这些引用也称为协作者或依赖项。
- 要在新创建的对象中设置的其他配置设置—例如,池的大小限制或要在管理连接池的bean中使用的连接数。
此元数据转换为组成每个bean定义的一组属性。下表描述了这些属性:
| 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来实现的,该方法返回DefaultListableBeanFactory实现。DefaultListableBeanFactory通过registerSingleton(..)和registerBeanDefinition(..)方法支持这种注册。然而,典型的应用程序只能使用通过常规bean定义元数据定义的bean。
Bean元数据和手动提供的单例实例需要尽可能早地注册,以便容器在自动装配和其他自检步骤期间正确地推断它们。虽然在某种程度上支持覆盖现有元数据和现有单例实例,但在运行时注册新bean(与对工厂的实时访问同时进行)不受官方支持,并且可能导致并发访问异常、bean容器中的不一致状态,或者两者都有。
1.3.1. 命名beans
每个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提供名称或id。如果您没有显式地提供名称或id,容器将为该bean生成唯一的名称。但是,如果希望通过使用ref元素或Service Locator样式查找来按名称引用该bean,则必须提供名称。不提供名称的动机与使用内部bean和自动装配协作器有关。
Bean命名约定
约定是在命名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的地方指定所有别名并不总是足够的。有时需要为在其他地方定义的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配置
如果使用Javaconfiguration,可以使用@Bean注释提供别名。有关详细信息,请参见使用@Bean注释。
1.3.2. 实例化bean
bean定义本质上是创建一个或多个对象的方法。容器在被请求时查看命名bean的配方,并使用该bean定义封装的配置元数据来创建(或获取)实际对象。
如果使用基于xml的配置元数据,则在元素的class属性中指定要实例化的对象的类型(或类)。这个类属性(在内部是BeanDefinition实例上的class属性)通常是强制性的。(有关异常,请参见使用实例工厂方法实例化和Bean定义继承。)你可以通过以下两种方式之一使用Class属性:
通常,在容器本身通过反射地调用其构造函数直接创建bean的情况下指定要构造的bean类,这有点相当于使用new操作符的Java代码。
指定实际的类,该类包含被调用来创建对象的静态工厂方法,在不太常见的情况下,容器调用类上的静态工厂方法来创建bean。调用静态工厂方法返回的对象类型可以是同一个类,也可以完全是另一个类。
嵌套类名
如果您想为嵌套类配置bean定义,您可以使用嵌套类的二进制名称或源名称。
例如,如果你在com中有一个叫SomeThing的类。这个SomeThing类有一个名为OtherThing的静态嵌套类,它们可以用美元符号(OtherThing或com.example.SomeThing.OtherThing。
使用构造函数进行实例化
当您通过构造函数方法创建bean时,所有普通的类都可以被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;
}
}
有关向工厂方法提供(可选)参数以及在对象从工厂返回后设置对象实例属性的机制的详细信息,请参阅详细信息中的依赖项和配置。
使用实例工厂方法进行实例化
与通过静态工厂方法进行实例化类似,使用实例工厂方法进行实例化从容器中调用现有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;
}
}
这种方法表明,工厂bean本身可以通过依赖注入(DI)进行管理和配置。详见依赖项和配置。
在Spring文档中,“工厂bean”指的是在Spring容器中配置的bean,它通过实例或静态工厂方法创建对象。相比之下,FactoryBean(注意大写)引用一个特定于spring的FactoryBean实现类。
确定Bean的运行时类型
特定bean的运行时类型很难确定。bean元数据定义中的指定类只是一个初始类引用,可能与已声明的工厂方法结合在一起,或者是一个FactoryBean类,这可能导致bean的不同运行时类型,或者在实例级工厂方法的情况下根本不设置(而是通过指定的工厂bean名称解析)。此外,AOP代理可以用基于接口的代理包装bean实例,并有限地公开目标bean的实际类型(只是其实现的接口)。
要了解特定bean的实际运行时类型,推荐使用BeanFactory。getType调用指定的bean名称。这将考虑以上所有情况,并返回BeanFactory所使用的对象类型。getBean调用将返回相同的bean名称。