Core Technologies
其中最重要的是 Spring 框架的控制反转(IoC)容器。对 Spring 框架的 IoC 容器进行彻底处理之后,将全面介绍 Spring 的面向方面编程(AOP)技术。 Spring 框架具有自己的 AOP 框架,该框架在概念上易于理解,并且成功解决了 Java 企业编程中 AOP 要求的 80%的难题。
还提供了 Spring 与 AspectJ 的集成(就功能而言,目前是功能最丰富的 Java,当然还有 Java 企业领域中最成熟的 AOP 实现)。
1、IOC容器
1.1Spring IoC容器和beans介绍
本章介绍了反转控制(IoC)原则的 Spring 框架实现。 (请参阅控制反转。)IoC 也称为依赖项注入(DI)。在此过程中,对象仅通过构造函数参数,工厂方法的参数或在构造或从工厂方法返回后在对象实例上设置的属性来定义其依赖项(即,与它们一起使用的其他对象) 。然后,容器在创建 bean 时注入那些依赖项。此过程从根本上讲是通过使用类的直接构造或诸如服务定位器模式之类的控件来控制其依赖项的实例化或位置的 bean 本身的逆过程(因此称为 Control Inversion)。
org.springframework.beans和org.springframework.context包是springframework的IoC容器的基础。
BeanFactory接口提供了一种高级配置机制,能够管理任何类型的对象。
ApplicationContext是BeanFactory的一个子接口。它增加了
-
与 Spring 的 AOP 功能轻松集成
-
消息资源处理(用于国际化)
-
Event publication
-
特定于应用程序层的上下文,例如用于 Web 应用程序的
WebApplicationContext
。
简而言之,BeanFactory提供了配置框架和基本功能,而ApplicationContext添加了更多特定于企业的功能。ApplicationContext是BeanFactory的一个完整超集,在本章中专门用于描述Spring的IoC容器。
在Spring中,构成应用程序主干并由Spring IoC容器管理的对象称为bean。bean是由Spring IoC容器实例化、组装和管理的对象。否则,bean只是应用程序中许多对象中的一个。Bean以及它们之间的依赖关系反映在容器使用的配置元数据中。
1.2.容器概述
接口org . Spring framework . context . applicationContext代表Spring IoC容器,负责实例化、配置和组装上述beans。容器通过读取配置元数据来获得关于实例化、配置和组装什么对象的指令。配置元数据用XML、Java注释或Java代码表示。它允许您表达组成应用程序的对象以及这些对象之间丰富的相互依赖关系。
Spring提供了几个现成的应用程序上下文接口实现。在独立的应用程序中,通常创建ClassPathXmlApplicationContext 或FileSystemXmlApplicationContext的实例。虽然XML是定义配置元数据的传统格式,但是您可以通过提供少量的XML配置来声明性地支持这些附加的元数据格式,从而指示容器使用Java注解或代码作为元数据格式。
在大多数应用程序场景中,不需要实例化用户代码即可实例化一个 Spring IoC 容器的一个或多个实例。例如,在一个web应用程序场景中,应用程序的web.xml文件中简单的八行:
<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>
您的应用程序类与配置元数据相结合,这样在创建和初始化应用程序上下文之后,您就拥有了一个完全配置和可执行的系统或应用程序。
1.2.1配置元数据
Spring IoC 容器使用一种形式的配置元数据。这种配置元数据代表了作为应用程序开发人员,您如何告诉Spring容器在您的应用程序中实例化、配置和组装对象。
配置元数据传统上以简单直观的XML格式提供,这是本章的大部分内容用来传达Spring IoC容器的关键概念和功能。
基于XML的元数据不是唯一允许的配置元数据形式。Spring IoC 容器本身与实际写入此配置元数据的格式完全脱钩。如今,许多开发人员为他们的Spring应用程序选择Java-based configuration。
-
Annotation-based configuration:Spring 2.5 引入了对基于注解的配置元数据的支持。
-
Java-based configuration:从 Spring 3.0 开始,Spring JavaConfig 项目提供的许多功能成为了核心 Spring Framework 的一部分。因此,您可以使用 Java 而不是 XML 文件来定义应用程序类外部的 bean。要使用这些新功能,请参见@Configuration,@Bean,@Import和@DependsOn注解。
Spring配置由容器必须管理的至少一个(通常不止一个)bean定义组成。基于XML的配置元数据将这些bean配置为顶层< beans/>元素中的< bean/>元素。Java配置通常在@Configuration类中使用@Bean注释方法。
这些bean定义对应于组成应用程序的实际对象。通常,您定义服务层对象、数据访问对象(DAOs)、表示对象(如Struts操作实例)、基础结构对象(如Hibernate会话工厂、JMS队列等)。通常不在容器中配置细粒度的域对象,因为创建和加载域对象通常是Dao和业务逻辑的责任。但是,您可以使用Spring与AspectJ的集成来配置在IoC容器控制之外创建的对象。
以下示例显示了基于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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions go here -->
</beans>
- (1)
id
属性是标识单个 bean 定义的字符串。 - (2)
class
属性定义 bean 的类型并使用完全限定的类名。
1.2.2.实例化容器
实例化Spring IoC容器很简单。提供给ApplicationContext
构造函数的位置路径是资源字符串,这些资源字符串使容器可以从各种外部资源(例如本地文件系统,Java CLASSPATH
等)加载配置元数据。
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
以下示例显示了服务层对象(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 https://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"/> <!-- additional collaborators and configuration for this bean go here --> </bean> <!-- more bean definitions for services go here --> </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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="accountDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao"> <!-- additional collaborators and configuration for this bean go here --> </bean> <bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao"> <!-- additional collaborators and configuration for this bean go here --> </bean> <!-- more bean definitions for data access objects go here --> </beans>
服务层由类PetStoreServiceImpl和两个类型为JpaAccountDao和JpaItemDao(基于JPA对象/关系映射标准)的数据访问对象组成。property name
元素引用 JavaBean 属性的名称,而ref
元素引用另一个 bean 定义的名称。id和ref元素之间的这种联系表达了协作对象之间的依赖关系。
编写基于XML的配置元数据
让bean定义跨越多个XML文件可能很有用。通常,每个单独的XML配置文件代表您的体系结构中的一个逻辑层或模块。
您可以使用应用程序上下文构造函数(如ClassPathXmlApplicationContext
)从所有这些XML片段中加载bean定义。这个构造函数采用多个资源位置,如前一节所示。或者,使用一个或多个< import/>元素从另一个或多个文件加载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模式,被导入文件的内容,包括顶层的< bean/>元素,必须是有效的XML bean定义。
可以使用相对引用父目录中的文件,但不建议这样做"../"路径。这样做会创建对当前应用程序之外的文件的依赖。特别是,对于“类classpath:”网址(例如,“类classpath:../services.xml),运行时解析过程选择“最近的”类路径根,然后查看其父目录。类路径配置更改可能会导致选择不同的不正确目录。
您可以始终使用完全限定的资源位置,而不是相对路径:例如,“文件:C:/config/services.xml”或“classpath:/config/services.xml”。但是,请注意,您正在将应用程序的配置耦合到特定的绝对位置。对于这种绝对位置,通常最好保持间接性,例如,通过运行时根据JVM系统属性解析的“$ {……}”占位符。
1.2.3.使用容器
ApplicationContext是高级工厂的接口,能够维护不同bean及其依赖项的注册表。使用 getBean(String name, Class<T> requiredType) 方法可以检索Bean的实例。
// create and configure beans ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");//维护注册 // retrieve configured instance PetStoreService service = context.getBean("petStore", PetStoreService.class);//获取bean实例
// use configured instance List<String> userList = service.getUsernameList();
最灵活的变体是与读取器委托相结合的GenericApplicationContext,例如,对于XML文件,使用XmlBeanDefinitionReader:
GenericApplicationContext context = new GenericApplicationContext(); new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml"); context.refresh();
如果需要,您可以在相同的ApplicationContext
上混合并匹配此类阅读器委托,从不同的配置源读取 Bean 定义。
然后,您可以使用getBean来检索Bean的实例。ApplicationContext接口有一些其他的方法来检索beans,但是理想情况下,您的应用程序代码不应该使用它们。事实上,您的应用程序代码应该根本没有对getBean()方法的调用,因此根本不依赖于Spring APIs。例如,Spring与网络框架的集成为各种网络框架组件(如控制器和JSF管理的bean)提供了依赖注入,允许您通过元数据(如自动连接注释)声明对特定bean的依赖。
1.3.Bean概述
Spring IoC容器管理一个或多个bean。这些 bean 是使用您提供给容器的配置元数据创建的(例如,以 XML <bean/>
定义的形式)。
在容器本身中,这些bean定义被表示为BeanDefinition对象,这些对象包含(除其他信息外)以下元数据:
-
包限定的类名:通常,定义了 Bean 的实际实现类。
-
Bean 行为配置元素,用于声明 Bean 在容器中的行为(作用域,生命周期回调等)。
-
引用其他 bean 进行其工作所需的 bean。这些引用也称为协作者或依赖项。
-
要在新创建的对象中设置的其他配置设置,例如,池的大小限制或在 管理连接池的 bean 中使用的连接数。
这些元数据转化为一组属性,组成了每个bean定义。
除了包含如何创建特定bean的信息的bean定义之外,ApplicationContext
实现还允许注册在容器外部(由用户)创建的现有对象。这是通过方法getBeanFactory()访问应用程序上下文的BeanFactory来完成的,该方法返回BeanFactory实现DefaultListableBeanFactory。DefaultListableBeanFactory通过方法registerSingleton(..)和registerBeanDefinition(..)。但是,典型的应用程序只能与通过常规 bean 定义元数据定义的 bean 一起使用。
Bean元数据和手动提供的单例实例需要尽早注册,以便容器在自动连接和其他自省步骤中能够正确地推理它们。虽然在某种程度上支持覆盖现有的元数据和现有的单例实例,但是官方不支持在运行时注册新的bean(同时对工厂进行实时访问),这可能会导致并发访问异常和/或bean容器中的状态不一致.
1.3.1.命名Bean
每个bean都有一个或多个标识符。这些标识符在承载bean的容器中必须是唯一的。一个bean通常只有一个标识符,但是如果它需要多个标识符,多余的标识符可以被认为是别名。
在基于 XML 的配置元数据中,您可以使用id
属性和name
属性,或同时使用这两者来指定 bean 标识符。 id
属性可让您精确指定一个 ID。按照惯例,这些名称是字母数字(“ myBean”,“ someService”等),但它们也可以包含特殊字符。如果要为 bean 引入其他别名,还可以在name
属性中指定它们,并用逗号(,
),分号(;
)或空格分隔。作为历史记录,在 Spring 3.1 之前的版本中,id
属性定义为xsd:ID
类型,该类型限制了可能的字符。从 3.1 开始,它被定义为xsd:string
类型。请注意,bean id
的唯一性仍由容器强制执行,尽管不再由 XML 解析器执行。
您不需要为bean提供名称或id。如果没有显式提供名称或id,容器将为该bean生成一个唯一的名称。但是,如果您想通过名称引用该bean,通过使用引用元素或服务定位器样式查找,您必须提供一个名称。不提供名称的动机与使用内部beans和自动连接协作者有关。
Bean命名约定
约定是在命名 bean 时将标准 Java 约定用于实例字段名称。也就是说,bean 名称以小写字母开头,并从那里用驼峰式大小写。这样的名称的示例包括accountManager
,accountService
,userDao
,loginController
等。
一致地命名 Bean 使您的配置更易于阅读和理解。另外,如果您使用 Spring AOP,则在将建议应用于按名称相关的一组 bean 时,它会很有帮助。
在Bean定义之外别名Bean(没有使用过)
在bean定义本身中,您可以为bean提供多个名称,方法是使用由id属性指定的最多一个名称和name属性中任意数量的其他名称的组合。这些名称可以是同一个bean的等效别名,在某些情况下很有用,例如允许应用程序中的每个组件通过使用特定于该组件本身的bean名称来引用公共依赖关系。
然而,指定实际定义bean的所有别名并不总是足够的。有时需要为在别处定义的bean引入一个别名。这在大型系统中是常见的情况,在大型系统中,配置被分割到每个子系统中,每个子系统都有自己的一组对象定义。在基于XML的配置元数据中,您可以使用< alias/>元素来实现这一点。
<alias name="fromName" alias="toName"/>
在这种情况下,一个名为fromName的bean(在同一个容器中)在使用这个别名定义后,也可以被称为toName。
例如,子系统 A 的配置元数据可以通过名称subsystemA-dataSource
引用数据源。子系统 B 的配置元数据可以通过名称subsystemB-dataSource
引用数据源。组成使用这两个子系统的主应用程序时,主应用程序使用myApp-dataSource
的名称引用数据源。要使所有三个名称都引用相同的对象,可以将以下别名定义添加到配置元数据中:
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/> <alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
现在,每个组件和主应用程序都可以通过一个唯一的名称来引用数据源,并保证不与任何其他定义冲突(有效地创建了一个名称空间),但它们引用的是同一个bean。
如果您使用的是Java配置,那么@Bean注释可以用来提供别名。有关详细信息,请参见使用@Bean注释。
1.3.2.实例化beans
bean定义本质上来说是创建一个或多个对象的方法。当问及一个命名bean时,容器会查看这个方法并使用bean定义中封装的配置元数据创建(或取得)一个实际的对象。
如果使用基于XML的配置元数据,则需要在< bean/>元素的class属性中指定要实例化的对象的类型(或类)。这个类属性在内部是BeanDefinition实例上的一个Class属性,通常是强制性的。(有关异常,请参见使用实例工厂方法和Bean定义继承的实例化。)您可以通过两种方式之一使用Class属性:
通常,在容器本身通过反射调用其构造函数直接创建bean的情况下,指定要构造的bean类,这在某种程度上等同于使用new操作符的Java代码。
要指定包含为创建对象而调用的静态工厂方法的实际类,在容器调用类上的静态工厂方法来创建bean的情况下就不太常见了。从静态工厂方法调用返回的对象类型可以是相同的类,也可以完全是另一个类。
内部类名(未使用过)
如果要为静态嵌套类配置bean定义,必须使用嵌套类的二进制名称。
例如,如果您在com.example包中有一个名为Foo的类,并且这个Foo类有一个名为Bar的静态嵌套类,那么bean定义上的“类”属性值将是…com.example.Foo$Bar
请注意,名称中使用了$字符来分隔嵌套类名和外部类名。
用构造函数实例化
Spring IoC容器几乎可以管理任何您希望它管理的类;它不限于管理真正的JavaBeans。大多数Spring用户更喜欢实际的JavaBeans,它只有一个默认的(无参数)构造函数和适当的setter 和 getter,它们是根据容器中的属性建模的。您的容器中还可以有更多外来的非bean风格的类。例如,如果您需要使用一个完全不符合JavaBean规范的遗留连接池,Spring也可以管理它。
使用基于XML的配置元数据,您可以按如下方式指定bean类:
<bean id="exampleBean" class="examples.ExampleBean"/> <bean name="anotherExample" class="examples.ExampleBeanTwo"/>
有关向构造函数提供参数(如果需要)以及在构造对象后设置对象实例属性的机制的详细信息,请参见注入依赖项。
用静态工厂方法实例化
定义使用静态工厂方法创建的bean时,可以使用class 属性指定包含静态工厂方法的类,并使用名为factory-method 的属性指定工厂方法本身的名称。您应该能够调用此方法(带有后面描述的可选参数)并返回一个活动对象,该对象随后被视为是通过构造函数创建的。这种bean定义的一个用途是在遗留代码中调用静态工厂
以下bean定义指定将通过调用factory-method来创建bean。定义没有指定返回对象的类型(类),只指定包含工厂方法的类。在本例中,createInstance()方法必须是static方法。
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
public class ClientService { private static ClientService clientService = new ClientService(); private ClientService() {} public static ClientService createInstance() { return clientService; } }
有关为工厂方法提供(可选)参数以及在对象从工厂返回后设置对象实例属性的机制的详细信息,请参见详细的依赖关系和配置。
使用实例工厂方法的实例化
实例工厂方法的实例化从容器调用现有bean的非静态方法来创建新bean。要使用这种机制,将class
属性保留为空,并在factory-bean属性中,指定当前(或父/祖先)容器中的bean的名称,该容器包含要调用来创建对象的实例方法。用factory-method属性设置工厂方法本身的名称。
<!-- the factory bean, which contains a method called createInstance() --> <bean id="serviceLocator" class="examples.DefaultServiceLocator"> <!-- inject any dependencies required by this locator bean --> </bean> <!-- the bean to be created via the factory bean --> <bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/>
public class DefaultServiceLocator { private static ClientService clientService = new ClientServiceImpl(); public ClientService createClientServiceInstance() { return clientService; } }
一个工厂类也可以包含多个工厂方法,如下所示:
<bean id="serviceLocator" class="examples.DefaultServiceLocator"> <!-- inject any dependencies required by this locator bean --> </bean> <bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/> <bean id="accountService" factory-bean="serviceLocator" factory-method="createAccountServiceInstance"/>
public class DefaultServiceLocator { private static ClientService clientService = new ClientServiceImpl(); private static AccountService accountService = new AccountServiceImpl(); public ClientService createClientServiceInstance() { return clientService; } public AccountService createAccountServiceInstance() { return accountService; } }
这种方法表明,工厂bean本身可以通过依赖注入(DI)来管理和配置。请参见详细的依赖关系和配置。
在Spring文档中,factory bean是指在Spring容器中配置的bean,它将通过实例或静态工厂方法创建对象。相比之下,FactoryBean(注意大写)指的是Spring特定的FactoryBean。
FactoryBean是一个工厂Bean,可以生成某一个类型Bean实例,它最大的一个作用是:可以让我们自定义Bean的创建过程。BeanFactory是Spring容器中的一个基本类也是很重要的一个类,在BeanFactory中可以创建和管理Spring容器中的Bean,它对于Bean的创建有一个统一的流程。
1.4.依赖性
1.4.1.依赖注入
依赖注入(DI)是一个过程,通过这个过程,对象定义它们的依赖关系,也就是说,它们使用的其他对象,只通过构造函数参数、工厂方法的参数,或者在对象实例被构造或从工厂方法返回后在对象实例上设置的属性。然后,容器在创建bean时注入这些依赖项。这个过程从根本上说是控制反转(IoC)的逆过程,即bean本身通过使用类的直接构造或服务定位器模式来控制其依赖关系的实例化或位置。
DI有两种主要的变体,基于构造函数的依赖注入和基于Setter的依赖注入。
基于构造函数的依赖注入
基于构造函数的DI是通过容器调用一个带有多个参数的构造函数来实现的,每个参数代表一个依赖关系。调用带有特定参数的静态工厂方法来构造bean几乎是等价的,并且本次讨论也将构造函数和static
工厂方法的参数视为类似。下面的示例显示了一个只能通过构造函数注入进行依赖注入的类。请注意,这个类没有什么特别之处,它是一个POJO,不依赖于容器特定的接口、基类或注释。
public class SimpleMovieLister { private MovieFinder movieFinder; public SimpleMovieLister(MovieFinder movieFinder) { this.movieFinder = movieFinder; } }
构造函数参数解析
构造函数参数解析匹配通过使用参数的类型进行。如果bean定义的构造函数参数中不存在潜在的模糊性,那么在bean定义中定义构造函数参数的顺序就是在实例化bean时将这些参数提供给适当的构造函数的顺序。
package x.y; public class Foo { public Foo(Bar bar, Baz baz) { // ... } }
假设bar和baz
类没有通过继承关联,则不存在潜在的歧义。因此,以下配置工作正常,并且您不需要在< constructor-arg/>元素中显式指定构造函数参数indexes/type。
<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; } }
构造函数参数类型匹配
在前面的场景中,如果使用type属性显式指定构造函数参数的类型,容器可以使用简单类型的类型匹配。例如:
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg type="int" value="7500000"/> <constructor-arg type="java.lang.String" value="42"/> </bean>
构造函数参数索引
使用index属性显式指定构造函数参数的索引。例如:
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg index="0" value="7500000"/> <constructor-arg index="1" value="42"/> </bean>
除了解决多个简单值的不确定性之外,指定索引还解决了构造函数有两个相同类型参数的不确定性。请注意,索引是基于0的。
构造函数参数名
您还可以使用构造函数参数名称来消除值的歧义:
<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的依赖注入
基于Setter的DI是通过容器调用bean上的setter方法,然后调用无参数构造函数或无参数静态工厂方法来实例化bean来实现的。
下面的示例显示了一个只能使用纯setter注入进行依赖注入的类。这个类是常规Java。它是一个POJO,不依赖于容器特定的接口、基类或注释。
public class SimpleMovieLister { private MovieFinder movieFinder; public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } }
因为您可以混合基于构造函数和基于设置函数的DI,所以对于强制依赖项使用构造函数,对于可选依赖项使用设置方法或配置方法是一个很好的经验法则。请注意,在setter方法上使用@Required注释可以使属性成为必需的依赖项。
Spring团队通常提倡构造函数注入,因为它使人们能够将应用程序组件实现为不可变的对象,并确保所需的依赖关系不为空。此外,构造函数注入的组件总是以完全初始化的状态返回给客户端(调用)代码。
Setter注入应该主要用于可选的依赖项,这些依赖项可以在类中被赋予合理的默认值。否则,必须在代码使用依赖关系的任何地方执行非空检查。setter注入的一个好处是,setter方法使该类的对象可以在以后重新配置或重新注入。
使用对特定类最有意义的DI样式。有时候,当处理第三方类时,你没有源代码,选择是为你做的。例如,如果第三方类没有公开任何setter方法,那么构造函数注入可能是DI唯一可用的形式。
依赖性解决过程
容器执行bean依赖关系解析,如下所示:
ApplicationContext是用描述所有bean的配置元数据创建和初始化的。配置元数据可以通过XML、Java代码或注解来指定。
对于每个bean,它的依赖关系以属性、构造函数参数或静态工厂方法的参数的形式表示,如果您使用的是静态工厂方法而不是普通的构造函数。这些依赖关系是在实际创建bean时提供给bean的。
每个属性或构造函数参数都是要设置的值的实际定义,或者是对容器中另一个bean的引用。
作为值的每个属性或构造函数参数都从其指定格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring可以将字符串格式的值转换为所有内置类型,如int、long、string、boolean等。
如果主要使用构造函数注入,就有可能创建一个无法解析的循环依赖场景。
比如:A类通过构造函数注入需要B类的一个实例,B类通过构造函数注入需要A类的一个实例。如果您将类A和类B的beans配置为相互注入,Spring IoC容器会在运行时检测到此循环引用,并抛出一个BeanCurrentLincreationException。
一个可能的解决方案是编辑一些类的源代码,由设置者而不是构造者来配置。或者,避免构造函数注入,只使用setter注入。
与典型情况(没有循环依赖)不同,bean A和bean B之间的循环依赖迫使其中一个bean在完全初始化之前被注入到另一个bean中(典型的先有鸡还是先有蛋的场景)。
一般可以相信Spring做的是对的。它在容器加载时检测配置问题,例如对不存在的beans和循环依赖的引用。在实际创建bean时,Spring尽可能晚地设置属性和解决依赖关系。
通常,您可以信任 Spring 做正确的事。它在容器加载时检测配置问题,例如对不存在的 Bean 的引用和循环依赖项。在实际创建 Bean 时,Spring 设置属性并尽可能晚地解决依赖关系。这意味着如果创建该对象或其依赖项之一时出现问题,则正确加载了 Spring 的容器以后可以在您请求对象时生成异常-例如,由于缺少或无效,Bean 引发异常属性。某些配置问题的这种潜在的延迟可见性是为什么默认情况下ApplicationContext
实现会实例化单例 bean。在实际需要它们之前,要花一些前期时间和内存来创建它们,您会在创建ApplicationContext
时发现配置问题,而不是稍后。您仍然可以覆盖此默认行为,以便单例 bean 延迟初始化,而不是预先实例化。
如果不存在循环依赖,当一个或多个协作bean被注入到一个依赖bean中时,每个协作bean在被注入到依赖bean之前都被完全配置好了。这意味着,如果bean A对bean B有依赖关系,那么Spring IoC容器在调用bean A上的setter方法之前会对bean B进行完全配置,换句话说,实例化了 bean(如果它不是预先实例化的单例) ),设置其依赖项,并调用相关的生命周期方法(例如配置的 init 方法或InitializingBean 回调方法)。
依赖注入的例子
以下示例将基于XML的配置元数据用于基于setter的DI。Spring XML配置文件的一小部分指定了一些bean定义:
<bean id="exampleBean" class="examples.ExampleBean"> <!-- setter injection using the nested ref element --> <property name="beanOne"> <ref bean="anotherExampleBean"/> </property> <!-- setter injection using the neater ref attribute --> <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; } }
在前面的示例中,设置器被声明为与XML文件中指定的属性相匹配。以下示例使用基于构造函数的DI:
<bean id="exampleBean" class="examples.ExampleBean"> <!-- constructor injection using the nested ref element --> <constructor-arg> <ref bean="anotherExampleBean"/> </constructor-arg> <!-- constructor injection using the neater ref attribute --> <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 { // a private constructor private ExampleBean(...) { ... } //静态工厂方法;此方法的参数可以是考虑到返回的bean的依赖性,不管这些参数实际上是如何使用的。 public static ExampleBean createInstance ( AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) { ExampleBean eb = new ExampleBean (...); // some other operations... return eb; } }
1.4.2.依赖性和详细配置
您可以将bean属性和构造函数参数定义为对其他托管bean(协作者)的引用,或者定义为内联定义的值。Spring基于XML的配置元数据支持其< property/>和< constructor-arg/>元素中的子元素类型。
直接值(原语、字符串等)
< property/>元素的value属性将属性或构造函数参数指定为人类可读的字符串表示形式。Spring的转换服务用于将这些值从字符串转换为属性或参数的实际类型。
<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命名空间进行更简洁的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
https://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>
您还可以将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属性编辑器机制将< value/>元素中的文本转换为java.util.Properties实例。这是一个很好的快捷方式,并且是Spring团队支持使用嵌套的< value/>元素而不是value属性样式的少数地方之一。
idref元素(值得一用)
idref元素只是将容器中另一个bean的id(字符串值,而不是引用)传递给< constructor-arg/>或< property/>元素的一种防错方式。
<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实际实例化时才会被发现(最有可能导致致命的结果)。如果客户端bean是一个原型bean,这个错别字和产生的异常可能只有在部署容器之后很久才会被发现。
< idref/>元素带来价值的一个常见之处(至少在Spring 2.0之前的版本中)是在一个ProxyFactoryBean定义中配置AOP拦截器。当您指定拦截器名称时,使用< idref/>元素可以防止您拼错拦截器id。
对其他beans(协作者)的引用
ref元素是<constructor-arg/>或<property/>定义元素中的最后一个元素。在这里,您将一个bean的指定属性的值设置为对由容器管理的另一个bean(协作者)的引用。被引用的bean是其属性将被设置的bean的依赖项,并且在设置属性之前根据需要对其进行初始化。(如果协作者是单例bean,它可能已经被容器初始化了。)所有引用最终都是对另一个对象的引用。作用域和验证取决于您是否通过bean
, local,
parent
属性指定了另一个对象的id/name。
通过<ref/>
标签的bean
属性指定目标 bean 是最通用的形式,并且允许创建对同一容器或父容器中任何 bean 的引用,而不管它是否在同一 XML 文件中。 bean
属性的值可以与目标 Bean 的id
属性相同,也可以与目标 Bean 的name
属性中的值之一相同。
<ref bean="someBean"/>
通过parent
属性指定目标bean会创建对当前容器的父容器中的bean的引用。parent
属性的值可以与目标bean的id属性相同,也可以是目标bean的name属性中的一个值,并且目标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
< property/>或< constructor-arg/>元素中的< bean/>元素定义了一个所谓的内部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中,也不可能独立访问它们。
集合
在<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">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</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>
map key或value或set value的值也可以是以下任何元素:
bean | ref | idref | list | set | map | props | value | null
集合合并
Spring 容器还支持合并集合。应用程序开发人员可以定义父级<list/>
,<map/>
,<set/>
或<props/>
元素,并使子级<list/>
,<map/>
,<set/>
或<props/>
元素继承并覆盖父级集合中的值。也就是说,子集合的值是合并父集合和子集合的元素的结果,子集合的元素将覆盖父集合中指定的值。
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">[emailprotected]</prop>
<prop key="support">[emailprotected]</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">[emailprotected]</prop>
<prop key="support">[emailprotected]</prop>
</props>
</property>
</bean>
<beans>
注意child
bean 定义的adminEmails
属性的<props/>
元素上使用了merge=true
属性。当child
bean 被容器解析并实例化时,生成的实例具有adminEmails
Properties
集合,该集合包含将子代的adminEmails
集合与父代的adminEmails
集合合并的结果。以下清单显示了结果:
[emailprotected]
[emailprotected]
[emailprotected]
子Properties
集合的值集继承了父<props/>
的所有属性元素,而子support
值的值覆盖父集合中的值。
此合并行为类似地适用于<list/>
,<map/>
和<set/>
集合类型。在<list/>
元素的特定情况下,将保留与List
集合类型关联的语义(即,值ordered
集合的概念)。父级的值位于所有子级列表的值之前。对于Map
,Set
和Properties
集合类型,不存在排序。因此,对于容器内部使用的相关Map
,Set
和Properties
实现类型基础的集合类型,没有排序语义有效。
集合合并的局限性
您不能合并不同的集合类型(如映射和列表),如果您尝试这样做,则会引发适当的异常。必须在较低的继承子定义上指定merge属性;在父集合定义上指定merge属性是多余的,并且不会导致所需的合并。
强类型集合
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>
当准备注入something
bean 的accounts
属性时,可以通过反射获得有关强类型Map<String, Float>
的元素类型的泛型信息。因此,Spring 的类型转换基础结构将各种值元素识别为Float
类型,并且字符串值(9.99, 2.75
和3.99
)被转换为实际的Float
类型。
Null 和 “”字符串
<bean class="ExampleBean"> <property name="email" value=""/> </bean>
exampleBean.setEmail("");
<bean class="ExampleBean"> <property name="email"> <null/> </property> </bean>
exampleBean.setEmail(null);
带有p命名空间的XML快捷方式
p-namespace使您能够使用bean元素的属性而不是嵌套的< property/>元素来描述您的属性值和/或协作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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean name="classic" class="com.example.ExampleBean"> <property name="email" value="foo@bar.com"/> </bean> <bean name="p-namespace" class="com.example.ExampleBean" p:email="foo@bar.com"/> </beans>
该示例在bean定义中显示了一个名为email的p-namespace属性。这告诉Spring包含一个属性声明。如前所述,p-namespace没有模式定义,因此您可以将属性的名称设置为属性名称。
<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 https://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 john到bean jane的引用,而第二个bean定义使用p:spouse
-ref="jane "作为属性来做同样的事情。在这种情况下,spouse是属性名,而-ref部分表明这不是一个直接的值,而是对另一个bean的引用。
带有c-namespace的XML快捷方式
与带有p-namespace的XML快捷方式类似,Spring 3.1中新引入的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 https://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="foo@bar.com"/> </bean> <!-- c-namespace declaration --> <bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/> </beans>
对于构造函数参数名不可用的极少数情况(通常是在没有调试信息的情况下编译字节码),可以使用回退到参数索引:
<!-- c-namespace index declaration -->
<bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>
实际上,构造函数解析机制在匹配参数方面非常有效,所以除非真的需要,否则我们建议在整个配置中使用名称符号。
复合属性名
设置bean属性时,可以使用复合或嵌套属性名,只要路径的所有组件(除了最终属性名)都不为空。
<bean id="foo" class="foo.Bar">
<property name="fred.bob.sammy" value="123" />
</bean>
foo bean有一个fred属性,它有一个bob属性,它有一个sammy属性,最终sammy属性被设置为值123。为了实现这一点,foo的fred属性和fred的bob属性在构造bean后不能为null,否则会引发NullPointerException。
1.4.3使用depends-on
如果一个bean是另一个bean的依赖项,这通常意味着一个bean被设置为另一个bean的属性。通常,您可以使用基于XML的配置元数据中的< ref/>元素来实现这一点。但是,有时候beans之间的依赖关系不那么直接;例如,类中的静态初始化器需要被触发,比如数据库驱动注册。
在初始化使用此元素的bean之前,depends-on属性可以显式强制初始化一个或多个bean。以下示例使用depends-on属性来表示对单个bean的依赖:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
要表示对多个bean的依赖,请提供一个bean名称列表作为依赖属性的值,用逗号、空格和分号作为有效分隔符:
<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定义中的dependent-on属性既可以指定初始化时的依赖关系,初始化时,被依赖的bean是先被初始化的;也可以指定相应的销毁时的依赖关系(仅限于单例bean)。depends-on适用于表面上看起来两个bean之间没有使用属性之类的强连接的bean,但是两个bean又确实存在前后依赖关系的情况,使用了depends-on的时候,依赖他人的bean是先于被依赖bean销毁的,因此依赖也可以控制关机顺序。
depends-on 强制的说明在该Bean 初始化之前,那些Bean必须先初始化!
ref通常用在一个Bean的属性指向另外一个Bean,这个Bean必须先初始化。
1.4.4.惰性初始化的beans
默认情况下,作为初始化过程的一部分,应用上下文实现急切地创建和配置所有的单例beans。通常,这种预实例化是可取的,因为配置或周围环境中的错误会立即被发现,而不是几小时甚至几天后。当这种行为不可取时,您可以通过将bean定义标记为惰性初始化来防止单例bean的预实例化。惰性初始化的bean告诉IoC容器在第一次被请求时创建一个bean实例,而不是在启动时。
在XML中,这种行为由< bean/>元素上的lazy-init属性控制;例如:
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>
当前面的配置被应用程序上下文使用时,名为lazy的bean在应用程序上下文启动时不会被提前实例化,而not.lazy bean会被提前实例化。
然而,当延迟初始化的bean是未延迟初始化的单例bean的依赖项时,应用程序上下文会在启动时创建延迟初始化的bean,因为它必须满足单例bean的依赖项。惰性初始化的bean被注入到未惰性初始化的单例bean中。
您还可以通过在< beans/>元素上使用default-lazy-init属性来控制容器级别的惰性初始化;例如:
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
1.4.5.Autowiring 协作者
Spring容器可以用自动装配协作bean之间的关系。您可以允许Spring通过检查ApplicationContext的内容来为您的bean自动解析协作者(其他bean)。自动装配有以下优点:
自动连接可以大大减少指定属性或构造函数参数的需要。
自动布线可以随着对象的发展更新配置。例如,如果您需要向一个类添加一个依赖项,该依赖项可以自动得到满足,而无需修改配置。因此,自动连接在开发过程中特别有用,当代码库变得更加稳定时,不排除切换到显式连接的选项。
使用基于 XML 的配置元数据时,可以使用<bean/>
元素的autowire
属性为 bean 定义指定自动装配模式。自动装配功能具有四种模式。您可以为每个 bean 指定自动装配,因此可以选择要自动装配的装配。下表描述了四种自动装配模式:
no (默认)无自动装配。Bean引用必须通过ref元素来定义。对于较大的部署,不建议更改默认设置,因为显式指定协作者可以提供更好的控制和清晰度。在某种程度上,它记录了一个系统的结构。
byName 按属性名自动装配。Spring 寻找与需要自动装配的属性同名的 bean。例如,如果一个bean定义按名称设置为autowire,并且它包含一个master属性(也就是说,它有一个setMaster(..)方法),Spring寻找一个名为master的bean定义,并使用它来设置属性。
byType 如果容器中恰好存在一个属性类型的bean,则使该属性自动装配。如果存在多个bean,则会引发致命异常,这表明您不能对该bean使用byType自动连接。
constructor 类似于byType,但适用于构造函数参数。如果容器中没有恰好一个构造函数参数类型的bean,则会引发致命错误。
自动布线autowiring的局限性和缺点
当自动布线在整个项目中一致使用时,效果最佳。如果通常不使用自动连接,开发人员可能会对使用它来连接一个或两个bean定义感到困惑。
1、property
和constructor-arg设置中的显式依赖关系总是覆盖自动连接。您不能自动连接所谓的简单属性,如原语、字符串和类(以及此类简单属性的数组)。这个限制是被设计出来的。
2、自动装配不如显式布线精确。尽管如上表所述,Spring小心翼翼地避免在可能产生意外结果的模糊情况下进行猜测,但是Spring管理的对象之间的关系不再被明确记录。
3、布线信息可能不适用于可能从Spring容器生成文档的工具。
4、容器中的多个bean定义可能与要自动连接的setter方法或构造函数参数指定的类型相匹配。对于数组、集合或映射,这不一定是个问题。然而,对于期望单个值的依赖项,这种模糊性并不是任意解决的。如果没有唯一的bean定义可用,则会引发异常。
1、放弃自动布线,支持显式布线。
2、通过将bean定义的autowire-candidate属性设置为false来避免自动连接bean定义。
3、通过将< bean/>元素的primary 属性设置为true,将单个bean定义指定为主要候选对象。
4、实现基于注解的配置中可用的更细粒度的控制。
从自动装配中排除 Bean
基于每个bean,您可以从自动连接中排除一个bean。在Spring的XML格式中,将< bean/>元素的autowire-candidate属性设置为false。容器使特定的 bean 定义对于自动装配基础结构不可用(包括注解样式配置,例如@Autowired)。
autowire-candidate
属性旨在仅影响基于类型的自动装配。它不会影响按名称显示的显式引用,即使未将指定的 Bean 标记为自动装配候选,该名称也可得到解析。因此,如果名称匹配,按名称自动装配仍会注入 Bean。
您还可以根据bean名称的模式匹配来限制自动连接候选对象。顶层< beans>元素在其default-autowire-candidates属性中接受一个或多个模式。例如,要将自动连线候选状态限制为名称以Repository结尾的任何bean,请提供*Repository的值。要提供多个模式,请在逗号分隔的列表中定义它们。bean定义autowire-candidates属性的显式值true或false总是优先,对于这种bean,模式匹配规则不适用。
这些技术对于您永远不想通过自动装配注入到其他bean中的bean非常有用。这并不意味着排除的bean本身不能使用自动连接进行配置。相反,bean本身并不是自动配置其他bean的候选对象。
1.4.6.方法注入
在大多数应用场景中,容器中的大多数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) { // 获取适当命令的新实例 Command command = createCommand(); // 在(希望是全新的)命令实例上设置状态 command.setState(commandState); return command.execute(); } protected Command createCommand() { // 注意spring的应用编程接口依赖性! return this.applicationContext.getBean("command", Command.class); } public void setApplicationContext( ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
前面提到的是不可取的,因为业务代码知道并耦合到Spring框架。方法注入是Spring IoC容器的一个高级特性,它允许以一种干净的方式处理这个用例。
查找方法注入
查找方法注入是容器重写容器托管bean上的方法,以返回容器中另一个命名bean的查找结果的能力。查找通常涉及一个原型bean,如前一节中描述的场景。Spring 框架通过使用从 CGLIB 库生成字节码来动态生成覆盖该方法的子类来实现此方法注入。
为了使这个动态子类化工作,Spring bean容器将子类化的类不能是final
的,要被覆盖的方法也不能是final
的。
对具有abstract
方法的类进行单元测试需要您自己对该类进行子类化,并提供abstract
方法的存根实现。
组件扫描也需要具体方法,这需要具体的类别。
另一个关键限制是,查找方法不能与工厂方法一起使用,尤其不能与配置类中的@Bean方法一起使用,因为在这种情况下,容器不负责创建实例,因此不能动态创建运行时生成的子类。
package fiona.apple; public abstract class CommandManager { public Object process(Object commandState) {
// 获取适当命令的新实例 Command command = createCommand(); // 在(希望是全新的)命令实例上设置状态 command.setState(commandState);
return command.execute();
}
protected abstract Command createCommand();
}
在包含要注入的方法的客户端类中(在本例中是命令管理器),要注入的方法需要以下形式的签名:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果方法是抽象的,则动态生成的子类实现该方法。否则,动态生成的子类将覆盖原始类中定义的具体方法。例如:
<!-- 作为原型部署的有状态bean(非单例)--> <bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype"> <!-- 根据需要在此处输入依赖项 --> </bean> <!-- 命令处理器使用状态命令和帮助 --> <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 中的任意方法。您可以放心地跳过本节的其余部分,直到您需要此功能为止。
借助基于 XML 的配置元数据,您可以使用replaced-method
元素将现有的方法实现替换为已部署的 Bean。考虑下面的类,它具有一个名为computeValue
的方法,我们想重写该方法:
public class MyValueCalculator { public String computeValue(String input) { } }
/** * 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 { String input = (String) args[0]; ... return ...; } }
<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"/>
1.5. Bean 作用域
表 3. Bean 作用域
Scope | Description |
---|---|
singleton | (默认)将每个 Spring IoC 容器的单个 bean 定义范围限定为单个对象实例。 |
prototype | 将单个 bean 定义的作用域限定为任意数量的对象实例。 |
request | 将单个 bean 定义的范围限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有一个在单个 bean 定义后面创建的 bean 实例。仅在可感知网络的 Spring ApplicationContext 中有效。 |
session | 将单个 bean 定义的范围限定为 HTTP Session 的生命周期。仅在可感知网络的 Spring ApplicationContext 上下文中有效。 |
application | 将单个 bean 定义的范围限定为ServletContext 的生命周期。仅在可感知网络的 Spring ApplicationContext 上下文中有效。 |
websocket | 将单个 bean 定义的范围限定为WebSocket 的生命周期。仅在可感知网络的 Spring ApplicationContext 上下文中有效。 |
Note
从 Spring 3.0 开始,线程作用域可用,但默认情况下未注册。有关更多信息,请参见SimpleThreadScope的文档。
1.5.1.单例范围
单例bean只有一个共享实例被管理,所有对具有与该bean定义匹配的一个或多个id的bean的请求都会导致Spring容器返回该特定的bean实例。
换句话说,当您定义一个bean定义并且它的作用域是单例时,Spring IoC容器恰好创建了由该bean定义的对象的一个实例。这个单个实例存储在这样的单例bean的缓存中,对该命名bean的所有后续请求和引用都会返回缓存的对象。
Spring的单例bean概念不同于“四人组”(g of)模式书中定义的单例模式。GoF Singleton对对象的范围进行硬编码,以便每个类加载器创建一个且只有一个特定类的实例。Spring singleton的范围最好按照每个容器和每个bean来描述。这意味着,如果您在一个单一的Spring容器中为一个特定的类定义了一个bean,那么Spring容器将为该bean定义的类创建一个且仅一个实例。singleton范围是Spring中的默认范围。要在XML中将bean定义为单例,您可以编写,例如:
<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.原型范围
每次对特定 bean 提出请求时,bean 部署的非单一原型范围都会导致创建一个新 bean 实例。也就是说,该bean被注入到另一个bean中,或者您通过容器上的getBean()方法调用来请求它。通常,对所有有状态bean使用原型范围,对无状态bean使用单例范围。
下图说明了Spring原型范围。(数据访问对象(DAO)通常不配置为原型,因为典型的 DAO 不拥有任何对话状态。对于我们而言,重用单例图的核心更为容易。)
以下示例将bean定义为XML中的原型:
<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>
与其他作用域相反,Spring不管理原型 Bean 的完整生命周期。容器将实例化,配置或组装原型对象,然后将其交给 Client 端,而无需对该原型实例的进一步记录。因此,尽管在不考虑范围的情况下在所有对象上都调用了初始化生命周期回调方法,但在原型的情况下,不会调用已配置的销毁生命周期回调。Client 端代码必须清除原型作用域内的对象,并释放原型 Bean 拥有的昂贵资源。要使 Spring 容器释放由原型作用域的 bean 占用的资源,请尝试使用自定义bean post-processor,该自变量包含对需要清理的 bean 的引用。
在某些方面,Spring 容器在原型作用域 bean 方面的角色是 Java new
运算符的替代。超过该时间点的所有生命周期管理必须由 Client 端处理。
1.5.3.具有原型bean依赖关系的单例bean
当您使用依赖于原型bean的单例范围的bean时,请注意依赖关系是在实例化时解决的。因此,如果将依赖项原型的 bean 依赖项注入到单例范围的 bean 中,则将实例化新的原型 bean,然后将依赖项注入到单例 bean 中。原型实例是曾经提供给单例范围的 bean 的唯一实例。
public class PrototypeBean {
}
public class SingletypeBean {
private PrototypeBean prototypeBean;
public
PrototypeBean getPrototypeBean() {
return prototypeBean;
}
public
void setPrototypeBean(PrototypeBean prototypeBean) {
this.prototypeBean = prototypeBean;
}
}
public class App
{
public static void main( String[] args )
{
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
SingletypeBean singletypeBean = context.getBean("singletypeBean", SingletypeBean.class);
System.out.println(singletypeBean);//com.spring.ioc.SingletypeBean@2df32bf7
SingletypeBean singletypeBean2 = context.getBean("singletypeBean", SingletypeBean.class);
System.out.println(singletypeBean2);//com.spring.ioc.SingletypeBean@2df32bf7
PrototypeBean prototypeBean1 = singletypeBean.getPrototypeBean();
PrototypeBean prototypeBean2 = singletypeBean2.getPrototypeBean();
System.out.println(prototypeBean1);//com.spring.ioc.PrototypeBean@6500df86 原型实例是曾经提供给单例范围的 bean 的唯一实例
System.out.println(prototypeBean2);//com.spring.ioc.PrototypeBean@6500df86 可以使用方法注入提供不同的bean实例。
PrototypeBean prototypeBean3 = context.getBean("prototypeBean", PrototypeBean.class);
System.out.println(prototypeBean3);//com.spring.ioc.PrototypeBean@70beb599
PrototypeBean prototypeBean4 = context.getBean("prototypeBean", PrototypeBean.class);
System.out.println(prototypeBean4);//com.spring.ioc.PrototypeBean@4e41089d
}
}
<?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" 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/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <bean id="singletypeBean" class="com.spring.ioc.SingletypeBean" scope="singleton"> <property name="prototypeBean" ref="prototypeBean"></property> </bean> <bean id="prototypeBean" class="com.spring.ioc.PrototypeBean" scope="prototype"> </bean> </beans>
但是,假设您希望单例范围的bean在运行时重复获取原型范围的bean的新实例。您不能将原型范围的bean依赖注入到您的单例bean中,因为当Spring容器实例化单例bean并解析和注入其依赖时,这种注入只发生一次。如果在运行时多次需要原型bean的新实例,请参见方法注入
1.5.4.请求、会话、应用程序和网络套接字范围
request
,session
,application
和websocket
作用域只有在您使用 Web感知的Spring ApplicationContext实现(如XmlWebApplicationContext)时才可用。如果您将这些作用域与常规的Spring IoC容器(如ClassPathXmlApplicationContext)一起使用,将引发IllegalStateException,抱怨未知的bean作用域。
初始web配置
为了在request
,session
,application
和websocket
级别支持bean的作用域(网络范围的 Bean),在定义bean之前需要一些小的初始配置。(对于标准范围singleton
和prototype
,不需要此初始设置。)
如何完成这个初始设置取决于您特定的Servlet环境。
实际上在 Spring DispatcherServlet
处理的请求中访问 Spring Web MVC 中的作用域 Bean,则不需要特殊的设置。 DispatcherServlet
已经公开了所有相关状态。
如果您使用Servlet 2.5 web容器,请求在Spring的DispatcherServlet之外处理(例如,当使用JSF或Struts时),您需要注册org . Spring framework . 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请求对象绑定到服务于该请求的线程。这使得请求和会话范围内的beans在调用链的更下游可用。
Request scope
<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>
Spring容器通过为每个请求使用LoginAction
bean定义来创建一个新的loginAction
bean实例。也就是说,loginAction
bean的作用域是在HTTP请求级别。您可以根据需要更改所创建实例的内部状态,因为从相同loginAction bean定义创建的其他实例在状态上看不到这些更改;它们是特定于个人要求的。当请求完成处理时,请求范围内的bean将被丢弃。
当使用注释驱动组件或Java Config时,@RequestScope注释可用于将组件分配给请求范围。
@RequestScope
@Component
public class LoginAction {
// ...
}
Session scope
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
Spring容器通过在单个HTTP会话的生存期内使用UserPreferences bean定义来创建userPreferences bean的新实例。换句话说,userPreferences bean的有效范围是在HTTP会话级别。与请求范围内的bean一样,您可以根据需要更改创建的实例的内部状态,因为您知道其他使用从相同UserPreferences bean定义创建的实例的HTTP会话实例不会看到这些状态更改,因为它们是特定于单个HTTP会话的。当最终丢弃该会话时,该会话范围内的bean也将被丢弃。
当使用注释驱动组件或Java Config时,@SessionScope注释可用于将组件分配给会话范围。
@SessionScope
@Component
public class UserPreferences {
// ...
}
Application scope
<bean id="appPreferences" class="com.foo.AppPreferences" scope="application"/>
Spring容器通过为整个网络应用程序使用一次appPreferences bean定义来创建AppPreferences bean的新实例。也就是说,appPreferences bean的作用域是在ServletContext级别,存储为一个常规的ServletContext属性。这有点类似于Spring singleton bean,但在两个重要方面有所不同:它是每个ServletContext的singleton,而不是每个Spring“application context”(在任何给定的web应用程序中可能有几个),它实际上是公开的,因此作为ServletContext属性可见。
当使用注释驱动组件或Java Config时,@ApplicationScope注释可用于将组件分配给应用程序范围。
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
作用域 Bean 作为依赖项(未使用过)
Spring IoC容器不仅管理对象(beans)的实例化,还管理协作者(或依赖者)的连接。如果您想将一个HTTP请求作用域bean注入(例如)到另一个更长作用域的bean中,您可以选择注入一个AOP代理来代替作用域bean。也就是说,您需要注入一个代理对象,该对象公开与作用域对象相同的公共接口,但也可以从相关的作用域中检索真实的目标对象(如一个HTTP请求),并将方法调用委托给真实的对象。
您也可以在作为singleton bean之间使用< aop:scoped-proxy/>,然后引用通过一个可序列化的中间代理,因此能够在反序列化时重新获得目标单例bean。
针对作用域prototype bean声明< aop:scoped-proxy/>时,共享代理上的每个方法调用都将导致创建一个新的目标实例,然后将该调用转发到该实例。
public class PrototypeBean {
}
public class SingletypeBean {
private PrototypeBean prototypeBean;
public
PrototypeBean getPrototypeBean() {
return prototypeBean;
}
public
void setPrototypeBean(PrototypeBean prototypeBean) {
this.prototypeBean = prototypeBean;
}
}
public class App
{
public static void main( String[] args )
{
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
SingletypeBean singletypeBean = context.getBean("singletypeBean", SingletypeBean.class);
System.out.println(singletypeBean);//com.spring.ioc.SingletypeBean@2df32bf7
SingletypeBean singletypeBean2 = context.getBean("singletypeBean", SingletypeBean.class);
System.out.println(singletypeBean2);//com.spring.ioc.SingletypeBean@2df32bf7
PrototypeBean prototypeBean1 = singletypeBean.getPrototypeBean();
PrototypeBean prototypeBean2 = singletypeBean2.getPrototypeBean();
System.out.println(prototypeBean1);//com.spring.ioc.PrototypeBean@6500df86
System.out.println(prototypeBean2);//com.spring.ioc.PrototypeBean@6500df45
PrototypeBean prototypeBean3 = context.getBean("prototypeBean", PrototypeBean.class);
System.out.println(prototypeBean3);//com.spring.ioc.PrototypeBean@70beb599
PrototypeBean prototypeBean4 = context.getBean("prototypeBean", PrototypeBean.class);
System.out.println(prototypeBean4);//com.spring.ioc.PrototypeBean@4e41089d
}
}
<?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" 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/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <bean id="singletypeBean" class="com.spring.ioc.SingletypeBean" scope="singleton"> <property name="prototypeBean" ref="prototypeBean"></property> </bean> <bean id="prototypeBean" class="com.spring.ioc.PrototypeBean" scope="prototype">
<aop:scoped-proxy/>
</bean>
</beans>
此外,作用域代理不是以生命周期安全的方式从较短的作用域访问bean的唯一方法。您也可以简单地将您的注入点(即the constructor/setter argument or autowired field)声明为ObjectFactory<MyTargetBean>,允许每次需要时调用getObject()来检索当前实例,而无需保留实例或单独存储它。
作为一个扩展变量,您可以声明ObjectProvider<MyTargetBean >,它提供了几个额外的访问变量,包括getIfAvailable和getIfUnique。
JSR-330变体被称为Provider,与Provider< MyTargetBean >声明一起使用,并为每次检索尝试提供相应的get()调用。
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://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>
<bean id="userService" class="com.foo.SimpleUserService">
<property name="userPreferences" ref="userPreferences"/>
</bean>
</beans>
要创建这样的代理,需要将一个子< aop:scoped-proxy/>元素插入到一个作用域bean定义中。为什么请求、会话和自定义范围级别的beans定义需要< aop:scoped-proxy/>元素?让我们检查下面的单例bean定义,并将其与您需要为上述范围定义的内容进行对比(注意,下面的用户首选项bean定义是不完整的)。
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/> <bean id="userManager" class="com.foo.UserManager"> <property name="userPreferences" ref="userPreferences"/> </bean>
在前面的例子中,单例bean userManager被注入了对HTTP会话范围内的bean userPreferences的引用。这里突出的一点是,userManager bean是单例的:它将在每个容器中被实例化一次,并且它的依赖项(在这种情况下userPreferences bean只有一个)也只被注入一次。这意味着用户管理器bean将只对完全相同的userPreferences 进行操作,也就是说,它最初被注入的那个对象。
当将较短的作用域bean注入较长的作用域bean时,这不是您想要的行为,例如,将HTTP会话作用域协作bean作为依赖项注入单例bean。相反,您需要一个单独的userManager 对象,在一个超文本传输协议会话的生命周期内,您需要一个特定于所述超文本传输协议会话的userPreferences 对象。因此,容器创建了一个对象,该对象公开了与UserPreferences 类完全相同的公共接口(理想情况下,该对象是UserPreferences 实例),该对象可以从作用域机制(HTTP请求、会话等)中获取真正的UserPreferences 对象。).容器将这个代理对象注入到userManager bean中,userManager bean不知道这个UserPreferences 引用是一个代理。在这个例子中,当一个userManager 实例在依赖注入的UserPreferences 对象上调用一个方法时,它实际上是在代理上调用一个方法。然后,代理从(在本例中)HTTP会话中获取真实UserPreferences 对象,并将方法调用委托给检索到的真实UserPreferences 对象。
因此,在将请求和会话范围的beans注入协作对象时,您需要以下正确而完整的配置:
<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>
选择要创建的代理类型
默认情况下,当Spring容器为标记有< aop:scoped-proxy/>元素的bean创建代理时,会创建一个基于CGLIB的类代理。
CGLIB代理只拦截公共方法调用!不要在这样的代理上调用非公共方法;它们不会被委托给实际作用域的目标对象。
或者,您可以通过为< aop:scoped-proxy/>使用基于JDK接口的代理意味着在应用程序类路径中不需要额外的库来实现这种代理。然而,这也意味着作用域bean的类必须实现至少一个接口,并且作用域bean被注入的所有协作者必须通过它的一个接口引用该bean。
<!-- 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.自定义范围
bean作用域机制是可扩展的;您可以定义自己的作用域,甚至重新定义现有的作用域,尽管后者被认为是不好的做法,并且您不能覆盖内置的单例和原型作用域。
创建自定义范围(未使用过)
要将您的自定义范围集成到Spring容器中,您需要实现org . Spring framework . Beans . Factory . config . Scope接口,这将在本节中介绍。
Scope
接口有四种方法可以从范围中获取对象,从范围中删除对象,然后销毁它们。
下列方法从基础范围返回对象。例如,会话范围实现返回会话范围的bean(如果它不存在,则该方法在将其绑定到会话以供将来参考后,返回bean的新实例)。
Object get(String name, ObjectFactory objectFactory)
下列方法从基础范围移除对象。例如,会话范围实现从基础会话中移除会话范围的bean。应该返回对象,但是如果找不到具有指定名称的对象,则可以返回null。
Object remove(String name)
下面的方法注册当作用域被销毁或作用域中的指定对象被销毁时作用域应该执行的回调。
void registerDestructionCallback(String name, Runnable destructionCallback)
下列方法获取基础范围的会话标识符。每个作用域的标识符都不同。对于会话范围的实现,该标识符可以是会话标识符。
String getConversationId()
使用自定义范围(未使用过)
在您编写和测试一个或多个自定义范围实现之后,您需要让Spring容器知道您的新范围。以下方法是向Spring容器注册新范围的中心方法:
void registerScope(String scopeName, Scope scope);
这个方法是在ConfigurableBeanFactory接口上声明的,该接口在大多数具体的应用程序上下文实现中都是可用的,这些实现通过BeanFactory属性与Spring一起提供。
registerScope的第一个参数(..)方法是与范围相关联的唯一名称;Spring容器本身中这种名称的例子有singleton和prototype。registerScope的第二个参数(..)方法是您希望注册和使用的自定义范围实现的实际实例。
假设您编写了您的自定义范围实现,然后如下所示注册它。
Note
下面的例子使用了Spring附带的SimpleThreadScope,但默认情况下没有注册。对于您自己的自定义范围实现,说明是相同的。
Scope threadScope = new SimpleThreadScope(); beanFactory.registerScope("thread", threadScope);
然后,您可以创建符合自定义范围的范围规则的bean定义:
<bean id="..." class="..." scope="thread">
使用自定义Scope
实现,您不仅可以通过程序注册该范围。您还可以通过使用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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://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>
当您将< aop:scoped-proxy/>放在FactoryBean实现中时,限定作用域的是FactoryBean本身,而不是从getObject()返回的对象。
1.6.自定义bean的性质
Spring 框架提供了许多接口,可用于自定义 Bean 的性质。本节将它们分组如下:
1.6.1.生命周期回调
为了与容器对bean生命周期的管理进行交互,您可以实现Spring InitializingBean和DisposableBean接口。容器为前者调用afterPropertiesSet(),为后者调用destroy(),以允许bean在初始化和销毁bean时执行某些操作。
JSR-250 @PostConstruct和@PreDestroy注释通常被认为是在现代Spring应用程序中接收生命周期回调的最佳实践。使用这些注释意味着您的beans没有耦合到Spring特定的接口。如果您不想使用JSR-250注释,但仍然希望消除耦合,请考虑使用init-method
和destroy-method
对象定义元数据。
在内部,Spring框架使用BeanPostProcessor实现来处理它可以找到的任何回调接口,并调用适当的方法。如果您需要Spring没有提供的定制特性或其他生命周期行为,您可以自己实现一个BeanPostProcessor。
除了初始化和销毁回调之外,Spring管理的对象还可以实现Lifecycle接口,这样这些对象就可以在容器自己的生命周期驱动下参与启动和关闭过程。
初始化回调
org . spring framework . Beans . factory . initializingBean接口允许bean在容器设置了bean的所有必要属性后执行初始化工作。InitializingBean接口指定了一个方法:
void afterPropertiesSet() throws Exception;
建议您不要使用InitializingBean接口,因为它不必要地将代码耦合到Spring。或者,使用@PostConstruct注释或指定一个POJO初始化方法。在基于XML的配置元数据的情况下,您可以使用init-method属性来指定具有无效无参数签名的方法的名称。使用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。
销毁回调
实现org . spring framework . Beans . factory . DisposableBean接口允许bean在包含它的容器被销毁时获得回调。处理器Bean接口指定了一个方法:
void destroy() throws Exception;
建议您不要使用DisposableBean回调接口,因为它不必要地将代码耦合到Spring。或者,使用@PreDestroy注释或指定bean定义支持的通用方法。使用基于XML的配置元数据,您可以在< bean/>上使用销毁方法属性。使用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
属性分配特殊的(inferred)
值,该值指示 Spring 自动检测特定 bean 类上的公共close
或shutdown
方法。 (因此,实现java.lang.AutoCloseable
或java.io.Closeable
的任何类都将匹配.)您还可以在<beans>
元素的default-destroy-method
属性上设置此特殊的(inferred)
值,以将此行为应用于整个 bean 组(请参见默认初始化和销毁方法)。请注意,这是 Java 配置的默认行为。
默认初始化和销毁方法
当您编写不使用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>
顶层< bean/>元素属性上的default-init-method属性的存在导致Spring IoC容器将bean上一个名为init的方法识别为初始化方法回调。当创建和组装bean时,如果bean类有这样的方法,它会在适当的时候被调用。
通过在顶层< beans>元素上使用default-destroy-method属性,可以类似地配置销毁方法回调(在XML中)。
如果现有的bean类已经有了与约定不一致的回调方法,您可以通过使用< bean/>本身的init-method和destroy-method属性指定方法名来覆盖默认值。
spring容器保证在向bean提供所有依赖项后,立即调用已配置的初始化回调。因此,初始化回调是在原始bean引用上调用的,这意味着AOP拦截器等等还没有应用到bean上。首先完全创建一个目标bean,然后应用带有拦截器链的AOP代理(例如)。如果目标bean和代理分别定义,您的代码甚至可以绕过代理与原始目标bean交互。因此,将拦截器应用于init方法是不一致的,因为这样做会将目标bean的生命周期与其代理/拦截器相耦合,并在您的代码直接与原始目标bean交互时留下奇怪的语义。
结合生命周期机制
从Spring 2.5开始,您有三个选项来控制bean生命周期行为:InitializingBean和DisposableBean回调接口;自定义init()和destroy()方法;和@PostConstruct和@PreDestroy注释。您可以结合这些机制来控制给定的bean。
如果为一个bean配置了多个生命周期机制,并且每个机制都配置了不同的方法名称,那么每个配置的方法都将按下面列出的顺序执行。但是,如果为多个生命周期机制配置了相同的方法名称(例如,初始化方法的init(),则该方法将执行一次。
为同一个bean配置的具有不同初始化方法的多个生命周期机制的调用如下:
用@PostConstruct注释的方法
由InitializingBean回调接口定义的afterPropertiesSet()
自定义配置的init()方法
销毁方法的调用顺序相同:
用@PreDestroy注释的方法
由DisposableBean回调接口定义的destroy()
自定义配置的destroy()方法
启动和关闭回调
Lifecycle接口为任何有自己生命周期需求的对象定义了基本方法(例如,启动和停止一些后台进程):
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
任何Spring管理的对象都可以实现该接口。然后,当应用上下文本身接收到开始和停止信号时,例如对于运行时的停止/重启场景,它将把这些调用级联到在该上下文中定义的所有Lifecycle
实现。它通过委托LifecycleProcessor
来完成此任务,如以下清单所示:
public interface LifecycleProcessor extends Lifecycle { void onRefresh(); void onClose(); }
请注意,LifecycleProcessor本身是Lifecycle接口的扩展。它还添加了另外两种方法,用于对正在刷新和关闭的上下文做出反应。
请注意,常规的org . spring framework . context . lifecycle接口只是显式启动/停止通知的简单约定,并不意味着在上下文刷新时自动启动。考虑改为实现org . spring framework . context . SmartLifeTime,以便对特定bean的自动启动(包括启动阶段)进行细粒度控制。此外,请注意,停止通知并不保证在销毁之前到来:在常规关闭时,所有Lifecycle 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
的“正常” Lifecycle
对象的默认相位是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()被显式调用一样,但是它将在上下文关闭时发生。另一方面,“刷新”回调启用了lifecycleProcessor bean的另一个功能。当上下文被刷新时(在所有对象都被实例化和初始化之后),回调将被调用,此时,默认的生命周期处理器将检查每个lifecycleProcessor对象的isAutoStartup()方法返回的布尔值。如果为“真”,则该对象将在该点启动,而不是等待显式调用上下文或其自己的start()方法(与上下文刷新不同,对于标准上下文实现,上下文启动不会自动发生)。“phase”值以及任何“depends-on”关系将以与上述相同的方式决定启动顺序。
在非web应用程序中正常关闭Spring IoC容器
本节仅适用于非web应用程序。Spring的基于web的ApplicationContext实现已经有了在相关web应用程序关闭时优雅地关闭Spring IoC容器的代码。
如果您在非web应用程序环境中使用Spring的IoC容器;例如,在富客户端桌面环境中;您向JVM注册了一个关闭钩子。这样做可以确保正常关机,并调用单例beans上的相关销毁方法,以便释放所有资源。当然,您仍然必须正确地配置和实现这些销毁回调。要注册关机挂钩,您需要调用在configurationableapplicationcontext接口上声明的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和BeanNameAware
当ApplicationContext创建一个实现org . spring framework . context . ApplicationContextAware接口的对象实例时,将为该实例提供对该application context的引用。
public interface ApplicationContextAware { void setApplicationContext(ApplicationContext applicationContext) throws BeansException; }
因此,bean可以通过ApplicationContext接口,或者通过将引用转换为该接口的一个已知子类(例如configurationAbleApplicationContext,以公开其他功能),以编程方式操作创建它们的应用程序上下文。一个用途是对其他bean的编程检索。有时这种能力是有用的;但是,一般来说,您应该避免它,因为它将代码耦合到Spring,并且不遵循控制反转风格,在这种风格中,协作者作为属性被提供给bean。ApplicationContext的其他方法提供对文件资源的访问、发布应用程序事件和访问消息源。
从Spring 2.5开始,自动连接是另一种获取应用程序上下文引用的方法。“传统”constructor和byType自动连接模式(如自动连接协作者中所述)可以分别为构造函数参数或 setter 方法参数提供ApplicationContext
类型的依赖项。为了获得更大的灵活性,包括能够自动连接字段和使用多个参数方法,请使用基于注解的新自动装配功能。如果这样做,ApplicationContext
将自动连接到期望使用ApplicationContext
类型的字段,构造函数自变量或方法参数中(如果有问题的字段,构造函数或方法带有@Autowired
注解)。
当ApplicationContext创建一个实现org . spring framework . beans . factory . BeanNameAware接口的类时,该类会被提供一个对其关联对象定义中定义的名称的引用。
public interface BeanNameAware { void setBeanName(String name) throws BeansException; }
回调在普通bean属性填充之后,但在初始化回调(如InitializingBean afterPropertiesSet或自定义init-method)之前调用。
1.6.3.其他感知接口
除了上面讨论的ApplicationContextAware和BeanNameAware之外,Spring还提供了广泛的感知回调接口,允许beans向容器指示它们需要某种基础架构依赖。最重要的感知接口总结如下——作为一般规则,名称是依赖性类型的良好指示:
Name(名字) | Injected Dependency(注入依赖) | Explained in…(解释于...) |
---|---|---|
|
Declaring |
|
|
Event publisher of the enclosing |
|
|
Class loader used to load the bean classes.(用于加载bean类的类加载器。) |
|
|
Declaring |
|
|
Name of the declaring bean(声明bean的名称) |
|
|
Resource adapter |
|
|
Defined weaver for processing class definition at load time(已定义用于在加载时处理类定义的weaver) |
|
|
Configured strategy for resolving messages (with support for parametrization and internationalization)(解析消息的配置策略(支持参数化和国际化)) |
|
|
Spring JMX notification publisher(Spring JMX 通知发布者) |
|
|
Configured loader for low-level access to resources(为资源的低级访问配置的加载程序) |
|
|
Current |
|
|
Current |
请再次注意,这些接口的使用将您的代码与Spring应用编程接口联系在一起,并且不遵循控制反转风格。因此,对于需要对容器进行编程访问的基础设施beans,建议使用它们。
1.7.Bean定义继承
bean定义可以包含许多配置信息,包括构造函数参数、属性值和容器特定的信息,如初始化方法、静态工厂方法名称等。子bean定义从父定义继承配置数据。子定义可以覆盖一些值,或者根据需要添加其他值。使用父bean和子bean定义可以节省大量的键入工作。实际上,这是一种模板形式。
如果以编程方式使用应用程序上下文接口,子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定义从父bean继承范围、构造函数参数值、属性值和方法重写,并具有添加新值的选项。您指定的任何范围、初始化方法、销毁方法和/或静态工厂方法设置都将覆盖相应的父设置。
其余的设置总是取自子定义:依赖、自动连线模式、依赖检查、单例、惰性初始化。depends on, autowire mode, dependency check, singleton, lazy init.
前面的示例使用abstract属性将父bean定义显式标记为抽象。如果父定义没有指定类,则需要将父bean定义显式标记为abstract,如下所示:
<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不能单独实例化,因为它是不完整的,并且它也被显式标记为抽象的。当定义为abstract时,它只能用作纯模板bean定义,作为子定义的父定义。试图单独使用这样一个抽象的父bean,将它作为另一个bean的ref属性引用,或者用父bean id进行显式getBean()调用,都会返回一个错误。同样,容器的内部preInstantiateSingletons()方法会忽略被定义为抽象的bean定义。
默认情况下,ApplicationContext预先实例化所有的单例。因此,重要的是(至少对于单例bean来说),如果您有一个只打算用作模板的(父)bean定义,并且这个定义指定了一个类,那么您必须确保将抽象属性设置为true,否则应用程序上下文实际上会(尝试)预实例化抽象bean。
1.8容器扩展点
通常,应用程序开发人员不需要子类化应用程序上下文实现类。相反,Spring IoC容器可以通过插入特殊集成接口的实现来扩展。接下来的几节描述了这些集成接口。
1.8.1.使用BeanPostProcessor自定义bean
BeanPostProcessor接口定义了回调方法,您可以实现这些方法来提供您自己的(或覆盖容器的默认)实例化逻辑、依赖项解析逻辑等等。如果您想在Spring容器完成bean的实例化、配置和初始化之后实现一些定制逻辑,您可以插入一个或多个定制的bean后置处理器实现。
您可以配置多个后处理器实例,并且可以通过设置order属性来控制这些后处理器的执行顺序。只有当后处理器实现有序接口时,才能设置此属性;如果你写了自己的后处理器,你也应该考虑实现Ordered接口。
BeanPostProcessor对bean(或对象)实例(实例属性)进行操作;也就是说,Spring IoC容器实例化了一个bean实例,然后BeanPostProcessors完成它们的工作。
BeanPostProcessor的作用域是每个容器。只有在使用容器层次结构时,这才是相关的。如果您在一个容器中定义了一个BeanPostProcessor,它将只对该容器中的bean进行后处理。换句话说,在一个容器中定义的beanss不会被在另一个容器中定义的BeanPostProcessor进行后处理,即使两个容器都是同一层次结构的一部分。
要更改实际的bean定义(即定义bean的蓝图),您需要使用BeanFactoryPostProcessor,如使用BeanFactoryPostProcessor自定义配置元数据中所述。
org . spring framework . beans . factory . config . BeanPostProcessor接口正好由两个回调方法组成。当这样的类在容器中注册为后处理器时,对于由容器创建的每个Bean实例,后处理器在调用容器初始化方法(如InitializingBean的afterPropertiesSet()或任何声明的init方法)之前以及在任何bean初始化回调之后都会从容器中获得一个回调。后处理器可以对bean实例采取任何操作,包括完全忽略回调。bean后处理器通常会检查回调接口,或者用代理来包装bean。一些Spring AOP基础设施类被实现为bean后处理器,以便提供代理包装逻辑。
应用程序上下文会自动检测配置元数据中定义的任何实现了BeanPostProcessor 接口的bean。ApplicationContext将这些bean注册为后处理器,以便以后在创建bean时可以调用它们。Bean后处理器可以像任何其他bean一样部署在容器中。
请注意,当在配置类上使用@Bean工厂方法声明BeanPostProcessor时,工厂方法的返回类型应该是实现类本身,或者至少是org . spring framework . Beans . Factory . config . BeanPostProcessor接口,清楚地表明该Bean的后处理器性质。否则,应用程序上下文将无法在完全创建它之前按类型自动检测它。由于BeanPostProcessor
需要在早期实例化,以便应用于上下文中其他bean的初始化,因此这种早期类型检测非常关键。
以编程方式注册BeanPostProcessor
虽然推荐的BeanPostProcessor
注册方法是通过应用程序上下文自动检测(如上所述),但也可以使用addBeanPostProcessor
方法根据可配置的ConfigurableBeanFactory
以编程方式注册它们。当需要在注册之前评估条件逻辑时,或者甚至在层次结构中跨上下文复制BeanPostProcessor
s 时,这可能很有用。但是请注意,以编程方式添加的后处理程序不尊重有序Ordered
接口。这里登记的顺序决定了执行的顺序。还要注意,以编程方式注册的BeanPostProcessors总是在那些通过自动检测注册的BeanPostProcessors之前进行处理,而不考虑任何显式的顺序
BeanPostProcessor
和AOP自动代理
实现BeanPostProcessor接口的类是特殊的,容器会对它们进行不同的处理。作为应用程序上下文的特殊启动阶段的一部分,所有BeanPostProcessor和它们直接引用的bean都在启动时被实例化。接下来,所有的BeanPostProcessor都以排序的方式注册,并应用于容器中的所有其他bean。因为AOP自动代理是作为BeanPostProcessor本身实现的,所以BeanPostProcessor和它们直接引用的bean都不适合自动代理,因此它们没有aspects 。
对于任何这样的bean,您应该会看到一条信息性的日志消息:“Bean foo不适合被所有的BeanPostProcessor接口处理(例如:不适合自动代理)”。
请注意,如果您使用自动连接或@Resource将bean连接到您的bean后处理器中(这可能会回到自动连接),Spring可能会在搜索类型匹配的依赖候选项时访问意外的bean,因此使它们不适合自动代理或其他类型的bean后处理。例如,如果您有一个用@Resource注释的依赖项,其中字段/设置器名称不直接对应于bean的声明名称,并且没有使用名称属性,那么Spring将访问其他bean,以便按类型匹配它们。
以下示例显示了如何在应用程序上下文中编写、注册和使用BeanPostProcessors。
示例:Hello World,BeanPostProcessor 风格
第一个例子说明了基本用法。该示例显示了一个定制的BeanPostProcessor实现,该实现在容器创建每个bean时调用其toString()方法,并将结果字符串打印到系统控制台。
package scripting; import org.springframework.beans.factory.config.BeanPostProcessor; public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor { public Object postProcessBeforeInitialization(Object bean, String beanName) { return bean; } 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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/lang https://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> <!-- when the above bean (messenger) is instantiated, this custom BeanPostProcessor implementation will output the fact to the system console --> <bean class="scripting.InstantiationTracingBeanPostProcessor"/> </beans>
请注意如何简单地定义实例化调用后处理器。它甚至没有名字,因为它是一个bean,所以它可以像任何其他bean一样被依赖注入。
以下简单的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
} }
示例:RequiredAnnotationBeanPostProcessor
将回调接口或注释与定制的BeanPostProcessor实现结合使用是扩展Spring IoC容器的一种常见方法。一个例子是Spring的RequiredAnnotationBeanPostProcessor
1.8.2.使用BeanFactoryPostProcessor自定义配置元数据
我们要看的下一个扩展点是org . spring framework . beans . factory . config . BeanFactoryPostProcessor,这个接口的语义和BeanPostProcessor的语义类似,主要区别是:BeanFactoryPostProcessor对bean配置元数据进行操作;也就是说,Spring IoC容器允许BeanFactoryPostProcessor读取配置元数据,并可能在容器实例化BeanFactoryPostProcessors之外的任何bean之前对其进行更改。它是BeanFactory级别的处理,是针对整个Bean的工厂进行处理。
您可以配置多个BeanFactoryPostProcessors,并且可以通过设置order属性来控制这些BeanFactoryPostProcessors的执行顺序。但是,只有当BeanFactoryPostProcessor实现有序Ordered
接口时,才能设置此属性。如果您编写自己的BeanFactoryPostProcessor,您也应该考虑实现有序Ordered
接口。
如果您想要更改实际的bean实例(即,从配置元数据创建的对象),那么您需要使用BeanPostProcessor (如上面使用BeanPostProcessor 定制bean中所述)。虽然在BeanFactoryPostProcessor中使用Bean实例在技术上是可能的(例如,使用BeanFactory.getBean()),但这样做会导致bean过早实例化,违反标准的容器生命周期。这可能会导致负面的副作用,例如绕过BeanPostProcessor 。
此外,BeanFactoryPostProcessors的作用域是每个容器。只有在使用容器层次结构时,这才是相关的。如果您在一个容器中定义了BeanFactoryPostProcessor,它将只应用于该容器中的bean定义。一个容器中的Bean定义不会被另一个容器中的BeanFactoryPostProcessors进行后处理,即使两个容器都是同一层次结构的一部分。
当BeanFactoryPostProcessors在应用程序上下文中声明时,它会自动执行,以便对定义容器的配置元数据进行更改。Spring包括许多预定义的bean工厂后处理器,如PropertyOverrideConfigurer和PropertyPlaceholderConfigurer。您还可以使用自定义BeanFactoryPostProcessor
,例如,注册自定义属性编辑器。
应用程序上下文自动检测部署到其中的任何实现BeanFactoryPostProcessor接口的bean。它在适当的时候使用这些bean作为BeanFactoryPostProcessor。您可以像部署任何其他bean一样部署这些后处理器bean。
与BeanPostProcessors 一样,您通常不想为BeanFactoryPostProcessors配置惰性初始化 。如果没有其他bean引用一个Bean(Factory)PostProcessor,那么该后处理器根本不会被实例化。因此,将它标记为惰性初始化将被忽略,并且Bean(工厂)后处理器将被急切地实例化,即使您在< Beans/>元素的声明中将default-lazy-init属性设置为true。
示例:类名替换 PropertyPlaceholderConfigurer
您可以使用PropertyPlaceholderConfigurer
使用标准 Java Properties
格式将 Bean 定义中的属性值外部化到单独的文件中。这样做使部署应用程序的人员可以自定义特定于环境的属性,例如数据库 URL 和密码,而无需为修改容器的主要 XML 定义文件而复杂或冒风险。
考虑以下基于 XML 的配置元数据片段,其中定义了带有占位符值的DataSource
。
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations" value="classpath:com/something/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>
该示例显示了从外部Properties
文件配置的属性。在运行时,将PropertyPlaceholderConfigurer
应用于元数据,它将替换数据源的某些属性。将要替换的值指定为${property-name}
形式的* placeholders *,它遵循 Ant/log4j/JSP EL 样式。
实际值来自标准 Java Properties
格式的另一个文件:
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
名称空间,您可以使用专用配置元素配置属性占位符。您可以在location
属性中以逗号分隔列表的形式提供一个或多个位置,如以下示例所示:
<context:property-placeholder location="classpath:com/something/jdbc.properties"/>
PropertyPlaceholderConfigurer
不仅在您指定的Properties
文件中查找属性。默认情况下,如果无法在指定的属性文件中找到属性,则还会检查 Java System
属性。您可以通过使用以下三个受支持的整数值之一设置配置程序的systemPropertiesMode
属性来自定义此行为:
-
never
(0):从不检查系统属性。 -
fallback
(1):检查系统属性是否在指定的属性文件中不可解析。这是默认值。 -
override
(2):在尝试指定的属性文件之前,请先检查系统属性。这使系统属性可以覆盖任何其他属性源。
您可以使用PropertyPlaceholderConfigurer
来替换类名,这在您必须在运行时选择特定的实现类时有时很有用。以下示例显示了如何执行此操作:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <value>classpath:com/something/strategy.properties</value> </property> <property name="properties"> <value>custom.strategy.class=com.something.DefaultStrategy</value> </property> </bean> <bean id="serviceStrategy" class="${custom.strategy.class}"/>
如果无法在运行时将类解析为有效的类,则将要创建的 bean 的解析将失败,即在非延迟初始化 bean 的ApplicationContext
的preInstantiateSingletons()
阶段。
示例:PropertyOverrideConfigurer
PropertyOverrideConfigurer
是另一个 Bean 工厂后处理程序,类似于PropertyPlaceholderConfigurer
,但是与后者不同,原始定义对于 Bean 属性可以具有默认值或完全没有值。如果覆盖的Properties
文件没有某个 bean 属性的条目,则使用默认的上下文定义。
注意,bean 定义不知道会被覆盖,因此从 XML 定义文件中不能立即看出正在使用覆盖配置器。如果有多个PropertyOverrideConfigurer
实例为同一个 bean 属性定义了不同的值,则由于覆盖机制,最后一个实例将获胜。
属性文件配置行采用以下格式:
beanName.property=value
下面的清单显示了格式的示例:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
此示例文件可与包含定义为dataSource
且具有driver
和url
属性的 Bean 的容器定义一起使用。
只要路径的每个组成部分(最终属性被覆盖)之外的所有组成部分都已经为非空(可能是由构造函数初始化),则也支持复合属性名。在以下示例中,将tom
bean 的fred
属性的bob
属性的sammy
属性设置为标量值123
:tom.fred.bob.sammy=123
Note
指定的替代值始终是 Literals 值。它们不会转换为 bean 引用。当 XML bean 定义中的原始值指定 bean 引用时,此约定也适用。
使用 Spring 2.5 中引入的context
名称空间,可以使用专用配置元素配置属性覆盖,如以下示例所示:
<
context:property-
override
location="classpath:override.properties"/>
1.8.3. 使用 FactoryBean 自定义实例化逻辑
您可以为本身就是工厂的对象实现org.springframework.beans.factory.FactoryBean
接口。
FactoryBean
接口是可插入 Spring IoC 容器的实例化逻辑的点。如果您有复杂的初始化代码,而不是(可能)冗长的 XML,可以用 Java 更好地表达,则可以创建自己的FactoryBean
,在该类中编写复杂的初始化,然后将自定义FactoryBean
插入容器。
FactoryBean
界面提供了三种方法:
-
Object getObject()
:返回此工厂创建的对象的实例。实例可以共享,具体取决于该工厂是否返回单例或原型。 -
boolean isSingleton()
:如果此FactoryBean
返回单例,则返回true
,否则返回false
。 -
Class getObjectType()
:返回由getObject()
方法或null
返回的对象类型(如果事先未知)。
当您需要向容器请求一个实际的FactoryBean
实例本身而不是它生成的 bean 时,请在调用ApplicationContext
的getBean()
方法时在 bean 的id
前面加上一个&符号(&
)。因此,对于给定的id
myBean
的FactoryBean
,在容器上调用getBean("myBean")
返回FactoryBean
的乘积,而调用getBean("&myBean")
则返回FactoryBean
实例本身。
- FactoryBean:是一个Java Bean,但是它是一个能生产对象的工厂Bean,它的实现和工厂模式及修饰器模式很像。比如下:我们把bean比作是人,那么FactoryBean可以算是一个女人,首先它本身也是一个人,但它能够生产人。【挺尴尬的比喻】。
- BeanFactory:这就是一个Factory,是一个IOC容器或者叫对象工厂,它里面存着很多的bean。还用上面那个比如:如果bean是人,那么它可以理解成学校,学校里面很多人,学校管理这么多的人。
1.9. 基于注解的容器配置
基于注解的配置提供了 XML 设置的替代方法,该配置依赖字节码元数据来连接组件,而不是尖括号声明。通过使用相关类,方法或字段声明上的 注解,开发人员无需使用 XML 来描述 bean 的连接,而是将配置移入组件类本身。如示例:RequiredAnnotationBeanPostProcessor中所述,结合使用BeanPostProcessor
和注解是扩展 Spring IoC 容器的常用方法。例如,Spring 2.0 引入了使用@Required注解 强制执行必需属性的可能性。 Spring 2.5 使遵循相同的通用方法来驱动 Spring 的依赖注入成为可能。本质上,@Autowired
注解提供了与Autowiring Collaborators中描述的功能相同的功能,但具有更细粒度的控制和更广泛的适用性。 Spring 2.5 还添加了对 JSR-250 注解的支持,例如@PostConstruct
和@PreDestroy
。 Spring 3.0 添加了对javax.inject
软件包(例如@Inject
和@Named
)中包含的 JSR-330(Java 依赖性注入)注解 的支持。
Note
注解注入在 XML 注入之前执行。因此,XML 配置将覆盖通过两种方法连接的属性的 注解。
与往常一样,您可以将它们注册为单独的 bean 定义,但是也可以通过在基于 XML 的 Spring 配置中包含以下标记来隐式注册它们(请注意包含context
名称空间):
<?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。)
Note
<context:annotation-config/>
仅在定义它的相同应用程序上下文中查找 bean 上的 注解。这意味着,如果您将<context:annotation-config/>
放在WebApplicationContext
中而不是DispatcherServlet
,则它仅检查控制器中的@Autowired
bean,而不检查服务。
1.9.1. @Required
@Required
注解 适用于 bean 属性设置器方法,如以下示例所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
此注解指示必须在配置时通过 bean 定义中的显式属性值或通过自动装配来填充受影响的 bean 属性。如果受影响的 bean 属性尚未填充,则容器将引发异常。这允许急切和显式的故障,避免以后再出现NullPointerException
个实例等。我们仍然建议您将 assert 放入 bean 类本身中(例如,放入 init 方法中)。这样做会强制执行那些必需的引用和值,即使您在容器外部使用该类也是如此。
1.9.2. 使用@Autowired
在本节中包含的示例中,可以使用 JSR 330 的@Inject
注解 代替 Spring 的@Autowired
注解。用来注册bean内部的属性值,默认按类型。
======================================================================
可以将@Autowired
注解 应用于构造函数
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
Note
从 Spring Framework 4.3 开始,如果目标 bean 仅定义一个构造函数作为开始,则不再需要在此类构造函数上使用@Autowired
注解。但是,如果有几个构造函数可用,则必须至少注解一个,以告诉容器使用哪个构造函数。
======================================================================
将@Autowired
注解 应用于“传统” setter 方法
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
注解 的注入点的类型一致地声明。否则,由于在运行时找不到类型匹配,注入可能会失败。
对于通过 Classpath 扫描找到的 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; } // ... }
Tip
如果希望数组或列表中的项按特定 Sequences 排序,则目标 bean 可以实现org.springframework.core.Ordered
接口或使用@Order
或标准@Priority
注解。否则,它们的 Sequences 将遵循容器中相应目标 bean 定义的注册 Sequences。
您可以在目标类级别和@Bean
方法上声明@Order
注解,可能通过单个 bean 定义来声明(如果使用同一 bean 类的多个定义)。 @Order
值可能会影响注入点的优先级,但是要注意,它们不会影响单例启动 Sequences,这是由依赖关系和@DependsOn
声明确定的正交关注点。
请注意,标准javax.annotation.Priority
注解 在@Bean
级别不可用,因为无法在方法上声明它。对于每种类型,可以通过@Order
值与@Primary
组合在单个 bean 上对其语义进行建模。
======================================================================
只要预期的密钥类型为String
,即使是键入的Map
实例也可以自动装配。 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;
}
// ...
}
Note
每个类仅可以将一个带注解的构造函数标记为必需,但可以对多个非必需的构造函数进行 注解。在这种情况下,每个候选对象都将被考虑在内,并且 Spring 使用最贪婪的构造函数,其依赖关系可以得到满足-即具有最多参数的构造函数。
建议在@Required
注解 上使用@Autowired
的必需属性。 required 属性表示自动装配不需要该属性。如果无法自动装配该属性,则将其忽略。另一方面,@Required
更强大,因为它可以强制执行通过容器支持的任何方式设置的属性。如果未注入任何值,则会引发相应的异常。
======================================================================
另外,您可以通过 Java 8 的java.util.Optional
表示特定依赖项的非必需性质,如以下示例所示:
public class SimpleMovieLister { @Autowired public void setMovieFinder(Optional<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
)将自动解析,而无需进行特殊设置。下面的示例自动连接ApplicationContext
对象:
public class MovieRecommender {
@Autowired
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
Note
@Autowired
,@Inject
,@Resource
和@Value
注解由 Spring BeanPostProcessor
实现处理。这意味着您不能在自己的BeanPostProcessor
或BeanFactoryPostProcessor
类型(如果有)中应用这些 注解。必须使用 XML 或 Spring @Bean
方法显式“连接”这些类型。
1.9.3. 使用@Primary 微调基于注解的自动装配
由于按类型自动布线可能会导致多个候选对象,因此通常有必要对选择过程进行更多控制。实现此目的的一种方法是使用 Spring 的@Primary
注解。 @Primary
表示当多个 bean 可以自动连接到单值依赖项的候选对象时,应优先考虑特定的 bean。如果候选中恰好存在一个主 bean,则它将成为自动装配的值。
@Configuration(定义xml) public class MovieConfiguration { @Bean(定义xml中的bean值) @Primary public MovieCatalog firstMovieCatalog() { ... } @Bean public MovieCatalog secondMovieCatalog() { ... } // ... }
使用前面的配置,下面的MovieRecommender
与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">
</bean>
<bean class="example.SimpleMovieCatalog">
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
1.9.4. 使用限定符对基于注解的自动装配进行微调
当可以确定一个主要候选对象时,@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 定义。
<?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"/> (1) </bean> <bean class="example.SimpleMovieCatalog"> <qualifier value="action"/> (2) </bean> <bean id="movieRecommender" class="example.MovieRecommender"/> </beans>
- (1) 具有
main
限定符值的 Bean 与限定有相同值的构造函数自变量连接。 - (2) 具有
action
限定符值的 Bean 与限定有相同值的构造函数参数连接。
对于后备匹配,bean 名称被视为默认的限定符值。因此,您可以用id
或main
定义 bean,而不是嵌套的限定符元素,从而得到相同的匹配结果。但是,尽管您可以使用此约定按名称引用特定的 bean,但@Autowired
基本上是关于带有可选语义限定符的类型驱动的注入。这意味着,即使带有 Bean 名称后备的限定符值,在类型匹配集中也始终具有狭窄的语义。它们没有在语义上表示对唯一 bean id
的引用。好的限定符值为main
或EMEA
或persistent
,它们表示特定组件的特性,它们独立于 Bean id
,如果是匿名 Bean 定义(例如上例中的定义),则可以自动生成这些组件。
限定词也适用于类型化的集合,如前面所述(例如,Set<MovieCatalog>
)。在这种情况下,根据声明的限定符,将所有匹配的 bean 作为集合注入。这意味着限定词不必是唯一的。相反,它们构成了过滤标准。例如,您可以使用相同的限定符值“ action”定义多个MovieCatalog
bean,所有这些都注入到以@Qualifier("action")
注解 的Set<MovieCatalog>
中。
Tip
在类型匹配的候选对象中,让限定符值针对目标 bean 名称进行选择,在注入点不需要@Qualifier
注解。如果没有其他解析度指示符(例如限定词或主标记),则对于非唯一依赖性情况,Spring 将注入点名称(即字段名称或参数名称)与目标 Bean 名称进行匹配,然后选择同名候选人(如果有)。
就是说,如果您打算按名称表示注解驱动的注入,则不要主要使用@Autowired
,即使它能够在类型匹配的候选对象中按 bean 名称进行选择。而是使用 JSR-250 @Resource
注解,该注解的语义定义是通过其唯一名称来标识特定目标组件,而声明的类型与匹配过程无关。 @Autowired
具有不同的语义:按类型选择候选 Bean 之后,仅在那些类型选择的候选中考虑指定的String
限定符值(例如,将account
限定符与标记有相同限定符标签的 Bean 匹配)。
对于本身定义为集合Map
或数组类型的 bean,使用@Resource
是一个很好的解决方案,它通过唯一的名称引用特定的集合或数组 bean。也就是说,从 4.3 版本开始,只要元素类型信息保留在@Bean
返回类型签名或集合继承层次结构中,就可以通过 Spring 的@Autowired
类型匹配算法来匹配Map
和数组类型。在这种情况下,您可以使用限定符值在同类型的集合中进行选择,如上一段所述。
@Autowired
适用于字段,构造函数和多参数方法,从而允许在参数级别缩小限定符注解的范围。相反,仅具有单个参数的字段和 bean 属性设置器方法支持@Resource
。因此,如果注入目标是构造函数或多参数方法,则应坚持使用限定符。
=============================================================================
您可以创建自己的自定义限定符 注解。为此,请定义一个注解并在定义中提供@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/>
标记的子元素,然后指定type
和value
以匹配您的自定义限定符 注解。该类型与注解的完全限定的类名匹配。另外,为方便起见,如果不存在名称冲突的风险,则可以使用简短的类名。
<?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>
=============================================================================
在某些情况下,使用没有值的注解就足够了。当注解用于更一般的用途并且可以应用于几种不同类型的依赖项时,这将很有用。例如,您可以提供一个脱机目录,当没有 Internet 连接可用时,可以对其进行搜索。首先,定义简单的 注解,如以下示例所示:
@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Offline { }
然后将注解添加到要自动装配的字段或属性
public class MovieRecommender {
@Autowired
@Offline (1)
private MovieCatalog offlineCatalog;
// ...
}
- (1) 此行添加了
@Offline
注解。
现在,bean 定义只需要一个限定符type
,如以下示例所示:
<bean class="example.SimpleMovieCatalog">
<qualifier type="Offline"/> (1)
<!-- inject any dependencies required by this bean -->
</bean>
- (1) 此元素指定限定符。
您还可以定义自定义限定符注解,除了简单的value
属性之外,还可以接受命名属性。如果随后在要自动装配的字段或参数上指定了多个属性值,则 bean 定义必须与所有此类属性值匹配才能被视为自动装配候选。例如,请考虑以下注解定义:
@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface MovieQualifier { String genre(); Format format(); }
public enum Format { VHS, DVD, BLURAY }
要自动装配的字段将用定制限定符进行 注解,并包括两个属性genre
和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. 将泛型用作自动装配限定符
除了@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
自动连接列表,Map
实例和数组时,通用限定符也适用。以下示例将自动连接通用List
:
@Autowired private List<Store<Integer>> s;
1.9.6. 使用 CustomAutowireConfigurer
CustomAutowireConfigurer是BeanFactoryPostProcessor
,即使您没有使用 Spring 的@Qualifier
注解 对您自己的自定义限定符注解类型进行注册,您也可以使用它们。以下示例显示了如何使用CustomAutowireConfigurer
:
<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/>
元素上可用的任何default-autowire-candidates
模式 -
@Qualifier
注解 和在CustomAutowireConfigurer
中注册的所有自定义注解的存在
当多个 bean 可以作为自动装配候选者时,确定“主要”的步骤如下:如果候选者中恰好有一个 bean 定义具有primary
属性设置为true
,则将其选中。
1.9.7. 用@Resource 注入
Spring 还通过在字段或 bean 属性设置器方法上使用 JSR-250 @Resource
注解 来支持注入。这是 Java EE 5 和 6 中的常见模式(例如,在 JSF 1.2 托管 Bean 或 JAX-WS 2.0 端点中)。 Spring 也为 SpringManagement 的对象支持此模式。
@Resource
具有名称属性。默认情况下,Spring 将该值解释为要注入的 Bean 名称。换句话说,它遵循名称语义,如以下示例所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder") (1)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
- (1) 此行注入
@Resource
。
如果未明确指定名称,则默认名称是从字段名称或 setter 方法派生的。如果是字段,则采用字段名称。在使用 setter 方法的情况下,它采用 bean 属性名称。以下示例将名为movieFinder
的 bean 注入其 setter 方法中:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
Note
注解提供的名称由CommonAnnotationBeanPostProcessor
知道的ApplicationContext
解析为 bean 名称。如果您显式配置 Spring 的SimpleJndiBeanFactory,则可以通过 JNDI 解析名称。但是,我们建议您依靠默认行为并使用 Spring 的 JNDI 查找功能来保留间接级别。
在未使用@Resource
且未指定显式名称且与@Autowired
类似的特殊情况下,@Resource
查找主类型匹配而不是特定的命名 bean,并解析众所周知的可解决依赖项:BeanFactory
,ApplicationContext
,ResourceLoader
,ApplicationEventPublisher
和MessageSource
接口。
因此,在下面的示例中,customerPreferenceDao
字段首先查找名为 customerPreferenceDao 的 bean,然后回退到类型CustomerPreferenceDao
的主类型匹配:
public class MovieRecommender { @Resource private CustomerPreferenceDao customerPreferenceDao; @Resource private ApplicationContext context; (1) public MovieRecommender() { } // ... }
- (1)
context
字段是根据已知的可解决依赖项类型ApplicationContext
注入的。
1.9.8. 使用@PostConstruct 和@PreDestroy
CommonAnnotationBeanPostProcessor
不仅可以识别@Resource
注解,还可以识别 JSR-250 生命周期 注解。在 Spring 2.5 中引入了对这些注解的支持,为initialization callbacks和destruction callbacks中描述的注解提供了另一种选择。假设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... } }
Note
有关组合各种生命周期机制的效果的详细信息,请参见组合生命周期机制。
1.10. Classpath 扫描和托管组件
本章中的大多数示例都使用 XML 来指定在 Spring 容器中生成每个BeanDefinition
的配置元数据。上一节(基于注解的容器配置)演示了如何通过源级注解提供许多配置元数据。但是,即使在这些示例中,“基本” bean 定义也已在 XML 文件中明确定义,而注解仅驱动依赖项注入。
本节介绍了通过扫描 Classpath 来隐式检测候选组件的选项。候选组件是与过滤条件匹配的类,并在容器中注册了相应的 Bean 定义。这消除了使用 XML 进行 bean 注册的需要。
相反,您可以使用 注解(例如@Component
),AspectJ 类型表达式或您自己的自定义过滤条件来选择哪些类已向容器注册了 bean 定义。
Note
从 Spring 3.0 开始,Spring JavaConfig 项目提供的许多功能是核心 Spring Framework 的一部分。这使您可以使用 Java 而不是使用传统的 XML 文件来定义 bean。请查看@Configuration
,@Bean
,@Import
和@DependsOn
注解,以获取有关如何使用这些新功能的示例。
1.10.1. @Component 和其他构造型 注解
@Repository
注解 是满足存储库的角色或构造型(也称为数据访问对象或 DAO)的任何类的标记。如Exception Translation中所述,此标记的用途是自动翻译异常。
Spring 提供了进一步的构造型 注解:@Component
,@Service
和@Controller
。 @Component
是任何 SpringManagement 的组件的通用构造型。 @Repository
,@Service
和@Controller
是@Component
的特化,用于更具体的用例(分别在持久层,服务层和表示层中)。因此,您可以使用@Component
注解 组件类,但是通过使用@Repository
,@Service
或@Controller
注解 组件类,则您的类更适合于通过工具进行处理或与方面相关联。例如,这些构造型注解成为切入点的理想目标。 @Repository
,@Service
和@Controller
在 Spring 框架的 Future 发行版中还可包含其他语义。因此,如果在服务层使用@Component
或@Service
之间进行选择,则@Service
显然是更好的选择。同样,如前所述,@Repository
已被支持作为持久层中自动异常转换的标记。
1.10.2. 使用元注解和组合注解
Spring 提供的许多注解都可以在您自己的代码中用作元注解。元注解是可以应用于另一个注解的注解。例如,提到的earlier的@Service
注解使用@Component
进行元注解,如以下示例所示:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Component (1) public @interface Service { // .... }
- (1)
Component
导致@Service
的处理方式与@Component
相同。
您还可以结合使用元注解来创建“组合 注解”。例如,Spring MVC 的@RestController
注解 由@Controller
和@ResponseBody
组成。此外,组合注解可以选择从元注解中重新声明属性,以允许自定义。当您只想公开元注解属性的子集时,这可能特别有用。
例如,Spring 的@SessionScope
注解 将作用域名称硬编码为session
,但仍允许自定义proxyMode
。以下清单显示了SessionScope
注解的定义:
@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; }
然后,您可以使用@SessionScope
而不用声明proxyMode
,如下所示:
@Service @SessionScope public class SessionScopedService { // ... }
您还可以覆盖proxyMode
的值,如以下示例所示:
@Service @SessionScope(proxyMode = ScopedProxyMode.INTERFACES) public class SessionScopedUserService implements UserService { // ... }
1.10.3. 自动检测类并注册 Bean 定义
Spring 可以自动检测构造型类,并向ApplicationContext
注册相应的BeanDefinition
实例。例如,以下两个类别有资格进行这种自动检测:
@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 {
...
}
Note
为简洁起见,前面的示例可能使用了注解的value
属性(即@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>
Tip
<context:component-scan>
的使用隐式启用<context:annotation-config>
的功能。使用<context:component-scan>
时通常不需要包含<context:annotation-config>
元素。
Note
此外,当您使用 component-scan 元素时,AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
都隐式包含在内。这意味着两个组件将被自动检测并连接在一起,而所有这些都不需要 XML 中提供任何 bean 配置元数据。
您可以通过包含注解设置属性false
来禁用AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
的注册。
1.10.4. 使用过滤器自定义扫描
默认情况下,唯一检测到的候选组件是用@Component
,@Repository
,@Service
,@Controller
注解 的类或本身用@Component
注解 的定制 注解。
但是,您可以通过应用自定义过滤器来修改和扩展此行为。将它们添加为@ComponentScan
注解的includeFilters
或excludeFilters
参数(或component-scan
元素的include-filter
或exclude-filter
子元素)。每个过滤器元素都需要type
和expression
属性。下表描述了过滤选项:
表 5.过滤器类型
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>
Note
您还可以通过在注解上设置useDefaultFilters=false
或通过提供use-default-filters="false"
作为<component-scan/>
元素的属性来禁用默认过滤器。实际上,这将禁用对带有@Component
,@Repository
,@Service
,@Controller
或@Configuration
注解 的类的自动检测。一般不会用。
1.10.5. 在组件中定义 Bean 元数据
Spring 组件还可以将 bean 定义元数据贡献给容器。您可以使用与@Configuration
带注解的类中定义 Bean 元数据相同的@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注解标识工厂方法和其他bean定义属性,例如通过@Qualifier注解的限定符值。
可以指定的其他方法级别注解是@Scope,@Lazy和自定义限定符注解。
Tip
除了用于组件初始化的角色外,还可以将@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 Expression Language 元素通过符号#{ <expression> }
定义属性的值。对于@Value
注解,表达式解析器已预先配置为在解析表达式文本时查找 bean 名称。
==========================================================================================
从 Spring Framework 4.3 开始,您还可以声明类型为InjectionPoint
(或其更具体的子类:DependencyDescriptor
)的工厂方法参数,以访问触发当前 bean 创建的请求注入点。注意,这仅适用于实际创建 bean 实例,而不适用于注入现有实例。因此,此功能对原型范围的 bean 最有意义。对于其他作用域,factory 方法仅在给定作用域中看到触发创建新 bean 实例的注入点(例如,触发创建惰性单例 bean 的依赖项)。在这种情况下,可以将提供的注入点元数据与语义一起使用。以下示例显示了如何使用InjectionPoint
:
@Component public class FactoryMethodComponent { @Bean @Scope("prototype") public TestBean prototypeInstance(InjectionPoint injectionPoint) { return new TestBean("prototypeInstance for " + injectionPoint.getMember()); } }
常规 Spring 组件中的@Bean
方法的处理方式与 Spring @Configuration
类中相应方法的处理方式不同。不同之处在于,CGLIB 并未增强@Component
类来拦截方法和字段的调用。 CGLIB 代理是调用@Configuration
类中@Bean
方法中的方法或字段中的字段的方法,用于创建 Bean 元数据引用以协作对象。此类方法不是用普通的 Java 语义调用的,而是通过容器进行的,以提供通常的生命周期 Management 和 Spring bean 的代理,即使通过编程调用@Bean
方法引用其他 bean 时也是如此。相反,在普通@Component
类内的@Bean
方法中调用方法或字段具有标准 Java 语义,而无需特殊的 CGLIB 处理或其他约束。
==========================================================================================
Note
您可以将@Bean
方法声明为static
,从而允许在不将其包含的配置类创建为实例的情况下调用它们。在定义后处理器 Bean(例如,类型BeanFactoryPostProcessor
或BeanPostProcessor
)时,这特别有意义,因为此类 Bean 在容器生命周期的早期进行了初始化,并且应避免在那时触发配置的其他部分。
由于技术限制,对静态@Bean
方法的调用永远不会被容器拦截,即使在@Configuration
类中也是如此(如本节前面所述),由于技术限制:CGLIB 子类只能覆盖非静态方法。因此,直接调用另一个@Bean
方法具有标准的 Java 语义,从而导致直接从工厂方法本身直接返回一个独立的实例。
@Bean
方法的 Java 语言可见性不会对 Spring 容器中的最终 bean 定义产生直接影响。您可以随意声明自己的工厂方法,以适合非@Configuration
类,也可以随处声明静态方法。但是,@Configuration
类中的常规@Bean
方法必须是可重写的—即,不得将它们声明为private
或final
。
还可以在给定组件或配置类的 Base Class 上以及在由组件或配置类实现的接口中声明的 Java 8 默认方法上找到@Bean
方法。这为组合复杂的配置安排提供了很大的灵活性,从 Spring 4.2 开始,通过 Java 8 默认方法甚至可以实现多重继承。
最后,单个类可以为同一个 bean 保留多个@Bean
方法,这取决于在运行时可用的依赖关系,从而可以使用多个工厂方法。这与在其他配置方案中选择“最贪婪”的构造函数或工厂方法的算法相同:在构造时将选择具有最大可满足依赖关系数量的变量,类似于容器在多个@Autowired
构造函数之间进行选择的方式。
1.10.6. 自动检测到的组件的命名
在扫描过程中自动检测到某个组件时,其 bean 名称由该扫描器已知的BeanNameGenerator
策略生成。默认情况下,任何包含名称value
的 Spring 构造型 注解(@Component
,@Repository
,@Service
和@Controller
)都会将该名称提供给相应的 bean 定义。
如果这样的注解不包含名称value
或任何其他检测到的组件(例如,由自定义过滤器发现的组件),则缺省 bean 名称生成器将返回不使用大写字母的非限定类名称。例如,如果检测到以下组件类,则名称将为myMovieLister
和movieFinderImpl
:
@Service("myMovieLister") public class SimpleMovieLister { // ... } @Repository public class MovieFinderImpl implements MovieFinder { // ... }
==========================================================================================
Note
如果不想依赖默认的 Bean 命名策略,则可以提供自定义 Bean 命名策略。首先,实现BeanNameGenerator接口,并确保包括默认的无参构造函数。然后,在配置扫描器时提供完全限定的类名,如以下示例注解和 Bean 定义所示:
@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. 提供自动检测到的组件的范围
通常,与 SpringManagement 的组件一样,自动检测到的组件的默认且最常见的范围是singleton
。但是,有时您需要由@Scope
注解 指定的其他范围。您可以在注解中提供范围的名称,如以下示例所示:
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
Note
@Scope
注解 仅在具体的 bean 类(对于带注解的组件)或工厂方法(对于@Bean
方法)上进行内省。与 XML bean 定义相反,没有 bean 定义继承的概念,并且在类级别的继承层次结构与元数据目的无关。
有关特定于 Web 的作用域的详细信息,例如 Spring 上下文中的“请求”或“会话”,请参见请求,会话,应用程序和 WebSocket 范围。与这些范围的预构建注解一样,您也可以使用 Spring 的元注解方法来编写自己的作用域 注解:例如,使用@Scope("prototype")
进行元注解的自定义 注解,也可能会声明自定义范围代理模式。
==========================================================================================
Note
要提供用于范围解析的自定义策略,而不是依赖于基于注解的方法,您可以实现ScopeMetadataResolver接口。确保包括默认的无参数构造函数。然后,可以在配置扫描程序时提供完全限定的类名,如以下注解和 Bean 定义示例所示:
@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 作为依赖项中描述。为此,在 component-scan 元素上可以使用 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. 提供带注解的限定符元数据
使用限定符对基于注解的自动装配进行微调中讨论了@Qualifier
注解。该部分中的示例演示了@Qualifier
注解和自定义限定符注解的使用,以在解析自动装配候选时提供细粒度的控制。因为这些示例是基于 XML bean 定义的,所以通过使用 XML 中bean
元素的qualifier
或meta
子元素,在候选 bean 定义上提供了限定符元数据。当依靠 Classpath 扫描来自动检测组件时,可以在候选类上为限定符元数据提供类型级别的 注解。下面的三个示例演示了此技术:
@Component @Qualifier("Action") public class ActionMovieCatalog implements MovieCatalog { // ... } @Component @Genre("Action") public class ActionMovieCatalog implements MovieCatalog { // ... } @Component @Offline public class CachingMovieCatalog implements MovieCatalog { // ... }
Note
与大多数基于注解的替代方法一样,请记住,注解元数据绑定到类定义本身,而 XML 的使用允许相同类型的多个 bean 提供其限定符元数据的变体,因为该元数据是按-instance 而不是按类。
1.10.9. 生成候选组件的索引
尽管 Classpath 扫描非常快,但可以通过在编译时创建候选静态列表来提高大型应用程序的启动性能。在这种模式下,应用程序的所有模块都必须使用此机制,因为当ApplicationContext
检测到这样的索引时,它将自动使用它而不是扫描 Classpath。
要生成索引,请向每个包含组件的模块添加附加依赖关系,这些组件是组件扫描指令的目标。以下示例显示了如何使用 Maven 进行操作:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.1.3.RELEASE</version>
<optional>true</optional>
</dependency>
</dependencies>
以下示例显示了如何使用 Gradle 进行操作:
dependencies {
compileOnly("org.springframework:spring-context-indexer:5.1.3.RELEASE")
}
该过程将生成 jar 文件中包含的META-INF/spring.components
文件。
Note
在 IDE 中使用此模式时,必须将spring-context-indexer
注册为注解处理器,以确保在更新候选组件时索引是最新的。
Tip
当在 Classpath 上找到META-INF/spring.components
时,索引将自动启用。如果某些库(或用例)的索引部分可用,但无法为整个应用程序构建,则可以通过将spring.index.ignore
设置为true
来回退到常规的 Classpath 安排(好像根本没有索引)。系统属性或 Classpath 根目录下的spring.properties
文件中。
1.11. 使用 JSR 330 标准 注解
从 Spring 3.0 开始,Spring 提供对 JSR-330 标准 注解(依赖注入)的支持。这些注解的扫描方式与 Spring注解 的扫描方式相同。要使用它们,您需要在 Classpath 中有相关的 jar。
如果使用 Maven,则javax.inject
工件在标准 Maven 存储库(http://repo1.maven.org/maven2/javax/inject/javax.inject/1/)中可用。您可以将以下依赖项添加到文件 pom.xml 中:
<dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1</version> </dependency>
1.11.1. @Inject 和@Named 的依赖注入
可以使用@javax.inject.Inject
代替@Autowired
,如下所示:
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
没有required
属性。以下一对示例显示了如何使用@Inject
和@Nullable
:
public class SimpleMovieLister { @Inject public void setMovieFinder(Optional<MovieFinder> movieFinder) { ... } } public class SimpleMovieLister { @Inject public void setMovieFinder(@Nullable MovieFinder movieFinder) { ... } }
1.11.2. @Named 和@ManagedBean:@Component注解 的标准等效项
代替@Component
,可以使用@javax.inject.Named
或javax.annotation.ManagedBean
,如以下示例所示:
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 { ... }
Note
与@Component
相反,JSR-330 @Named
和 JSR-250 ManagedBean
注解 是不可组合的。您应该使用 Spring 的构造型模型来构建自定义组件 注解。
1.11.3. JSR-330 标准注解的局限性
表 6. Spring 组件模型元素与 JSR-330 变体
Spring | javax.inject.* | javax.inject 限制/注解 |
---|---|---|
@Autowired | @Inject | @Inject 没有“必需”属性。可以与 Java 8 的Optional 一起使用。 |
@Component | @Named/@ManagedBean | JSR-330 不提供可组合的模型,仅提供一种识别命名组件的方法。 |
@Scope("singleton") | @Singleton | JSR-330 的默认范围类似于 Spring 的prototype 。但是,为了使其与 Spring 的常规默认设置保持一致,默认情况下,在 Spring 容器中声明的 JSR-330 bean 为singleton 。为了使用singleton 以外的范围,您应该使用 Spring 的@Scope 注解。 javax.inject 还提供@Scope注解。但是,此仅用于创建自己的 注解。 |
@Qualifier | @ Qualifier/@ Named | javax.inject.Qualifier 只是用于构建自定义限定符的元 注解。可以通过javax.inject.Named 关联具体的String 限定词(如带有值的 Spring 的@Qualifier )。 |
@Value | - | no equivalent |
@Required | - | no equivalent |
@Lazy | - | no equivalent |
ObjectFactory | Provider | javax.inject.Provider 是 Spring 的ObjectFactory 的直接替代方法,只是使用较短的get() 方法名。它也可以与 Spring 的@Autowired 或未注解的构造函数和 setter 方法结合使用。 |
1.12. 基于 Java 的容器配置
1.12.1. 基本概念:@Bean 和@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>
完整的@Configuration 与“精简” @Bean 模式?
当在未使用@Configuration
注解 的类中声明@Bean
方法时,它们被称为以“精简”模式进行处理。在@Component
或什至在普通的旧类中声明的 Bean 方法被认为是“精简版”,其中包含类的主要目的不同,而@Bean
方法在那里具有某种优势。例如,服务组件可以通过每个适用的组件类上的附加@Bean
方法向容器公开管理视图。在这种情况下,@Bean
方法是一种通用的工厂方法机制。
与完整@Configuration
不同,lite @Bean
方法无法声明 Bean 之间的依赖关系。取而代之的是,它们在其包含组件的内部状态上运行,并且可以选择地在它们可以声明的参数上运行。因此,此类@Bean
方法不应调用其他@Bean
方法。实际上,每个此类方法仅是用于特定 bean 引用的工厂方法,而没有任何特殊的运行时语义。这里的积极副作用是,不必在运行时应用 CGLIB 子类,因此在类设计方面没有任何限制(即,包含类可能为final
等)。
在常见情况下,将在@Configuration
类中声明@Bean
方法,以确保始终使用“完全”模式,因此跨方法引用将重定向到容器的生命周管理。这样可以防止通过常规 Java 调用意外地调用同一@Bean
方法,从而有助于减少在“精简”模式下运行时难以追查的细微错误。
以下各节将对@Bean
和@Configuration
注解 进行深入讨论。但是,首先,我们介绍了通过基于 Java 的配置使用创建 spring 容器的各种方法。
1.12.2. 使用 AnnotationConfigApplicationContext 实例化 Spring 容器
以下各节记录了 Spring 3.0 中引入的 Spring 的AnnotationConfigApplicationContext
。这种通用的ApplicationContext
实现不仅可以接受@Configuration
类作为 Importing,而且还可以接受普通@Component
类和带有 JSR-330 元数据注解的类。
当提供@Configuration
类作为 Importing 时,@Configuration
类本身被注册为 bean 定义,并且该类中所有已声明的@Bean
方法也被注册为 bean 定义。
当提供@Component
和 JSR-330 类时,它们将注册为 bean 定义,并且假定在必要时在这些类中使用了诸如@Autowired
或@Inject
之类的 DI 元数据。
Simple Construction
与实例化ClassPathXmlApplicationContext
时将 Spring XML 文件用作 Importing 的方式几乎相同,实例化AnnotationConfigApplicationContext
时可以将@Configuration
类用作 Importing。如下面的示例所示,这允许完全不使用 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 带注解的类作为 Importing 提供给构造函数,如以下示例所示:
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。
使用register以编程方式构建容器(Class<?>…)
您可以使用无参构造函数实例化AnnotationConfigApplicationContext
,然后使用register()
方法对其进行配置。以编程方式构建AnnotationConfigApplicationContext
时,此方法特别有用。以下示例显示了如何执行此操作:
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();
}
使用 scan(String ...)启用组件扫描
要启用组件扫描,您可以如下注解您的@Configuration
类:
@Configuration
@ComponentScan(basePackages = "com.acme") (1)
public class AppConfig {
...
}
- (1) 此注解启用组件扫描。
Tip
有经验的 Spring 用户可能熟悉 Spring 的context:
名称空间中的等效 XML 声明,如以下示例所示:
<beans>
<context:component-scan base-package="com.acme"/>
</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
是meta-annotated,因此它们是组件扫描的候选对象。在前面的示例中,假定在com.acme
包(或下面的任何包)中声明了AppConfig
,则在对scan()
的调用期间将其拾取。在refresh()
上,将处理其所有@Bean
方法并将其注册为容器内的 bean 定义。
通过 AnnotationConfigWebApplicationContext 支持 Web 应用程序
AnnotationConfigWebApplicationContext
可提供AnnotationConfigApplicationContext
的WebApplicationContext
变体。在配置 Spring ContextLoaderListener
servlet 侦听器,Spring MVC DispatcherServlet
等时,可以使用此实现。以下web.xml
片段配置了典型的 Spring MVC Web 应用程序(请注意contextClass
context-param 和 init-param 的使用):
<web-app> <!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext instead of the default XmlWebApplicationContext --> <context-param> <param-name>contextClass</param-name> <param-value> org.springframework.web.context.support.AnnotationConfigWebApplicationContext </param-value> </context-param> <!-- Configuration locations must consist of one or more comma- or space-delimited fully-qualified @Configuration classes. Fully-qualified packages may also be specified for component-scanning --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>com.acme.AppConfig</param-value> </context-param> <!-- Bootstrap the root application context as usual using ContextLoaderListener --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- Declare a Spring MVC DispatcherServlet as usual --> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext instead of the default XmlWebApplicationContext --> <init-param> <param-name>contextClass</param-name> <param-value> org.springframework.web.context.support.AnnotationConfigWebApplicationContext </param-value> </init-param> <!-- Again, config locations must consist of one or more comma- or space-delimited and fully-qualified @Configuration classes --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>com.acme.web.MvcConfig</param-value> </init-param> </servlet> <!-- map all requests for /app/* to the dispatcher servlet --> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/app/*</url-pattern> </servlet-mapping> </web-app>
1.12.3. 使用@Bean注解
@Bean
是方法级 注解,是 XML <bean/>
元素的直接类似物。注解 支持<bean/>
提供的某些属性,例如:* init-method * destroy-method * autowiring * name
。
您可以在@Configuration
注解 的类或@Component
注解 的类中使用@Bean
注解。
声明一个 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>
这两个声明使一个名为transferService
的 bean 在ApplicationContext
中可用,并绑定到TransferServiceImpl
类型的对象实例,如以下文本图像所示:
transferService -> com.acme.TransferServiceImpl
====================================================
您还可以使用接口(或 Base Class)返回类型声明@Bean
方法,如以下示例所示:
@Configuration public class AppConfig { @Bean public TransferService transferService() { return new TransferServiceImpl(); } }
但是,这将提前类型预测的可见性限制为指定的接口类型(TransferService
)。然后,使用只对容器知道一次的完整类型(TransferServiceImpl
),实例化受影响的单例 bean。非惰性单例 bean 根据其声明 Sequences 实例化,因此您可能会看到不同的类型匹配结果,具体取决于另一个组件何时尝试通过未声明的类型进行匹配(例如@Autowired TransferServiceImpl
,该实例仅在实例化transferService
bean 时才解析.)。
Bean Dependencies
带有@Bean
注解 的方法可以具有任意数量的参数,这些参数描述构建该 bean 所需的依赖关系。例如,如果我们的TransferService
要求AccountRepository
,则可以使用方法参数实现该依赖关系,如以下示例所示:
@Configuration public class AppConfig { @Bean public TransferService transferService(AccountRepository accountRepository) { return new TransferServiceImpl(accountRepository); } }
解析机制与基于构造函数的依赖注入几乎相同。
接收生命周期回调
任何使用@Bean
注解 定义的类都支持常规的生命周期回调,并且可以使用 JSR-250 中的@PostConstruct
和@PreDestroy
注解。有关更多详细信息,请参见JSR-250 annotations。
还完全支持常规的 Spring lifecycle回调。如果 bean 实现InitializingBean
,DisposableBean
或Lifecycle
,则容器将调用它们各自的方法。
也完全支持*Aware
接口的标准集合(例如BeanFactoryAware,BeanNameAware,MessageSourceAware,ApplicationContextAware等)。
@Bean
注解 支持指定任意的初始化和销毁回调方法,就像bean
元素上的 Spring XML 的init-method
和destroy-method
属性一样,如以下示例所示:
public class BeanOne { public void init() { // initialization logic } } public class BeanTwo { public void cleanup() { // destruction logic } } @Configuration public class AppConfig { @Bean(initMethod = "init") public BeanOne beanOne() { return new BeanOne(); } @Bean(destroyMethod = "cleanup") public BeanTwo beanTwo() { return new BeanTwo(); } }
Note
默认情况下,使用 Java 配置定义的具有公共close
或shutdown
方法的 bean 会自动通过销毁回调注册。如果您有一个公共的close
或shutdown
方法,并且您不希望在容器关闭时调用它,则可以将@Bean(destroyMethod="")
添加到 bean 定义中以禁用默认的(inferred)
模式。
默认情况下,您可能要对通过 JNDI 获取的资源执行此操作,因为其生命周期是在应用程序外部进行 Management 的。特别是,请确保始终对DataSource
进行操作,因为这在 Java EE 应用程序服务器上是有问题的。
以下示例显示了如何防止DataSource
的自动销毁回调:
@Bean(destroyMethod="") public DataSource dataSource() throws NamingException { return (DataSource) jndiTemplate.lookup("MyDS"); }
另外,对于@Bean
方法,通常使用程序化 JNDI 查找,方法是使用 Spring 的JndiTemplate
或JndiLocatorDelegate
帮助器,或者直接使用 JNDI InitialContext
用法,而不使用JndiObjectFactoryBean
变体(这将迫使您将返回类型声明为FactoryBean
类型,而不是实际的目标。类型,使其更难以在打算引用此处提供的资源的其他@Bean
方法中用于交叉引用调用。
对于前面注解中的示例中的BeanOne
,在构造期间直接调用init()
方法同样有效,如以下示例所示:
@Configuration public class AppConfig { @Bean public BeanOne beanOne() { BeanOne beanOne = new BeanOne(); beanOne.init(); return beanOne; } // ... }
Tip
当您直接使用 Java 工作时,您可以对对象执行任何操作,而不必总是依赖于容器生命周期。
指定 Bean 范围
Spring 包含@Scope
注解,以便您可以指定 bean 的范围。
使用@Scope 注解
您可以指定使用@Bean
注解 定义的 bean 应该具有特定范围。您可以使用Bean Scopes部分中指定的任何标准范围。
默认范围是singleton
,但是您可以使用@Scope
注解覆盖它,如以下示例所示:
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
@Scope 和 scoped-proxy
Spring 提供了一种通过scoped proxies处理范围内的依赖项的便捷方法。使用 XML 配置时创建此类代理的最简单方法是<aop:scoped-proxy/>
元素。使用@Scope
注解 在 Java 中配置 bean 可以提供与proxyMode
属性等效的支持。缺省值为无代理(ScopedProxyMode.NO
),但是您可以指定ScopedProxyMode.TARGET_CLASS
或ScopedProxyMode.INTERFACES
。
@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;
}
自定义 Bean 命名
默认情况下,配置类使用@Bean
方法的名称作为结果 bean 的名称。但是,可以使用name
属性覆盖此功能,如以下示例所示:
@Configuration
public class AppConfig {
@Bean(name = "myThing")
public Thing thing() {
return new Thing();
}
}
Bean 别名
如Naming Beans中所述,有时希望为单个 bean 提供多个名称,称为 bean 别名。 @Bean
注解的name
属性为此目的接受一个 String 数组。以下示例说明如何为 bean 设置多个别名:
@Configuration
public class AppConfig {
@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
Bean 描述
有时,提供有关 bean 的更详细的文本描述会很有帮助。当出于监视目的而暴露(可能通过 JMX)bean 时,这尤其有用。
要将说明添加到@Bean
,可以使用@Description注解,如以下示例所示:
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Thing thing() {
return new Thing();
}
}
1.12.4. 使用@Configuration 注解
@Configuration
是类级别的 注解,指示对象是 Bean 定义的源。 @Configuration
类通过@Bean
注解公共方法来声明 Bean。对@Configuration
类的@Bean
方法的调用也可以用于定义 Bean 间的依赖关系。
注入 Bean 间的依赖关系
当 bean 相互依赖时,表示这种依赖关系就像让一个 bean 方法调用另一个一样简单,如以下示例所示:
@Configuration public class AppConfig { @Bean public BeanOne beanOne() { return new BeanOne(beanTwo()); } @Bean public BeanTwo beanTwo() { return new BeanTwo(); } }
在前面的示例中,beanOne
通过构造函数注入接收对beanTwo
的引用。
Note
仅当在@Configuration
类中声明@Bean
方法时,此声明 bean 间依赖性的方法才有效。您不能通过使用普通@Component
类来声明 Bean 间的依赖关系。
查找方法注入
如前所述,查找方法注入是您不应该使用的高级功能。在单例作用域的 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(); // inject dependencies here as required return command; } @Bean public CommandManager commandManager() { // return new anonymous implementation of CommandManager with command() overridden // to return a new prototype Command object return new CommandManager() { protected Command createCommand() { return asyncCommand(); } } }
有关基于 Java 的配置在内部如何工作的更多信息
考虑下面的示例,该示例显示了一个被两次调用的@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
的新实例并返回它,因此通常希望有两个实例(每个服务一个)。那绝对是有问题的:在 Spring 中,实例化的 bean 默认具有singleton
范围。这就是神奇的地方:所有@Configuration
类在启动时都使用CGLIB
子类化。在子类中,子方法在调用父方法并创建新实例之前,首先检查容器中是否有任何缓存(作用域)的 bean。
Note
根据 bean 的范围,行为可能有所不同。我们在这里谈论单例。
Note
从 Spring 3.2 开始,不再需要将 CGLIB 添加到您的 Classpath 中,因为 CGLIB 类已经在org.springframework.cglib
下重新打包并直接包含在 spring-core JAR 中。
Tip
由于 CGLIB 在启动时会动态添加功能,因此存在一些限制。特别是,配置类不能是最终的。但是,从 4.3 版本开始,配置类中允许使用任何构造函数,包括对默认注入使用@Autowired
或单个非默认构造函数声明。
如果您希望避免任何 CGLIB 施加的限制,请考虑在非@Configuration
类(例如,在普通@Component
类上)声明@Bean
方法。然后,不会拦截@Bean
方法之间的跨方法调用,因此您必须专门依赖那里的构造函数或方法级别的依赖项注入。
1.12.5. 组成基于 Java 的配置
Spring 的基于 Java 的配置功能使您可以编写注解,这可以降低配置的复杂性。
使用@import 注解
就像 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
类。
Tip
从 Spring Framework 4.2 开始,@Import
还支持引用常规组件类,类似于AnnotationConfigApplicationContext.register
方法。如果要通过使用一些配置类作为入口点来显式定义所有组件,从而避免组件扫描,则此功能特别有用。
注入对导入的@Bean 定义的依赖关系
前面的示例有效,但过于简单。在大多数实际情况下,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:这意味着它们可以利用@Autowired
和@Value
注入以及与任何其他 bean 相同的其他功能。
Warning
确保以这种方式注入的依赖项只是最简单的一种。 @Configuration
类是在上下文初始化期间非常早地处理的,并且强制以这种方式注入依赖项可能导致意外的早期初始化。如上例所示,请尽可能使用基于参数的注入。
另外,要特别注意BeanPostProcessor
和BeanFactoryPostProcessor
到@Bean
的定义。通常应将它们声明为static @Bean
方法,而不触发其包含的配置类的实例化。否则,@Autowired
和@Value
不适用于配置类本身,因为它太早被创建为 Bean 实例。
以下示例说明如何将一个 bean 自动连接到另一个 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"); }
从 Spring Framework 4.3 开始,仅支持@Configuration
类中的构造方法注入。还要注意,如果目标 bean 仅定义一个构造函数,则无需指定@Autowired
。在前面的示例中,RepositoryConfig
构造函数上不需要@Autowired
。
===========================================================
完全合格的 importbean,便于导航
在前面的场景中,使用@Autowired
可以很好地工作并提供所需的模块化,但是确切地确定自动装配的 Bean 定义在何处声明仍然有些模棱两可。例如,当开发人员查看ServiceConfig
时,您如何确切知道@Autowired AccountRepository
bean 的声明位置?它在代码中不是明确的,这可能很好。请记住,Spring 工具套件提供的工具可以呈现图形,显示所有连线的方式,这可能就是您所需要的。另外,您的 Java IDE 可以轻松找到AccountRepository
类型的所有声明和使用,并快速向您显示返回该类型的@Bean
方法的位置。
如果这种歧义是不可接受的,并且您希望从 IDE 内直接从一个@Configuration
类导航到另一个@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
类及其依赖项进行导航变得与导航基于接口的代码的通常过程没有什么不同。
Tip
如果要影响某些 Bean 的启动创建 Sequences,请考虑将其中一些声明为@Lazy
(用于首次访问而不是在启动时创建)或@DependsOn
声明其他某些 Bean(确保在当前 Bean 之前创建了特定的其他 Bean) ,而不是后者的直接依赖项所暗示的含义)。
===========================================================
有条件地包含@Configuration 类或@Bean 方法
基于某些任意系统状态,有条件地启用或禁用完整的@Configuration
类甚至单个@Bean
方法通常很有用。一个常见的示例是仅在 Spring Environment
中启用了特定概要文件时才使用@Profile
注解 来激活 bean
@Profile
注解 实际上是通过使用更灵活的称为@Conditional的注解来实现的。 @Conditional
注解指示在注册@Bean
之前应参考的org.springframework.context.annotation.Condition
特定实现。
Condition
接口的实现提供了一个matches(…)
方法,该方法返回true
或false
。例如,以下清单显示了用于@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; }
======================================================
结合 Java 和 XML 配置
Spring 的@Configuration
类支持并非旨在 100%完全替代 Spring XML。某些工具(例如 Spring XML 名称空间)仍然是配置容器的理想方法。在使用 XML 方便或有必要的情况下,您可以选择:通过使用ClassPathXmlApplicationContext
以“以 XML 为中心”的方式实例化容器,或通过使用AnnotationConfigApplicationContext
和以“以 Java 为中心”的方式实例化容器。 @ImportResource
注解 以根据需要导入 XML。
以 XML 为中心的@Configuration 类的使用
最好从 XML 引导 Spring 容器,并以即席方式包含@Configuration
类。例如,在使用 Spring XML 的大型现有代码库中,根据需要创建@Configuration
类并从现有 XML 文件中包含它们很容易。在本节的后面,我们将介绍在这种“以 XML 为中心”的情况下使用@Configuration
类的选项。
将@Configuration
类声明为纯 Spring <bean/>
元素
请记住,@Configuration
类最终是容器中的 bean 定义。在本系列示例中,我们创建一个名为AppConfig
的@Configuration
类,并将其作为<bean/>
定义包含在system-test-config.xml
中。 由于<context:annotation-config/>
已打开,因此容器会识别@Configuration
注解并正确处理AppConfig
中声明的@Bean
方法。
以下示例显示了 Java 中的普通配置类:
@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); // ... }
Note
在system-test-config.xml
文件中,AppConfig
<bean/>
没有声明id
元素。尽管这样做是可以接受的,但是由于没有其他 bean 曾经引用过它,因此这是不必要的,并且不太可能通过名称从容器中显式获取。类似地,DataSource
bean 只能按类型自动装配,因此并不需要严格要求显式 bean id
。
因为@Configuration
使用@Component
进行元 注解,所以@Configuration
注解 的类自动成为组件扫描的候选对象。使用与上一个示例中描述的场景相同的场景,我们可以重新定义system-test-config.xml
以利用组件扫描的优势。请注意,在这种情况下,我们无需显式声明<context:annotation-config/>
,因为<context:component-scan/>
启用相同的功能。
<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 以类为中心的 XML 与@ImportResource 的使用
在@Configuration
类是配置容器的主要机制的应用程序中,仍然可能有必要至少使用一些 XML。在这些情况下,您可以使用@ImportResource
并仅定义所需的 XML。这样做实现了“以 Java 为中心”的方法来配置容器,并将 XML 保持在最低限度。以下示例(包括配置类,定义 Bean 的 XML 文件,属性文件和main
类)显示了如何使用@ImportResource
注解 来实现按需使用 XML 的“以 Java 为中心”的配置:
@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抽象
Environment接口是集成在容器中的抽象接口,用于对应用程序环境的两个关键方面进行建模:profiles和properties。
profile是仅在给定profile处于活动状态时才向容器注册的 Bean 定义的命名逻辑组。可以将 Bean 分配给profile,无论是以 XML 定义还是带有注解。 Environment
对象与配置文件相关的作用是确定当前哪些配置文件(如果有)处于活动状态,以及默认情况下哪些配置文件(如果有)应处于活动状态。
Properties 在几乎所有应用程序中都起着重要作用,并且可能源自多种来源:属性文件,JVM 系统属性,系统环境变量,JNDI,Servlet 上下文参数,即席Properties
对象,Map
对象等等。 Environment
对象相对于属性的作用是为用户提供方便的服务界面,用于配置属性源并从中解析属性。
1.13.1. Bean 定义配置文件
Bean 定义配置文件在核心容器中提供了一种机制,该机制允许在不同环境中注册不同的 Bean。 “环境”一词对不同的用户可能具有不同的含义,并且此功能可以帮助解决许多用例,包括:
-
在开发中针对内存中的数据源进行工作,而不是在进行 QA 或生产时从 JNDI 查找相同的数据源。
-
仅在将应用程序部署到性能环境中时注册监视基础结构。
-
为 ClientA 和 ClientB 部署注册 bean 的自定义实现。
考虑实际应用中需要DataSource
的第一个用例。在测试环境中,配置可能类似于以下内容:
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build();
}
现在,假设该应用程序的数据源已在生产应用程序服务器的 JNDI 目录中注册,请考虑如何将该应用程序部署到 QA 或生产环境中。现在,我们的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"); } }
Note
如前所述,对于@Bean
方法,通常选择使用程序化 JNDI 查找,方法是使用 Spring 的JndiTemplate
/JndiLocatorDelegate
帮助器或前面显示的直接 JNDI InitialContext
用法,而不使用JndiObjectFactoryBean
变体,这将迫使您将返回类型声明为FactoryBean
类型。
配置文件字符串可以包含简单的配置文件名称(例如production
)或配置文件表达式。配置文件表达式允许表达更复杂的配置文件逻辑(例如production & us-east
)。概要文件表达式中支持以下运算符:
-
!
:配置文件的逻辑“非” -
&
:配置文件的逻辑“与” -
|
:配置文件的逻辑“或”
Note
不使用括号不能混合使用&
和|
运算符。例如,production & us-east | eu-central
不是有效的表达式。它必须表示为production & (us-east | eu-central)
。
您可以将@Profile
用作meta-annotation,以创建自定义的合成 注解。以下示例定义了一个自定义@Production
注解,您可以将其用作@Profile("production")
的替代品:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Profile("production") public @interface Production { }
Tip
如果@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") (1) 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") (2) public DataSource jndiDataSource() throws Exception { Context ctx = new InitialContext(); return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); } }
- (1)
standaloneDataSource
方法仅在development
Profile 中可用。 - (2)
jndiDataSource
方法仅在production
Profile 中可用。
Note
对于@Bean
方法上的@Profile
,可能适用特殊情况:对于具有相同 Java 方法名称的@Bean
方法重载(类似于构造函数重载),必须在所有重载方法上一致声明@Profile
条件。如果条件不一致,则仅重载方法中第一个声明的条件很重要。因此,@Profile
不能用于选择具有特定自变量签名的重载方法。在创建时,相同 bean 的所有工厂方法之间的解析都遵循 Spring 的构造函数解析算法。
如果要定义具有不同概要文件条件的备用 Bean,请使用@Bean
name 属性使用不同的 Java 方法名称来指向相同的 Bean 名称,如前面的示例所示。如果参数签名都相同(例如,所有变体都具有 no-arg 工厂方法),则这是首先在有效 Java 类中表示这种排列的唯一方法(因为只能有一个特定名称和参数签名的方法)。
XML Bean 定义配置文件
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/2001/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
已被限制为仅允许这些元素作为文件中的最后一个元素。这应该有助于提供灵 Active,而不会引起 XML 文件混乱。
Note
XML 对应项不支持前面描述的配置文件表达式。但是,可以使用!
运算符取消配置文件。也可以通过嵌套配置文件来应用逻辑“和”,如以下示例所示:
<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="production"> <beans profile="us-east"> <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/> </beans> </beans> </beans>
在前面的示例中,如果production
和us-east
配置文件都处于活动状态,则dataSource
bean 被公开。
激活 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 中的条目来指定。
请注意,配置文件不是“非此即彼”的命题。您可以一次激活多个配置文件。您可以通过编程方式为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();
}
}
如果没有任何配置文件处于活动状态,则创建dataSource
。您可以看到这是为一个或多个 bean 提供默认定义的一种方法。如果启用了任何配置文件,则默认配置文件将不适用。
您可以通过在Environment
上使用setDefaultProfiles()
或pass 语句spring.profiles.default
属性来更改默认配置文件的名称。
1.13.2. PropertySource 抽象
Spring 的Environment
抽象提供了对PropertySource 的可配置层次结构的搜索操作。考虑以下清单:
ApplicationContext ctx = new GenericApplicationContext(); Environment env = ctx.getEnvironment(); boolean containsMyProperty = env.containsProperty("my-property"); System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);
在前面的代码片段中,我们看到了一种询问 Spring 是否为当前环境定义my-property
属性的高级方法。为了回答这个问题,Environment
对象对一组PropertySource对象执行搜索。
PropertySource
是对任何键-值对源的简单抽象,而 Spring 的StandardEnvironment配置了两个 PropertySource 对象-一个代表 JVM 系统属性集(System.getProperties()
)和一个代表系统环境变量集(System.getenv()
)。
Note
这些默认属性源针对StandardEnvironment
存在,供独立应用程序使用。 StandardServletEnvironment填充了其他默认属性源,包括 servlet 配置和 servlet 上下文参数。它可以选择启用JndiPropertySource。
具体来说,当您使用StandardEnvironment
时,如果在运行时存在my-property
系统属性或my-propertyi
环境变量,则对env.containsProperty("my-property")
的调用将返回 true。
Tip
执行的搜索是分层的。默认情况下,系统属性优先于环境变量。因此,如果在调用env.getProperty("my-property")
的过程中在两个地方都同时设置了my-property
属性,则系统属性值“ wins”并返回。请注意,属性值不会合并,而是会被前面的条目完全覆盖。
对于常见的StandardServletEnvironment
,完整层次结构如下,最高优先级条目位于顶部:
-
ServletConfig 参数(如果适用,例如,在
DispatcherServlet
上下文的情况下) -
ServletContext 参数(web.xml 上下文参数条目)
-
JNDI 环境变量(
java:comp/env/
个条目) -
JVM 系统属性(
-D
个命令行参数) -
JVM 系统环境(os 环境变量)
最重要的是,整个机制是可配置的。也许您具有要集成到此搜索中的自定义属性源。为此,请实现并实例化自己的PropertySource
并将其添加到当前Environment
的PropertySources
集合中。以下示例显示了如何执行此操作:
ConfigurableApplicationContext ctx = new GenericApplicationContext(); MutablePropertySources sources = ctx.getEnvironment().getPropertySources(); sources.addFirst(new MyPropertySource());
在前面的代码中,在搜索中添加了具有最高优先级的MyPropertySource
。如果它包含my-property
属性,则会检测到并返回该属性,而支持其他PropertySource
中的任何my-property
属性。
1.13.3. 使用@PropertySource
@PropertySource注解 为将PropertySource
添加到 Spring 的Environment
提供了一种方便的声明性机制。
给定名为app.properties
的文件,其中包含键值对testbean.name=myTestBean
,以下@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
存在于已注册的属性源之一(例如,系统属性或环境变量)中,则占位符将解析为相应的值。如果不是,则使用default/path
作为默认值。如果未指定默认值并且无法解析属性,则会引发IllegalArgumentException
。
Note
根据 Java 8 约定,@PropertySource
注解 是可重复的。但是,所有此类@PropertySource
注解都需要在同一级别上声明,可以直接在配置类上声明,也可以在同一自定义注解中声明为元注解。不建议将直接注解和元注解混合使用,因为直接注解会有效地覆盖元 注解。
1.13.4. 声明中的占位符解析
从历史上看,元素中占位符的值只能根据 JVM 系统属性或环境变量来解析。这已不再是这种情况。由于Environment
抽象集成在整个容器中,因此很容易通过它路由占位符的解析。这意味着您可以按照自己喜欢的任何方式配置解析过程。您可以更改搜索系统属性和环境变量的优先级,也可以完全删除它们。您还可以根据需要将自己的属性源添加到组合中。
具体来说,只要在Environment
中可用,以下语句无论在customer
属性的定义位置如何都有效:
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
</beans>
1.14. 注册一个 LoadTimeWeaver
LoadTimeWeaver
被 Spring 使用,以在将类加载到 Java 虚拟机(JVM)中时对其进行动态转换。
要启用加载时编织,可以将@EnableLoadTimeWeaving
添加到@Configuration
类之一,如以下示例所示:
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
另外,对于 XML 配置,可以使用context:load-time-weaver
元素:
<beans>
<context:load-time-weaver/>
</beans>
一旦为ApplicationContext
配置,该ApplicationContext
内的任何 bean 都可以实现LoadTimeWeaverAware
,从而接收到对加载时编织器实例的引用。这与Spring 的 JPA 支持结合使用特别有用,在这种情况下,JPA 类转换可能需要进行加载时编织。
1.15. ApplicationContext 的其他功能
org.springframework.beans.factory
包提供了用于 Management 和操作 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. 使用 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>
该示例假定您在 Classpath 中定义了三个名为format
,exceptions
和windows
的资源包。任何解析消息的请求都通过 JDK 标准的ResourceBundle
对象解析消息来处理。就本示例而言,假定上述两个资源束文件的内容如下:
# 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
的文件中定义的,该文件位于 Classpath 的根目录下。 messageSource
bean 定义通过其basenames
属性引用了许多资源包。列表中传递给basenames
属性的三个文件以 Classpath 的根文件形式存在,分别称为format.properties
,exceptions.properties
和windows.properties
。
下一个示例显示了传递给消息查找的参数。这些参数将转换为String
对象,并插入到查找消息中的占位符中。
<beans> <!-- this MessageSource is being used in a web application --> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="exceptions"/> </bean> <!-- lets inject the above MessageSource into this POJO --> <bean id="example" class="com.something.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
。
Note
作为ResourceBundleMessageSource
的替代方法,Spring 提供了ReloadableResourceBundleMessageSource
类。此变体支持相同的 Binding 文件格式,但比基于标准 JDK 的ResourceBundleMessageSource
实现更灵活。特别是,它允许从任何 Spring 资源位置(不仅从 Classpath)读取文件,并支持热重载 Binding 属性文件(同时在它们之间进行有效缓存)。
1.15.2. 标准和自定义事件
ApplicationContext
中的事件处理是通过ApplicationEvent
类和ApplicationListener
接口提供的。如果将实现ApplicationListener
接口的 bean 部署到上下文中,则每次将ApplicationEvent
发布到ApplicationContext
时,都会通知该 bean。本质上,这是标准的 观察者设计模式。
表 7.内置事件
Event | Explanation |
---|---|
ContextRefreshedEvent |
在初始化或刷新ApplicationContext 时发布(例如,通过使用ConfigurableApplicationContext 接口上的refresh() 方法)。在这里,“已初始化”是指所有 Bean 都已加载,检测到并激活了后处理器 Bean,已预先实例化单例,并且已准备好使用ApplicationContext 对象。只要尚未关闭上下文,只要选定的ApplicationContext 实际上支持这种“热”刷新,就可以多次触发刷新。例如,XmlWebApplicationContext 支持热刷新,但GenericApplicationContext 不支持。 |
ContextStartedEvent |
在ConfigurableApplicationContext 界面上使用start() 方法启动ApplicationContext 时发布。在这里,“启动”是指所有Lifecycle bean 都收到一个明确的启动 signal。通常,此 signal 用于在显式停止后重新启动 Bean,但也可以用于启动尚未配置为自动启动的组件(例如,尚未在初始化时启动的组件)。 |
ContextStoppedEvent |
在ConfigurableApplicationContext 接口上使用stop() 方法停止ApplicationContext 时发布。此处,“已停止”表示所有Lifecycle bean 都收到一个明确的停止 signal。停止的上下文可以通过start() 调用重新启动。 |
ContextClosedEvent |
在ConfigurableApplicationContext 接口上使用close() 方法关闭ApplicationContext 时发布。此处,“封闭”表示所有单例 bean 都被破坏。封闭的情境到了生命的尽头。无法刷新或重新启动。 |
RequestHandledEvent |
一个特定于 Web 的事件,告诉所有 Bean HTTP 请求已得到服务。请求完成后,将发布此事件。此事件仅适用于使用 Spring 的DispatcherServlet 的 Web 应用程序。 |
您还可以创建和发布自己的自定义事件。以下示例显示了一个简单的类,该类扩展了 Spring 的ApplicationEvent
Base Class:
public class BlackListEvent extends ApplicationEvent { private final String address; private final String content; public BlackListEvent(Object source, String address, String content) { super(source); this.address = address; this.content = content; } // 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 content) { if (blackList.contains(address)) { publisher.publishEvent(new BlackListEvent(this, address, content)); 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()
方法将阻塞,直到所有侦听器都已完成对事件的处理为止。这种同步和单线程方法的一个优点是,当侦听器收到事件时,如果有可用的事务上下文,它将在发布者的事务上下文内部进行操作。
以下示例显示了用于注册和配置上述每个类的 Bean 定义:
<bean id="emailService" class="example.EmailService"> <property name="blackList"> <list> <value>[emailprotected]</value> <value>[emailprotected]</value> <value>[emailprotected]</value> </list> </property> </bean> <bean id="blackListNotifier" class="example.BlackListNotifier"> <property name="notificationAddress" value="[emailprotected]"/> </bean>
将所有内容放在一起,当调用emailService
bean 的sendEmail()
方法时,如果有任何电子邮件消息应列入黑名单,则会发布BlackListEvent
类型的自定义事件。 blackListNotifier
bean 注册为ApplicationListener
并接收BlackListEvent
,此时它可以通知适当的参与者。
基于注解的事件侦听器
从 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() {
...
}
也可以通过使用定义SpEL expression的注解的condition
属性来添加其他运行时过滤,该属性应匹配以针对特定事件实际调用该方法。
以下示例显示了仅当事件的content
属性等于my-event
时,才可以重写我们的通知程序以进行调用:
@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlackListEvent(BlackListEvent blEvent) {
// notify appropriate parties via notificationAddress...
}
表 8. Event SpEL 可用的元数据
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 或#a0 (您也可以使用#p0 或#p<#arg> 表示法作为别名) |
如果由于处理另一个事件而需要发布一个事件,则可以更改方法签名以返回应发布的事件,如以下示例所示:
@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
Note
asynchronous listeners不支持此功能。
此新方法为上述方法处理的每个BlackListEvent
发布一个新的ListUpdateEvent
。如果您需要发布多个事件,则可以返回Collection
事件。
Asynchronous Listeners
如果希望特定的侦听器异步处理事件,则可以重用常规@Async 支持。以下示例显示了如何执行此操作:
@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
// BlackListEvent is processed in a separate thread
}
使用异步事件时,请注意以下限制:
-
如果事件监听器抛出
Exception
,则它不会传播到调用者。有关更多详细信息,请参见AsyncUncaughtExceptionHandler
。 -
此类事件侦听器无法发送答复。如果您需要发送另一个事件作为处理结果,请注入ApplicationEventPublisher以手动发送事件。
Ordering Listeners
如果需要先调用一个侦听器,则可以将@Order
注解添加到方法声明中,如以下示例所示:
@EventListener @Order(42) public void processBlackListEvent(BlackListEvent event) { // notify appropriate parties via notificationAddress... }
Generic Events
您还可以使用泛型来进一步定义事件的结构。考虑使用EntityCreatedEvent<T>
,其中T
是已创建的实际实体的类型。例如,您可以创建以下侦听器定义以仅接收_4 的EntityCreatedEvent
:
@EventListener public void onPersonCreated(EntityCreatedEvent<Person> event) { ... }
由于类型擦除,只有在触发的事件解析了事件侦听器所依据的通用参数(即诸如class PersonCreatedEvent extends EntityCreatedEvent<Person> { … }
)时,此方法才起作用。
在某些情况下,如果所有事件都遵循相同的结构,这可能会变得很乏味(就像前面示例中的事件一样)。在这种情况下,您可以实现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())); } }
1.15.3. 方便地访问低级资源
为了最佳使用和理解应用程序上下文,您应该熟悉 Spring 的Resource
抽象,如Resources中所述。
应用程序上下文是ResourceLoader
,可用于加载Resource
对象。 Resource
本质上是 JDK java.net.URL
类的功能更丰富的版本。实际上,Resource
的实现在适当的地方包装了java.net.URL
的实例。 Resource
可以从几乎任何位置以透明方式获取低级资源,包括从 Classpath,文件系统位置,可使用标准 URL 描述的任何位置以及其他一些变体。如果资源位置字符串是没有任何特殊前缀的简单路径,则这些资源的来源是特定的,并且适合于实际的应用程序上下文类型。
您可以配置部署到应用程序上下文中的 Bean,以实现特殊的回调接口ResourceLoaderAware
,以便在初始化时使用应用程序上下文本身作为ResourceLoader
进行自动回调。您还可以公开Resource
类型的属性,以用于访问静态资源。它们像其他任何属性一样注入其中。您可以将这些Resource
属性指定为简单的String
路径,并在部署 bean 时依靠特殊的 JavaBean PropertyEditor
(由上下文自动注册)将这些文本字符串转换为实际的Resource
对象。
提供给ApplicationContext
构造函数的一个或多个位置路径实际上是资源字符串,并且根据特定的上下文实现以简单的形式对其进行了适当处理。例如ClassPathXmlApplicationContext
将简单的位置路径视为 Classpath 位置。您也可以使用带有特殊前缀的位置路径(资源字符串)来强制从 Classpath 或 URL 中加载定义,而不管实际的上下文类型如何。
1.15.4. Web 应用程序的便捷 ApplicationContext 实例化
您可以使用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.xml
作为默认值。当参数确实存在时,侦听器将使用 sched 义的定界符(逗号,分号和空格)来分隔String
,并将这些值用作搜索应用程序上下文的位置。还支持 Ant 风格的路径模式。示例为/WEB-INF/*Context.xml
(对于名称以Context.xml
结尾并且位于WEB-INF
目录中的所有文件)和/WEB-INF/**/*Context.xml
(对于WEB-INF
的任何子目录中的所有此类文件)。
1.15.5. 将 Spring ApplicationContext 部署为 Java EE RAR 文件
可以将 Spring ApplicationContext
部署为 RAR 文件,并将上下文及其所有必需的 Bean 类和库 JAR 封装在 Java EE RAR 部署单元中。这等效于引导独立的ApplicationContext
(仅托管在 Java EE 环境中)能够访问 Java EE 服务器功能。对于部署无头 WAR 文件的情况,RAR 部署是一种更自然的选择-实际上,这种 WAR 文件没有任何 HTTP 入口点,仅用于在 Java EE 环境中引导 Spring ApplicationContext
。
对于不需要 HTTP 入口点而仅由消息端点和计划的作业组成的应用程序上下文,RAR 部署是理想的选择。在这样的上下文中,Bean 可以使用应用程序服务器资源,例如 JTA 事务 Management 器,与 JNDI 绑定的 JDBC DataSource
实例和 JMS ConnectionFactory
实例,还可以在平台的 JMX 服务器上注册-整个过程都通过 Spring 的标准事务 Management 以及 JNDI 和 JMX 支持工具进行。应用程序组件还可以通过 Spring 的TaskExecutor
抽象与应用程序服务器的 JCA WorkManager
进行交互。
1.16. bean 工厂
BeanFactory
API 为 Spring 的 IoC 功能提供了基础。它的特定 Contract 主要用于与 Spring 的其他部分以及相关的第三方框架集成,并且其DefaultListableBeanFactory
实现是更高级别GenericApplicationContext
容器中的关键委托。
BeanFactory
和相关接口(例如BeanFactoryAware
,InitializingBean
,DisposableBean
)是其他框架组件的重要集成点。通过不需要任何注解甚至反射,它们可以在容器及其组件之间进行非常有效的交互。应用程序级 Bean 可以使用相同的回调接口,但通常更喜欢通过注解或通过程序配置进行声明式依赖注入。
请注意,核心BeanFactory
API 级别及其DefaultListableBeanFactory
实现不对配置格式或要使用的任何组件注解进行假设。所有这些风味都是通过拓展(例如XmlBeanDefinitionReader
和AutowiredAnnotationBeanPostProcessor
)引入的,并以共享的BeanDefinition
对象作为核心元数据表示形式进行操作。这就是使 Spring 的容器如此灵活和可扩展的本质。
1.16.1. BeanFactory 或 ApplicationContext?
本节说明BeanFactory
和ApplicationContext
容器级别之间的区别以及对引导的影响。
除非有充分的理由,否则应使用ApplicationContext
,除非GenericApplicationContext
及其子类AnnotationConfigApplicationContext
作为自定义引导的常见实现,否则应使用它们。对于所有常见目的,这些都是 Spring 核心容器的主要入口点:加载配置文件,触发 Classpath 扫描,以编程方式注册 Bean 定义和带注解的类,以及(从 5.0 版本开始)注册功能性 Bean 定义。
因为ApplicationContext
包含BeanFactory
的所有功能,所以通常建议在纯BeanFactory
上使用,除非需要完全控制 Bean 处理的情况。在ApplicationContext
(例如GenericApplicationContext
实现)内,按惯例(即,按 Bean 名称或 Bean 类型(尤其是后处理器))检测到几种 Bean,而普通的DefaultListableBeanFactory
则与任何特殊的 Bean 无关。
对于许多扩展的容器功能(例如注解处理和 AOP 代理),BeanPostProcessor 扩展点是必不可少的。如果仅使用普通DefaultListableBeanFactory
,则默认情况下不会检测到此类后处理器然后并将其激活。这种情况可能令人困惑,因为您的 bean 配置实际上并没有错。而是在这种情况下,需要通过其他设置完全引导容器。
下表列出了BeanFactory
和ApplicationContext
接口和实现提供的功能。
表 9.功能列表
Feature | BeanFactory | ApplicationContext |
---|---|---|
Bean instantiation/wiring | Yes | Yes |
集成生命周期 Management | No | Yes |
自动BeanPostProcessor 注册 |
No | Yes |
自动BeanFactoryPostProcessor 注册 |
No | Yes |
方便的MessageSource 访问权限(用于内部化) |
No | Yes |
内置的ApplicationEvent 发布机制 |
No | Yes |
要使用DefaultListableBeanFactory
显式注册 bean 后处理器,您需要以编程方式调用addBeanPostProcessor
,如以下示例所示:
DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); // populate the factory with bean definitions // now register any needed BeanPostProcessor instances factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor()); factory.addBeanPostProcessor(new MyBeanPostProcessor()); // now start using the factory
==========================================================================================================
要将BeanFactoryPostProcessor
应用于普通DefaultListableBeanFactory
,需要调用其postProcessBeanFactory
方法,如以下示例所示:
DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
//从属性文件中引入一些属性值
PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));
//现在实际做替换
cfg.postProcessBeanFactory(factory);
Note
AnnotationConfigApplicationContext
已注册了所有通用注解后处理器,并且可以通过诸如@EnableTransactionManagement
之类的配置注解在幕后引入其他处理器。在 Spring 基于注解的配置模型的抽象级别上,bean 后处理器的概念仅是内部容器详细信息。
Spring中提供一些Aware结尾相关接口,像是BeanFactoryAware、 BeanNameAware、ApplicationContextAware、ResourceLoaderAware、ServletContextAware等等。
实现这些 Aware接口的Bean在被实例化 之后,可以取得一些相对应的资源,例如实现BeanFactoryAware的Bean在实例化后,Spring容器将会注入BeanFactory的实例,而实现ApplicationContextAware的Bean,在Bean被实例化后,将会被注入 ApplicationContext的实例等等。
通过重写setter方法,当前bean被实例化后实现相关实例的注入。
以Loader结尾的类,可以加载某些相应的资源。