【翻译 Spring 5.0.4.RELEASE】1.The IoC container
其中最重要的是Spring框架的控制反转(IoC)容器。 Spring框架的IoC容器的全面处理紧随其后,全面涵盖了Spring的面向方面编程(AOP)技术。 Spring框架拥有自己的AOP框架,这个框架在概念上很容易理解,并且成功地解决了Java企业编程中AOP需求的80%甜点。
覆盖Spring与AspectJ的集成(目前在功能方面最为丰富 - 当然也是Java企业领域最成熟的AOP实现)。
1. The IoC container
1.1. Introduction to the Spring IoC container and beans
本章介绍Spring Framework实现控制反转(IoC)[1]原理。 IoC也被称为依赖注入(DI)。 它是一个过程,对象通过构造函数参数,工厂方法的参数或在工厂方法构造或返回后在对象实例上设置的属性来定义它们的依赖关系,即它们使用的其他对象。 容器在创建bean时会注入这些依赖关系。 这个过程从根本上来说是相反的,因此名为控制反转(IoC),bean本身通过使用类的直接构造或诸如Service Locator模式之类的机制来控制其依赖关系的实例化或位置。
org.springframework.beans和org.springframework.context
包是Spring Framework的IoC容器的基础。 BeanFactory
接口提供了一种能够管理任何类型对象的高级配置机制。 ApplicationContex
t是BeanFactory
的一个子接口。 它增加了与Spring的AOP功能更容易的集成; 消息资源处理(用于国际化),事件发布; 以及Web应用程序上下文(如Web应用程序上下文)以用于Web应用程序。
简而言之,BeanFactory
提供了配置框架和基本功能,而ApplicationContext
添加了更多的企业特定功能。 ApplicationContext
是BeanFactory
的一个完整的超集,在本章中专门用于描述Spring的IoC容器。 有关使用BeanFactory
而不是ApplicationContext
的更多信息,请参阅BeanFactory
。
在Spring中,构成应用程序主干和由Spring IoC容器管理的对象称为bean。 bean是一个实例化,组装并由Spring IoC容器管理的对象。 否则,bean只是应用程序中众多对象中的一个。 Bean和它们之间的依赖关系反映在容器使用的配置元数据中。
1.2. Container overview
接口org.springframework.context.ApplicationContext
表示Spring IoC容器,并负责实例化,配置和组装上述bean。 容器通过读取配置元数据获取有关要实例化,配置和组装的对象的指示信息。 配置元数据用XML、Java注释或Java代码表示。 它允许您表示组成应用程序的对象以及这些对象之间丰富的相互依赖关系。
Spring提供了几个ApplicationContext
接口的实现。 在独立应用程序中,通常会创建ClassPathXmlApplicationContext
或FileSystemXmlApplicationContext
的实例。 虽然XML是用于定义配置元数据的传统格式,但您可以通过提供少量的XML配置来指示容器使用Java注释或代码作为元数据格式,以声明方式支持这些其他元数据格式。
在大多数应用场景中,显式用户代码不需要实例化Spring IoC容器的一个或多个实例。 例如,在Web应用程序场景中,应用程序的web.xml文件中简单的8个(或多个)样板Web描述符XML行就足够了(请参阅Web应用程序的便捷ApplicationContext实例化)。 如果您使用的是Spring工具套件Eclipse驱动的开发环境,则只需点击几下鼠标或按键即可轻松创建此样板配置。
下图是Spring如何工作的高级视图。 您的应用程序类与配置元数据相结合,以便在创建并初始化ApplicationContext之后,您拥有完全配置且可执行的系统或应用程序。
Figure 1. The Spring IoC container
1.2.1. Configuration metadata
如上图所示,Spring IoC容器使用一种形式的配置元数据; 此配置元数据表示作为应用程序开发人员如何告诉Spring容器在您的应用程序中实例化,配置和组装对象。
传统上,配置元数据是以简单直观的XML格式提供的,这是本章的大部分内容用来传达Spring IoC容器的关键概念和功能。
基于XML的元数据不是唯一允许的配置元数据形式。 Spring IoC容器本身完全与此配置元数据实际写入的格式分离。 现在很多开发人员为他们的Spring应用程序选择基于Java的配置。
有关在Spring容器中使用其他形式的元数据的信息,请参阅:
- 基于注释的配置:Spring 2.5引入了对基于注释的配置元数据的支持。
- 基于Java的配置:从Spring 3.0开始,Spring JavaConfig项目提供的许多功能成为核心Spring框架的一部分。 因此,您可以使用Java而不是XML文件来定义应用程序类外部的Bean。 要使用这些新功能,请参阅
@Configuration
,@Bean
,@Import
和@DependsOn
注释。
Spring配置由容器必须管理的至少一个,通常是多个bean定义组成。 基于XML的配置元数据将这些bean配置为*元素内的元素。 Java配置通常在@Configuration
类中使用@Bean
注释的方法。
这些bean定义对应于组成应用程序的实际对象。 通常,您可以定义服务层对象,数据访问对象(DAO),Struts Action实例等表示对象,Hibernate SessionFactories等基础结构对象,JMS队列等。 通常,不会在容器中配置细粒度的域对象,因为创建和加载域对象通常是DAO和业务逻辑的责任。 但是,您可以使用Spring与AspectJ的集成来配置在IoC容器控制之外创建的对象。 请参阅使用AspectJ依赖注入Spring对象域对象。
以下示例显示了基于XML的配置元数据的基本结构:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="..." class="...">
</bean>
<bean id="..." class="...">
</bean>
</beans>
id属性是一个字符串,用于标识单个bean定义。 class属性定义了bean的类型并使用完全限定的类名。 id属性的值是指协作对象。 本示例中未显示用于引用协作对象的XML; 有关更多信息,请参阅依赖关系。
1.2.2. Instantiating a container
实例化Spring IoC容器很简单。 提供给ApplicationContext
构造函数的位置路径实际上是资源字符串,它允许容器从各种外部资源(例如本地文件系统,Java CLASSPATH等等)加载配置元数据。
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
在了解了Spring的IoC容器之后,您可能想了解更多关于Spring的资源抽象的知识,如参考资料中所述,它提供了一种从URI语法中定义的位置读取InputStream的方便机制。 特别是,资源路径用于构建应用程序上下文,如应用程序上下文和资源路径中所述。
以下示例显示服务层对象(services.xml)
配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- services -->
<bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="itemDao" ref="itemDao"/>
</bean>
</beans>
以下示例显示数据访问对象daos.xml
文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="accountDao"
class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
</bean>
<bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
</bean>
</beans>
在前面的示例中,服务层由PetStoreServiceImpl
类和两个类型为JpaAccountDao
和JpaItemDao
的数据访问对象(基于JPA对象/关系映射标准)组成。 属性名称元素引用了JavaBean属性的名称,ref元素引用另一个bean定义的名称。 id和ref元素之间的这种联系表示协作对象之间的依赖关系。 有关配置对象依赖关系的详细信息,请参阅依赖关系。
Composing XML-based configuration metadata
让bean定义跨越多个XML文件可能很有用。 通常,每个单独的XML配置文件都代表了架构中的逻辑层或模块。
您可以使用应用程序上下文构造函数从所有这些XML片段中加载bean定义。 这个构造函数有多个资源位置,如前一节所示。 或者,使用一个或多个元素从另一个或多个文件加载bean定义。 例如:
<beans>
<import resource="services.xml"/>
<import resource="resources/messageSource.xml"/>
<import resource="/resources/themeSource.xml"/>
<!-- 正如你所看到的,一个前导斜线被忽略,但是鉴于这些路径是相对的,最好不要使用斜线 -->
<bean id="bean1" class="..."/>
<bean id="bean2" class="..."/>
</beans>
在前面的示例中,从三个文件加载外部bean定义:services.xml
,messageSource.xml
和themeSource.xml
。 所有位置路径都与导入的定义文件相关,因此services.xml
必须位于与导入文件相同的目录或类路径位置,而messageSource.xml
和themeSource.xml
必须位于位置下方的资源位置 的导入文件。 正如你所看到的,一个前导斜线被忽略,但是鉴于这些路径是相对的,最好不要使用斜线。 根据Spring架构,正在导入的文件(包括顶层元素)的内容必须是有效的XML bean定义。
可能但不推荐使用相对的“../”路径引用父目录中的文件。 这样做会创建对当前应用程序外部的文件的依赖关系。 特别是,不建议将此引用用于“classpath:”URL(例如“classpath:../ services.xml”),其中运行时解析过程选择“最近”的类路径根,然后查看其父目录。 类路径配置更改可能会导致选择不同的,不正确的目录。
您始终可以使用完全限定的资源位置而不是相对路径:例如,“file:C:/config/services.xml”或“classpath:/config/services.xml”。 但是,请注意,您将应用程序的配置与特定的绝对位置相关联。 通常最好保持这种绝对位置的间接性,例如通过在运行时根据JVM系统属性解析的“$ {…}”占位符。
import指令是由bean名称空间本身提供的一项功能。 除了普通bean定义以外的其他配置特性可用于由Spring提供的选择的XML名称空间,例如, “上下文”和“util”命名空间。
The Groovy Bean Definition DSL
作为外部化配置元数据的另一个例子,bean的定义也可以在Spring的Groovy Bean Definition DSL中表达,如Grails框架所知。 通常,这样的配置将存在于“.groovy”文件中,结构如下:
beans {
dataSource(BasicDataSource) {
driverClassName = "org.hsqldb.jdbcDriver"
url = "jdbc:hsqldb:mem:grailsDB"
username = "sa"
password = ""
settings = [mynew:"setting"]
}
sessionFactory(SessionFactory) {
dataSource = dataSource
}
myService(MyService) {
nestedBean = { AnotherBean bean ->
dataSource = dataSource
}
}
}
这种配置风格很大程度上等同于XML bean定义,甚至支持Spring的XML配置名称空间。 它还允许通过“importBeans”指令导入XML bean定义文件。
1.2.3. Using the container
ApplicationContext
是高级工厂的接口,能够维护不同Bean及其依赖项的注册表。 使用方法T getBean(String name,Class <T> requiredType)
,可以检索bean的实例。
ApplicationContext
使您可以读取bean定义并按如下方式访问它们:
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// use configured instance
List<String> userList = service.getUsernameList();
使用Groovy配置,bootstrapping看起来非常相似,只是一个不同的上下文实现类,它可以感知Groovy(但也理解XML bean定义):
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");
最灵活的变体是GenericApplicationContext
与Reader delegates 组合,例如, 使用XML文件的XmlBeanDefinitionReader
:
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
或者使用Groovy文件的GroovyBeanDefinitionReader
:
GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();
如果需要,这样的阅读器代理可以在同一个ApplicationContext
上混合和匹配,从不同的配置源读取bean定义。
然后可以使用getBean
来检索bean的实例。 ApplicationContext
接口还有其他一些检索bean的方法,但理想情况下,应用程序代码不应该使用它们。 事实上,你的应用程序代码根本不应该调用getBean()
方法,因此完全不依赖于Spring API。 例如,Spring与Web框架的集成为各种Web框架组件(如控制器和JSF托管的Bean)提供了依赖注入,允许您通过元数据(例如自动装配注释)声明对特定Bean的依赖关系。
1.3. Bean overview
Spring IoC容器管理一个或多个bean。 这些bean是使用您提供给容器的配置元数据创建的,例如,以XML 定义的形式。
在容器本身中,这些bean定义表示为BeanDefinition
对象,其中包含以下元数据(以及其他信息):
- 包限定的类名称:通常是所定义的bean的实际实现类。
- Bean行为配置元素,它说明bean在容器中的行为(范围,生命周期回调等等)。
- 引用bean为其工作所需的其他bean; 这些引用也称为协作者或依赖关系。
- 在新创建的对象中设置的其他配置设置,例如,用于管理连接池的Bean的连接数量或池的大小限制。
这个元数据转化为一组构成每个bean定义的属性。
Table 1. The bean definition
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(与实时访问工厂同时)并未得到正式支持,并且可能导致并发访问异常和/或bean容器中的状态不一致。
1.3.1. Naming beans
每个bean都有一个或多个标识符。 这些标识符在托管bean的容器内必须是唯一的。 一个bean通常只有一个标识符,但是如果它需要多个标识符,额外的标识符可以被认为是别名。
在基于XML的配置元数据中,您使用id
和/或name
属性来指定bean标识符。id
属性允许你指定一个id。 通常,这些名称是字母数字(’myBean’,’fooService’等),但也可能包含特殊字符。 如果要将其他别名引入到bean中,还可以在name属性中指定它们,并用逗号(,),分号(;)或空格分隔。 作为历史记录,在Spring 3.1之前的版本中,id
属性被定义为xsd:ID
类型,它限制了可能的字符。 从3.1开始,它被定义为一个xsd:string
类型。 请注意,bean id唯一性仍由容器强制执行,尽管不再由XML解析器执行。
您不需要为bean提供名称或标识。 如果没有显式提供名称或标识,容器为该bean生成一个唯一的名称。 但是,如果您想通过名称引用该bean,则通过使用ref
元素或Service Locator样式查找,您必须提供一个名称。 不提供名称的动机与使用内部bean和自动装配协作者有关。
Bean命名约定
约定是在命名bean时使用标准Java约定作为实例字段名称。 也就是说,bean名称以小写字母开头,并且从此开始以骆驼为基础。 这样的名字的例子是(不带引号)`'accountManager','accountService','userDao','loginController'
等等。命名bean始终使您的配置更易于阅读和理解,如果您使用的是Spring AOP,则将建议应用于与名称相关的一组bean时会有很大帮助。
通过类路径中的组件扫描,Spring根据以上规则为未命名的组件生成bean名称:本质上,采用简单的类名称并将其初始字符转为小写。 然而,在(不寻常的)特殊情况下,当有多个字符并且第一个和第二个字符都是大写字母时,原始外壳将被保留。 这些规则与
java.beans.Introspector.decapitalize
(Spring在此处使用)定义的规则相同。
Aliasing a bean outside the bean definition
在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
引用数据源。 要让所有三个名称都引用您添加到MyApp配置元数据中的同一对象,请使用以下别名定义:
<alias name="subsystemA-dataSource" alias="subsystemB-dataSource"/>
<alias name="subsystemA-dataSource" alias="myApp-dataSource" />
现在,每个组件和主应用程序都可以通过一个唯一的名称来引用dataSource,并保证不会与任何其他定义冲突(有效地创建名称空间),但它们引用同一个bean。
Java的配置
如果您正在使用Java配置,则可以使用@Bean
注释来提供别名,请参阅使用@Bean注释以获取详细信息。
1.3.2. Instantiating beans
bean定义本质上是创建一个或多个对象的配方。 容器在被询问时查看命名bean的配方,并使用由该bean定义封装的配置元数据来创建(或获取)实际对象。
如果您使用基于XML的配置元数据,则可以指定要在元素的class属性中实例化的对象的类型(或类)。 这个类属性在内部是一个BeanDefinition
实例的Class属性,通常是强制性的。 (有关例外情况,请参阅使用实例工厂方法和Bean定义继承进行实例化。)可以通过以下两种方式之一使用Class属性:
- 通常,在容器本身通过反射性地调用其构造函数直接创建bean的情况下,指定要构建的bean类,这与使用new运算符的Java代码有些相同。
- 要指定包含将被调用来创建对象的静态工厂方法的实际类,那么容器在类上调用静态工厂方法以创建该Bean的情况较少。 从调用静态工厂方法返回的对象类型可以是完全相同的类或另一个类。
Inner class names
如果要为静态嵌套类配置一个bean定义,则必须使用嵌套类的二进制名称。例如,如果在
com.example包
中有一个名为Foo
的类,并且此Foo
类具有一个名为Bar
的静态嵌套类,那么bean定义上’class’属性的值将是com.example.Foo$Bar
注意在名称中使用$字符将嵌套类名与外部类名分开。
Instantiation with a constructor
当您通过构造函数方法创建一个bean时,所有普通类都可以被Spring使用并兼容。也就是说,正在开发的类不需要实现任何特定的接口或以特定的方式编码。只需指定bean类就足够了。但是,根据您用于特定bean的IoC类型,您可能需要一个默认(空)构造函数。
Spring IoC容器几乎可以管理任何您想要管理的类;它不限于管理真正的JavaBeans。大多数Spring用户更喜欢实际的JavaBeans,它只有一个默认的(无参数)构造函数,以及在容器中的属性之后建模的合适的setter和getter。您也可以在容器中使用更具异国情调的非Bean风格类。例如,如果您需要使用绝对不符合JavaBean规范的传统连接池,Spring也可以管理它。
使用基于XML的配置元数据,您可以按如下方式指定您的bean类:
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
有关在构造对象后为参数提供参数(如果需要)和设置对象实例属性的机制的详细信息,请参阅注入依赖项。
Instantiation with a static factory method
在定义一个使用静态工厂方法创建的bean时,可以使用class属性来指定包含静态工厂方法的类和名为factory-method
的属性,以指定工厂方法本身的名称。 您应该能够调用此方法(使用后面介绍的可选参数)并返回一个活动对象,随后将其视为通过构造函数创建的对象。 这种bean定义的一个用途是在传统代码中调用静态工厂。
以下bean定义指定将通过调用工厂方法来创建该bean。 该定义没有指定返回对象的类型(类),而只指定了包含工厂方法的类。 在这个例子中,createInstance()
方法必须是一个静态方法。
<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;
}
}
有关从工厂返回对象之后向工厂方法提供参数(可选)和设置对象实例属性的机制的详细信息,请参阅依赖关系和详细配置。
Instantiation using an instance factory method
与通过静态工厂方法实例化类似,使用实例工厂方法的实例化从容器调用现有bean的非静态方法来创建新的bean。 要使用此机制,请将类属性保留为空,并在factory-bean
属性中指定当前(或父/祖代)容器中包含要调用以创建对象的实例方法的bean的名称。 使用factory-method属性设置工厂方法本身的名称。
<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
</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">
</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
。
1.4. Dependencies
典型的企业应用程序不包含单个对象(或Spring的说法中的bean)。 即使最简单的应用程序也有几个对象一起工作来展示最终用户将其视为一个连贯的应用程序。 下一节将介绍如何从定义许多独立的bean定义到完全实现的应用程序,在这些应用程序中对象协作实现目标。
1.4.1. Dependency Injection
依赖注入(DI)是一个过程,通过这种过程,对象可以通过构造函数参数,工厂方法参数或者在构造或返回对象实例后设置的属性来定义它们的依赖关系,也就是说,它们使用的其他对象从工厂方法。容器在创建bean时会注入这些依赖关系。这个过程从根本上说是相反的,因此名为控制反转(IoC),它本身通过使用类的直接构造或服务定位器模式来控制它自己的依赖关系的实例化或位置。
代码与DI原理相比更加清晰,并且在对象提供依赖关系时解耦更有效。该对象不查找其依赖项,并且不知道依赖项的位置或类。因此,您的类变得更容易测试,特别是当依赖关系位于接口或抽象基类上时,它们允许在单元测试中使用存根或模拟实现。
DI存在两种主要的变体,基于构造器的依赖注入和基于Setter的依赖注入。
Constructor-based dependency injection
基于构造器的DI通过容器调用具有多个参数的构造函数完成,每个参数表示一个依赖项。 调用具有特定参数的静态工厂方法来构造bean几乎是等价的,本讨论类似地将参数视为构造函数和静态工厂方法。 以下示例显示了只能通过构造函数注入进行依赖注入的类。 请注意,这个类没有什么特别之处,它是一个POJO,它不依赖于容器特定的接口,基类或注释。
public class SimpleMovieLister {
private MovieFinder movieFinder;
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
Constructor argument resolution
使用参数的类型发生构造函数参数解析匹配。 如果bean定义的构造函数参数中没有可能存在的歧义,那么在bean定义中定义构造函数参数的顺序就是在实例化bean时将这些参数提供给相应构造函数的顺序。 考虑以下课程:
package x.y;
public class Foo {
public Foo(Bar bar, Baz baz) {
// ...
}
}
假设Bar
和Baz
类没有继承关系,就不存在潜在的歧义。 因此,以下配置可以正常工作,并且不需要在元素中明确指定构造函数参数索引和/或类型。
<beans>
<bean id="foo" class="x.y.Foo">
<constructor-arg ref="bar"/>
<constructor-arg ref="baz"/>
</bean>
<bean id="bar" class="x.y.Bar"/>
<bean id="baz" class="x.y.Baz"/>
</beans>
当引用另一个bean时,类型是已知的,并且可以发生匹配(就像前面的例子那样)。 当使用简单类型时,例如<value> true </ value>
,Spring无法确定值的类型,因此无法在没有帮助的情况下按类型进行匹配。 考虑以下课程:
package examples;
public class ExampleBean {
private int years;
private String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
Constructor argument type matching
在前面的场景中,如果使用type
属性显式指定构造函数参数的类型,则容器可以使用简单类型的类型匹配。 例如:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
Constructor argument index
使用index
属性明确指定构造函数参数的索引。 例如:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
除了解决多个简单值的歧义之外,指定索引还解决了构造函数具有相同类型的两个参数的不明确性。 请注意,该索引是基于0的。
Constructor argument name
您也可以使用构造函数参数名称进行值消歧:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
请记住,要使这项工作脱离框架,您的代码必须在启用了调试标志的情况下编译,以便Spring可以从构造函数中查找参数名称。 如果你不能用调试标志编译你的代码(或不想),你可以使用@ConstructorProperties
JDK注释来显式地命名你的构造函数参数。 示例类将不得不如下所示:
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
Setter-based dependency injection
在调用无参数构造函数或无参数静态工厂方法来实例化bean之后,基于Setter的DI通过调用bean上的容器调用setter方法来完成。
以下示例显示了一个只能使用纯setter注入进行依赖注入的类。 这个类是传统的Java。 这是一个POJO,它不依赖于容器特定的接口,基类或注释。
public class SimpleMovieLister {
private MovieFinder movieFinder;
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
ApplicationContext
支持它所管理的bean的基于构造函数和基于setter的DI。 它也支持基于setter的DI之后,一些依赖已经通过构造器方法注入。 您可以以BeanDefinition
的形式配置依赖项,您可以使用它与PropertyEditor
实例一起将属性从一种格式转换为另一种格式。 然而,大多数Spring用户不直接使用这些类(即以编程方式),而是使用XML bean定义,带注释的组件(即用@Component,@Controlle
r等注释的类)或基于Java的@Bean
方法` @Configuration
类。 然后将这些源内部转换为BeanDefinition
的实例,并用于加载整个Spring IoC容器实例。
Constructor-based or setter-based DI?
既然你可以混合使用基于构造函数的和基于setter的DI,那么使用强制依赖和构造方法的构造函数或可选依赖的配置方法是一个很好的经验法则。请注意,可以使用setter方法上的@Required
注释来使该属性成为必需的依赖项。Spring团队通常提倡构造器注入,因为它使得可以将应用程序组件实现为不可变对象,并确保所需的依赖项不为空。此外,构造器注入的组件总是以完全初始化的状态返回给客户端(调用)代码。作为一个侧面说明,大量的构造函数参数是一种糟糕的代码异味,这意味着该类可能有太多的责任,应该重构以更好地解决问题的分离问题。
Setter注入主要只应用于可选的依赖关系,这些依赖关系可以在类中分配合理的默认值。否则,在代码使用依赖关系的任何地方都必须执行非空检查。 setter注入的一个好处是setter方法使得该类的对象可以重新配置或稍后重新注入。通过JMX MBeans进行管理因此是一个引人注目的setter注入用例。
使用对特定班级最有意义的DI风格。有时候,在处理没有源代码的第三方课程时,可以选择你。例如,如果第三方类不公开任何setter方法,则构造函数注入可能是DI的唯一可用形式。
Dependency resolution process
该容器执行bean依赖性解析如下:
- ApplicationContext是使用描述所有Bean的配置元数据创建和初始化的。 配置元数据可以通过XML,Java代码或注释来指定。
- 对于每个bean,如果使用该属性而不是普通构造函数,则它的依赖关系以属性,构造函数参数或静态工厂方法的参数的形式表示。 当bean被实际创建时,这些依赖被提供给bean。
- 每个属性或构造函数参数都是要设置的值的实际定义,或者是对容器中另一个bean的引用。
- 作为值的每个属性或构造函数参数都从其指定的格式转换为该属性或构造函数参数的实际类型。 默认情况下,Spring可以将以字符串格式提供的值转换为所有内置类型,例如int,long,String,boolean等。
Spring容器在容器创建时验证每个bean的配置。 但是,在实际创建bean之前,bean属性本身不会被设置。 Beans是单身作用域并且被设置为预先实例化的(默认的)是在创建容器时创建的。 范围在Bean范围中定义。 否则,只有在请求时才创建bean。 创建一个bean可能会导致创建一个bean图,因为bean的依赖关系及其依赖关系的依赖关系(等等)被创建和分配。 请注意,这些依赖项之间的解决方案不匹配可能会出现较晚,即第一次创建受影响的bean。
循环依赖
如果您主要使用构造函数注入,则可能会创建一个无法解析的循环依赖方案。例如:类A需要通过构造函数注入的类B的实例,而类B需要通过构造函数注入的类A的实例。如果将类A和B的bean配置为相互注入,则Spring IoC容器会在运行时检测到此循环引用,并引发
BeanCurrentlyInCreationException
。一种可能的解决方案是编辑某些类的源代码,以便由setter而不是构造器进行配置。或者,避免构造函数注入并仅使用setter注入。换句话说,虽然不推荐,但您可以使用setter注入来配置循环依赖关系。
与典型情况(没有循环依赖)不同,bean A和bean B之间的循环依赖关系迫使其中一个bean在被完全初始化之前注入另一个bean(经典的鸡/鸡蛋场景)。
通常你可以相信Spring做正确的事情。它在容器加载时检测配置问题,例如引用不存在的bean和循环依赖关系。当bean实际创建时,Spring会尽可能晚地设置属性并解决依赖关系。这意味着,如果在创建该对象或其某个依赖关系时遇到问题,那么请求对象时,正确加载的Spring容器可能会稍后生成异常。例如,由于缺少或无效的属性,bean抛出异常。某些配置问题的可能延迟可见性是为什么ApplicationContext实现默认预先实例化单例bean。为了在实际需要它们之前创建这些bean,需要花费一些预先的时间和内存,在创建ApplicationContext时会发现配置问题,而不是稍后。您仍然可以重写此默认行为,以便单例bean将会进行延迟初始化,而不是预先实例化。
如果不存在循环依赖关系,则当一个或多个协作bean被注入到一个依赖bean中时,每个协作bean都被注入到依赖bean之前完全配置。这意味着如果bean A对bean B有依赖性,Spring IoC容器在调用bean A上的setter方法之前完全配置bean B.换句话说,bean被实例化(如果不是预先实例化的单例),它的将设置依赖关系,并调用相关的生命周期方法(例如配置的init方法或InitializingBean回调方法)。
Examples of dependency injection
以下示例使用基于设置者的DI的基于XML的配置元数据。 Spring XML配置文件的一小部分指定了一些bean定义:
<bean id="exampleBean" class="examples.ExampleBean">
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public void setBeanOne(AnotherBean beanOne) {
this.beanOne = beanOne;
}
public void setBeanTwo(YetAnotherBean beanTwo) {
this.beanTwo = beanTwo;
}
public void setIntegerProperty(int i) {
this.i = i;
}
}
在前面的例子中,setters被声明为与XML文件中指定的属性相匹配。 以下示例使用基于构造函数的DI:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg>
<ref bean="anotherExampleBean"/>
</constructor-arg>
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg type="int" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public ExampleBean(
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
this.beanOne = anotherBean;
this.beanTwo = yetAnotherBean;
this.i = i;
}
}
在bean定义中指定的构造函数参数将用作ExampleBean构造函数的参数。
现在考虑一下这个例子的一个变种,在这个例子中,不是使用构造函数,而是让Spring调用一个静态工厂方法来返回对象的一个实例:
<bean id="exampleBean" class="examples.ExampleBean"
factory-method="createInstance">
<constructor-arg ref="anotherExampleBean"/>
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {
private ExampleBean(...) {
...
}
public static ExampleBean createInstance (
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
ExampleBean eb = new ExampleBean (...);
// some other operations...
return eb;
}
}
静态工厂方法的参数通过元素提供,就像构造函数实际使用一样。 工厂方法返回的类的类型不必与包含静态工厂方法的类的类型相同,尽管在本例中它是。 实例(非静态)工厂方法将以基本相同的方式使用(除了使用factory-bean属性而不是class属性),因此在此不讨论细节。
1.4.2. Dependencies and configuration in detail
如前一节所述,您可以将bean属性和构造函数参数定义为对其他受管Bean(协作者)的引用,或者将其定义为内联定义的值。 Spring的基于XML的配置元数据为此支持其和元素中的子元素类型。
Straight values (primitives, Strings, and so on)
元素的value属性将属性或构造函数参数指定为可读的字符串表示形式。 Spring的转换服务用于将这些值从String转换为属性或参数的实际类型。
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- results in a setDriverClassName(String) call -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="masterkaoli"/>
</bean>
以下示例使用p-namespace进行更简洁的XML配置。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/mydb"
p:username="root"
p:password="masterkaoli"/>
</beans>
前面的XML更简洁; 然而,在运行时而不是设计时发现错字,除非您在创建bean定义时使用支持自动属性完成的IDE(如IntelliJ IDEA或Spring Tool Suite(STS))。 强烈建议这种IDE帮助。
您还可以将java.util.Properties实例配置为:
<bean id="mappings"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<!-- typed as a java.util.Properties -->
<property name="properties">
<value>
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
</value>
</property>
</bean>
Spring容器通过使用JavaBeans PropertyEditor机制将元素中的文本转换为java.util.Properties实例。 这是一个很好的捷径,也是Spring团队倾向于使用嵌套的元素覆盖value属性样式的几个地方之一。
The idref element
idref元素只是一种防错的方式,可以将容器中另一个bean的id(字符串值 - 不是引用)传递给或元素。
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="theTargetBean"/>
</property>
</bean>
上面的bean定义片段与下面的片段完全等价(在运行时):
<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
<property name="targetName" value="theTargetBean"/>
</bean>
第一种形式比第二种形式更好,因为使用idref
标签允许容器在部署时验证被引用的命名bean实际存在。 在第二种变体中,不会对传递给客户机bean的targetName属性的值执行验证。 当客户端bean实际实例化时,才会发现Typos(最可能致命的结果)。 如果客户端bean是原型bean,则此类型错误和生成的异常可能仅在部署容器后很长时间才能发现。
4.0 bean xsd中不再支持idref元素的本地属性,因为它不再提供超过常规bean引用的值。 升级到4.0架构时,只需将现有的
idref local
引用更改为idref bean
。
一个普通的地方(至少在Spring 2.0之前的版本中)元素带来价值的地方在于ProxyFactoryBean bean定义中的AOP拦截器的配置。 指定拦截器名称时使用元素可以防止拼写错误拦截器ID。
References to other beans (collaborators)
ref元素是或定义元素中的最后一个元素。在这里,您将bean的指定属性的值设置为对容器管理的另一个bean(协作者)的引用。被引用的bean是其属性将被设置的bean的依赖项,并且在属性设置之前根据需要初始化它。 (如果协作者是单身bean,它可能已被容器初始化。)所有引用最终都是对另一个对象的引用。范围和验证取决于您是通过bean,local或parent属性指定另一个对象的id / name。
通过标签的bean属性指定目标bean是最通用的形式,并允许创建对同一个容器或父容器中的任何bean的引用,而不管它是否位于同一个XML文件中。 bean属性的值可以与目标bean的id属性相同,或者作为目标bean的name属性中的一个值。
<ref bean="someBean"/>
通过父属性指定目标bean将创建对当前容器的父容器中的bean的引用。 父属性的值可以与目标bean的id属性相同,也可以与目标bean的name属性中的一个值相同,并且目标bean必须位于当前bean的父容器中。 您主要在具有容器层次结构时使用此bean参考变体,并且想要使用与父bean名称相同的代理将父容器中的现有bean包装在父容器中。
<!-- in the parent context -->
<bean id="accountService" class="com.foo.SimpleAccountService">
<!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
</property>
<!-- insert other configuration and dependencies as required here -->
</bean>
4.0 bean xsd不再支持ref元素的本地属性,因为它不再提供超过常规bean引用的值。 升级到4.0模式时,只需将现有的ref本地引用更改为ref bean。
Inner beans
或元素中的元素定义了一个所谓的内部bean。
<bean id="outer" class="...">
<!-- instead of using a reference to a target bean, simply define the target bean inline -->
<property name="target">
<bean class="com.example.Person"> <!-- this is the inner bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
内部bean定义不需要定义的id或名称; 如果指定,容器不使用这样的值作为标识符。 容器在创建时也会忽略范围标志:内部bean始终是匿名的,并且它们始终使用外部bean创建。 不可能将内部bean注入到除了封装bean之外的协作bean中,或者独立访问它们。
作为角落案例,可以从自定义范围接收销毁回调,例如, 对于包含在单例bean中的请求范围的内部bean:内部bean实例的创建将绑定到它的包含bean,但是销毁回调允许它参与请求范围的生命周期。 这不是一个常见的情况; 内部bean通常简单地分享它们包含的bean的范围。
Collections
在<list />
,<set />
,<map />
和<props /
>元素中,分别设置Java集合类型List,Set,Map和Properties的属性和参数。
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">aaa@qq.com</prop>
<prop key="support">aaa@qq.com</prop>
<prop key="development">aaa@qq.com</prop>
</props>
</property>
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
映射键或值或设置值的值也可以是以下任何元素:
bean | ref | idref | list | set | map | props | value | null
Collection merging
Spring容器也支持集合的合并。 应用程序开发人员可以定义父级样式的<list />
,<map />
,<set />
或<props />
元素,并具有子样式的<list />
,<map />
,<set />
或 <props />
元素继承并覆盖父集合中的值。 也就是说,子集合的值是合并父集合和子集合元素的结果,子集合元素覆盖父集合中指定的值。
这部分关于合并讨论了父子bean机制。 不熟悉父代和子代bean定义的读者可能希望在继续之前阅读相关章节。
以下示例演示了集合合并:
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">aaa@qq.com</prop>
<prop key="support">aaa@qq.com</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<!-- the merge is specified on the child collection definition -->
<props merge="true">
<prop key="sales">aaa@qq.com</prop>
<prop key="support">aaa@qq.com</prop>
</props>
</property>
</bean>
<beans>
注意在子bean定义的adminEmails属性的元素上使用merge = true属性。 当子bean由容器解析并实例化时,生成的实例具有一个adminEmails属性集合,该集合包含合并子级的adminEmails集合与父级的adminEmails集合的结果。
aaa@qq.com.com
aaa@qq.com.com
aaa@qq.com.co.uk
子集“属性”集合的值集继承父级中的所有属性元素,并且子级的支持值值覆盖父级集合中的值。
这种合并行为类似地适用于<list />,<map />
和<set />
集合类型。 在<list />
元素的特定情况下,与List集合类型关联的语义(即有序值集合的概念)将保留; 父项的值在所有子项列表的值之前。 对于Map,Set和Properties集合类型,不存在排序。 因此,对于容器在内部使用的关联的Map,Set和Properties实现类型的集合类型,没有任何排序语义生效。
Limitations of collection merging
您不能合并不同的集合类型(例如Map和List),并且如果确实尝试这样做,则会引发适当的Exception。 合并属性必须在较低的继承的子定义上指定; 在父集合定义上指定合并属性是多余的,并且不会导致所需的合并。
Strongly-typed collection
随着Java 5中引入泛型类型,您可以使用强类型集合。 也就是说,可以声明一个Collection类型,使其只能包含String元素(例如)。 如果您使用Spring将强类型集合依赖注入到bean中,则可以利用Spring的类型转换支持,以便强类型Collection实例的元素在添加之前转换为适当的类型 集合。
public class Foo {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
<beans>
<bean id="foo" class="x.y.Foo">
<property name="accounts">
<map>
<entry key="one" value="9.99"/>
<entry key="two" value="2.75"/>
<entry key="six" value="3.99"/>
</map>
</property>
</bean>
</beans>
当foo bean的accounts属性准备注入时,有关强类型Map <String,Float>
的元素类型的泛型信息可通过反射获得。 因此,Spring的类型转换基础结构将各种值元素识别为类型为Float,并将字符串值9.99,2.75和3.99转换为实际的Float类型。
Null and empty string values
Spring将空的参数作为空字符串处理。 以下基于XML的配置元数据片段将email属性设置为空字符串值(“”)。
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
前面的示例等同于以下Java代码:
exampleBean.setEmail("");
<null />
元素处理空值。 例如:
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
以上配置相当于以下Java代码:
exampleBean.setEmail(null);
XML shortcut with the p-namespace
p-命名空间使您可以使用bean元素的属性来代替嵌套的<property />
元素来描述属性值和/或合作bean。
Spring支持带有命名空间的可扩展配置格式,这些命名空间基于XML模式定义。 本章中讨论的bean配置格式是在XML Schema文档中定义的。 但是,p-namespace并未在XSD文件中定义,而只存在于Spring的核心中。
以下示例显示了解析为相同结果的两个XML片段:第一个使用标准XML格式,第二个使用p命名空间。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="aaa@qq.com"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="aaa@qq.com"/>
</beans>
该示例在bean定义中显示了名为email的p名称空间中的一个属性。 这告诉Spring包含一个属性声明。 如前所述,p-名称空间没有模式定义,因此您可以将该属性的名称设置为属性名称。
下一个示例包含两个更多的bean定义,这两个定义都可以引用另一个bean:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="john-classic" class="com.example.Person">
<property name="name" value="John Doe"/>
<property name="spouse" ref="jane"/>
</bean>
<bean name="john-modern"
class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>
<bean name="jane" class="com.example.Person">
<property name="name" value="Jane Doe"/>
</bean>
</beans>
正如你所看到的,这个例子不仅包含使用p-命名空间的属性值,还使用特殊格式来声明属性引用。 第一个bean定义使用<property name =“spouse”ref =“jane”/>
来创建bean bean的引用,第二个bean定义使用p:spouse-ref =“jane”
作为属性 完全一样的东西。 在这种情况下,配偶是属性名称,而-ref部分表明这不是一个正值,而是对另一个bean的引用。
p-名称空间不如标准XML格式那么灵活。 例如,声明属性引用的格式与以Ref结尾的属性冲突,而标准XML格式则不会。 我们建议您谨慎选择您的方法,并将其传达给您的团队成员,以避免生成同时使用这三种方法的XML文档。
XML shortcut with the c-namespace
与带有p-名称空间的XML快捷方式类似,Spring 3.1中新引入的c-名称空间允许使用内联属性来配置构造函数参数,而不是嵌套构造函数arg元素。
让我们回顾一下基于构造函数的依赖注入与c:namespace的例子:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bar" class="x.y.Bar"/>
<bean id="baz" class="x.y.Baz"/>
<!-- traditional declaration -->
<bean id="foo" class="x.y.Foo">
<constructor-arg ref="bar"/>
<constructor-arg ref="baz"/>
<constructor-arg value="aaa@qq.com"/>
</bean>
<!-- c-namespace declaration -->
<bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="aaa@qq.com"/>
</beans>
c:命名空间使用与p:1相同的约定(尾随-ref作为bean引用),用于通过名称设置构造函数参数。 同样,即使它没有在XSD模式中定义(但它存在于Spring内核中),也需要声明它。
对于构造函数参数名称不可用的罕见情况(通常如果字节码是在没有调试信息的情况下编译的),可以使用回退参数索引:
<!-- c-namespace index declaration -->
<bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>
由于XML语法,索引表示法要求存在前导_,因为XML属性名称不能以数字开头(即使某些IDE允许)。
实际上,构造函数解析机制在匹配参数方面非常有效,所以除非真的需要,否则我们建议在整个配置中使用名称符号。
Compound property names
在设置bean属性时,只要最终属性名称以外的路径的所有组件都不为null,就可以使用复合或嵌套属性名称。 考虑下面的bean定义。
<bean id="foo" class="foo.Bar">
<property name="fred.bob.sammy" value="123" />
</bean>
foo bean有一个fred属性,它有一个bob属性,它具有sammy属性,并且最终sammy属性被设置为值123.为了使其工作,foo的fred属性和bob属性 在构造bean之后,fred一定不能为null,否则抛出NullPointerException。
1.4.3. Using depends-on
如果一个bean是另一个bean的依赖,那通常意味着一个bean被设置为另一个bean的属性。 通常,您可以使用基于XML的配置元数据中的元素来完成此操作。 但是,有时豆类之间的依赖性不那么直接; 例如,类中的静态初始化器需要被触发,例如数据库驱动程序注册。 depends-on属性可以在使用此元素的bean初始化之前明确强制一个或多个bean被初始化。 以下示例使用depends-on属性来表示对单个bean的依赖关系:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
要表示对多个bean的依赖关系,请提供一个bean名称列表作为depends-on属性的值,并使用逗号,空格和分号作为有效分隔符:
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
<property name="manager" ref="manager" />
</bean>
<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
bean定义中的depends-on属性可以指定一个初始化时间依赖项,并且在单例bean的情况下,可以指定相应的销毁时间依赖项。 定义依赖于给定bean的依赖关系的依赖bean在销毁给定bean之前首先销毁。 因此,依赖也可以控制关闭顺序。
1.4.4. Lazy-initialized beans
默认情况下,ApplicationContext实现作为初始化过程的一部分,急切地创建和配置所有的singleton bean。 通常,这种预先实例化是可取的,因为配置或周围环境中的错误是立即发现的,而不是几小时甚至几天后。 当这种行为不可取时,可以通过将bean定义标记为lazy-initialized
来防止单例bean的预先实例化。 一个惰性初始化bean告诉IoC容器在第一次请求时创建一个bean实例,而不是在启动时。
在XML中,此行为由元素上的lazy-init属性控制; 例如:
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>
当一个ApplicationContext使用前面的配置时,名为lazy的bean在ApplicationContext启动时并不急于预先实例化,而not.lazy bean则急切地预先实例化。
然而,当一个懒惰初始化bean是一个未经过延迟初始化的单例bean的依赖时,ApplicationContext在启动时创建了懒惰初始化bean,因为它必须满足单例的依赖关系。 懒惰初始化的bean被注入一个单独的bean中,并且没有被初始化。
您还可以通过在元素上使用default-lazy-init属性来控制容器级别的延迟初始化; 例如:
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
1.4.5. Autowiring collaborators
Spring容器可以自动连接合作bean之间的关系。您可以允许Spring通过检查ApplicationContext的内容来自动为您的bean解析协作者(其他bean)。自动装配具有以下优点:
- 自动装配可以显着减少指定属性或构造函数参数的需要。 (其他机制,例如本章其他地方讨论的bean模板,在这方面也很有价值。)
- 随着对象的发展,自动装配可以更新配置。例如,如果需要向类中添加依赖项,则可以自动满足该依赖项,而无需修改配置。因此,自动装配在开发过程中可能特别有用,而且不会影响代码库变得更加稳定时切换到显式布线的选项。
当使用基于XML的配置元数据[2]时,可以使用元素的autowire
属性为bean定义指定自动装配模式。自动装配功能有四种模式。您可以指定每个bean的自动装配,因此可以选择自动装配的自动装配。
Table 2. Autowiring modes
Mode | Explanation |
---|---|
no | (默认)无自动装配。 Bean引用必须通过ref元素定义。 建议不要将更改默认设置用于较大的部署,因为指定的协作者明确提供了更好的控制和清晰度。 它在一定程度上记录了系统的结构。 |
byName | 按物业名称自动装配。 Spring会查找与需要自动装配的属性同名的bean。 例如,如果一个bean定义被设置为autowire的名称,并且它包含一个主属性(也就是说,它有一个setMaster(..)方法),Spring会查找一个名为master的bean定义,并使用它来设置 属性。 |
byType | 如果属性类型中只有一个bean存在于容器中,则允许属性为自动装配。 如果存在多于一个,则会引发致命异常,这表明您可能不会为该bean使用byType自动装配。 如果没有匹配的bean,则什么都不会发生; 该物业未设置。 |
constructor | 类似于byType,但适用于构造函数参数。 如果容器中不存在唯一的构造函数参数类型的bean,则会引发致命错误。 |
使用byType或构造函数自动装配模式,您可以连线阵列和类型集合。 在这种情况下,容器中的所有符合期望类型的自动装配候选都被提供来满足依赖关系。 如果预期的键类型是字符串,则可以自动装入强类型映射。 自动装配的Maps值将由所有与预期类型匹配的bean实例组成,Maps键将包含相应的bean名称。
您可以将autowire行为与依赖性检查结合起来,这是在自动装配完成后执行的。
自动装配的局限和缺点
自动装配在项目中一致使用时效果最佳。 如果通常不使用自动装配,开发人员可能会使用它来仅连接一个或两个bean定义。
考虑自动装配的局限性和缺点:
- 属性和构造函数参数中的显式依赖关系始终会覆盖自动装配。 您不能自动调用所谓的简单属性,如基元,字符串和类(以及这些简单属性的数组)。 这个限制是通过设计。
- 自动装配不如准确布线。 虽然,如上表所述,Spring在注意避免猜测可能会有意想不到的结果的情况下进行猜测,但您的Spring管理的对象之间的关系不再明确记录。
- 布线信息可能无法用于可能从Spring容器生成文档的工具。
- 容器中的多个bean定义可以匹配由setter方法或构造函数参数指定的类型以进行自动装配。 对于数组,集合或地图,这不一定是个问题。 然而,对于期望单一值的依赖关系,这种不明确性不是任意解决的。 如果没有唯一的bean定义可用,则抛出异常。
后一种情况下,您有几种选择:
- 放弃autowiring以支持显式wiring。
- 通过将autowire-candidate属性设置为false,避免自动装配bean定义,如下一节所述。
- 通过将其元素的主属性设置为true,指定单个bean定义作为主要候选者。
- 如基于注释的容器配置中所述,实施基于注释的配置提供的更精细的控制。
Excluding a bean from autowiring
在每个bean的基础上,您可以从自动装配中排除一个bean。 在Spring的XML格式中,将元素的autowire-candidate属性设置为false; 该容器使该特定的bean定义对自动装配基础结构不可用(包括诸如@Autowired之类的注释样式配置)。
autowire-candidate属性旨在仅影响基于类型的自动装配。 它不会影响按名称显式引用,即使指定的bean未标记为自动连线候选,也会得到解决。 因此,如果名称匹配,通过名称自动装配将注入一个bean。
您还可以根据与bean名称的模式匹配来限制自动导向候选项。 *元素在其默认自动装配候选属性中接受一个或多个模式。 例如,要将autowire候选者状态限制为名称以存储库结尾的任何bean,请提供值* Repository。 要提供多种模式,请在逗号分隔列表中定义它们。 对于bean定义autowire-candidate属性,显式值true或false的值始终优先,对于这些bean,模式匹配规则不适用。
这些技术对于不想通过自动装配注入其他bean的bean非常有用。 这并不意味着排除的bean本身不能使用自动装配进行配置。 相反,该bean本身不是自动装配其他bean的候选者。
1.4.6. Method injection
在大多数应用场景中,容器中的大部分bean都是单例。 当单例bean需要与另一个单例bean协作,或者非单例bean需要与另一个非单例bean协作时,通常通过将一个bean定义为另一个bean的属性来处理依赖。 当bean生命周期不同时会出现问题。 假设单例bean A需要使用非单例(原型)bean B,可能在A上的每个方法调用上。容器只创建一次单例bean A,因此只有一次机会来设置属性。 每次需要时,容器都不能向bean A提供bean B的新实例。
解决方案是放弃一些控制反转。 您可以通过实现ApplicationContextAware接口来让bean A知道容器,并且每当bean A需要时,通过对容器的getBean(“B”)调用请求(通常是新的)bean B实例。 以下是这种方法的一个例子:
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
前面的内容是不可取的,因为业务代码知道并且耦合到Spring框架。 方法注入是Spring IoC容器的一个高级特性,它允许以干净的方式处理这个用例。
您可以在此博客条目中阅读更多关于方法注入的动机。
Lookup method injection
查找方法注入是容器覆盖容器管理的bean上方法的能力,以返回容器中另一个命名bean的查找结果。 查找通常包含一个原型bean,如前一节所述。 Spring Framework通过使用CGLIB库中的字节码生成来动态生成覆盖该方法的子类,从而实现了此方法注入。
- 为了使这个动态子类工作,Spring bean容器将继承的类不能是最终的,并且被覆盖的方法也不能是最终的。
- 对具有抽象方法的类进行单元测试需要您自己对该类进行子类化并提供抽象方法的存根实现。
- 组件扫描也需要具体的方法,这需要具体的类来提取。
- 另一个关键的限制是查找方法不适用于工厂方法,特别是不用于配置类中的@Bean方法,因为在这种情况下容器不负责创建实例,因此无法创建运行时生成的子类 在飞行中。
查看前面的代码片段中的CommandManager类,可以看到Spring容器将动态覆盖createCommand()
方法的实现。 你的CommandManager类不会有任何的Spring依赖关系,在重做的例子中可以看到:
package fiona.apple;
// no more Spring imports!
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
在包含要注入的方法的客户端类(在这种情况下为CommandManager)中,要注入的方法需要以下形式的签名:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果方法是抽象的,则动态生成的子类将实现该方法。 否则,动态生成的子类会覆盖原始类中定义的具体方法。 例如:
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="myCommand"/>
</bean>
标识为commandManager的bean在需要myCommand bean的新实例时调用其自己的方法createCommand()。 您必须小心地将myCommand bean作为原型部署,如果这实际上是需要的话。 如果它是单身人士,则每次返回myCommand bean的相同实例。
或者,在基于注解的组件模型中,您可以通过@Lookup注释声明一个查找方法:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup("myCommand")
protected abstract Command createCommand();
}
或者,更习惯地说,您可能依赖于针对查找方法的声明返回类型解析的目标bean:
public abstract class CommandManager {
public Object process(Object commandState) {
MyCommand command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract MyCommand createCommand();
}
请注意,您通常会使用具体的存根实现声明这样的带注释的查找方法,以便它们与Spring的组件扫描规则兼容,其中抽象类默认情况下会被忽略。 这种限制不适用于显式注册或显式导入的bean类。
访问不同范围的目标Bean的另一种方式是ObjectFactory / Provider注入点。 检查Scoped bean作为依赖关系。
感兴趣的读者也可以找到ServiceLocatorFactoryBean(在org.springframework.beans.factory.config包中)以供使用。
Arbitrary method replacement
与查找方法注入相比,不太有用的方法注入形式是能够用另一个方法实现来替换托管bean中的任意方法。 在实际需要功能之前,用户可以安全地跳过本节的其余部分。
使用基于XML的配置元数据,您可以使用replaced-method元素将现有的方法实现替换为已部署的bean。 考虑下面的类,我们想要覆盖一个方法computeValue:
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
实现org.springframework.beans.factory.support.MethodReplacer
接口的类提供了新的方法定义。
/**
* meant to be used to override the existing computeValue(String)
* implementation in MyValueCalculator
*/
public class ReplacementComputeValue implements MethodReplacer {
public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
// get the input value, work with it, and return a computed result
String input = (String) args[0];
...
return ...;
}
}
部署原始类并指定方法覆盖的bean定义如下所示:
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
<!-- arbitrary method replacement -->
<replaced-method name="computeValue" replacer="replacementComputeValue">
<arg-type>String</arg-type>
</replaced-method>
</bean>
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
您可以在<replaced-method />
元素中使用一个或多个包含的<arg-type />
元素来指示被覆盖的方法的方法签名。 只有当方法过载并且类中存在多个变体时,参数的签名才是必需的。 为了方便,参数的类型字符串可能是完全限定类型名称的子字符串。 例如,以下全部匹配java.lang.String:
java.lang.String
String
Str
由于参数的数量通常足以区分每种可能的选择,因此只需键入与参数类型匹配的最短字符串,此快捷键就可以节省大量输入。
1.5. Bean scopes
当你创建一个bean定义时,你创建一个配方来创建由该bean定义定义的类的实际实例。 bean定义是一个配方的想法很重要,因为它意味着,就像一个类一样,您可以从一个配方创建许多对象实例。
您不仅可以控制要插入到从特定的bean定义创建的对象中的各种依赖项和配置值,还可以控制从特定的bean定义创建的对象的范围。这种方法功能强大且灵活,因为您可以选择通过配置创建的对象的范围,而不必在Java类级别上烘焙对象的范围。 Bean可以定义为部署在多个作用域中的一个:Spring框架支持六个作用域,其中四个作用域只有在使用Web感知的ApplicationContext时才可用。
开箱即用支持以下范围。您也可以创建自定义范围。
Table 3. Bean scopes
Scope | Description |
---|---|
singleton | 默认)每个Spring IoC容器将单个bean定义作用于单个对象实例。 |
prototype | 将任何数量的对象实例的单个bean定义作用域。 |
request | 将单个bean定义作用于单个HTTP请求的生命周期; 也就是说,每个HTTP请求都有自己的实例,这个实例是在单个bean定义的背后创建的。 只有在Web感知的Spring ApplicationContext的上下文中才有效。 |
session | 在HTTP会话的生命周期中定义一个单一的bean定义。 只有在Web感知的Spring ApplicationContext的上下文中才有效。 |
application | 将一个单独的bean定义作用于ServletContext的生命周期。 只有在Web感知的Spring ApplicationContext的上下文中才有效。 |
websocket | 在WebSocket的生命周期中定义一个单一的bean定义。 只有在Web感知的Spring ApplicationContext的上下文中才有效。 |
从Spring 3.0开始,线程范围可用,但默认情况下未注册。 有关更多信息,请参阅SimpleThreadScope的文档。 有关如何注册此或任何其他自定义作用域的说明,请参阅使用自定义作用域。
1.5.1. The singleton scope
只管理单个bean的一个共享实例,并且具有与该bean定义匹配的id或id的bean的所有请求都会导致Spring容器返回一个特定的bean实例。
换句话说,当你定义一个bean定义并将其作为一个singleton作用域时,Spring IoC容器恰好创建了该bean定义定义的对象的一个实例。 这个单实例存储在这些单例bean的缓存中,并且该命名bean的所有后续请求和引用都会返回缓存的对象。
Spring的单例bean概念与*(GoF)模式书中定义的Singleton模式不同。 GoF Singleton对一个对象的范围进行硬编码,以便每个ClassLoader创建一个特定类的唯一一个实例。 Spring单例的范围最好按容器和每个bean来描述。 这意味着如果您为单个Spring容器中的特定类定义一个bean,那么Spring容器将创建该bean定义所定义的类的一个且仅有的一个实例。 单例作用域是Spring中的默认作用域。 要将一个bean定义为XML中的单例,您可以编写,例如:
<bean id="accountService" class="com.foo.DefaultAccountService"/>
<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>
1.5.2. The prototype scope
bean的部署的非单实例原型范围导致每次创建一个新的bean实例时,都会创建一个对该特定bean的请求。 也就是说,该bean被注入到另一个bean中,或者通过容器上的getBean()方法调用来请求它。 通常,为所有有状态bean和无状态bean的单例作用域使用原型作用域。
下图说明了Spring原型范围。 数据访问对象(DAO)通常不配置为原型,因为典型的DAO不具有任何对话状态; 这位作者更容易重用单体图的核心。
以下示例将bean定义为XML中的原型:
<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>
与其他范围相比,Spring不管理原型bean的完整生命周期:容器实例化,配置和以其他方式组装原型对象,并将其交给客户端,而不再记录该原型实例。因此,尽管在所有对象上调用初始化生命周期回调方法而不管范围,但在原型的情况下,不调用配置的销毁生命周期回调。客户端代码必须清理原型范围的对象并释放原型bean持有的昂贵资源。为了让Spring容器释放原型范围bean所拥有的资源,请尝试使用一个自定义bean后处理器,该后处理器保存对需要清理的bean的引用。
在某些方面,Spring容器在原型范围bean方面的作用是Java新运算符的替代。所有生命周期管理过去都必须由客户来处理。 (有关Spring容器中bean的生命周期的详细信息,请参阅生命周期回调。)
1.5.3. Singleton beans with prototype-bean dependencies
当您使用具有原型bean依赖关系的单一范围bean时,请注意,在实例化时会解析依赖关系。 因此,如果您将原型范围的bean依赖注入到单例范围的bean中,则将实例化新的原型bean,然后将依赖注入到单例bean中。 原型实例是唯一提供给单例范围bean的唯一实例。
但是,假设您希望单例范围的bean在运行时重复获取原型范围的bean的新实例。 你不能依赖注入一个原型范围的bean到你的单例bean中,因为这个注入只发生一次,当Spring容器实例化单例bean并解析并注入它的依赖关系时。 如果您不止一次在运行时需要一个原型bean的新实例,请参阅方法注入.
1.5.4. Request, session, application, and WebSocket scopes
请求,会话,应用程序和websocket范围仅在使用Web感知的Spring ApplicationContext实现(如XmlWebApplicationContext)时可用。 如果您将这些范围与常规Spring IoC容器(如ClassPathXmlApplicationContext)一起使用,则会抛出IllegalStateException异常,抱怨未知的bean范围。
Initial web configuration
要在请求,会话,应用程序和websocket级别(web范围的bean)中支持bean的范围界定,在定义bean之前需要一些次要的初始配置。 (标准示波器,单件和原型不需要此初始设置。)
您如何完成此初始设置取决于您特定的Servlet环境。
如果你在Spring Web MVC中访问有限范围的bean,实际上,在由Spring DispatcherServlet处理的请求中,不需要特别的设置:DispatcherServlet已经公开了所有相关的状态。
如果您使用Servlet 2.5 Web容器,并且在Spring的DispatcherServlet
之外处理请求(例如,使用JSF或Struts时),则需要注册org.springframework.web.context.request.RequestContextListener
ServletRequestListener。对于Servlet 3.0+,这可以通过WebApplicationInitializer接口以编程方式完成。或者,对于较老的容器,将以下声明添加到Web应用程序的web.xml文件中:
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
...
</web-app>
或者,如果监听器设置有问题,请考虑使用Spring的RequestContextFilter。 过滤器映射取决于周围的Web应用程序配置,因此您必须根据需要进行更改。
<web-app>
...
<filter>
<filter-name>requestContextFilter</filter-name>
<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>requestContextFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
...
</web-app>
DispatcherServlet,RequestContextListener和RequestContextFilter都完全相同,即将HTTP请求对象绑定到为该请求提供服务的线程。 这使得请求和会话范围的bean可以在调用链的更下方进行访问。
Request scopem
考虑以下用于bean定义的XML配置:
<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>
Spring容器通过为每个HTTP请求使用loginAction bean定义来创建LoginAction bean的新实例。 也就是说,loginAction bean的作用域是HTTP请求级别。 您可以根据需要更改创建的实例的内部状态,因为从同一个loginAction bean定义创建的其他实例不会看到这些状态变化; 他们对个人的要求很特别。 当请求完成处理时,作用于该请求的bean将被丢弃。
使用注释驱动组件或Java Config时,可以使用@RequestScope注释将组件分配给请求范围。
@RequestScope
@Component
public class LoginAction {
// ...
}
Session scope
考虑以下用于bean定义的XML配置:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
Spring容器通过在单个HTTP会话的生命周期中使用userPreferences bean定义来创建UserPreferences bean的新实例。 换句话说,userPreferences bean实际上在HTTP会话级别有效。 与请求范围的bean一样,您可以更改所创建的实例的内部状态,因为知道其他使用同一userPreferences bean定义创建的实例的HTTP Session实例在状态中看不到这些更改 ,因为它们对于单独的HTTP会话是特定的。 当HTTP会话最终被丢弃时,限制到该特定HTTP会话的bean也被丢弃。
在使用注释驱动组件或Java Config时,可以使用@SessionScope注释将组件分配到会话作用域。
@SessionScope
@Component
public class UserPreferences {
// ...
}
Application scope
考虑以下用于bean定义的XML配置:
<bean id="appPreferences" class="com.foo.AppPreferences" scope="application"/>
Spring容器通过对整个Web应用程序使用appPreferences bean定义一次来创建AppPreferences bean的新实例。 也就是说,appPreferences bean的作用域在ServletContext级别,作为常规的ServletContext属性存储。 这有点类似于Spring单例bean,但在两个重要方面有所不同:它是每个ServletContext的单例,而不是每个Spring’ApplicationContext’(在任何给定的Web应用程序中可能有几个),并且它实际上是公开的,因此 作为ServletContext属性可见。
使用注释驱动的组件或Java Config时,可以使用@ApplicationScope注释将组件分配给应用程序范围。
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
Scoped beans as dependencies
Spring IoC容器不仅管理对象(bean)的实例化,还管理协作者(或依赖项)的连接。 如果您想要将HTTP请求范围的bean注入(例如)一个更长寿命范围的另一个bean中,您可以选择注入一个AOP代理来代替范围bean。 也就是说,您需要注入一个代理对象,该对象公开与作用域对象相同的公共接口,但也可以从相关作用域(例如HTTP请求)中检索真实目标对象,并将方法调用委托给实际对象。
您也可以在范围为singleton的bean之间使用
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- an HTTP Session-scoped bean exposed as a proxy -->
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
<!-- instructs the container to proxy the surrounding bean -->
<aop:scoped-proxy/>
</bean>
<!-- a singleton-scoped bean injected with a proxy to the above bean -->
<bean id="userService" class="com.foo.SimpleUserService">
<!-- a reference to the proxied userPreferences bean -->
<property name="userPreferences" ref="userPreferences"/>
</bean>
</beans>
要创建这样的代理,您需要将一个子
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
<bean id="userManager" class="com.foo.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
在前面的示例中,singleton bean userManager注入了对HTTP Session范围的bean userPreferences的引用。这里的要点是userManager bean是一个单例:它将在每个容器中被实例化一次,并且它的依赖关系(在本例中只有一个,userPreferences bean)也只被注入一次。这意味着userManager bean只能在完全相同的userPreferences对象上运行,即它最初注入的那个对象。
这不是将寿命较短的作用域bean注入更长寿命作用域bean时所需的行为,例如将HTTP会话作用域的合作bean作为依赖注入到单例bean中。相反,您需要一个userManager对象,并且在HTTP Session的生命周期中,您需要一个特定于所述HTTP Session的userPreferences对象。因此,容器创建一个对象,该对象公开与UserPreferences类(理想情况下为UserPreferences实例的对象)完全相同的公共接口,该对象可以从作用域机制(HTTP请求,Session等)获取真实的UserPreferences对象。容器将这个代理对象注入到userManager bean中,但不知道这个UserPreferences引用是代理。在这个例子中,当一个UserManager实例在依赖注入的UserPreferences对象上调用一个方法时,它实际上是在代理上调用一个方法。代理然后从HTTP会话(本例中)中提取真实的UserPreferences对象,并将方法调用委托给检索到的实际UserPreferences对象。
因此,在将请求和会话范围的bean注入协作对象时,您需要以下正确且完整的配置:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
<aop:scoped-proxy/>
</bean>
<bean id="userManager" class="com.foo.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
Choosing the type of proxy to create
默认情况下,当Spring容器为使用
<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.foo.DefaultUserPreferences" scope="session">
<aop:scoped-proxy proxy-target-class="false"/>
</bean>
<bean id="userManager" class="com.foo.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
有关选择基于类或基于接口的代理的更多详细信息,请参阅代理机制。
1.5.5. Custom scopes
bean范围机制是可扩展的; 您可以定义自己的作用域,或者重新定义现有的作用域,尽管后者被认为是不好的做法,并且不能覆盖内置的单例和原型作用域。
Creating a custom scope
要将自定义作用域集成到Spring容器中,您需要实现本节中描述的org.springframework.beans.factory.config.Scope
接口。 有关如何实现自己的作用域的想法,请参阅随Spring Framework本身提供的Scope实现和Scope javadocs,它解释了需要更详细实现的方法。
Scope接口有四种方法可以从作用域中获取对象,将它们从作用域中移除并允许它们被销毁。
以下方法从基础范围返回对象。 例如,会话范围实现返回会话范围的bean(如果它不存在,该方法在将其绑定到会话以供将来参考之后返回该bean的新实例)。
Object get(String name, ObjectFactory objectFactory)
以下方法将该对象从基础范围中移除。 例如,会话范围实现从基础会话中删除会话范围的bean。 应该返回该对象,但如果找不到具有指定名称的对象,则可以返回null。
Object remove(String name)
以下方法注册范围在销毁时或范围内的指定对象被销毁时应执行的回调。 有关销毁回调的更多信息,请参阅javadocs或Spring范围实现。
void registerDestructionCallback(String name, Runnable destructionCallback)
以下方法获取基础范围的对话标识符。 这个标识符对于每个范围是不同的。 对于会话范围的实现,这个标识符可以是会话标识符。
String getConversationId()
Using a custom scope
在您编写和测试一个或多个自定义Scope实现后,您需要使Spring容器知道您的新作用域。 以下方法是使用Spring容器注册新Scope的核心方法:
void registerScope(String scopeName, Scope scope);
此方法在ConfigurableBeanFactory接口上声明,该接口在Spring通过BeanFactory属性提供的大多数具体ApplicationContext实现中都可用。
registerScope(..)方法的第一个参数是与范围关联的唯一名称; Spring容器中的这些名字的例子是单例和原型。 registerScope(..)方法的第二个参数是您希望注册和使用的自定义Scope实现的实际实例。
假设您编写自定义的Scope实现,然后如**册它。
下面的例子使用Spring包含的SimpleThreadScope,但默认情况下未注册。 这些说明对于您自己的自定义Scope实现而言是相同的。
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
然后创建符合自定义作用域范围规则的bean定义:
<bean id="..." class="..." scope="thread">
使用自定义的范围实现,您不限于范围的程序注册。 您还可以使用CustomScopeConfigurer类以声明方式执行Scope注册:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="thread">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
</property>
</bean>
<bean id="bar" class="x.y.Bar" scope="thread">
<property name="name" value="Rick"/>
<aop:scoped-proxy/>
</bean>
<bean id="foo" class="x.y.Foo">
<property name="bar" ref="bar"/>
</bean>
</beans>
在FactoryBean实现中放置
1.6. Customizing the nature of a bean
1.6.1. Lifecycle callbacks
要与bean生命周期的容器管理进行交互,可以实现Spring InitializingBean
和DisposableBean
接口。 容器为前者调用afterPropertiesSet()
,为后者调用destroy()
以允许bean在初始化和销毁bean时执行某些操作。
JSR-250 @PostConstruct和@PreDestroy注释通常被认为是在现代Spring应用程序中接收生命周期回调的最佳实践。 使用这些注释意味着你的bean没有耦合到Spring特定的接口。 有关详细信息,请参阅@PostConstruct和@PreDestroy。
如果您不想使用JSR-250注释,但仍想要移除耦合,请考虑使用init-method和destroy-method对象定义元数据。
在内部,Spring框架使用BeanPostProcessor
实现来处理它可以找到的任何回调接口并调用适当的方法。 如果您需要自定义功能或其他生命周期行为,Spring不提供开箱即用的功能,您可以自己实现BeanPostProcessor
。 有关更多信息,请参阅容器扩展点。
除了初始化和销毁回调,Spring管理的对象还可以实现生命周期接口,以便这些对象可以参与由容器自身生命周期驱动的启动和关闭过程。
本节描述生命周期回调接口。
Initialization callbacks
org.springframework.beans.factory.InitializingBean
接口允许bean在bean的所有必要属性已由容器设置后执行初始化工作。 InitializingBean接口指定一个方法:
void afterPropertiesSet() throws Exception;
建议您不要使用InitializingBean
接口,因为它不必要地将代码耦合到Spring。 或者,使用@PostConstruct注释或指定一个POJO初始化方法。 对于基于XML的配置元数据,您可以使用init-method属性来指定具有void无参数签名的方法的名称。 使用Java配置,您可以使用@Bean的initMethod属性,请参阅接收生命周期回调。 例如,以下内容:
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {
public void init() {
// do some initialization work
}
}
…和……完全一样
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {
public void afterPropertiesSet() {
// do some initialization work
}
}
但不会将代码耦合到Spring。
Destruction callbacks
实现org.springframework.beans.factory.DisposableBean
接口允许bean在包含它的容器被销毁时获得回调。 DisposableBean接口指定一个方法:
void destroy() throws Exception;
建议您不要使用DisposableBean
回调接口,因为它不必要地将代码耦合到Spring。 或者,使用@PreDestroy
注释或指定bean定义支持的通用方法。 使用基于XML的配置元数据时,可以使用<bean />
上的destroy-method
属性。 使用Java配置,您可以使用@Bean
的destroyMethod
属性,请参阅接收生命周期回调。 例如,下面的定义:
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {
public void cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
与以下内容完全相同:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
但不会将代码耦合到Spring。
可以为
<bean>
元素的destroy-method
属性赋予一个特殊的(推断的)值,它指示Spring自动检测特定bean类(实现java.lang.AutoCloseable或java的任何类)上的public close或shutdown方法。 因此io.Closeable会匹配)。 这个特殊的(推断的)值也可以在<beans>
元素的default-destroy-method
属性上设置,以将此行为应用于整个bean集合(请参阅默认初始化和销毁方法)。 请注意,这是Java配置的默认行为。
Default initialization and destroy methods
当您编写不使用特定于Spring的InitializingBean
和DisposableBean
回调接口的初始化和销毁方法回调时,通常会使用诸如init(),initialize(),dispose()等名称编写方法。 理想情况下,此类生命周期回调方法的名称在项目中标准化,以便所有开发人员使用相同的方法名称并确保一致性。
您可以配置Spring容器以查找命名初始化并销毁每个bean上的回调方法名称。 这意味着,作为应用程序开发人员,您可以编写应用程序类并使用称为init()的初始化回调,而无需为每个bean定义配置init-method =“init”
属性。 Spring IoC容器在创建bean时(并根据前面描述的标准生命周期回调协议)调用该方法。 此功能还为初始化和销毁方法回调强制执行一致的命名约定。
假设你的初始化回调方法被命名为init()
,并且销毁回调方法被命名为destroy()
。 在下面的例子中,你的课程将类似于课堂。
public class DefaultBlogService implements BlogService {
private BlogDao blogDao;
public void setBlogDao(BlogDao blogDao) {
this.blogDao = blogDao;
}
// this is (unsurprisingly) the initialization callback method
public void init() {
if (this.blogDao == null) {
throw new IllegalStateException("The [blogDao] property must be set.");
}
}
}
<beans default-init-method="init">
<bean id="blogService" class="com.foo.DefaultBlogService">
<property name="blogDao" ref="blogDao" />
</bean>
</beans>
顶层<beans />
元素属性中default-init-method
属性的存在导致Spring IoC容器识别出一个名为init的bean方法作为初始化方法回调。 当一个bean被创建和组装时,如果bean类有这样一个方法,它会在适当的时候被调用。
通过在*<beans />
元素上使用default-destroy-method属性,可以类似地配置destroy方法回调(即在XML中)。
在现有bean类已经具有与惯例不同的回调方法的情况下,可以通过使用<bean />
的init-method和destroy-method属性指定方法名称(即XML)来覆盖缺省值 本身。
Spring容器保证了一个配置好的初始化回调函数在bean被提供了所有的依赖关系后立即被调用。 因此初始化回调在原始bean引用上被调用,这意味着AOP拦截器等等还没有被应用到bean。 目标bean首先被完全创建,然后应用带有其拦截器链的AOP代理(例如)。 如果目标bean和代理是分别定义的,那么代码甚至可以绕过代理与原始目标bean进行交互。 因此,将拦截器应用于init方法会不一致,因为这样会将目标bean的生命周期与代理/拦截器耦合在一起,并在代码直接与原始目标bean交互时留下奇怪的语义。
Combining lifecycle mechanisms
从Spring 2.5开始,您有三个控制bean生命周期行为的选项:InitializingBean
和DisposableBean
回调接口; 自定义init()
和destroy()
方法; 和@PostConstruct
和@PreDestroy
注释。 你可以结合这些机制来控制给定的bean。
如果为bean配置了多个生命周期机制,并且每个机制都配置了不同的方法名称,那么每个配置的方法都按照下面列出的顺序执行。 但是,如果为这些生命周期机制中的多个生命周期机制配置了相同的方法名称(例如,初始化方法的init()),则该方法将执行一次,如前一部分所述。
为相同的bean配置多种生命周期机制,使用不同的初始化方法,如下所示:
- 用@PostConstruct注解的方法
- afterPropertiesSet()由InitializingBean回调接口定义
- 自定义配置的init()方法
销毁方法以相同的顺序被调用:
- 用@PreDestroy注释的方法
- destroy()由DisposableBean回调接口定义
- 自定义配置的destroy()方法
Startup and shutdown callbacks
生命周期界面为任何具有自己生命周期要求的对象(例如启动和停止一些后台进程)定义基本方法:
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
任何Spring管理的对象都可以实现该接口。 然后,当ApplicationContext本身接收到启动和停止信号时,例如 对于运行时的停止/重新启动场景,它会将这些调用级联到在该上下文中定义的所有Lifecycle实现。 它通过委派给LifecycleProcessor来完成此操作:
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
请注意,LifecycleProcessor本身就是生命周期界面的扩展。 它还添加了两种其他方法来对正在刷新和关闭的上下文作出反应。
请注意,常规的org.springframework.context.Lifecycle接口只是显式启动/停止通知的普通协定,并不意味着在上下文刷新时自动启动。 考虑实施org.springframework.context.SmartLifecycle,而不是对特定bean的自动启动(包括启动阶段)进行细粒度控制。 此外,请注意,停止通知不保证在销毁之前发生:在正常关闭时,所有生命周期bean将在传播通用销毁回调之前首先收到停止通知; 然而,在上下文的生命周期中的热刷新或中止刷新尝试时,只会调用销毁方法。
启动和关闭调用的顺序可能很重要。 如果任何两个对象之间存在“依赖关系”,则依赖方将在其依赖关系之后启动,并且在依赖关系之前停止。 但是,有时直接依赖关系是未知的。 您可能只知道某种类型的对象应该在另一种类型的对象之前启动。 在这些情况下,SmartLifecycle接口定义了另一个选项,即在其超级接口Phased上定义的getPhase()方法。
public interface Phased {
int getPhase();
}
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
启动时,相位最低的物体首先启动,停止时跟随相反的顺序。因此,一个实现SmartLifecycle并且其getPhase()方法返回Integer.MIN_VALUE的对象将成为第一个开始和最后一个停止的对象。在频谱的另一端,Integer.MAX_VALUE的相位值将指示该对象应该最后开始并且首先停止(可能是因为它取决于要运行的其他进程)。在考虑相位值时,了解任何未实现SmartLifecycle的“正常”生命周期对象的默认阶段为0也很重要。因此,任何负相位值都表示对象应在这些标准组件之前启动(并且在它们之后停止),反之亦然,对于任何正相位值。
正如您所看到的,SmartLifecycle定义的stop方法接受回调。任何实现必须在该实现的关闭过程完成后调用该回调的run()方法。这可以在需要时进行异步关闭,因为LifecycleProcessor接口的默认实现DefaultLifecycleProcessor将等待每个阶段中的对象组的超时值以调用该回调。每个阶段的默认超时时间是30秒。您可以通过在上下文中定义一个名为“lifecycleProcessor”的bean来覆盖默认的生命周期处理器实例。如果您只想修改超时值,那么定义以下就足够了:
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
如前所述,LifecycleProcessor接口还定义了用于刷新和关闭上下文的回调方法。后者将简单地驱动关闭过程,就好像stop()已被显式调用一样,但是当上下文关闭时会发生。另一方面,’刷新’回调启用了SmartLifecycle豆的另一个功能。当刷新上下文时(在所有对象被实例化并初始化之后),该回调将被调用,并且在那时默认生命周期处理器将检查由每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值。如果为“true”,那么该对象将在那个时候启动,而不是等待显式调用上下文或自己的start()方法(与上下文刷新不同,上下文启动不会自动执行标准上下文) 。 “阶段”值以及任何“依赖”关系将以与上述相同的方式确定启动顺序。
Shutting down the Spring IoC container gracefully in non-web applications
本节仅适用于非Web应用程序。 Spring的基于Web的ApplicationContext实现已经有适当的代码来在关闭相关Web应用程序时正常关闭Spring IoC容器。
如果您在非Web应用程序环境中使用Spring的IoC容器, 例如,在富客户端桌面环境中; 您使用JVM注册了一个关闭钩子。 这样做可以确保正常关闭并在单例bean上调用相关的销毁方法,从而释放所有资源。 当然,您仍然必须正确配置和实施这些销毁回调。
要注册一个关闭挂钩,可以调用ConfigurableApplicationContext接口上声明的registerShutdownHook()方法:
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Boot {
public static void main(final String[] args) throws Exception {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
// add a shutdown hook for the above context...
ctx.registerShutdownHook();
// app runs here...
// main method exits, hook is called prior to the app shutting down...
}
}
1.6.2. ApplicationContextAware and BeanNameAware
当一个ApplicationContext创建一个实现org.springframework.context.ApplicationContextAware
接口的对象实例时,该实例提供了对该ApplicationContext的引用。
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
因此,bean可以通过ApplicationContext接口或通过将引用强制转换为此接口的已知子类(如ConfigurableApplicationContext)来创建它们,从而以编程方式操作ApplicationContext,该类可公开其他功能。一个用途是对其他豆的程序化检索。有时候这种能力是有用的;但是,通常你应该避免它,因为它将代码耦合到Spring,并且不遵循Inversion of Control风格,其中协作者被提供给bean作为属性。 ApplicationContext的其他方法提供对文件资源的访问,发布应用程序事件以及访问MessageSource。这些附加功能在ApplicationContext的附加功能中进行了描述
从Spring 2.5开始,自动装配是获得对ApplicationContext的引用的另一种方法。 “传统”构造函数和byType自动装配模式(如自动装配协作者中所述)可以分别为构造函数参数或setter方法参数提供ApplicationContext类型的依赖关系。为了获得更大的灵活性,包括自动装配字段和多个参数方法的能力,请使用新的基于注释的自动装配功能。如果这样做,则ApplicationContext将自动装入字段,构造函数参数或方法参数中,如果所涉及的字段,构造函数或方法携带@Autowired注释,则该参数将期望ApplicationContext类型。有关更多信息,请参阅@Autowired。
当ApplicationContext创建一个实现org.springframework.beans.factory.BeanNameAware
接口的类时,该类将提供对其关联对象定义中定义的名称的引用。
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
这个回调函数是在正常的bean属性填充之后,但在初始化回调之前调用的,比如InitializingBean afterPropertiesSet或者一个自定义的init方法。
1.6.3. Other Aware interfaces
除了上面讨论的ApplicationContextAware和BeanNameAware之外,Spring提供了一系列Aware接口,允许bean向容器指示它们需要某种基础设施依赖性。 最重要的Aware接口总结如下 - 作为一般规则,名称是依赖类型的一个很好的指示:
Table 4. Aware interfaces
Name | Injected Dependency | Explained in…? |
---|---|---|
ApplicationContextAware | 声明ApplicationContext | ApplicationContextAware and BeanNameAware |
ApplicationEventPublisherAware | 封装ApplicationContext的事件发布者 | Additional capabilities of the ApplicationContext |
BeanClassLoaderAware | 用于加载Bean类的类加载器 | Instantiating beans |
BeanFactoryAware | 声明BeanFactory | ApplicationContextAware and BeanNameAware |
BeanNameAware | 声明bean的名称 | ApplicationContextAware and BeanNameAware |
BootstrapContextAware | 资源适配器BootstrapContext容器在其中运行。通常仅在支持JCA的ApplicationContexts中可用 | JCA CCI |
LoadTimeWeaverAware | 定义编织器用于在加载时处理类定义 | Load-time weaving with AspectJ in the Spring Framework |
MessageSourceAware | 用于解析消息的配置策略(支持参数化和国际化) | Additional capabilities of the ApplicationContext |
NotificationPublisherAware | Spring JMX通知发布者 | Notifications |
ResourceLoaderAware | 配置的加载器可以实现对资源的低级访问 | Resources |
ServletConfigAware | 当前的ServletConfig容器运行。仅在Web感知的Spring ApplicationContext中有效 | Spring MVC |
ServletContextAware | 容器运行的当前ServletContext。仅在Web感知的Spring ApplicationContext中有效 | Spring MVC |
再次注意,这些接口的使用将您的代码绑定到Spring API,并且不遵循控制反转样式。 因此,它们被推荐用于需要对容器进行编程访问的基础架构bean。
1.7. Bean definition inheritance
一个bean定义可以包含很多配置信息,包括构造函数参数,属性值和特定于容器的信息,例如初始化方法,静态工厂方法名称等等。 子bean定义从父定义继承配置数据。 根据需要,子定义可以覆盖一些值或添加其他值。 使用父和子bean定义可以节省大量的输入。 实际上,这是一种模板形式。
如果以编程方式使用ApplicationContext接口,则子Bean定义由ChildBeanDefinition类表示。 大多数用户在这个级别上不使用它们,而是用类似ClassPathXmlApplicationContext的方式声明性地配置bean定义。 在使用基于XML的配置元数据时,可以使用父属性指定子bean定义,并指定父bean作为此属性的值。
<bean id="inheritedTestBean" abstract="true"
class="org.springframework.beans.TestBean">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="inheritsWithDifferentClass"
class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBean" init-method="initialize">
<property name="name" value="override"/>
<!-- the age property value of 1 will be inherited from parent -->
</bean>
如果没有指定,则子bean定义使用父定义中的bean类,但也可以覆盖它。 在后一种情况下,子bean类必须与父类兼容,也就是说,它必须接受父类的属性值。
子bean定义继承了父级的范围,构造函数参数值,属性值和方法重写,并且可以添加新值。 您指定的任何范围,初始化方法,销毁方法和/或静态工厂方法设置都将覆盖相应的父设置。
其余设置始终从子定义中获取:依赖于,自动装配模式,依赖关系检查,单例,延迟初始化。
前面的示例通过使用abstract属性将父bean定义显式标记为抽象。 如果父定义没有指定类,则需要将父类定义显式标记为抽象,如下所示:
<bean id="inheritedTestBeanWithoutClass" abstract="true">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBeanWithoutClass" init-method="initialize">
<property name="name" value="override"/>
<!-- age will inherit the value of 1 from the parent bean definition-->
</bean>
父bean不能自行实例化,因为它是不完整的,并且它也明确标记为抽象。 当定义像这样抽象时,它只能用作纯模板bean定义,作为子定义的父定义。 尝试单独使用这样的抽象父bean,通过将其作为另一个bean的ref属性或使用父bean id执行显式getBean()调用返回一个错误。 同样,容器的内部preInstantiateSingletons()方法也会忽略定义为抽象的bean定义。
ApplicationContext默认预先实例化所有单例。 因此,重要的是(至少对于单例bean),如果你有一个你打算只用作模板的(父)bean定义,并且这个定义指定了一个类,那么你必须确保将abstract属性设置为true ,否则应用程序上下文将实际(尝试)预先实例化抽象bean。
1.8. Container Extension Points
通常,应用程序开发人员不需要继承ApplicationContext实现类。 相反,Spring IoC容器可以通过插入特殊集成接口的实现来扩展。 接下来的几节将介绍这些集成接口。
1.8.1. Customizing beans using a BeanPostProcessor
BeanPostProcessor接口定义了您可以实现的回调方法,以提供您自己的(或覆盖容器的默认)实例化逻辑,依赖关系解析逻辑等等。 如果你想在Spring容器完成实例化,配置和初始化bean之后实现一些定制逻辑,你可以插入一个或多个BeanPostProcessor实现。
您可以配置多个BeanPostProcessor实例,并且您可以通过设置订单属性来控制这些BeanPostProcessors执行的顺序。 只有BeanPostProcessor实现Ordered接口时,才可以设置此属性; 如果你编写自己的BeanPostProcessor,你应该考虑实现Ordered接口。 有关更多详细信息,请参阅BeanPostProcessor和Ordered接口的javadocs。 另请参阅以下有关BeanPostProcessor的程序注册的注释。
BeanPostProcessors对bean(或对象)实例进行操作; 也就是说,Spring IoC容器实例化一个bean实例,然后BeanPostProcessors完成它们的工作。
BeanPostProcessors是每个容器的作用域。 这只有在使用容器层次结构时才有意义。 如果你在一个容器中定义一个BeanPostProcessor,它只会在该容器中后处理bean。 换句话说,在一个容器中定义的bean不会被另一个容器中定义的BeanPostProcessor进行后处理,即使两个容器都是同一层次结构的一部分。
要更改实际的bean定义(即定义bean的蓝图),您需要使用BeanFactoryPostProcessor,如使用BeanFactoryPostProcessor定制配置元数据中所述。
org.springframework.beans.factory.config.BeanPostProcessor接口恰好包含两个回调方法。当这样的类被注册为容器的后处理器时,对于由容器创建的每个bean实例,后处理器都会在容器初始化方法(如InitializingBean的afterPropertiesSet()之前)和容器声明的init方法)以及任何bean初始化回调之后被调用。后处理器可以对bean实例执行任何操作,包括完全忽略回调。一个bean后处理器通常检查回调接口,或者可能用一个代理包装一个bean。一些Spring AOP基础设施类被实现为bean后处理器,以提供代理包装逻辑。
ApplicationContext自动检测实现BeanPostProcessor接口的配置元数据中定义的任何Bean。 ApplicationContext将这些bean注册为后处理器,以便稍后在创建bean时调用它们。 Bean后处理器可以像任何其他bean一样部署在容器中。
请注意,在配置类中使用@Bean工厂方法声明BeanPostProcessor时,工厂方法的返回类型应该是实现类本身,或者至少是org.springframework.beans.factory.config.BeanPostProcessor接口,清楚地指示该bean的后处理器特性。否则,在完全创建它之前,ApplicationContext将无法按类型自动检测它。由于BeanPostProcessor需要尽早实例化以适用于上下文中其他bean的初始化,因此这种早期类型检测非常重要。
以编程方式注册BeanPostProcessors
虽然BeanPostProcessor注册的推荐方法是通过ApplicationContext自动检测(如上所述),但也可以使用addBeanPostProcessor方法以编程方式将其注册到ConfigurableBeanFactory。 当需要在注册之前评估条件逻辑,或者甚至跨层次结构中的上下文复制Bean后处理器时,这会非常有用。 但请注意,以编程方式添加的BeanPostProcessors不尊重Ordered界面。 这是注册顺序决定执行顺序。 还要注意,以编程方式注册的BeanPostProcessors总是在通过自动检测注册的BeanPostProcessors之前进行处理,而不管任何明确的排序。
BeanPostProcessors和AOP自动代理
实现BeanPostProcessor接口的类是特殊的,并且容器对其进行不同的处理。作为ApplicationContext特殊启动阶段的一部分,它们直接引用的所有BeanPostProcessors和Bean都将在启动时实例化。接下来,所有BeanPostProcessors都以已排序的方式注册并应用于容器中的所有其他bean。因为AOP自动代理被实现为BeanPostProcessor本身,所以BeanPostProcessors和它们直接引用的bean都不适用于自动代理,因此没有编入它们的方面。对于任何这样的bean,您应该看到一条信息性日志消息:“Bean foo不适合通过所有BeanPostProcessor接口进行处理(例如:不适合自动代理)”。
请注意,如果使用自动装配或@Resource(可能会回退到自动装配)将Bean连接到BeanPostProcessor中,Spring可能会在搜索类型匹配的依赖关系候选时访问意外的Bean,因此使它们不适用于自动代理或其他类型豆后期处理。例如,如果你有@Resource标注的依赖项,其中的字段/设置者名称不直接对应于bean的声明名称,并且没有使用名称属性,那么Spring将访问其他bean以便按类型匹配它们。
以下示例显示如何在ApplicationContext中编写,注册和使用BeanPostProcessors。
Example: Hello World, BeanPostProcessor-style
这第一个例子说明了基本用法。 该示例显示了一个自定义的BeanPostProcessor实现,该实现调用每个bean的toString()方法,因为它是由容器创建的,并将结果字符串打印到系统控制台。
在自定义BeanPostProcessor实现类定义下面查找:
package scripting;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
// simply return the instantiated bean as-is
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean; // we could potentially return any object reference here...
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("Bean '" + beanName + "' created : " + bean.toString());
return bean;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:lang="http://www.springframework.org/schema/lang"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/lang
http://www.springframework.org/schema/lang/spring-lang.xsd">
<lang:groovy id="messenger"
script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
<lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
</lang:groovy>
<!--
当上面的bean(messenger)被实例化时,这个自定义
BeanPostProcessor实现将输出事实到系统控制台
-->
<bean class="scripting.InstantiationTracingBeanPostProcessor"/>
</beans>
注意InstantiationTracingBeanPostProcessor是如何定义的。 它甚至没有名称,因为它是一个bean,它可以像其他任何bean一样依赖注入。 (前面的配置也定义了一个由Groovy脚本支持的bean。Spring动态语言支持在标题为动态语言支持的章节中有详细介绍。)
以下简单的Java应用程序执行前面的代码和配置:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;
public final class Boot {
public static void main(final String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
Messenger messenger = (Messenger) ctx.getBean("messenger");
System.out.println(messenger);
}
}
前面的应用程序的输出类似于以下内容:
Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961
Example: The RequiredAnnotationBeanPostProcessor
将回调接口或注释与自定义BeanPostProcessor实现结合使用是扩展Spring IoC容器的常用方法。 Spring的RequiredAnnotationBeanPostProcessor就是一个例子,它是Spring发行版中的一个BeanPostProcessor实现,它确保标记有(任意)注释的bean的JavaBean属性实际(配置为)依赖注入一个值。
1.8.2. Customizing configuration metadata with a BeanFactoryPostProcessor
下一个我们将看到的扩展点是org.springframework.beans.factory.config.BeanFactoryPostProcesso
r。 这个接口的语义与BeanPostProcessor相似,主要区别在于:BeanFactoryPostProcessor在bean配置元数据上运行; 也就是说,Spring IoC容器允许BeanFactoryPostProcessor读取配置元数据并在容器实例化除BeanFactoryPostProcessor之外的任何Bean之前对其进行更改。
您可以配置多个BeanFactoryPostProcessors,并且您可以通过设置订单属性来控制这些BeanFactoryPostProcessors执行的顺序。 但是,如果BeanFactoryPostProcessor实现Ordered接口,则只能设置此属性。 如果你编写你自己的BeanFactoryPostProcessor,你应该考虑实现Ordered接口。 请参阅BeanFactoryPostProcessor和Ordered接口的javadoc以获取更多详细信息。
如果您想更改实际的bean实例(即从配置元数据创建的对象),则需要使用BeanPostProcessor(如上所述,使用BeanPostProcessor定制bean)。 虽然技术上可以在BeanFactoryPostProcessor中使用bean实例(例如,使用BeanFactory.getBean()),但这样做会导致bean过早实例化,从而违反标准容器生命周期。 这可能会导致负面影响,如绕过豆后处理。
另外,BeanFactoryPostProcessors是每个容器的作用域。 这只有在使用容器层次结构时才有意义。 如果您在一个容器中定义了一个BeanFactoryPostProcessor,它将只应用于该容器中的bean定义。 一个容器中的Bean定义将不会由另一个容器中的BeanFactoryPostProcessors进行后处理,即使这两个容器都是同一层次结构的一部分。
一个bean工厂后处理器在ApplicationContext中声明时会自动执行,以便将更改应用于定义容器的配置元数据。 Spring包含许多预定义的bean工厂后处理器,例如PropertyOverrideConfigurer和PropertyPlaceholderConfigurer。 例如,自定义BeanFactoryPostProcessor也可用于注册自定义属性编辑器。
ApplicationContext自动检测部署到其中的实现BeanFactoryPostProcessor接口的任何Bean。 它在适当的时候使用这些bean作为bean工厂后处理器。 您可以像任何其他bean一样部署这些后处理器bean。
和BeanPostProcessors一样,您通常不希望将BeanFactoryPostProcessors配置为延迟初始化。 如果没有其他Bean引用Bean(Factory)PostProcessor,则该后处理器根本不会被实例化。 因此,将它标记为延迟初始化将被忽略,即使您在
<beans />
元素的声明中将default-lazy-init属性设置为true,Bean(Factory)PostProcessor也将被急切实例化。
Example: the Class name substitution PropertyPlaceholderConfigurer
您可以使用PropertyPlaceholderConfigurer,通过使用标准的Java属性格式在一个单独的文件中将Bean定义的属性值进行外部化。 通过这样做,部署应用程序的人员可以自定义特定于环境的属性,如数据库URL和密码,而无需修改容器主XML定义文件或文件的复杂性或风险。
考虑以下基于XML的配置元数据片段,其中定义了包含占位符值的DataSource。 该示例显示了从外部属性文件配置的属性。 在运行时,一个PropertyPlaceholderConfigurer被应用于将取代DataSource的一些属性的元数据。 要替换的值被指定为遵循Ant / log4j / JSP EL样式的$ {property-name}形式的占位符。
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath:com/foo/jdbc.properties"/>
</bean>
<bean id="dataSource" destroy-method="close"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
实际值来自标准Java属性格式中的另一个文件:
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
因此,字符串$ {jdbc.username}在运行时被替换为值’sa’,同样适用于与属性文件中的键匹配的其他占位符值。 PropertyPlaceholderConfigurer检查大多数属性中的占位符和bean定义的属性。 此外,占位符前缀和后缀可以自定义。
通过Spring 2.5中引入的上下文命名空间,可以使用专用的配置元素来配置属性占位符。 一个或多个位置可以作为位置属性中的逗号分隔列表提供。
<context:property-placeholder location="classpath:com/foo/jdbc.properties"/>
PropertyPlaceholderConfigurer不仅在您指定的属性文件中查找属性。 默认情况下,它也检查Java系统属性,如果它无法在指定的属性文件中找到属性。 您可以通过使用以下三个支持的整数值之一来设置configurer的systemPropertiesMode属性来自定义此行为:
- never (0):从不检查系统属性
- fallback (1):如果不能在指定的属性文件中解析,请检查系统属性。 这是默认设置。
- override (2):在尝试指定的属性文件之前,首先检查系统属性。 这允许系统属性覆盖任何其他属性源。
有关更多信息,请查阅PropertyPlaceholderConfigurer javadocs。
您可以使用PropertyPlaceholderConfigurer来替换类名,当您必须在运行时选择特定的实现类时,这有时很有用。 例如:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <value>classpath:com/foo/strategy.properties</value> </property> <property name="properties">
custom.strategy.class=com.foo.DefaultStrategy
如果在运行时无法将类解析为有效的类,那么当它即将创建时,bean的解析失败,这是在非惰性初始化Bean的ApplicationContext的preInstantiateSingletons()阶段期间。
Example: the PropertyOverrideConfigurer
PropertyOverrideConfigurer是另一个bean工厂后处理器,类似于PropertyPlaceholderConfigurer,但与后者不同,原始定义对于bean属性可以具有默认值或根本没有值。 如果重写的属性文件没有某个bean属性的条目,则使用默认的上下文定义。
请注意,bean定义并不知道被重写,所以从XML定义文件中不会立即明显地看到正在使用覆盖配置器。 在为同一个bean属性定义不同值的多个PropertyOverrideConfigurer实例的情况下,由于重载机制,最后一个获胜。
属性文件配置行采用以下格式:
beanName.property=value
例如:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
这个示例文件可以与容器定义一起使用,该容器定义包含一个名为dataSource的bean,该bean具有驱动程序和url属性。
只要路径中除最终属性被重写的每个组件都已经非空(可能由构造函数初始化),也支持复合属性名称。 在这个例子中…
foo.fred.bob.sammy=123
foo bean的fred属性的bob属性的sammy属性被设置为标量值123。
指定的覆盖值总是文字值; 它们不会被翻译成bean引用。 当XML bean定义中的原始值指定一个bean引用时,这个约定也适用。
使用Spring 2.5中引入的上下文命名空间,可以使用专用配置元素配置属性覆盖:
<context:property-override location="classpath:override.properties"/>
1.8.3. Customizing instantiation logic with a FactoryBean
为自己工厂的对象实现org.springframework.beans.factory.FactoryBean
接口。
FactoryBean接口是Spring IoC容器实例化逻辑的可插入点。如果你有复杂的初始化代码,用Java可以更好地表达,而不是(可能)冗长的XML,你可以创建自己的FactoryBean,在该类中写入复杂的初始化,然后将自定义的FactoryBean插入到容器中。
FactoryBean接口提供三种方法:
- Object getObject():返回此工厂创建的对象的一个??实例。这个实例可能是共享的,这取决于这个工厂是否返回单例或原型。
- boolean isSingleton():如果此FactoryBean返回单例,则返回true,否则返回false。
- Class getObjectType():返回getObject()方法返回的对象类型,如果事先未知类型,则返回null。
FactoryBean的概念和接口用于Spring框架的许多地方; FactoryBean接口的50多个实现与Spring本身一起提供。
当你需要向一个实际的FactoryBean实例本身而不是它产生的bean请求一个容器时,在调用ApplicationContext的getBean()方法时,用&符号(&)作为序言。因此,对于具有myBean标识的给定FactoryBean,在容器上调用getBean(“myBean”)将返回FactoryBean的产品;而调用getBean(“&myBean”)则返回FactoryBean实例本身。
1.9. Annotation-based container configuration
注释比用于配置Spring的XML更好吗?
引入基于注释的配置引发了这种方法是否比XML更好的问题。简短的答案取决于。长久的答案是每种方法都有其优缺点,通常由开发人员决定哪种策略更适合他们。由于它们被定义的方式,注释在其声明中提供了很多上下文,从而导致更短,更简洁的配置。但是,XML在接触组件时不需要触及其源代码或重新编译它们就能胜任。一些开发人员更喜欢布线接近源,而另一些开发人员则认为注释类不再是POJO,而且配置变得分散,难以控制。无论选择什么,Spring都可以适应两种风格,甚至可以将它们混合在一起。值得指出的是,通过JavaConfig选项,Spring允许以非侵入方式使用注释,而无需触及目标组件源代码,并且在工具方面,Spring Tool Suite支持所有配置样式。
基于注释的配置提供了XML设置的替代方法,该配置依赖字节码元数据来连接组件而不是角括号声明。开发人员不用XML来描述bean布线,而是通过在相关的类,方法或字段声明中使用注释将配置移动到组件类本身中。如示例中所述:RequiredAnnotationBeanPostProcessor将BeanPostProcessor与注释一起使用是扩展Spring IoC容器的常用方法。例如,Spring 2.0引入了使用@Required注解强制执行所需属性的可能性。 Spring 2.5使得遵循相同的通用方法来驱动Spring的依赖注入成为可能。实质上,@ Autowired注释提供了与Autowiring合作者中描述的功能相同的功能,但具有更细致的控制和更广泛的适用性。 Spring 2.5还增加了对JSR-250注释的支持,如@PostConstruct和@PreDestroy。 Spring 3.0增加了对包含在javax.inject包(例如@Inject和@Named)中的JSR-330(Dependency Injection for Java)注释的支持。有关这些注释的详细信息可以在相关部分找到。
注释注入是在XML注入之前执行的,因此后者配置将覆盖通过两种方法连接的属性的前者。
与往常一样,您可以将它们注册为单独的bean定义,但也可以通过在基于XML的Spring配置中包含以下标记来隐式注册它们(注意包含上下文名称空间):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
(隐式注册的后处理器包括AutowiredAnnotationBeanPostProcessor,CommonAnnotationBeanPostProcessor,PersistenceAnnotationBeanPostProcessor以及前述的RequiredAnnotationBeanPostProcessor。)
<context:annotation-config />
仅在与其定义的应用程序上下文中查找bean上的注释。 这意味着,如果将DispatcherServlet
的<context:annotation-config />
放在WebApplicationContext中,它只会检查控制器中的@Autowired bean,而不是您的服务。 有关更多信息,请参阅DispatcherServlet。
1.9.1. @Required
@Required注释适用于bean属性设置器方法,如下例所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
这个注解简单地表明受影响的bean属性必须在配置时通过bean定义中的显式属性值或通过自动装配来填充。 如果受影响的bean属性尚未填充,容器将引发异常; 这允许急切和明确的失败,以后避免NullPointerExceptions等。 仍然建议您将断言放入bean类本身,例如,放入init方法中。 这样做即使在容器外部使用该类时也会强制执行那些必需的引用和值。
1.9.2. @Autowired
在下面的例子中,JSR 330的@Inject注解可以用来代替Spring的@Autowired注解。 详情请看这里。
您可以将@Autowired注释应用于构造函数:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
从Spring Framework 4.3开始,如果目标bean只定义了一个构造函数,那么这种构造函数上的@Autowired注释就不再需要了。 但是,如果有几个构造函数可用,则必须至少注明一个构造函数来教授容器使用哪一个。
正如所料,您还可以将@Autowired注释应用于“传统”设置器方法:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
您还可以将注释应用于具有任意名称和/或多个参数的方法:
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
您也可以将@Autowired应用于字段,甚至可以将其与构造函数混合使用:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
private MovieCatalog movieCatalog;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
确保您的目标组件(例如MovieCatalog,CustomerPreferenceDao)由用于@ Autowired注释的注入点的类型一致地声明。 否则由于在运行时找不到类型匹配,注入可能会失败。
对于通过类路径扫描找到的XML定义的bean或组件类,容器通常会预先知道具体类型。 但是,对于@Bean工厂方法,您需要确保声明的返回类型具有足够的表达力。 对于实现多个接口的组件或可能由其实现类型引用的组件,请考虑在工厂方法中声明最具体的返回类型(至少按照注入点对bean引用的要求)。
通过将注释添加到需要该类型数组的字段或方法,可以从ApplicationContext提供特定类型的所有Bean:
public class MovieRecommender {
@Autowired
private MovieCatalog[] movieCatalogs;
// ...
}
类型化的集合也是如此:
public class MovieRecommender {
private Set<MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
您的目标bean可以实现
org.springframework.core.Ordered
接口,或者如果希望数组或列表中的项目按特定顺序排序,则使用@Order或标准@Priority注释。 否则,他们的订单将遵循容器中相应目标bean定义的注册顺序。@Order注释可以在目标类层次上声明,也可以在@Bean方法中声明,可能每个bean定义都是非常单独的(在具有相同bean类的多个定义的情况下)。 @订单值可能影响注入点的优先级,但请注意,它们不影响单身启动顺序,这是由依赖关系和@DependsOn声明确定的正交关系。
请注意,标准的javax.annotation.Priority注解在@Bean级别不可用,因为它不能在方法上声明。 它的语义可以通过@Order值和@Primary在每个类型的单个bean上建模。
只要预期的键类型是字符串,即使键入的地图也可以自动装配。 Map值将包含期望类型的所有bean,并且键将包含相应的bean名称:
public class MovieRecommender {
private Map<String, MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
默认情况下,只要零候选bean可用,自动装配失败; 默认行为是将注释的方法,构造函数和字段视为指示所需的依赖项。 这种行为可以改变,如下所示。
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired(required = false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
从Spring Framework 5.0开始,您还可以使用@Nullable注释(任何包中的任何类型,例如来自JSR-305的javax.annotation.Nullable):
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
...
}
}
您还可以使用@Autowired作为众所周知的可解析依赖项的接口:BeanFactory,ApplicationContext,Environment,ResourceLoader,ApplicationEventPublisher和MessageSource。 这些接口及其扩展接口(如ConfigurableApplicationContext或ResourcePatternResolver)会自动解析,无需进行特殊设置。
public class MovieRecommender {
@Autowired
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
@Autowired,@Inject,@Resource和@Value注解由Spring BeanPostProcessor实现处理,这意味着您不能在您自己的BeanPostProcessor或BeanFactoryPostProcessor类型(如果有)中应用这些注释。 这些类型必须通过XML或使用Spring @Bean方法明确“连线”。
1.9.3. Fine-tuning annotation-based autowiring with @Primary
由于按类型自动装配可能会导致多个候选人,因此通常需要对选择过程有更多的控制权。 一种方法是使用Spring的@Primary注解。 @Primary表示当多个bean可以被自动装配成单值依赖项时,应该给予一个特定的bean优先。 如果候选人中只有一个“主要”bean,它将是自动装配的值。
假设我们有以下配置,将firstMovieCatalog定义为主MovieCatalog。
@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
通过这种配置,下面的MovieRecomder将通过firstMovieCatalog自动装配。
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
// ...
}
相应的bean定义如下所示。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog" primary="true">
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
1.9.4. Fine-tuning annotation-based autowiring with qualifiers
@Primary是一种有效的方法,可以在确定一个主要候选人时使用具有多个实例的类型的自动装配。 当需要对选择过程进行更多控制时,可以使用Spring的@Qualifier注释。 您可以将限定符值与特定参数相关联,缩小匹配类型的集合,以便为每个参数选择特定的bean。 在最简单的情况下,这可以是一个简单的描述性值:
public class MovieRecommender {
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
// ...
}
@Qualifier注解也可以在单独的构造函数参数或方法参数中指定:
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(@Qualifier("main")MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
相应的bean定义如下所示。 具有限定符值“main”的bean与用相同值限定的构造函数参数连线。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog">
<qualifier value="main"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<qualifier value="action"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
对于回退匹配,bean名称被视为默认限定符值。因此,您可以使用id“main”而不是嵌套的限定符元素来定义bean,从而得到相同的匹配结果。但是,虽然可以使用此惯例通过名称引用特定的bean,但@Autowired基本上是可选的语义限定符的类型驱动注入。这意味着限定符值(即使使用bean名称后备)也会在匹配类型集合内缩小语义;它们不会在语义上表达对唯一bean id的引用。良好的限定符值是“main”或“EMEA”或“persistent”,表示与bean id无关的特定组件的特征,如果使用匿名bean定义(如上例中的匿名bean定义) 。
限定符也适用于键入的集合,如上所述,例如,Set <MovieCatalog>
。在这种情况下,根据声明的限定符的所有匹配的Bean将作为集合注入。这意味着限定词不必是唯一的;它们只是构成过滤标准。例如,您可以使用相同的限定符值“action”定义多个MovieCatalog bean,所有这些都将注入到由@Qualifier(“action”)注释的Set <MovieCatalog>
中。
在类型匹配的候选者中,针对目标bean名称选择限定符值,在注入点甚至不需要@Qualifier注释。如果没有其他解析指示符(例如限定符或主标记),那么对于非唯一的依赖性情况,Spring将匹配注入点名称(即字段名称或参数名称)与目标bean名称,并选择相同的名称,命名候选人,如果有的话。
也就是说,如果您打算按名称表示注解驱动的注入,则不要主要使用@Autowired,即使能够在类型匹配的候选中按bean名称进行选择。相反,使用JSR-250 @Resource注释,该注释在语义上定义为通过其唯一名称来标识特定的目标组件,并且声明的类型与匹配过程无关。 @Autowired具有相当不同的语义:在按类型选择候选bean之后,指定的字符串限定符值将仅在这些类型选择的候选者中被考虑,例如,将“account”限定符与标有相同限定符标签的bean相匹配。
对于本身被定义为集合/映射或数组类型的bean,@Resource是一个很好的解决方案,通过唯一名称引用特定集合或数组bean。也就是说,只要元素类型信息保存在@Bean返回类型签名或集合继承层次结构中,就可以通过Spring的@Autowired类型匹配算法来匹配集合/映射和数组类型。在这种情况下,可以使用限定符值在相同类型的集合中进行选择,如前一段所述。
从4.3开始,@Autowired还考虑自引用注入,即引用回当前注入的bean。请注意,自我注入是后备;对其他组件的正常依赖关系始终具有优先权。从这个意义上说,自我引用不参与正规的候选人选择,因此尤其不是主要的;相反,它们总是以最低优先权结束。在实践中,仅使用自引用作为最后的手段,例如,用于通过bean的事务代理调用同一实例上的其他方法:考虑在这种情况下将受影响的方法分解为单独的委托bean。或者,使用@Resource可以通过其唯一名称获取代理返回到当前bean。
@Autowired适用于字段,构造函数和多参数方法,允许通过参数级别的限定符注释进行缩小。相比之下,@Resource仅支持具有单个参数的字段和bean属性设置方法。因此,如果注入目标是构造函数或多参数方法,则坚持使用限定符。
您可以创建自己的自定义限定符注释。 只需定义一个注释并在您的定义中提供@Qualifier注释:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
String value();
}
然后,您可以在自动布线字段和参数上提供自定义限定符:
public class MovieRecommender {
@Autowired
@Genre("Action")
private MovieCatalog actionCatalog;
private MovieCatalog comedyCatalog;
@Autowired
public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
this.comedyCatalog = comedyCatalog;
}
// ...
}
接下来,提供候选bean定义的信息。 您可以将<qualifier />
标记添加为<bean />
标记的子元素,然后指定类型和值以匹配您的自定义限定符注释。 该类型与注释的完全限定类名相匹配。 或者,如果没有相互冲突名称存在的风险,为了方便起见,您可以使用短名称。 以下示例演示了这两种方法。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog">
<qualifier type="Genre" value="Action"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<qualifier type="example.Genre" value="Comedy"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
在Classpath扫描和托管组件中,您将看到一种基于注释的替代方法来提供XML中的限定符元数据。 具体而言,请参阅提供限定符元数据和注释。
在某些情况下,使用没有值的注释可能就足够了。 当注释提供更通用的用途并且可以应用于多种不同类型的依赖关系时,这可能很有用。 例如,您可以提供一个脱机目录,当没有可用的Internet连接时将搜索该目录。 首先定义简单的注释:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
然后将注释添加到要自动装配的字段或属性中:
public class MovieRecommender {
@Autowired
@Offline
private MovieCatalog offlineCatalog;
// ...
}
现在,bean定义只需要一个限定符类型:
<bean class="example.SimpleMovieCatalog">
<qualifier type="Offline"/>
<!-- inject any dependencies required by this bean -->
</bean>
您还可以定义接受命名属性的自定义限定符注释,而不是简单的值属性。 如果在一个字段或参数上指定多个属性值进行自动装配,则一个bean定义必须匹配所有这些属性值才能被视为自动装配候选。 作为示例,请考虑以**释定义:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
String genre();
Format format();
}
在这种情况下,Format是一个枚举:
public enum Format {
VHS, DVD, BLURAY
}
要自动装配的字段使用自定义限定符进行注释,并包含两个属性的值: genre and format。
public class MovieRecommender {
@Autowired
@MovieQualifier(format=Format.VHS, genre="Action")
private MovieCatalog actionVhsCatalog;
@Autowired
@MovieQualifier(format=Format.VHS, genre="Comedy")
private MovieCatalog comedyVhsCatalog;
@Autowired
@MovieQualifier(format=Format.DVD, genre="Action")
private MovieCatalog actionDvdCatalog;
@Autowired
@MovieQualifier(format=Format.BLURAY, genre="Comedy")
private MovieCatalog comedyBluRayCatalog;
// ...
}
最后,bean定义应该包含匹配的限定符值。 这个例子还演示了bean meta属性可以用来代替<qualifier /
>子元素。 如果可用,则<qualifier />
及其属性优先,但如果不存在此类限定符,则自动装配机制将回退到<meta />
标记中提供的值,如以下示例中的最后两个bean定义所示。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog">
<qualifier type="MovieQualifier">
<attribute key="format" value="VHS"/>
<attribute key="genre" value="Action"/>
</qualifier>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<qualifier type="MovieQualifier">
<attribute key="format" value="VHS"/>
<attribute key="genre" value="Comedy"/>
</qualifier>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<meta key="format" value="DVD"/>
<meta key="genre" value="Action"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<meta key="format" value="BLURAY"/>
<meta key="genre" value="Comedy"/>
<!-- inject any dependencies required by this bean -->
</bean>
</beans>
1.9.5. Using generics as autowiring qualifiers
除了@Qualifier注释之外,还可以使用Java通用类型作为隐式形式的限定条件。 例如,假设您有以下配置:
@Configuration
public class MyConfiguration {
@Bean
public StringStore stringStore() {
return new StringStore();
}
@Bean
public IntegerStore integerStore() {
return new IntegerStore();
}
}
假设上面的Bean实现了一个通用接口,即Store <String>
和Store <Integer>
,您可以@Autowire Store接口和泛型将用作限定符:
@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean
@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean
自动装配列表,地图和阵列时也适用通用限定符:
// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
private List<Store<Integer>> s;
1.9.6. CustomAutowireConfigurer
CustomAutowireConfigurer是一个BeanFactoryPostProcessor,它使您可以注册自己的自定义限定符注释类型,即使它们没有使用Spring的@Qualifier注释进行注释。
<bean id="customAutowireConfigurer"
class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
<property name="customQualifierTypes">
<set>
<value>example.CustomQualifier</value>
</set>
</property>
</bean>
AutowireCandidateResolver通过以下方式确定自动装配候选者:
- 每个bean定义的autowire-candidate值
-
<beans />
元素上可用的任何默认自动布线候选模式 - 存在@Qualifier批注和任何通过CustomAutowireConfigurer注册的自定义批注
当多个bean有资格作为autowire候选者时,“主要”的确定如下所示:如果候选者中恰好有一个bean定义的主属性设置为true,则它将被选中。
1.9.7. @Resource
Spring还支持使用字段上的JSR-250 @Resource批注或bean属性设置器方法进行注入。 这是Java EE 5和6中的一种常见模式,例如在JSF 1.2托管bean或JAX-WS 2.0端点中。 Spring也支持Spring管理对象的这种模式。
@Resource接受一个名称属性,默认情况下,Spring将该值解释为要注入的bean名称。 换句话说,它遵循名义语义,如本例所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder")
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
如果没有明确指定名称,则默认名称是从字段名称或setter方法派生的。 如果是字段,则需要字段名称; 在setter方法的情况下,它采用bean属性名称。 所以下面的例子将把名为“movieFinder”的bean注入到它的setter方法中:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
与注释一起提供的名称通过CommonContentAnnotationBeanPostProcessor所了解的ApplicationContext被解析为bean名称。 如果您明确配置Spring的SimpleJndiBeanFactory,可以通过JNDI解析名称。 但是,建议您依赖默认行为并简单地使用Spring的JNDI查找功能来保持间接级别。
在@Resource用法中,没有明确指定名称,类似于@Autowired,@Resource查找主类型匹配而不是特定命名bean,并且解析了众所周知的可解析依赖关系:BeanFactory,ApplicationContext,ResourceLoader,ApplicationEventPublisher, 和MessageSource接口。
因此,在以下示例中,customerPreferenceDao字段首先查找名为customerPreferenceDao的bean,然后返回到CustomerPreferenceDao类型的主类型匹配。 “context”字段是基于已知可解析依赖类型ApplicationContext注入的。
public class MovieRecommender {
@Resource
private CustomerPreferenceDao customerPreferenceDao;
@Resource
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
1.9.8. @PostConstruct and @PreDestroy
CommonAnnotationBeanPostProcessor不仅可以识别@Resource注解,还可以识别JSR-250生命周期注释。 在Spring 2.5中引入的对这些注释的支持为初始化回调函数和销毁回调函数提供了另一种替代方案。 如果CommonAnnotationBeanPostProcessor在Spring ApplicationContext中注册,则在生命周期的相同时间点调用携带其中一个注释的方法,该方法与生命周期中相应的Spring生命周期接口方法或显式声明的回调方法相同。 在下面的示例中,缓存将在初始化时预填充,并在销毁时清除。
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
public void clearMovieCache() {
// clears the movie cache upon destruction...
}
}
有关组合各种生命周期机制的效果的详细信息,请参阅组合生命周期机制。
1.10. Classpath scanning and managed components
本章中的大多数示例都使用XML来指定在Spring容器中生成每个BeanDefinition的配置元数据。 上一节(基于注释的容器配置)演示了如何通过源代码级注释提供大量配置元数据。 但是,即使在这些示例中,“基本”bean定义在XML文件中显式定义,而注释仅驱动依赖注入。 本节介绍通过扫描类路径隐式检测候选组件的选项。 候选组件是与过滤条件相匹配的类,并具有在容器中注册的相应的bean定义。 这消除了使用XML来执行bean注册的需要; 相反,您可以使用注释(例如@Component),AspectJ类型表达式或您自己的自定义过滤条件来选择哪些类将具有注册到容器的bean定义。
从Spring 3.0开始,Spring JavaConfig项目提供的许多功能都是核心Spring框架的一部分。 这使您可以使用Java定义bean,而不是使用传统的XML文件。 查看@Configuration,@Bean,@Import和@DependsOn注释以获取如何使用这些新功能的示例。
1.10.1. @Component and further stereotype annotations
@Repository注释是任何实现存储库(也称为数据访问对象或DAO)的角色或构造型的类的标记。这种标记的用法是异常翻译中所述的自动翻译异常。
Spring提供了更多的构造型注释:@Component,@Service和@Controller。 @Component是任何Spring管理组件的通用构造型。对于更具体的用例,@Repository,@Service和@Controller是@Component的特化,例如,分别在持久层,服务和表示层中。因此,您可以使用@Component注释您的组件类,但通过使用@Repository,@Service或@Controller注释它们,您的类更适合通过工具进行处理或与方面相关联。例如,这些刻板印象注解为切入点提供了理想的目标。 @Repository,@Service和@Controller也可能在Spring Framework的未来版本中携带额外的语义。因此,如果您在为服务层使用@Component或@Service之间进行选择,@Service显然是更好的选择。同样,如上所述,已经支持@Repository作为持久层自动异常转换的标记。
1.10.2. Meta-annotations
Spring提供的许多注释可以在您自己的代码中用作元注释。 元注释只是一个可以应用于其他注释的注释。 例如,上面提到的@Service注释使用@Component进行元注释:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // Spring will see this and treat @Service in the same way as @Component
public @interface Service {
// ....
}
元注释也可以组合起来创建组合注释。 例如,Spring MVC的@RestController注释由@Controller和@ResponseBody组成。
另外,组合的注释可以可选地重新声明来自元注释的属性以允许用户定制。 当您只想暴露元注释属性的子集时,这可能特别有用。 例如,Spring的@SessionScope注解将范围名称硬编码为会话,但仍允许定制proxyMode。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {
/**
* Alias for {@link Scope#proxyMode}.
* <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
*/
@AliasFor(annotation = Scope.class)
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
然后可以在不声明proxyMode的情况下使用@SessionScope,如下所示:
@Service
@SessionScope
public class SessionScopedService {
// ...
}
或者为proxyMode重写一个值,如下所示:
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
// ...
}
有关更多详细信息,请参阅Spring注释编程模型wiki页面。
1.10.3. Automatically detecting classes and registering bean definitions
Spring可以自动检测定型类,并使用ApplicationContext注册相应的BeanDefinitions。 例如,以下两个类适用于这种自动检测:
@Service
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
@Repository
public class JpaMovieFinder implements MovieFinder {
// implementation elided for clarity
}
要自动检测这些类并注册相应的bean,需要将@ComponentScan添加到您的@Configuration类中,其中basePackages属性是这两个类的公共父包。 (或者,您可以指定包含每个类的父包的以逗号/分号/空格分隔的列表。)
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
...
}
为简洁起见,上面可能使用了注释的值属性,即@ComponentScan(“org.example”)
以下是使用XML的替代方法
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.example"/>
</beans>
使用
<context:component-scan>
隐式启用<context:annotation-config>
的功能。 当使用<context:component-scan>
时,通常不需要包含<context:annotation-config>
元素。
类路径包的扫描要求类路径中存在相应的目录条目。 在使用Ant构建JAR时,请确保不要**JAR任务的仅文件开关。 此外,在某些环境中,类路径目录可能不会基于安全策略暴露,例如, JDK 1.7.0_45及更高版本上的独立应用程序(需要在您的清单中设置“可信库”;请参阅http://*.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。
在JDK 9的模块路径(Jigsaw)中,Spring的类路径扫描一般按预期工作。 但是,请确保您的组件类在模块信息描述符中导出; 如果您希望Spring调用类的非公共成员,请确保它们是“打开”的(即在模块信息描述符中使用打开声明而不是导出声明)。
此外,当您使用组件扫描元素时,AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor都会隐式包含。 这意味着这两个组件都是自动检测和连接在一起的 - 所有这些都没有以XML提供的任何bean配置元数据。
您可以通过将annotation-config属性的值包含为false来禁用AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor的注册。
1.10.4. Using filters to customize scanning
默认情况下,使用@Component,@Repository,@Service,@Controller注释的类或者本身使用@Component注释的自定义注释是唯一检测到的候选组件。 但是,只需应用自定义过滤器即可修改和扩展此行为。 将它们添加为@ComponentScan注释的includeFilters或excludeFilters参数(或者作为component-scan元素的include-filter或exclude-filter子元素)。 每个过滤器元素都需要类型和表达式属性。 下表介绍了过滤选项。
Table 5. Filter Types
Filter Type | Example Expression | Description |
---|---|---|
annotation (default) | org.example.SomeAnnotation | 注释将出现在目标组件的类型级别上。 |
assignable | org.example.SomeClass | 目标组件可分配给的类(或接口)(扩展/实现)。 |
aspectj | org.example..*Service+ | 一个由目标组件匹配的AspectJ类型表达式。 |
regex | org.example.Default.* | 一个正则表达式要与目标组件类名匹配. |
custom | org.example.MyTypeFilter | org.springframework.core.type .TypeFilter接口的自定义实现。 |
以下示例显示了忽略所有@Repository注释并使用“存根”存储库的配置。
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
...
}
和使用XML的等价物
<beans>
<context:component-scan base-package="org.example">
<context:include-filter type="regex"
expression=".*Stub.*Repository"/>
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
</beans>
您还可以通过在注释中设置useDefaultFilters = false或提供use-default-filters =“false”作为
<component-scan />
元素的属性来禁用默认过滤器。 这将实际上禁用自动检测用@Component,@Repository,@Service,@Controller或@Configuration注解的类。
1.10.5. Defining bean metadata within components
Spring组件也可以将bean定义元数据提供给容器。 您可以使用相同的@Bean注释来定义@Configuration注释类中的bean元数据。 这是一个简单的例子:
@Component
public class FactoryMethodComponent {
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
public void doWork() {
// Component method implementation omitted
}
}
这个类是一个Spring组件,它的doWork()方法中包含了特定于应用程序的代码。 但是,它也提供了一个bean定义,它具有引用publicInstance()方法的工厂方法。 @Bean注释通过@Qualifier注释来标识工厂方法和其他bean定义属性,例如限定符值。 其他可以指定的方法级别注释是@Scope,@Lazy和自定义限定符注释。
除了用于组件初始化的角色外,@Lazy注释还可以放置在标有@Autowired或@Inject的注入点上。 在这种情况下,它导致注入一个懒惰的解析代理。
如前所述,支持自动布线的字段和方法,并支持自动装配@Bean方法:
@Component
public class FactoryMethodComponent {
private static int i;
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
// use of a custom qualifier and autowiring of method parameters
@Bean
protected TestBean protectedInstance(
@Qualifier("public") TestBean spouse,
@Value("#{privateInstance.age}") String country) {
TestBean tb = new TestBean("protectedInstance", 1);
tb.setSpouse(spouse);
tb.setCountry(country);
return tb;
}
@Bean
private TestBean privateInstance() {
return new TestBean("privateInstance", i++);
}
@Bean
@RequestScope
public TestBean requestScopedInstance() {
return new TestBean("requestScopedInstance", 3);
}
}
该示例将String方法参数country自动装入另一个名为privateInstance的bean的Age属性的值。 Spring表达式语言元素通过符号#{<表达式>}定义属性的值。对于@Value注释,表达式解析器预先配置为在解析表达式文本时查找bean名称。
从Spring Framework 4.3开始,您还可以声明InjectionPoint类型的工厂方法参数(或其更具体的子类DependencyDescriptor),以访问触发创建当前bean的请求注入点。请注意,这只适用于实际创建的bean实例,而不适用于注入现有实例。因此,对于原型范围的bean来说,这个特性最有意义。对于其他作用域,factory方法只会看到触发在给定范围内创建新bean实例的注入点:例如,触发创建惰性单例bean的依赖关系。在这种情况下使用提供的注入点元数据和语义保护。
@Component
public class FactoryMethodComponent {
@Bean @Scope("prototype")
public TestBean prototypeInstance(InjectionPoint injectionPoint) {
return new TestBean("prototypeInstance for " + injectionPoint.getMember());
}
}
常规Spring组件中的@Bean方法的处理方式与Spring @Configuration类中的对应方法不同。 不同之处在于,@Component类不会使用CGLIB来拦截方法和字段的调用。 CGLIB代理是通过@Configuration类中的@Bean方法中的调用方法或字段创建对协作对象的bean元数据引用的方式; 这种方法不是用普通的Java语义来调用,而是通过容器来提供Spring bean的通常的生命周期管理和代理,即使通过对@Bean方法的编程调用来引用其他bean。 相比之下,在普通的@Component类中调用@Bean方法中的方法或字段具有标准的Java语义,不需要应用特殊的CGLIB处理或其他约束。
您可以将@Bean方法声明为静态方法,允许在不创建其包含的配置类作为实例的情况下调用它们。当定义后处理器豆时,这是特别有意义的,例如BeanFactoryPostProcessor或BeanPostProcessor类型,因为这些bean将在容器生命周期的早期初始化,并且应避免在此时触发配置的其他部分。
请注意,对静态@Bean方法的调用永远不会被容器拦截,即使在@Configuration类中也是如此(参见上文)。这是由于技术限制:CGLIB子类只能覆盖非静态方法。因此,直接调用另一个@Bean方法将具有标准Java语义,从而导致独立实例从工厂方法本身直接返回。
@Bean方法的Java语言可见性不会立即影响Spring容器中的结果bean定义。你可以*地声明你的工厂方法,就像你在非@配置类中看到的那样,也可以在任何地方用静态方法。然而,@Configuration类中的常规@Bean方法需要被覆盖,即不能将它们声明为private或final。
@Bean方法也将在给定组件或配置类的基类上以及在由组件或配置类实现的接口中声明的Java 8默认方法上发现。这为构建复杂的配置安排提供了很大的灵活性,从Spring 4.2起,通过Java 8默认方法甚至可以实现多重继承。
最后,请注意,单个类可能为同一个bean保存多个@Bean方法,因为要根据运行时可用的依赖关系来使用多个工厂方法。这与在其他配置方案中选择“最贪婪的”构造函数或工厂方法的算法相同:在构建时将选择具有最大可满足依赖项数的变体,类似于容器在多个@Autowired构造函数之间进行选择的方式。
1.10.6. Naming autodetected components
当一个组件作为扫描进程的一部分被自动检测时,它的bean名称由该扫描器已知的BeanNameGenerator策略生成。 默认情况下,包含名称值的任何Spring构造型注解(@Component,@ Repository,@Service和@Controller)都会将该名称提供给相应的bean定义。
如果这样的注释不包含任何名称值或其他任何检测到的组件(例如自定义过滤器发现的那些),那么默认的bean名称生成器将返回未注册的非限定类名称。 例如,如果检测到以下两个组件,则名称将为myMovieLister和movieFinderImpl:
@Service("myMovieLister")
public class SimpleMovieLister {
// ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
如果你不想依赖默认的bean命名策略,你可以提供一个自定义的bean命名策略。 首先,实现BeanNameGenerator接口,并确保包含默认的无参数构造函数。 然后,在配置扫描仪时提供完全合格的类名称:
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
...
}
<beans>
<context:component-scan base-package="org.example"
name-generator="org.example.MyNameGenerator" />
</beans>
作为一般规则,当其他组件可能对其进行明确引用时,请考虑在注释中指定名称。 另一方面,只要容器负责布线,自动生成的名称就足够了。
1.10.7. Providing a scope for autodetected components
与一般的Spring管理组件一样,自动检测组件的默认和最常见的作用域是单例。 但是,有时您需要一个可以通过@Scope注释指定的不同范围。 只需在注释中提供范围的名称即可:
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
有关特定于Web的范围的详细信息,请参阅请求,会话,应用程序和WebSocket范围。
要为范围解析提供自定义策略而不是依赖基于注释的方法,请实现ScopeMetadataResolver接口,并确保包含默认的无参数构造函数。 然后,在配置扫描仪时提供完全合格的类名称:
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
...
}
<beans>
<context:component-scan base-package="org.example"
scope-resolver="org.example.MyScopeResolver" />
</beans>
在使用某些非单例作用域时,可能需要为作用域对象生成代理。 原因在范围bean中描述为依赖关系。 为此,组件扫描元素上提供了scoped-proxy属性。 三个可能的值是:no,interfaces和targetClass。 例如,以下配置将导致标准的JDK动态代理:
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
...
}
<beans>
<context:component-scan base-package="org.example"
scoped-proxy="interfaces" />
</beans>
1.10.8. Providing qualifier metadata with annotations
@Qualifier批注在Fine-tuning基于注释的自动装配中用限定符进行讨论。 该部分中的示例演示了在解析自动导向候选时,使用@Qualifier注释和自定义限定符注释来提供细粒度控制。 由于这些示例基于XML bean定义,因此限定符元数据是使用XML中bean元素的限定符或元子元素在候选bean定义上提供的。 当依靠类路径扫描来自动检测组件时,您可以在候选类上提供限定符元数据和类型级别注释。 以下三个示例演示了这种技术:
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
// ...
}
与大多数基于注解的替代方案一样,请记住,注释元数据绑定到类定义本身,而XML的使用允许相同类型的多个bean提供其限定符元数据的变体,因为元数据是按 - 而不是每班。
1.10.9. Generating an index of candidate components
虽然类路径扫描速度非常快,但通过在编译时创建候选静态列表,可以提高大型应用程序的启动性能。 在这种模式下,应用程序的所有模块都必须使用这种机制,因为当ApplicationContext检测到这样的索引时,它将自动使用它而不是扫描类路径。
要生成索引,只需向包含组件扫描指令目标组件的每个模块添加附加依赖项即可:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.0.4.RELEASE</version>
<optional>true</optional>
</dependency>
</dependencies>
或者,使用Gradle:
dependencies {
compileOnly("org.springframework:spring-context-indexer:5.0.4.RELEASE")
}
该进程将生成一个将包含在jar中的META-INF / spring.components文件。
在IDE中使用此模式时,必须将spring-context-indexer注册为注释处理器,以确保在更新候选组件时索引是最新的。
当在类路径上找到META-INF / spring.components时,索引会自动启用。 如果索引对于某些库(或用例)部分可用,但无法为整个应用程序构建,则可以通过设置spring.index.ignore来回退到常规类路径安排(即根本没有索引) 为true,可以是系统属性,也可以是类路径根目录下的spring.properties文件。
1.11. Using JSR 330 Standard Annotations
从Spring 3.0开始,Spring提供对JSR-330标准注释(依赖注入)的支持。 这些注释以与Spring注释相同的方式进行扫描。 你只需要在你的类路径中有相关的jar。
如果您使用的是Maven,则标准Maven存储库中可以使用javax.inject工件(http://repo1.maven.org/maven2/javax/inject/javax.inject/1/)。 您可以将以下依赖项添加到您的文件pom.xml中:
<dependency>
javax.inject
javax.inject
1
/dependency>
###1.11.1. Dependency Injection with @Inject and @Named
可以使用@ javax.inject.Inject而不是@Autowired,如下所示:
```java
import javax.inject.Inject;
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
public void listMovies() {
this.movieFinder.findMovies(...);
...
}
}
与@Autowired一样,可以在字段级别,方法级别和构造函数参数级别使用@Inject。 此外,您可以将注入点声明为Provider,允许按需访问较短范围的bean或通过Provider.get()调用对其他Bean的惰性访问。 作为上述示例的变体:
import javax.inject.Inject;
import javax.inject.Provider;
public class SimpleMovieLister {
private Provider<MovieFinder> movieFinder;
@Inject
public void setMovieFinder(Provider<MovieFinder> movieFinder) {
this.movieFinder = movieFinder;
}
public void listMovies() {
this.movieFinder.get().findMovies(...);
...
}
}
如果您想为应该注入的依赖项使用限定名称,则应该按如下方式使用@Named注释:
import javax.inject.Inject;
import javax.inject.Named;
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
像@Autowired一样,@Inject也可以和java.util.Optional或@Nullable一起使用。 这更适用于此,因为@Inject没有必需的属性。
public class SimpleMovieLister {
@Inject
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
...
}
}
public class SimpleMovieLister {
@Inject
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
...
}
}
1.11.2. @Named and @ManagedBean: standard equivalents to the @Component annotation
可以使用@ javax.inject.Named或javax.annotation.ManagedBean来代替@Component,如下所示:
import javax.inject.Inject;
import javax.inject.Named;
@Named("movieListener") // @ManagedBean("movieListener") could be used as well
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
使用@Component而不指定组件的名称是很常见的。 @Named可以以类似的方式使用:
import javax.inject.Inject;
import javax.inject.Named;
@Named
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
使用@Named或@ManagedBean时,可以像使用Spring注释一样使用组件扫描:
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
...
}
与@Component相反,JSR-330 @Named和JSR-250 ManagedBean注释不可组合。 请使用Spring的构造型模型来构建自定义组件注释。
1.11.3. Limitations of JSR-330 standard annotations
在使用标准注释时,重要的是要知道一些重要的功能不可用,如下表所示:
Table 6. Spring component model elements vs. JSR-330 variants
Spring | javax.inject.* | javax.inject restrictions / comments |
---|---|---|
@Autowired | @Inject | @Inject没有“必需”属性; 可以与Java 8的可选项一起使用。 |
@Component | @Named / @ManagedBean | JSR-330不提供可组合的模型,只是识别命名组件的一种方法。 |
@Scope(“singleton”) | @Singleton | JSR-330的默认范围就像Spring的原型。 然而,为了保持它与Spring的一般默认值一致,默认情况下,在Spring容器中声明的JSR-330 bean是单例。 为了使用除单例之外的作用域,您应该使用Spring的@Scope注释。 javax.inject还提供了@Scope注释。 尽管如此,这只是用来创建自己的注释。 |
@Qualifier | @Qualifier / @Named | javax.inject.Qualifier只是用于构建自定义限定符的元注释。 具体的字符串限定符(比如Spring的带有值的@Qualifier)可以通过javax.inject.Named关联。 |
@Value | - | no equivalent |
@Required | - | no equivalent |
@Lazy | - | no equivalent |
ObjectFactory | Provider | javax.inject.Provider是Spring的ObjectFactory的一个直接替代方法,只是使用较短的get()方法名称。 它也可以与Spring的@Autowired或者带有未注释的构造函数和setter方法结合使用。 |
1.12. Java-based container configuration
1.12.1. Basic concepts: @Bean and @Configuration
Spring新的Java配置支持中的中心构件是@Configuration
注释的类和@ Bean注释的方法。
@Bean注释用于表示一个方法实例化,配置并初始化一个新的对象,以便由Spring IoC容器管理。 对于那些熟悉Spring的<beans />
XML配置的人来说,@Bean注释和<bean />
元素具有相同的作用。 你可以对任何Spring @Component使用@Bean注解方法,但是,它们通常与@Configuration bean一起使用。
用@Configuration注解一个类表明它的主要目的是作为bean定义的来源。 此外,@Configuration类允许通过简单地调用同一个类中的其他@Bean方法来定义bean间依赖关系。 最简单可能的@Configuration类将如下所示:
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
上面的AppConfig类将等同于以下Spring <beans />
XML:
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
Full @Configuration vs’lite’@Bean模式?
当@Bean方法在没有使用@Configuration注释的类中声明时,它们被称为在’精简’模式下处理。在@Component中甚至在普通的旧类中声明的Bean方法将被视为’lite’,其中包含的类的主要用途不同,并且@Bean方法仅仅是一种奖励。例如,服务组件可能会通过每个适用组件类上的附加@Bean方法向容器公开管理视图。在这种情况下,@Bean方法是一种简单的通用工厂方法机制。与完整的@Configuration不同,lite @Bean方法不能声明bean间依赖关系。相反,他们对包含组件的内部状态和可选的参数进行操作,它们可能会声明。这样的@Bean方法因此不应该调用其他的@Bean方法;每个这样的方法实际上只是一个特定的bean引用的工厂方法,没有任何特殊的运行时语义。这里的积极副作用是,在运行时不需要应用CGLIB子类,所以在类设计方面没有限制(即,包含的类可能是最终的等)。
在常见情况下,@Bean方法将在@Configuration类中声明,确保始终使用’full’模式,并且跨方法引用将因此重定向到容器的生命周期管理。这将防止相同的@Bean方法被意外地通过常规的Java调用调用,这有助于减少在’精简’模式下操作时难以追踪的细微错误。
@Bean和@Configuration注解将在下面的章节中深入讨论。 首先,我们将介绍使用基于Java的配置创建Spring容器的各种方法。
1.12.2. Instantiating the Spring container using AnnotationConfigApplicationContext
下面的章节介绍Spring的AnnotationConfigApplicationContext,这是Spring 3.0中的新功能。 这种多功能的ApplicationContext实现不仅可以接受@Configuration类作为输入,还可以接受用JSR-330元数据注释的普通@Component类和类。
当提供@Configuration类作为输入时,@Configuration类本身被注册为一个bean定义,并且该类中所有声明的@Bean方法也被注册为bean定义。
当提供@Component和JSR-330类时,它们被注册为bean定义,并且假定在必要时在这些类中使用DI元数据(例如@Autowired或@Inject)。
Simple construction
与实例化ClassPathXmlApplicationContext时使用Spring XML文件作为输入的方式大致相同,在实例化AnnotationConfigApplicationContext时,@Configuration类可用作输入。 这允许完全无XML地使用Spring容器:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
如上所述,AnnotationConfigApplicationContext不仅限于使用@Configuration类。 任何@Component或JSR-330注释类都可以作为输入提供给构造函数。 例如:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
上面假设MyServiceImpl,Dependency1和Dependency2使用Spring依赖注入注释,例如@Autowired。
Building the container programmatically using register(Class
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
Enabling component scanning with scan(String…?)
要启用组件扫描,只需注释您的@Configuration类如下:
@Configuration
@ComponentScan(basePackages = "com.acme")
public class AppConfig {
...
}
经验丰富的Spring用户将熟悉Spring的context:namespace中的XML声明
<beans>
/beans>
在上面的示例中,将扫描com.acme包,查找任何@ Component注释的类,并且这些类将在容器中注册为Spring bean定义。 AnnotationConfigApplicationContext公开scan(String …)方法以允许使用相同的组件扫描功能:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}
请记住@Configuration类是用@Component进行元注释的,所以它们是组件扫描的候选对象! 在上面的例子中,假设AppConfig是在com.acme包(或下面的任何包)中声明的,它将在调用scan()期间被拾取,并且在refresh()后,将处理其所有的@Bean方法, 在容器中注册为bean定义。
Support for web applications with AnnotationConfigWebApplicationContext
AnnotationConfigApplicationContext的WebApplicationContext变体可与AnnotationConfigWebApplicationContext一起使用。 当配置Spring ContextLoaderListener servlet监听器,Spring MVC DispatcherServlet等时,可以使用此实现。接下来是配置典型Spring MVC Web应用程序的web.xml片段。 请注意contextClass context-param和init-param的使用:
<web-app>
<!-- 配置ContextLoaderListener以使用AnnotationConfigWebApplicationContext
而不是默认的XmlWebApplicationContext -->
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<!-- 配置位置必须包含一个或多个以逗号或空格分隔的位置
完全合格的@Configuration类。 完全合格的软件包也可以
指定用于组件扫描 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.AppConfig</param-value>
</context-param>
<!-- 像往常一样使用ContextLoaderListener引导根应用程序上下文 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--照常声明一个Spring MVC DispatcherServlet -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置DispatcherServlet以使用AnnotationConfigWebApplicationContext
而不是默认的XmlWebApplicationContext -->
<init-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</init-param>
<!-- 同样,配置位置必须包含一个或多个以逗号或空格分隔的位置
和完全合格的@Configuration类 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.web.MvcConfig</param-value>
</init-param>
</servlet>
<!-- 将/ app / *的所有请求映射到调度程序servlet -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
1.12.3. Using the @Bean annotation
@Bean是一个方法级别的注释,并且是XML <bean />
元素的直接模拟。 注解支持<bean />
提供的一些属性,例如:init-method,destroy-method,autowiring和name。
您可以在带有@ Configuration注释或带有@ Component注释的类中使用@Bean注释。
Declaring a bean
要声明一个bean,只需使用@Bean注释来注释一个方法即可。 您可以使用此方法在指定为方法返回值的类型的ApplicationContext中注册bean定义。 默认情况下,bean名称将与方法名称相同。 以下是一个@Bean方法声明的简单示例:
@Configuration
public class AppConfig {
@Bean
public TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
上述配置完全等同于以下Spring XML:
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
这两个声明都在ApplicationContext中创建一个名为transferService的bean,绑定到TransferServiceImpl类型的对象实例:
transferService -> com.acme.TransferServiceImpl
你也可以用接口(或基类)返回类型声明你的@Bean方法:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
但是,这会将预测类型预测的可见性限制为指定的接口类型(TransferService),然后,一旦实例化受影响的单例bean,该类型(TransferServiceImpl)只能由容器知道。 非懒惰的singleton bean根据它们的声明顺序被实例化,因此您可能会看到不同的类型匹配结果,具体取决于另一个组件尝试通过非声明类型进行匹配的时间(例如@Autowired TransferServiceImpl,它只会解析一次“transferService” bean已经被实例化)。
如果您始终通过声明的服务接口来引用您的类型,那么您的@Bean返回类型可以安全地加入该设计决策。 但是,对于实现多个接口的组件或其实现类型可能引用的组件,声明最具体的返回类型是可能的(至少与注入点引用您的bean所要求的相同)是比较安全的。
Bean dependencies
@Bean注释的方法可以有任意数量的参数来描述构建bean所需的依赖关系。 例如,如果我们的TransferService需要一个AccountRepository,我们可以通过一个方法参数实现这个依赖:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
解析机制与基于构造函数的依赖注入非常相似,请参阅相关部分以获取更多详细信息。
Receiving lifecycle callbacks
任何使用@Bean注释定义的类都支持常规生命周期回调,并且可以使用JSR-250中的@PostConstruct和@PreDestroy注释,请参阅JSR-250注释以获取更多详细信息。
常规的Spring生命周期回调也被完全支持。 如果一个bean实现了InitializingBean,DisposableBean或Lifecycle,那么它们各自的方法由容器调用。
标准的* Aware接口,如BeanFactoryAware,BeanNameAware,MessageSourceAware,ApplicationContextAware等也完全支持。
@Bean注解支持指定任意的初始化和销毁回调方法,就像Spring XML的bean元素的init-method和destroy-method属性一样:
public class Foo {
public void init() {
// initialization logic
}
}
public class Bar {
public void cleanup() {
// destruction logic
}
}
@Configuration
public class AppConfig {
@Bean(initMethod = "init")
public Foo foo() {
return new Foo();
}
@Bean(destroyMethod = "cleanup")
public Bar bar() {
return new Bar();
}
}
默认情况下,使用具有公共关闭或关闭方法的Java配置定义的bean将自动列入销毁回调。 如果你有一个public close或shutdown方法,并且你不希望在容器关闭时调用它,只需在你的bean定义中添加@Bean(destroyMethod =“”)来禁用默认(推断)模式。
您可能希望为通过JNDI获取的资源默认执行此操作,因为其生命周期在应用程序外部进行管理。 特别是,确保始终为DataSource执行此操作,因为它已知在Java EE应用程序服务器上存在问题。
@Bean(destroyMethod="") public DataSource dataSource() throws NamingException {
return (DataSource) jndiTemplate.lookup(“MyDS”);
}
此外,使用@Bean方法,通常会选择使用编程式JNDI查找:使用Spring的JndiTemplate / JndiLocatorDelegate帮助器或直接使用JNDI InitialContext,但不使用JndiObjectFactoryBean变体,这会强制您将返回类型声明为FactoryBean类型,而不是 实际的目标类型,这使得在其他@Bean方法中用于引用所提供资源的交叉引用调用更加困难。
当然,就上面的Foo而言,在构造过程中直接调用init()方法同样有效:
@Configuration
public class AppConfig {
@Bean
public Foo foo() {
Foo foo = new Foo();
foo.init();
return foo;
}
// ...
}
当您直接使用Java进行工作时,您可以对您的对象执行任何您喜欢的操作,并不总是需要依赖容器生命周期!
Specifying bean scope
Using the @Scope annotation
您可以指定使用@Bean注释定义的bean应具有特定范围。 您可以使用Bean Scopes部分中指定的任何标准范围。
默认范围是singleton,但您可以使用@Scope注释覆盖它:
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
@Scope and scoped-proxy
Spring提供了一种通过作用域代理来处理作用域依赖关系的便捷方式。 使用XML配置时创建此类代理的最简单方法是<aop:scoped-proxy />
元素。 使用@Scope注释在Java中配置bean提供了与proxyMode属性等效的支持。 默认值是无代理(ScopedProxyMode.NO),但您可以指定ScopedProxyMode.TARGET_CLASS或ScopedProxyMode.INTERFACES。
如果您使用Java将范围代理示例从XML参考文档(请参阅前面的链接)移植到我们的@Bean,它将如下所示:
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
public UserPreferences userPreferences() {
return new UserPreferences();
}
@Bean
public Service userService() {
UserService service = new SimpleUserService();
// a reference to the proxied userPreferences bean
service.setUserPreferences(userPreferences());
return service;
}
Customizing bean naming
默认情况下,配置类使用@Bean方法的名称作为结果bean的名称。 但是,可以使用name属性覆盖此功能。
@Configuration
public class AppConfig {
@Bean(name = "myFoo")
public Foo foo() {
return new Foo();
}
}
Bean aliasing
正如在命名bean中所讨论的,有时需要为单个bean提供多个名称,否则称为bean别名。 @Bean注释的name属性为此接受一个String数组。
@Configuration
public class AppConfig {
@Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" })
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
Bean description
有时候提供一个更详细的bean的文本描述是有帮助的。 当bean暴露(可能通过JMX)用于监视目的时,这可能特别有用。
要将描述添加到@Bean,可以使用@Description注释:
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Foo foo() {
return new Foo();
}
}
1.12.4. Using the @Configuration annotation
@Configuration是一个类级注释,指示一个对象是一个bean定义的来源。 @Configuration类通过public @Bean注释方法声明bean。 调用@Configuration类上的@Bean方法也可以用来定义bean间的依赖关系。 有关一般介绍,请参阅基本概念:@Bean和@Configuration。
Injecting inter-bean dependencies
当@Beans彼此依赖时,表达这种依赖就如同一个bean方法调用另一个一样简单:
@Configuration
public class AppConfig {
@Bean
public Foo foo() {
return new Foo(bar());
}
@Bean
public Bar bar() {
return new Bar();
}
}
在上面的示例中,foo bean通过构造函数注入接收对bar的引用。
这种声明bean间依赖关系的方法只有在@Configuration类中声明@Bean方法时才有效。 你不能用简单的@Component类来声明bean间的依赖关系。
Lookup method injection
如前所述,查找方法注入是一种您很少使用的高级功能。 在单例范围的bean对原型范围的bean具有依赖关系的情况下,它很有用。 对这种类型的配置使用Java提供了实现这种模式的自然方法。
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
使用Java配置支持,您可以创建CommandManager的子类,其中抽象的createCommand()方法被重写,以便查找新的(原型)命令对象:
@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
AsyncCommand command = new AsyncCommand();
// 根据需要在此注入依赖关系
return command;
}
@Bean
public CommandManager commandManager() {
// 用command()重写新的匿名实现CommandManager来返回一个新的原型Command对象
return new CommandManager() {
protected Command createCommand() {
return asyncCommand();
}
}
}
Further information about how Java-based configuration works internally
以下示例显示了一个被调用两次的@Bean注释方法:
@Configuration
public class AppConfig {
@Bean
public ClientService clientService1() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientService clientService2() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientDao clientDao() {
return new ClientDaoImpl();
}
}
clientDao()在clientService1()中被调用一次,在clientService2()中被调用一次。 由于此方法创建ClientDaoImpl的新实例并将其返回,因此通常期望拥有2个实例(每个服务一个实例)。 这肯定会有问题:在Spring中,实例化的bean默认具有单例作用域。 这就是神奇的地方:所有@Configuration类在启动时都使用CGLIB进行子类化。 在子类中,child方法在调用父方法并创建新实例之前首先检查容器是否有缓存的(范围)bean。 请注意,从Spring 3.2开始,不再需要将CGLIB添加到类路径中,因为CGLIB类已在org.springframework.cglib下重新打包,并直接包含在Spring-Core JAR中。
根据您的bean的范围,行为可能会有所不同。 我们在这里讨论单身人士。
由于CGLIB在启动时动态添加功能,因此存在一些限制,特别是配置类不能是最终的。 但是,从4.3开始,任何构造函数都可以在配置类上使用,包括对默认注入使用@Autowired或单个非默认构造函数声明。
如果您希望避免任何CGLIB限制,请考虑在非@配置类上声明您的@Bean方法,例如 而是使用简单的@Component类。 @Bean方法之间的跨方法调用不会被拦截,因此您必须在构造方法或方法级别专门依赖依赖注入。
1.12.5. Composing Java-based configurations
Using the @Import annotation
就像在Spring XML文件中使用<import />
元素以帮助模块化配置一样,@Import注释允许从另一个配置类加载@Bean定义:
@Configuration
public class ConfigA {
@Bean
public A a() {
return new A();
}
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B b() {
return new B();
}
}
现在,在实例化上下文时,不需要同时指定ConfigA.class和ConfigB.class,只需要显式提供ConfigB:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
// now both beans A and B will be available...
A a = ctx.getBean(A.class);
B b = ctx.getBean(B.class);
}
这种方法简化了容器实例化,因为只有一个类需要处理,而不需要开发人员在构建过程中记住大量的@Configuration类。
从Spring Framework 4.2开始,@Import还支持对常规组件类的引用,类似于AnnotationConfigApplicationContext.register方法。 如果您想要避免组件扫描,使用几个配置类作为明确定义所有组件的入口点,这特别有用。
Injecting dependencies on imported @Bean definitions
上面的例子工作,但是很简单。 在大多数实际场景中,bean将跨配置类彼此依赖。 当使用XML时,这本身并不是一个问题,因为不涉及编译器,并且可以简单地声明ref =“someBean”并相信Spring将在容器初始化期间解决它。 当然,在使用@Configuration类时,Java编译器会对配置模型施加约束,因为对其他bean的引用必须是有效的Java语法。
幸运的是,解决这个问题很简单。 正如我们已经讨论过的,@Bean方法可以有任意数量的描述bean依赖关系的参数。 让我们考虑一个更现实的场景,其中有几个@Configuration类,每个类都依赖于其他类中声明的bean:
@Configuration
public class ServiceConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
@Bean
public AccountRepository accountRepository(DataSource dataSource) {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
还有另一种方法可以达到相同的结果。 请记住@Configuration类最终只是容器中的另一个bean:这意味着它们可以像任何其他bean一样利用@Autowired和@Value注入等等!
确保以这种方式注入的依赖关系只有最简单的一种。 @Configuration类在上下文初始化期间处理得相当早,并强制依赖性以这种方式注入可能会导致意外的早期初始化。 在可能的情况下,采用基于参数的注入,如上例所示。
此外,通过@Bean特别小心BeanPostProcessor和BeanFactoryPostProcessor定义。 这些通常应该声明为静态的@Bean方法,而不是触发其包含的配置类的实例化。 否则,@Autowired和@Value将不会在配置类本身上工作,因为它太早创建为bean实例。
@Configuration
public class ServiceConfig {
@Autowired
private AccountRepository accountRepository;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
private final DataSource dataSource;
@Autowired
public RepositoryConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
@Configuration类中的构造函数注入仅在Spring Framework 4.3中受支持。 另请注意,如果目标bean只定义一个构造函数,则不需要指定@Autowired; 在上面的例子中,RepositoryConfig构造函数不需要@Autowired。
Fully-qualifying imported beans for ease of navigation
在上面的场景中,使用@Autowired可以很好地工作并提供所需的模块化,但是确切地确定自动布线bean定义的声明位置仍然有些模糊。 例如,作为开发人员查看ServiceConfig,您如何确切知道@Autowired AccountRepository bean的声明位置? 它在代码中并不明确,这可能会很好。 请记住,Spring Tool Suite提供的工具可以呈现图表,显示如何连接所有东西 - 这可能就是您所需要的。 此外,您的Java IDE可以轻松找到AccountRepository类型的所有声明和用法,并且可以快速向您显示返回该类型的@Bean方法的位置。
在这种不明确性不可接受的情况下,如果您希望从IDE内的一个@Configuration类直接导航到另一个类,请考虑自动装配配置类本身:
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
// navigate 'through' the config class to the @Bean method!
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
在上面的情况中,它完全明确了AccountRepository的定义。 但是,ServiceConfig现在与RepositoryConfig紧密耦合; 这是权衡。 通过使用基于接口的或基于抽象的基于类的@Configuration类,可以稍微缓解这种紧密耦合。 考虑以下:
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
@Configuration
public interface RepositoryConfig {
@Bean
AccountRepository accountRepository();
}
@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(...);
}
}
@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the concrete config!
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
现在ServiceConfig与具体的DefaultRepositoryConfig松散耦合,并且内置IDE工具仍然有用:开发人员可以轻松获得RepositoryConfig实现的类型层次结构。 通过这种方式,浏览@Configuration类及其依赖关系与导航基于接口的代码的常见过程无异。
如果您想影响某些bean的启动创建顺序,请考虑将其中的一些声明为@Lazy(用于在第一次访问时创建,而不是在启动时创建)或在某些其他bean上声明为@DependsOn(确保特定的其他Bean将 在当前bean之前创建,超出后者的直接依赖意味)。
Conditionally include @Configuration classes or @Bean methods
根据某些任意系统状态,有条件地启用或禁用完整的@Configuration类,甚至单个的@Bean方法通常很有用。 一个常见的例子是,只有在Spring环境中启用了特定的配置文件时,才使用@Profile注释来**bean(有关详细信息,请参阅Bean定义配置文件)。
@Profile注释实际上是使用一种更为灵活的名为@Conditional的注释来实现的。 @Conditional注释表示在@Bean注册之前应该查阅的特定org.springframework.context.annotation.Condition实现。
Condition接口的实现只是提供一个返回true或false的matches(…)方法。 例如,以下是用于@Profile的实际Condition实现:
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() != null) {
// Read the @Profile annotation attributes
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
return true;
}
}
return false;
}
}
return true;
}
有关更多详细信息,请参阅@Conditional javadocs。
Combining Java and XML configuration
Spring的@Configuration类支持并不旨在成为Spring XML的100%完全替代品。 Spring XML命名空间等一些工具仍然是配置容器的理想方式。 在XML方便或必要的情况下,您可以选择:使用例如ClassPathXmlApplicationContext以“以XML为中心”的方式实例化容器,或者使用AnnotationConfigApplicationContext和@ImportResource注解以“以Java为中心”的方式实例化容器 根据需要导入XML。
XML-centric use of @Configuration classes
最好从XML引导Spring容器,并以临时方式包含@Configuration类。 例如,在使用Spring XML的大型现有代码库中,根据需要创建@Configuration类并从现有XML文件中包含它们会更容易。 下面你会发现在这种“以XML为中心”的情况下使用@Configuration类的选项。
将@Configuration类声明为普通的Spring<bean />
元素
请记住,@Configuration类最终只是容器中的bean定义。 在这个例子中,我们创建一个名为AppConfig的@Configuration类,并将它作为定义包含在system-test-config.xml中。 由于
@Configuration
public class AppConfig {
@Autowired
private DataSource dataSource;
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
@Bean
public TransferService transferService() {
return new TransferService(accountRepository());
}
}
system-test-config.xml:
<beans>
<!-- enable processing of annotations such as @Autowired and @Configuration -->
<context:annotation-config/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="com.acme.AppConfig"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
jdbc.properties:
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
在上面的system-test-config.xml中,AppConfig
<bean /
>没有声明一个id元素。 尽管这样做是可以接受的,但没有必要考虑到其他bean将不会引用它,并且它不太可能通过名称明确地从容器中获取。 与DataSource bean类似 - 它只能通过类型自动装配,因此不需要显式的bean id。
Using<context:component-scan/>
to pick up @Configuration classes
由于@Configuration是使用@Component进行元注释的,所以@ Configuration-annotated类自动成为组件扫描的候选对象。 使用与上面相同的场景,我们可以重新定义system-test-config.xml以利用组件扫描。 请注意,在这种情况下,我们不需要显式声明<context:annotation-config />
,因为<context:component-scan />
启用了相同的功能。
system-test-config.xml:
<beans>
<!-- picks up and registers AppConfig as a bean definition -->
<context:component-scan base-package="com.acme"/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
@Configuration class-centric use of XML with @ImportResource
在@Configuration类是配置容器的主要机制的应用程序中,仍然可能有必要使用至少一些XML。 在这些场景中,只需使用@ImportResource并根据需要定义尽可能多的XML。 这样做可以实现“以Java为中心”的方式来配置容器并将XML保持最低限度。
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource() {
return new DriverManagerDataSource(url, username, password);
}
}
properties-config.xml
<beans>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
1.13. Environment abstraction
环境是集成在容器中的抽象,它模拟应用程序环境的两个关键方面:配置文件和属性。
配置文件是只有在给定配置文件处于活动状态时才能在容器中注册的命名的逻辑bean定义组。 豆可以被分配给配置文件,不管是用XML还是通过注释来定义。 环境对象与配置文件相关的角色是确定哪些配置文件(如果有)当前处于活动状态,以及哪些配置文件(如果有)在默认情况下应处于活动状态。
属性在几乎所有的应用程序中都扮演着重要的角色,可能来自各种来源:属性文件,JVM系统属性,系统环境变量,JNDI,servlet上下文参数,ad-hoc属性对象,地图等等。 与对象相关的Environment对象的作用是为用户提供一个方便的服务接口,用于配置属性来源并解析属性。
1.13.1. Bean definition profiles
Bean定义配置文件是核心容器中的一种机制,允许在不同的环境中注册不同的bean。 环境这个词对于不同的用户可能意味着不同的东西,这个特性可以帮助很多用例,其中包括:
- 与开发中的内存数据源相比,在QA或生产环境中查找来自JNDI的相同数据源
- 仅在将应用程序部署到性能环境中时注册监视基础结构
- 为客户A和客户B部署注册定制的bean实现
让我们考虑需要数据源的实际应用中的第一个用例。 在测试环境中,配置可能如下所示:
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build();
}
现在让我们考虑如何将此应用程序部署到QA或生产环境中,假定应用程序的数据源将注册到生产应用程序服务器的JNDI目录中。 我们的dataSource bean现在看起来像这样:
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
问题是如何在基于当前环境使用这两种变化之间切换。 随着时间的推移,Spring用户设计了很多方法来完成这个任务,通常依赖于系统环境变量和包含$ {placeholder}令牌的XML<import /
>语句的组合,这些令牌根据值解析为正确的配置文件路径 的环境变量。 Bean定义配置文件是提供解决此问题的核心容器功能。
如果我们概括上面特定于环境的bean定义的示例用例,我们最终需要在特定的上下文中注册某些bean定义,而在其他情况下则不需要。 你可以说你想在情况A中注册一个特定的bean定义配置文件,在情形B中需要一个不同的配置文件。让我们先看看我们如何更新我们的配置以反映这种需求。
@Profile
@Profile批注允许您在一个或多个指定配置文件处于活动状态时指示组件符合注册条件。 使用我们上面的例子,我们可以重写dataSource配置如下:
@Configuration
@Profile("development")
public class StandaloneDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
@Configuration
@Profile("production")
public class JndiDataConfig {
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
如前所述,使用@Bean方法,您通常会选择使用编程式JNDI查找:使用Spring的JndiTemplate / JndiLocatorDelegate帮助程序或上面显示的直接JNDI InitialContext用法,而不是JndiObjectFactoryBean变体,这会强制您将返回类型声明为 FactoryBean类型。
@Profile可以用作创建自定义组合注释的元注释。 以下示例定义了可用作@Profile(“production”)的插入替换的自定义@Production注释:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
如果@Configuration类标记为@Profile,则除非一个或多个指定的配置文件处于活动状态,否则与该类关联的所有@Bean方法和@Import批注都将被绕过。 如果@Component或@Configuration类标记为@Profile({“p1”,“p2”}),那么除非配置文件’p1’和/或’p2’已被**,否则不会注册/处理该类。 如果给定的配置文件以NOT运算符(!)作为前缀,则如果配置文件未处于活动状态,则注释的元素将被注册。 例如,给定@Profile({“p1”,“!p2”}),如果配置文件’p1’处于活动状态或配置文件’p2’未**,则会发生注册。
也可以在方法级别声明@Profile以仅包含配置类的一个特定bean,例如 对于特定bean的替代变体:
@Configuration
public class AppConfig {
@Bean("dataSource")
@Profile("development")
public DataSource standaloneDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
@Bean("dataSource")
@Profile("production")
public DataSource jndiDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
在@Bean方法上使用@Profile时,可能会出现一个特殊情况:对于具有相同Java方法名称的重载@Bean方法(类似于构造函数重载),必须在所有重载方法上一致地声明@Profile条件。如果条件不一致,那么只有重载方法中的第一个声明的条件才重要。因此@Profile不能用于选择一个重载的方法,并使用特定的参数签名而不是另一个;同一个bean的所有工厂方法之间的分辨率在创建时遵循Spring的构造函数解析算法。
如果您想要定义具有不同配置文件条件的备用bean,请使用不同的Java方法名称,通过@Bean名称属性指向同一个bean名称,如上例所示。如果参数签名完全相同(例如所有变体都没有arg工厂方法),那么这是首先在有效的Java类中表示这种安排的唯一方法(因为只能有一种方法一个特定的名字和参数签名)。
XML bean definition profiles
XML对应的是<beans>
元素的profile属性。 上面的示例配置可以用两个XML文件重写,如下所示:
<beans profile="development"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="...">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/20
01/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/
>
</beans>
也可以避免在同一个文件中拆分和嵌套<beans />
元素:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<!-- other bean definitions -->
<beans profile="development">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
</beans>
spring-bean.xsd被限制为仅允许这些元素作为文件中的最后一个元素。 这应该有助于提供灵活性,而不会在XML文件中产生混乱。
Activating a profile
现在我们已经更新了配置,我们仍然需要指示Spring哪个配置文件处于活动状态。 如果我们现在开始我们的示例应用程序,我们会看到抛出NoSuchBeanDefinitionException,因为容器找不到名为dataSource的Spring bean。
**一个配置文件可以通过几种方式来完成,但最直接的方法是通过编程方式对照通过ApplicationContext提供的Environment API:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
另外,配置文件也可以通过spring.profiles.active属性声明性地**,该属性可以通过系统环境变量,JVM系统属性,web.xml中的servlet上下文参数或甚至作为JNDI中的条目来指定(请参阅PropertySource抽象)。 在集成测试中,可以通过spring-test模块中的@ActiveProfiles注释来声明活动配置文件(请参阅使用环境配置文件的上下文配置)。
请注意,配置文件不是一个“或 - 或”的命题; 可以一次**多个配置文件。 以编程方式,只需将多个配置文件名称提供给setActiveProfiles()方法,该方法接受String … varargs:
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
声明地说,spring.profiles.active可以接受逗号分隔的配置文件名称列表:
-Dspring.profiles.active="profile1,profile2"
Default profile
默认配置文件表示默认情况下启用的配置文件。 考虑以下:
@Configuration
@Profile("default")
public class DefaultDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build();
}
}
如果没有配置文件处于活动状态,则会创建上面的数据源; 这可以被看作是为一个或多个bean提供默认定义的一种方式。 如果启用了任何配置文件,则默认配置文件将不适用。
可以使用环境中的setDefaultProfiles()或使用spring.profiles.default属性声明性地更改默认配置文件的名称。
1.13.2. PropertySource abstraction
Spring的环境抽象提供了对属性源的可配置层次结构的搜索操作。 为了充分解释,请考虑以下几点:
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsFoo = env.containsProperty("foo");
System.out.println("Does my environment contain the 'foo' property? " + containsFoo);
在上面的代码片段中,我们看到了一种高级别的方式来询问Spring是否为当前环境定义了foo属性。 为了回答这个问题,Environment对象对一组PropertySource对象执行搜索。 PropertySource是任何键值对的简单抽象,Spring的StandardEnvironment配置了两个PropertySource对象 - 一个表示一组JVM系统属性(一个System.getProperties()),另一个表示一组系统环境 变量(一个System.getenv())。
这些默认属性源用于StandardEnvironment,以用于独立应用程序。 StandardServletEnvironment使用其他默认属性来源填充,包括servlet配置和servlet上下文参数。 它可以选择启用JndiPropertySource。 有关详细信息,请参阅javadocs。
具体而言,在使用StandardEnvironment时,如果在运行时存在foo系统属性或foo环境变量,则对env.containsProperty(“foo”)的调用将返回true。
执行的搜索是分层次的。 默认情况下,系统属性优先于环境变量,因此如果在调用env.getProperty(“foo”)期间foo属性恰好在两个位置中设置,则系统属性值将为“赢”,并优先于 环境变量。 请注意,属性值不会被合并,而会被前面的条目完全覆盖。
对于常见的StandardServletEnvironment,完整的层次结构如下所示,最高优先级条目位于顶部:
- ServletConfig参数(如果适用,例如在DispatcherServlet上下文的情况下)
- ServletContext参数(web.xml上下文参数条目)
- JNDI环境变量(“java:comp / env /”条目)
- JVM系统属性(“-D”命令行参数)
- JVM系统环境(操作系统环境变量)
最重要的是,整个机制是可配置的。 也许你有一个自定义的属性来源,你想集成到这个搜索。 没问题 只需实现并实例化您自己的PropertySource并将其添加到当前环境的PropertySources
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource);
在上面的代码中,MyPropertySource在搜索中的优先级最高。 如果它包含foo属性,它将在任何其他PropertySource中的任何foo属性之前被检测到并返回。 MutablePropertySources API公开了许多允许精确操作属性集的方法。
1.13.3. @PropertySource
@PropertySource注解提供了一个便捷的声明机制,用于将一个PropertySource添加到Spring的环境中。
给定一个包含键/值对testbean.name = myTestBean的文件“app.properties”,以下@Configuration类使用@PropertySource,以便对testBean.getName()的调用返回“myTestBean”.
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
存在于@PropertySource资源位置的任何$ {…}占位符将根据已针对该环境注册的一组属性源进行解析。 例如:
@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
假设“my.placeholder”存在于已经注册的其中一个财产来源中,例如 系统属性或环境变量,占位符将被解析为相应的值。 如果不是,则默认使用“默认/路径”。 如果未指定默认值并且属性无法解析,则会抛出IllegalArgumentException。
1.13.4. Placeholder resolution in statements
从历史上看,元素中占位符的价值只能根据JVM系统属性或环境变量来解决。 不再是这种情况。 因为环境抽象被集成到整个容器中,所以很容易通过它来路由占位符的解析。 这意味着您可以以任何您喜欢的方式配置解析过程:更改通过系统属性和环境变量进行搜索的优先级,或者完全删除它们; 根据需要添加您自己的房源。
具体来说,只要在环境中可用,无论客户属性是在哪里定义的,以下语句都可以工作:
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
</beans>
1.14. Registering a LoadTimeWeaver
Spring使用LoadTimeWeaver在类加载到Java虚拟机(JVM)时动态转换类。
要启用加载时织入,请将@EnableLoadTimeWeaving添加到其中一个@Configuration类中:
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
或者,对于XML配置,使用上下文:load-time-weaver元素:
<beans>
<context:load-time-weaver/>
</beans>
一旦配置了ApplicationContext。 该ApplicationContext中的任何bean都可以实现LoadTimeWeaverAware,从而接收对加载时织机实例的引用。 这与Spring的JPA支持相结合特别有用,因为JPA类转换可能需要加载时织入。 有关更多详细信息,请参阅LocalContainerEntityManagerFactoryBean javadocs。 有关AspectJ加载时织入的更多信息,请参阅Spring框架中使用AspectJ进行加载时织入。
1.15. Additional capabilities of the ApplicationContext
正如在章节介绍中讨论的那样,org.springframework.beans.factory包提供了用于管理和操作bean的基本功能,包括以编程的方式。 org.springframework.context包添加了ApplicationContext接口,该接口扩展了BeanFactory接口,并扩展了其他接口以提供更多应用程序框架风格的附加功能。 许多人以完全声明的方式使用ApplicationContext,甚至没有以编程方式创建它,而是依赖支持类(如ContextLoader)自动实例化ApplicationContext,作为Java EE Web应用程序正常启动过程的一部分。
为了以更加面向框架的风格增强BeanFactory功能,上下文包还提供了以下功能:
- 通过MessageSource接口以i18n风格访问消息。
- 通过ResourceLoader接口访问资源,如URL和文件。
- 通过使用ApplicationEventPublisher接口将事件发布到实现ApplicationListener接口的bean。
- 加载多个(分层)上下文,允许每个上下文通过HierarchicalBeanFactory接口集中在一个特定层上,例如应用程序的Web层。
1.15.1. Internationalization using MessageSource
ApplicationContext接口扩展了一个名为MessageSource的接口,因此提供了国际化(i18n)功能。 Spring还提供接口HierarchicalMessageSource,它可以分层解析消息。 这些接口一起为Spring特效消息解析提供了基础。 这些接口上定义的方法包括:
-
String getMessage(String code,Object [] args,String default,Locale loc)
:用于从MessageSource中检索消息的基本方法。 如果未找到指定语言环境的消息,则使用默认消息。 使用标准库提供的MessageFormat功能,传入的任何参数都将成为替换值。 -
String getMessage(String code,Object [] args,Locale loc)
:与前面的方法基本相同,但有一点不同:不能指定默认消息; 如果消息无法找到,则抛出NoSuchMessageException。 -
String getMessage(MessageSourceResolvable resolvable,Locale locale)
:前面方法中使用的所有属性也都包含在一个名为MessageSourceResolvable的类中,您可以使用该方法。
当一个ApplicationContext被加载时,它会自动搜索在上下文中定义的MessageSource bean。 该bean必须具有名称messageSource。 如果找到这样的一个bean,所有对前面方法的调用都被委托给消息源。 如果找不到消息源,则ApplicationContext将尝试查找包含具有相同名称的bean的父代。 如果有,它将使用该Bean作为MessageSource。 如果ApplicationContext找不到任何消息源,则会实例化一个空的DelegatingMessageSource,以便能够接受对上面定义的方法的调用。
Spring提供了两个MessageSource实现,ResourceBundleMessageSource和StaticMessageSource。 两者都实现HierarchicalMessageSource以进行嵌套消息传递。 StaticMessageSource很少使用,但提供了编程方式将消息添加到源。 以下示例中显示了ResourceBundleMessageSource:
<beans>
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>format</value>
<value>exceptions</value>
<value>windows</value>
</list>
</property>
</bean>
</beans>
在这个例子中,假设你在你的类路径中定义了三个名为format,exceptions和windows的资源包。 任何解析消息的请求都将以通过ResourceBundles解析消息的JDK标准方式进行处理。 出于示例的目的,假设上述两个资源包文件的内容是……
# in format.properties
message=Alligators rock!
# in exceptions.properties
argument.required=The {0} argument is required.
下一个示例中显示了执行MessageSource功能的程序。 请记住,所有ApplicationContext实现也都是MessageSource实现,因此可以转换为MessageSource接口。
public static void main(String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("message", null, "Default", null);
System.out.println(message);
}
从上述程序产生的输出将是…
Alligators rock!
总之,MessageSource被定义在名为beans.xml的文件中,该文件存在于您的类路径的根目录中。 messageSource bean定义通过其基本名称属性引用许多资源包。 在列表中传递给基本名称属性的三个文件作为文件存在于类路径的根目录中,分别称为format.properties,exceptions.properties和windows.properties。
下一个示例显示传递给消息查找的参数; 这些参数将转换为字符串并插入查找消息中的占位符。
<beans>
<!-- 此MessageSource正在Web应用程序中使用 -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="exceptions"/>
</bean>
<!-- 让我们将上面的MessageSource注入到这个POJO中 -->
<bean id="example" class="com.foo.Example">
<property name="messages" ref="messageSource"/>
</bean>
</beans>
public class Example {
private MessageSource messages;
public void setMessages(MessageSource messages) {
this.messages = messages;
}
public void execute() {
String message = this.messages.getMessage("argument.required",
new Object [] {"userDao"}, "Required", null);
System.out.println(message);
}
}
调用execute()方法的结果输出将是…
The userDao argument is required.
关于国际化(i18n),Spring的各种MessageSource实现遵循与标准JDK ResourceBundle相同的区域设置解析和回退规则。 简而言之,继续前面定义的messageSource示例,如果要根据英式(en-GB)语言环境解析消息,则需要分别创建名为format_en_GB.properties,exceptions_en_GB.properties和windows_en_GB.properties的文件。
通常,区域设置解析由应用程序的周围环境管理。 在这个例子中,(英国)消息将被解析的地区是手动指定的。
# in exceptions_en_GB.properties
argument.required=Ebagum lad, the {0} argument is required, I say, required.
public static void main(final String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("argument.required",
new Object [] {"userDao"}, "Required", Locale.UK);
System.out.println(message);
}
从上述程序运行得到的输出将是…
Ebagum lad, the 'userDao' argument is required, I say, required.
您还可以使用MessageSourceAware接口获取对已定义的任何MessageSource的引用。 在创建和配置bean时,在实现MessageSourceAware接口的ApplicationContext中定义的任何bean都会注入应用程序上下文的MessageSource。
作为ResourceBundleMessageSource的替代方法,Spring提供了一个ReloadableResourceBundleMessageSource类。 该变体支持相同的包文件格式,但比标准的基于JDK的ResourceBundleMessageSource实现更灵活。 特别是,它允许从任何Spring资源位置(而不仅仅是从类路径)读取文件,并支持热重载bundle属性文件(同时有效地缓存它们)。 查看ReloadableResourceBundleMessageSource javadoc获取详细信息。
1.15.2. Standard and custom events
ApplicationContext中的事件处理通过ApplicationEvent类和ApplicationListener接口提供。 如果将实现ApplicationListener接口的bean部署到上下文中,则每次将ApplicationEvent发布到ApplicationContext时,都会通知该Bean。 实质上,这是标准Observer设计模式。
从Spring 4.2开始,事件基础结构得到了显着改进,并提供了基于注释的模型以及发布任意事件的能力,这是一个不一定从ApplicationEvent扩展的对象。 当这样的对象发布时,我们将它包装在一个事件中。
Spring提供了以下标准事件:
Table 7. Built-in Events
Event | Explanation |
---|---|
ContextRefreshedEvent | 例如,使用ConfigurableApplicationContext 接口上的refresh() 方法初始化或刷新ApplicationContext 时发布。 这里的“初始化”意味着所有的Bean都被加载,检测并**后处理器Bean,单例被预先实例化,并且ApplicationContext 对象已准备好使用。 只要上下文没有关闭,只要所选的ApplicationContext 实际上支持这种“热”刷新,就可以多次触发刷新。 例如,XmlWebApplicationContext 支持热刷新,但GenericApplicationContext 不支持。 |
ContextStartedEvent | 在ApplicationContext 启动时发布,在ConfigurableApplicationContext 接口上使用start() 方法。 这里的“开始”意味着所有生命周期bean都会收到明确的启动信号。 通常,此信号用于在显式停止后重新启动Bean,但它也可用于启动尚未配置为自动启动的组件,例如尚未启动初始化的组件。 |
ContextStoppedEvent | 在ApplicationContext 停止时发布,在ConfigurableApplicationContext 接口上使用stop() 方法。 这里“停止”意味着所有生命周期bean都会收到明确的停止信号。 停止的上下文可以通过start()调用重新启动。 |
ContextClosedEvent | 在ApplicationContext 关闭时发布,在ConfigurableApplicationContext 接口上使用close() 方法。 这里的“关闭”意味着所有的单身豆被销毁。 封闭的环境达到其生命的尽头; 它不能被刷新或重新启动。 |
RequestHandledEvent | 一个特定于web的事件,告知所有bean HTTP请求已被服务。 此事件在请求完成后发布。 该事件仅适用于使用Spring的DispatcherServlet的Web应用程序。 |
您还可以创建和发布自己的自定义事件。 这个例子演示了一个扩展Spring的ApplicationEvent基类的简单类:
public class BlackListEvent extends ApplicationEvent {
private final String address;
private final String test;
public BlackListEvent(Object source, String address, String test) {
super(source);
this.address = address;
this.test = test;
}
// accessor and other methods...
}
要发布自定义ApplicationEvent,请在ApplicationEventPublisher
上调用`publishEvent()
方法。 通常这是通过创建一个实现ApplicationEventPublisherAware
并将其注册为Spring bean的类来完成的。 以下示例演示了这样一个类:
public class EmailService implements ApplicationEventPublisherAware {
private List<String> blackList;
private ApplicationEventPublisher publisher;
public void setBlackList(List<String> blackList) {
this.blackList = blackList;
}
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void sendEmail(String address, String text) {
if (blackList.contains(address)) {
BlackListEvent event = new BlackListEvent(this, address, text);
publisher.publishEvent(event);
return;
}
// send email...
}
}
在配置时,Spring容器将检测到该EmailService实现了ApplicationEventPublisherAware,并将自动调用setApplicationEventPublisher()。 实际上,传入的参数将是Spring容器本身; 您只需通过其ApplicationEventPublisher接口与应用程序上下文进行交互。
要接收自定义ApplicationEvent,请创建一个实现ApplicationListener的类并将其注册为Spring bean。 以下示例演示了这样一个类:
public class BlackListNotifier implements ApplicationListener<BlackListEvent> {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
public void onApplicationEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
请注意,ApplicationListener通常使用您的自定义事件BlackListEvent的类型进行参数化。 这意味着onApplicationEvent()方法可以保持类型安全,避免任何向下转换的需要。 您可以根据需要注册许多事件侦听器,但请注意,默认情况下事件侦听器会同步接收事件。 这意味着publishEvent()方法会阻塞,直到所有侦听器完成处理事件。 这种同步和单线程方法的一个优点是,当侦听器接收到事件时,如果事务上下文可用,它将在发布者的事务上下文内部运行。 如果需要另一个事件发布策略,请参考Spring的ApplicationEventMulticaster接口的javadoc。
以下示例显示了用于注册和配置上述每个类的bean定义:
<bean id="emailService" class="example.EmailService">
<property name="blackList">
<list>
<value>aaa@qq.com</value>
<value>aaa@qq.com</value>
<value>aaa@qq.com</value>
</list>
</property>
</bean>
<bean id="blackListNotifier" class="example.BlackListNotifier">
<property name="notificationAddress" value="aaa@qq.com"/>
</bean>
综合起来,当调用emailService bean的sendEmail()方法时,如果有任何应被列入黑名单的电子邮件,则会发布BlackListEvent类型的自定义事件。 blackListNotifier bean被注册为一个ApplicationListener,并因此接收到BlackListEvent,此时它可以通知相关方。
Spring的事件机制被设计为在同一个应用程序上下文中的Spring bean之间进行简单的通信。 然而,对于更复杂的企业集成需求,单独维护的Spring Integration项目为构建轻量级,面向模式的事件驱动架构提供完全支持,该架构基于着名的Spring编程模型。
Annotation-based event listeners
从Spring 4.2开始,事件侦听器可以通过EventListener注解在托管bean的任何公共方法上注册。 BlackListNotifier可以被重写如下:
public class BlackListNotifier {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
@EventListener
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
正如您在上面看到的,方法签名再次声明它监听的事件类型,但是这次使用灵活的名称并且不实现特定的监听器接口。 只要实际事件类型在其实现层次结构中解析泛型参数,事件类型也可以通过泛型进行缩小。
如果你的方法应该监听几个事件,或者如果你想要根本没有参数定义它,事件类型也可以在注释本身上指定:
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
...
}
也可以通过注释的condition属性添加额外的运行时过滤,该属性定义了一个SpEL表达式,该表达式应匹配以实际调用特定事件的方法。
例如,如果事件的测试属性等于foo,我们的通知器可以被重写为仅被调用:
@EventListener(condition = "#blEvent.test == 'foo'")
public void processBlackListEvent(BlackListEvent blEvent) {
// notify appropriate parties via notificationAddress...
}
每个SpEL表达式再次评估一个专用上下文。 下表列出了可用于上下文的项目,以便可以将它们用于条件事件处理:
Table 8. Event SpEL available metadata
Name | Location | Description | Example |
---|---|---|---|
Event | root object | 实际的ApplicationEvent | root.event |
Arguments array | root object | 用于调用目标的参数(如数组) | root.args[0] |
Argument name | evaluation context | 任何方法参数的名称。 如果由于某种原因名称不可用(例如,没有调试信息),参数名称也可在#a <#arg>下使用,其中#arg代表参数索引(从0开始)。 | blEvent or #a0 (one can also use #p0 or #p<#arg> notation as an alias). |
请注意,#root.event允许您访问基础事件,即使您的方法签名实际上引用了已发布的任意对象。
如果您需要发布一个事件作为处理另一个事件的结果,只需更改方法签名以返回应该发布的事件,如下所示:
@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
// 通过notificationAddress通知相关方,然后发布ListUpdateEvent ...
}
异步侦听器不支持此功能。
这个新方法将为每个由上述方法处理的BlackListEvent发布一个新的ListUpdateEvent。 如果您需要发布多个事件,则只需返回一组事件。
Asynchronous Listeners
如果您希望特定的侦听器异步处理事件,只需重用常规的@Async支持即可:
@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
// BlackListEvent is processed in a separate thread
}
使用异步事件时请注意以下限制:
- 如果事件侦听器抛出异常,它将不会传播给调用者,请检查AsyncUncaughtExceptionHandler以获取更多详细信息。
- 这种事件监听器不能发送回复。 如果您需要发送另一个事件作为处理结果,请注入ApplicationEventPublisher以手动发送事件。
Ordering listeners
如果需要在另一个侦听器之前调用侦听器,只需将@Order注释添加到方法声明中即可:
@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
Generic events
您也可以使用泛型来进一步定义事件的结构。 考虑一个EntityCreatedEvent ,其中T是创建的实际实体的类型。 您可以创建以下侦听器定义以仅接收Person的EntityCreatedEvent:
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
...
}
由于类型擦除,只有当被触发的事件解决了事件侦听器过滤的泛型参数时(这与类PersonCreatedEvent extends EntityCreatedEvent {…})类似,才会起作用。
在某些情况下,如果所有事件都遵循相同的结构(这应该是上述事件的情况),则这可能变得非常乏味。 在这种情况下,您可以实现ResolvableTypeProvider以指导框架超出运行时环境所提供的范围:
public class EntityCreatedEvent<T>
extends ApplicationEvent implements ResolvableTypeProvider {
public EntityCreatedEvent(T entity) {
super(entity);
}
@Override
public ResolvableType getResolvableType() {
return ResolvableType.forClassWithGenerics(getClass(),
ResolvableType.forInstance(getSource()));
}
}
这不仅适用于ApplicationEvent,而且适用于作为事件发送的任意对象。
1.15.3. Convenient access to low-level resources
为了最佳使用和理解应用程序上下文,用户通常应该熟悉Spring的资源抽象,如资源一章所述。
应用程序上下文是一个ResourceLoader,可用于加载资源。资源本质上是JDK类java.net.URL的功能更丰富的版本,实际上,资源的实现在适当的情况下包装了java.net.URL的实例。资源可以透明方式从几乎任何位置获取低级资源,包括从类路径,文件系统位置,任何可用标准URL描述的位置以及其他变体。如果资源位置字符串是一个没有任何特殊前缀的简单路径,那么这些资源来自特定且适合于实际应用程序上下文类型。
您可以配置一个部署到应用程序上下文中的bean来实现特殊的回调接口ResourceLoaderAware,该接口将在初始化时自动回调,同时应用程序上下文本身作为ResourceLoader传入。您还可以公开用于访问静态资源的Resource类型的属性;它们将像其他任何属性一样被注入到它中。您可以将这些资源属性指定为简单的String路径,并依赖由上下文自动注册的特殊JavaBean PropertyEditor,以便在部署Bean时将这些文本字符串转换为实际的Resource对象。
提供给ApplicationContext构造函数的位置路径或路径实际上是资源字符串,并且以简单形式适当地处理特定的上下文实现。 ClassPathXmlApplicationContext将简单的位置路径视为类路径位置。您还可以使用带有特殊前缀的位置路径(资源字符串)来强制从类路径或URL中加载定义,而不管实际的上下文类型如何。
1.15.4. Convenient ApplicationContext instantiation for web applications
您可以使用例如ContextLoader以声明方式创建ApplicationContext实例。 当然,您也可以通过使用ApplicationContext实现之一以编程方式创建ApplicationContext实例。
您可以使用ContextLoaderListener注册一个ApplicationContext,如下所示:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
监听器检查contextConfigLocation
参数。 如果该参数不存在,那么侦听器将默认使用/WEB-INF/applicationContext.xm
l。 当参数确实存在时,侦听器使用预定义的分隔符(逗号,分号和空白)来分隔字符串,并将这些值用作应用程序上下文将被搜索的位置。 也支持Ant风格的路径模式。 示例是/WEB-INF/*Context.xml
,用于名称以“Context.xml”结尾,驻留在“WEB-INF”目录中的所有文件,以及/WEB-INF/**/*Context.xml
这些文件位于“WEB-INF”的任何子目录中。
1.15.5. Deploying a Spring ApplicationContext as a Java EE RAR file
可以将Spring ApplicationContext部署为RAR文件,将上下文及其所有必需的bean类和库JAR封装在Java EE RAR部署单元中。这相当于引导了一个独立的ApplicationContext,它只是在Java EE环境中托管,能够访问Java EE服务器设施。 RAR部署是部署无头WAR文件的场景中更自然的选择,实际上,WAR文件没有任何HTTP入口点,仅用于在Java EE环境中引导Spring ApplicationContext。
RAR部署非常适合不需要HTTP入口点但仅包含消息端点和预定作业的应用程序上下文。在这种情况下,Bean可以使用应用服务器资源,例如JTA事务管理器和JNDI绑定的JDBC DataSources和JMS ConnectionFactory实例,也可以通过Spring的标准事务管理和JNDI和JMX支持工具向平台的JMX服务器注册。应用程序组件还可以通过Spring的TaskExecutor抽象与应用程序服务器的JCA WorkManager进行交互。
查看SpringContextResourceAdapter类的javadoc,了解RAR部署中涉及的配置详细信息。
对于将Spring ApplicationContext简单部署为Java EE RAR文件:将所有应用程序类打包到RAR文件中,该文件是具有不同文件扩展名的标准JAR文件。将所有必需的库JAR添加到RAR归档的根目录中。添加一个“META-INF / ra.xml”部署描述符(如SpringContextResourceAdapters javadoc所示)和相应的Spring XML bean定义文件(通常为“META-INF / applicationContext.xml”),并放弃生成的RAR文件到您的应用程序服务器的部署目录。
这种RAR部署单元通常是独立的; 它们不会将组件暴露给外部世界,甚至不会暴露给同一应用程序的其他模块。 与基于RAR的ApplicationContext的交互通常通过它与其他模块共享的JMS目标发生。 例如,基于RAR的ApplicationContext也可以调度一些作业,对文件系统中的新文件(或诸如此类)作出反应。 如果需要允许从外部进行同步访问,则可以导出RMI端点,这当然可以由同一台机器上的其他应用程序模块使用。
1.16. The BeanFactory
BeanFactory为Spring的IoC功能提供了基础基础,但它仅直接用于与其他第三方框架的集成,现在对于Spring的大多数用户来说本质上是历史性的。 BeanFactory和相关接口(如BeanFactoryAware,InitializingBean,DisposableBean)在Spring中仍然存在,目的是为了与大量与Spring集成的第三方框架向后兼容。 通常第三方组件不能使用更多的现代对等项目,例如@PostConstruct或@PreDestroy,以避免依赖JSR-250。
本节提供了BeanFactory和ApplicationContext之间差异的额外背景,以及如何通过经典的单例查找直接访问IoC容器。
1.16.1. BeanFactory or ApplicationContext?
除非你有充分的理由不这样做,否则使用ApplicationContext。
因为ApplicationContext包含了BeanFactory的所有功能,所以通常推荐使用BeanFactory,除了少数情况,例如在资源受限的设备上运行的嵌入式应用程序中,这些设备的内存消耗可能至关重要,少数额外的千字节可能会产生影响。 但是,对于大多数典型的企业应用程序和系统,ApplicationContext就是您想要使用的。 Spring大量使用BeanPostProcessor扩展点(以实现代理等)。 如果您只使用简单的BeanFactory,则相当数量的支持(如事务和AOP)不会生效,至少在您没有执行某些额外步骤的情况下不会生效。 这种情况可能会令人困惑,因为配置没有任何问题。
下表列出了BeanFactory和ApplicationContext接口和实现提供的功能。
Table 9. Feature Matrix
Feature | BeanFactory | ApplicationContext |
---|---|---|
Bean instantiation/wiring | Yes | Yes |
自动BeanPostProcessor注册 | No | Yes |
自动BeanFactoryPostProcessor注册 | No | Yes |
便捷的MessageSource访问(针对i18n) | No | Yes |
ApplicationEvent发布 | No | Yes |
要使用BeanFactory实现显式注册Bean后处理器,您需要编写如下代码:
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// populate the factory with bean definitions
// now register any needed BeanPostProcessor instances
MyBeanPostProcessor postProcessor = new MyBeanPostProcessor();
factory.addBeanPostProcessor(postProcessor);
// now start using the factory
要在使用BeanFactory实现时显式注册BeanFactoryPostProcessor,您必须编写如下代码:
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));
// bring in some property values from a Properties file
PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));
// now actually do the replacement
cfg.postProcessBeanFactory(factory);
在这两种情况下,显式注册步骤都很不方便,这是为什么各种ApplicationContext实现比绝大多数Spring支持的应用程序中的纯BeanFactory实现更受欢迎的原因之一,尤其是使用BeanFactoryPostProcessor和BeanPostProcessors时。 这些机制实现了重要的功能,如资产占位符替换和AOP。
上一篇: 服务器:走向全栈开发的第一步
下一篇: 小程序实现商品属性选择或规格选择