参考文档的这一部分涵盖了Spring框架中不可或缺的所有技术。
其中最重要的是Spring框架的控制反转(IoC)容器。在对Spring框架的IoC容器进行了全面的处理之后,还对Spring的面向方面编程(AOP)技术进行了全面的介绍。Spring框架有自己的AOP框架,它在概念上很容易理解,并且成功地解决了Java企业编程中80%的AOP需求。
还提供了Spring与AspectJ集成的内容(就特性而言,AspectJ是目前最丰富的,当然也是Java企业空间中最成熟的AOP实现)。
IoC容器
本章讨论Spring的控制反转(IoC)容器。
介绍Spring IoC容器和bean
本章介绍了控制反转(IoC)原理的Spring框架实现。IoC也称为依赖项注入(DI)。这是一个对象仅通过构造函数参数、工厂方法的参数或对象实例构造或从工厂方法返回后在对象实例上设置的属性来定义其依赖项(即与之一起工作的其他对象)的过程。然后容器在创建bean时注入这些依赖项。这个过程基本上是bean本身的逆过程(因此称为控制反转),通过使用类的直接构造或一种机制(如服务定位器模式)来控制其依赖项的实例化或位置。
org.springframework.beans和org.springframework.context包是Spring框架的IoC容器的基础。BeanFactory接口提供了能够管理任何类型对象的高级配置机制。ApplicationContext是BeanFactory的子接口。它补充了:
- 更容易与Spring的AOP特性集成
- 消息资源处理(用于国际化)
- 事件发布
- 特定于应用程序层的上下文,如web应用程序中使用的WebApplicationContext。
简而言之,BeanFactory提供了配置框架和基本功能,而ApplicationContext添加了更多企业特定的功能。ApplicationContext是BeanFactory的一个完整的超集,在本章描述Spring的IoC容器时专门使用它。有关使用BeanFactory而不是ApplicationContext的更多信息,请参见BeanFactory。
在Spring中,构成应用程序主干并由Spring IoC容器管理的对象称为bean。bean是由Spring IoC容器实例化、组装和管理的对象。另外,bean只是应用程序中的众多对象之一。bean及其之间的依赖关系反映在容器使用的配置元数据中。
容器概述
org.springframework.context.ApplicationContext接口表示Spring IoC容器,负责实例化、配置和组装bean。容器通过读取配置元数据获取关于要实例化、配置和组装哪些对象的指令。配置元数据用XML、Java注释或Java代码表示。它允许您表达组成应用程序的对象以及这些对象之间丰富的相互依赖关系。
ApplicationContext接口的几个实现由Spring提供。在独立应用程序中,通常会创建ClassPathXmlApplicationContext或FileSystemXmlApplicationContext的实例。虽然XML一直是定义配置元数据的传统格式,但是可以通过提供少量XML配置以声明方式支持这些额外的元数据格式,从而指示容器使用Java注释或代码作为元数据格式。
在大多数应用程序场景中,不需要显式的用户代码来实例化一个或多个Spring IoC容器实例。例如,在web应用程序场景中,在应用程序的web. XML文件中使用8行(大约)简单的样板web描述符XML通常就足够了(请参阅web应用程序的方便的ApplicationContext实例化)。如果您使用Eclipse的Spring工具(一种Eclipse支持的开发环境),那么只需几次鼠标点击或击键,您就可以轻松地创建这个样板配置。
下图显示了Spring如何工作的高级视图。您的应用程序类与配置元数据相结合,这样,在创建并初始化ApplicationContext之后,您就拥有了一个完全配置和可执行的系统或应用程序。
配置元数据
如上图所示,Spring IoC容器使用配置元数据的一种形式。此配置元数据表示作为应用程序开发人员,您如何告诉Spring容器实例化、配置和组装应用程序中的对象。
配置元数据通常以简单直观的XML格式提供,本章的大部分内容都使用这种格式来传达Spring IoC容器的关键概念和特性。
基于xml的元数据不是惟一允许的配置元数据形式。Spring IoC容器本身与实际编写配置元数据的格式完全解耦。现在,许多开发人员为他们的Spring应用程序选择基于java的配置。
有关在Spring容器中使用其他形式的元数据的信息,请参阅:
- 基于注释的配置:spring2.5引入了对基于注释的配置元数据的支持。
- 基于java的配置:从Spring 3.0开始,Spring JavaConfig项目提供的许多特性成为Spring核心框架的一部分。因此,您可以使用Java而不是XML文件来定义应用程序类外部的bean。要使用这些新特性,请参见@Configuration、@Bean、@Import和@DependsOn注释。
Spring配置由容器必须管理的至少一个和通常多个bean定义组成。基于xml的配置元数据将这些bean配置为顶级元素中的元素。Java配置通常在@Configuration类中使用@bean注释的方法。
这些bean定义对应于组成应用程序的实际对象。通常,您要定义服务层对象、数据访问对象(DAOs)、表示对象(如Struts Action实例)、基础设施对象(如Hibernate SessionFactories)、JMS队列等等。通常,不会在容器中配置细粒度域对象,因为通常由DAOs和业务逻辑负责创建和加载域对象。但是,您可以使用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>
- id属性是标识单个bean定义的字符串。
- class属性定义bean的类型并使用完全限定的类名。
id属性的值引用协作对象。本例中没有显示用于引用协作对象的XML。有关更多信息,请参见依赖项。
实例化一个容器
提供给ApplicationContext构造函数的位置路径是资源字符串,它允许容器从各种外部资源(如本地文件系统、Java类路径等)装载配置元数据。
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
在了解了Spring的IoC容器之后,您可能想了解更多关于Spring的资源抽象(如资源中所述)的信息,它提供了一种方便的机制,用于从URI语法中定义的位置读取InputStream。特别是,资源路径用于构造应用程序上下文,如应用程序上下文和资源路径中所述。
下面的示例显示了服务层对象(services.xml)配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
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配置文件表示体系结构中的逻辑层或模块。
您可以使用应用程序上下文构造函数从所有这些XML片段加载bean定义。此构造函数接受多个资源位置,如前一节所示。或者,使用一个或多个`元素来从另一个或多个文件加载bean定义。下面的例子演示了如何做到这一点:
<beans>
<import resource="services.xml"/>
<import resource="resources/messageSource.xml"/>
<import resource="/resources/themeSource.xml"/>
<bean id="bean1" class="..."/>
<bean id="bean2" class="..."/>
</beans>
在前面的示例中,外部bean定义是从三个文件加载的:服务。xml, messageSource。xml和themeSource.xml。所有位置路径都相对于执行导入的定义文件,因此services.xml必须与执行导入的文件位于相同的目录或类路径位置,而messageSource.xml和themeSource.xml必须位于导入文件位置之下的资源位置。可以看到,前面的斜杠被忽略了。但是,考虑到这些路径是相对的,所以最好不要使用斜杠。根据Spring模式,要导入的文件的内容,包括顶级的<beans/>
元素,必须是有效的XML bean定义。
它是可能的,但不推荐,在父目录中引用文件使用一个亲戚"…/”路径。这样做会在当前应用程序之外的文件上创建一个依赖项。特别地,对于classpath: URLs(例如,classpath:… ./services.xml),不推荐使用这个引用,因为运行时解析过程会选择“最近的”classpath根目录,然后查看它的父目录。类路径配置更改可能导致选择不同的、不正确的目录。
您总是可以使用完全限定的资源位置,而不是相对路径:例如,file:C:/config/services.xml或classpath:/config/services.xml。但是,请注意,您正在将应用程序的配置耦合到特定的绝对位置。通常更可取的做法是为这些绝对位置保留一个间接的地址——例如,通过“${…}”占位符,这些占位符在运行时根据JVM系统属性解析。
名称空间本身提供了import指令特性。除了普通bean定义之外,还有一些配置特性可以在Spring提供的XML名称空间选择中找到——例如,上下文和util名称空间。
Groovy Bean定义DSL
作为外部化配置元数据的另一个示例,bean定义也可以用Spring的Groovy bean定义DSL表示,这在Grails框架中是已知的。通常,这样的配置位于“.groovy”文件,其结构如下例所示:
beans {
dataSource(BasicDataSource) {
driverClassName = "org.hsqldb.jdbcDriver"
url = "jdbc:hsqldb:mem:grailsDB"
username = "sa"
password = ""
settings = [mynew:"setting"]
}
sessionFactory(SessionFactory) {
dataSource = dataSource
}
myService(MyService) {
nestedBean = { AnotherBean bean ->
dataSource = dataSource
}
}
}
这种配置风格在很大程度上等同于XML bean定义,甚至支持Spring的XML配置名称空间。它还允许通过importBeans指令导入XML bean定义文件。
使用容器
ApplicationContext是能够维护不同bean及其依赖项的注册表的高级工厂的接口。通过使用方法T getBean(String name,Class<T>
requiredType),您可以检索bean的实例。
ApplicationContext允许您读取bean定义并访问它们,如下面的示例所示:
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// use configured instance
List<String> userList = service.getUsernameList();
对于Groovy配置,引导看起来非常类似。它有一个不同的上下文实现类,它支持groovy(但也理解XML bean定义)。下面的示例展示了Groovy配置:
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");
最灵活的变体是GenericApplicationContext与reader委托相结合—例如,XmlBeanDefinitionReader用于XML文件,如下例所示:
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
您还可以为Groovy文件使用GroovyBeanDefinitionReader,如下面的示例所示:
GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();
您可以在相同的ApplicationContext上混合和匹配这样的reader委托,从不同的配置源读取bean定义。
然后可以使用getBean检索bean的实例。ApplicationContext接口有一些用于检索bean的其他方法,但在理想情况下,应用程序代码不应该使用它们。实际上,您的应用程序代码应该完全不调用getBean()方法,因此完全不依赖于Spring api。例如,Spring与web框架的集成为各种web框架组件(如控制器和jsf管理的bean)提供了依赖项注入,允许您通过元数据(如自动装配注释)声明对特定bean的依赖项。
Bean概述
Spring IoC容器管理一个或多个bean。这些bean是使用您提供给容器的配置元数据创建的(例如,以XML <bean/>
定义的形式)。
在容器内部,这些bean定义被表示为BeanDefinition对象,其中包含(其他信息)以下元数据:
- 包限定的类名:通常是定义的bean的实际实现类。
- Bean行为配置元素,它表示Bean在容器中的行为(范围、生命周期回调,等等)。
- 对该bean执行其工作所需的其他bean的引用。这些引用也称为协作者或依赖项。
- 要在新创建的对象中设置的其他配置设置—例如,池的大小限制或在管理连接池的bean中使用的连接数。
此元数据转换为组成每个bean定义的一组属性。下表描述了这些属性:
属性 | 在…解释 |
---|---|
类 | 实例化bean |
名称 | 命名bean |
作用域 | bean作用域 |
构造参数 | 依赖注入 |
属性 | 依赖注入 |
自动注入模式 | 自动装配协作者 |
延迟初始化模式 | 延迟初始化的bean |
初始化方法 | 初始化回调 |
破坏方法 | 破坏回调 |
除了包含关于如何创建特定bean的信息的bean定义之外,ApplicationContext实现还允许注册容器外创建的现有对象(由用户创建)。这是通过getBeanFactory()方法访问ApplicationContext的BeanFactory来实现的,该方法返回BeanFactory的DefaultListableBeanFactory实现。DefaultListableBeanFactory通过registerSingleton(…)和registerBeanDefinition(…)方法支持这种注册。但是,典型的应用程序只使用通过常规bean定义元数据定义的bean。
Bean元数据和手动提供的单例实例需要尽早注册,以便容器在自动装配和其他内省步骤期间正确地推断它们。虽然在某种程度上支持覆盖现有的元数据和现有的单例实例,但是在运行时注册新bean(与对工厂的实时访问并发)并没有得到官方的支持,这可能会导致并发访问异常、bean容器中的不一致状态,或者两者都有。
命名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,则必须通过使用ref元素或服务定位器样式查找来提供名称。不提供名称的动机与使用内部bean和自动装配协作者有关。
Bean命名约定
约定是在命名bean时使用标准Java约定作为实例字段名。也就是说,bean名称以小写字母开头,并从那里开始采用驼峰格式。此类名称的示例包括accountManager、accountService、userDao、loginController等。
一致地命名bean使您的配置更容易阅读和理解。另外,如果您使用Spring AOP,在将建议应用到一组按名称关联的bean时,它会有很大
的帮助。
通过在类路径中扫描组件,Spring按照前面描述的规则为未命名的组件生成bean名称:本质上,使用简单的类名并将其初始字符转换为小写。但是,在(不寻常的)特殊情况下,如果有多个字符,并且第一个和第二个字符都是大写的,则保留原来的大小写。这些规则与java.beans.Introspector.decapitalize (Spring在这里使用)定义的规则相同。
在Bean的定义之外别名Bean
在bean定义本身中,通过使用id属性指定的最多一个名称和name属性中任意数量的其他名称的组合,可以为bean提供多个名称。这些名称可以是相同bean的别名,在某些情况下非常有用,比如通过使用特定于该组件本身的bean名称,让应用程序中的每个组件引用公共依赖项。
然而,指定实际定义bean的所有别名并不总是足够的。有时需要为在其他地方定义的bean引入别名。这在大型系统中是很常见的,在这些系统中,配置在每个子系统之间被分割,每个子系统都有自己的一组对象定义。在基于xml的配置元数据中,可以使用元素来完成此任务。下面的例子演示了如何做到这一点:
<alias name="fromName" alias="toName"/>
在这种情况下,在使用别名定义之后,名为fromName的bean(在同一个容器中)也可以被称为toName。
例如,子系统A的配置元数据可以通过子系统-数据源的名称引用数据源。子系统B的配置元数据可以通过子系统-数据源的名称引用数据源。在组合使用这两个子系统的主应用程序时,主应用程序以myApp-dataSource的名称引用数据源。要使所有三个名称都引用同一个对象,可以将以下别名定义添加到配置元数据:
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
现在,每个组件和主应用程序都可以通过一个惟一的名称来引用数据源,这个名称可以保证不与任何其他定义冲突(有效地创建一个名称空间),但是它们引用的是同一个bean。
Java-configuration
如果使用Javaconfiguration,则可以使用@Bean注释来提供别名。有关详细信息,请参见使用@Bean注释。
实例化bean
bean定义本质上是创建一个或多个对象的配方。当被请求时,容器查看指定bean的配方,并使用该bean定义封装的配置元数据来创建(或获取)一个实际对象。
如果使用基于xml的配置元数据,则指定要在<bean/>
元素的class属性中实例化的对象的类型(或类)。这个类属性(在内部是BeanDefinition实例上的一个类属性)通常是强制性的。(有关异常,请参见使用实例工厂方法和Bean定义继承进行实例化。)你可以通过以下两种方式之一使用Class属性:
- 通常,在容器本身通过反射调用其构造函数直接创建bean的情况下,指定要构造的bean类,这在某种程度上相当于使用new操作符的Java代码。
- 要指定包含用于创建对象的静态工厂方法的实际类,在不太常见的情况下,容器调用类上的静态工厂方法来创建bean。从静态工厂方法调用返回的对象类型可以是同一个类,也可以完全是另一个类。
内部类的名字
如果希望为静态嵌套类配置bean定义,则必须使用嵌套类的二进制名。
例如,如果您在com中有一个类名为SomeThing。这个类有一个静态的嵌套类,叫做OtherThing, bean定义上的class属性的值是com。example。SomeThing O t h e r T h i n g 。 注 意 , 在 名 称 中 使 用 OtherThing。 注意,在名称中使用 OtherThing。注意,在名称中使用字符将嵌套的类名与外部类名分隔开。
用构造函数实例化
当您通过构造函数方法创建一个bean时,所有的普通类都可以被Spring使用并与Spring兼容。也就是说,正在开发的类不需要实现任何特定的接口,也不需要以特定的方式进行编码。只需指定bean类就足够了。但是,根据您为特定bean使用的IoC类型,您可能需要一个默认(空)构造函数。
Spring IoC容器几乎可以管理您希望它管理的任何类。它不仅限于管理真正的javabean。大多数Spring用户更喜欢实际的javabean,它只有一个默认的(无参数的)构造函数,以及根据容器中的属性建模的适当的setter和getter方法。您还可以在容器中包含更多非bean风格的类。例如,如果您需要使用完全不符合JavaBean规范的遗留连接池,Spring也可以管理它。
使用基于xml的配置元数据,您可以按如下方式指定bean类:
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
用静态工厂方法实例化
在定义使用静态工厂方法创建的bean时,使用class属性指定包含静态工厂方法的类,使用factory-method属性指定工厂方法本身的名称。您应该能够调用这个方法(带有可选参数,如后面所述)并返回一个活动对象,该对象随后被视为是通过构造函数创建的。这种bean定义的一个用途是在遗留代码中调用静态工厂。
下面的bean定义指定通过调用工厂方法来创建bean。定义不指定返回对象的类型(类),只指定包含工厂方法的类。在本例中,createInstance()方法必须是一个静态方法。下面的例子展示了如何指定一个工厂方法:
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
下面的例子展示了一个可以与前面的bean定义一起工作的类:
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
有关向工厂方法提供(可选)参数和在从工厂返回对象后设置对象实例属性的机制的详细信息,请参阅依赖项和配置。
通过使用实例工厂方法实例化
与通过静态工厂方法实例化类似,使用实例工厂方法实例化将从容器中调用现有bean的非静态方法来创建新bean。要使用这种机制,将class属性保留为空,并在factory-bean属性中指定当前(或父或父)容器中bean的名称,该容器包含要调用来创建对象的实例方法。使用factory-method属性设置工厂方法本身的名称。下面的例子展示了如何配置这样一个bean:
<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<!-- the bean to be created via the factory bean -->
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
下面的例子展示了相应的类:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
一个工厂类也可以包含多个工厂方法,如下例所示:
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
<bean id="accountService"
factory-bean="serviceLocator"
factory-method="createAccountServiceInstance"/>
下面的例子展示了相应的类:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private static AccountService accountService = new AccountServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
public AccountService createAccountServiceInstance() {
return accountService;
}
}
这种方法表明,可以通过依赖项注入(DI)来管理和配置工厂bean本身。详细查看依赖项和配置。
在Spring文档中,“factory bean”指的是在Spring容器中配置的bean,它通过实例或静态工厂方法创建对象。相反,FactoryBean(注意大小写)指的是特定于spring的FactoryBean。
依赖
典型的企业应用程序不包含单个对象(或Spring中的bean)。即使是最简单的应用程序也有几个对象一起工作,以呈现最终用户所看到的一致的应用程序。下一节将解释如何从定义许多独立的bean定义过渡到一个完全实现的应用程序,在这个应用程序中,对象通过协作来实现一个目标。
依赖注入
依赖项注入(DI)是一个过程,在这个过程中,对象仅通过构造函数参数、工厂方法的参数或从工厂方法构造或返回后在对象实例上设置的属性来定义它们的依赖项(即它们与之一起工作的其他对象)。然后容器在创建bean时注入这些依赖项。这个过程基本上是bean本身的逆过程(因此称为控制反转),它通过直接构造类或服务定位器模式来控制依赖项的实例化或位置。
使用DI原则,代码更简洁,并且当对象提供其依赖项时,解耦更有效。对象不查找其依赖项,也不知道依赖项的位置或类。结果,您的类变得更容易测试,特别是当依赖关系在接口或抽象基类上时,这允许在单元测试中使用存根或模拟实现。
DI存在于两个主要的变体中:基于构造的依赖项注入和基于setter的依赖项注入。
Constructor-based依赖注入
基于构造函数的DI是通过容器调用带有许多参数的构造函数来完成的,每个参数代表一个依赖项。调用带有特定参数的静态工厂方法来构造bean几乎是等价的,本讨论将参数分别用于构造函数和静态工厂方法。下面的例子展示了一个只能依赖注入构造函数注入的类:
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private MovieFinder movieFinder;
// a constructor so that the Spring container can inject a MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
注意,这个类没有什么特别之处。它是一个不依赖于容器特定接口、基类或注释的POJO。
构造函数参数解析
构造函数的参数解析匹配是通过使用参数的类型来实现的。如果bean定义的构造函数参数中不存在潜在的歧义,则在bean定义中定义构造函数参数的顺序与在实例化bean时将这些参数提供给适当的构造函数的顺序相同。考虑以下类别:
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
假设ThingTwo和ThingThree类没有继承关系,那么就不存在潜在的歧义。因此,下面的配置工作得很好,您不需要在<constructor-arg/>
元素中显式地指定构造函数参数索引或类型。
<beans>
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg ref="beanTwo"/>
<constructor-arg ref="beanThree"/>
</bean>
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
</beans>
当引用另一个bean时,类型是已知的,可以进行匹配(就像前面的例子一样)。当使用简单类型(如<value>
true</value>
)时,Spring无法确定值的类型,因此在没有帮助的情况下无法根据类型进行匹配。考虑以下类别:
package examples;
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private int years;
// The Answer to Life, the Universe, and Everything
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可以从构造函数中查找参数名。如果不能或不希望使用debug标志编译代码,可以使用@ConstructorProperties JDK注释显式地命名构造函数参数。然后,示例类必须如下所示:
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
Setter-based依赖注入
基于setter的DI是在调用无参数构造函数或无参数静态工厂方法来实例化bean之后,通过容器调用bean上的setter方法来完成的。
下面的示例显示了一个只能通过使用纯setter注入来依赖注入的类。这个类是传统的Java。它是一个不依赖于容器特定接口、基类或注释的POJO。
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on the MovieFinder
private MovieFinder movieFinder;
// a setter method so that the Spring container can inject a MovieFinder
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
ApplicationContext为它管理的bean支持基于构造函数和基于setter的DI。在通过构造函数方法注入了一些依赖项之后,它还支持基于setter的DI。您可以将依赖项配置为BeanDefinition的形式,并将其与PropertyEditor实例一起使用,以便将属性从一种格式转换为另一种格式。但是,大多数Spring用户并不直接使用这些类(也就是说,通过编程方式),而是使用XML bean定义、带注释的组件(也就是说,使用@Component、@Controller等进行注释的类)或基于java的@Configuration类中的@Bean方法。然后,这些源在内部转换为BeanDefinition实例,并用于加载整个Spring IoC容器实例。
基于构造函数还是基于setter的DI?
由于您可以混合使用基于构造函数和基于setter的DI,所以对于强制依赖项使用构造函数,对于可选依赖项使用setter方法或配置方法,这是一个很好的经验法则。注意,在setter方法上使用@Required注释可以使属性成为必需的依赖项;但是,最好使用带有参数编程式验证的构造函数注入。
Spring团队通常提倡构造函数注入,因为它允许您将应用程序组件实现为不可变的对象,并确保所需的依赖项不是空的。此外,注入构造函数的组件总是以完全初始化的状态返回给客户机(调用)代码。附带说明一下,大量的构造函数参数是一种不好的代码味道,这意味着类可能有太多的责任,应该对其进行重构,以更好地解决问题的适当分离。
Setter注入应该主要用于可选的依赖项,这些依赖项可以在类中分配合理的默认值。否则,必须在代码使用依赖项的任何地方执行非空检查。setter注入的一个好处是,setter方法使该类对象可以在以后进行重新配置或重新注入。因此,通过JMX mbean进行管理是setter注入的一个引人注目的用例。
使用对特定类最有意义的DI样式。有时,在处理您没有源代码的第三方类时,需要为您做出选择。例如,如果第三方类没有公开任何setter方法,那么构造函数注入可能是惟一可用的DI形式。
依赖性解析过程
容器执行bean依赖项解析如下:
- ApplicationContext是用描述所有bean的配置元数据创建和初始化的。配置元数据可以由XML、Java代码或注释指定。
- 对于每个bean,其依赖项都以属性、构造函数参数或静态工厂方法的参数的形式表示(如果您使用该方法而不是普通的构造函数)。这些依赖项是在bean实际创建时提供给bean的。
- 每个属性或构造函数参数都是要设置的值的实际定义,或者是对容器中另一个bean的引用。
- 值的每个属性或构造函数参数都从其指定的格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring可以将以字符串格式提供的值转换为所有内置类型,如int、long、string、boolean等。
在创建容器时,Spring容器验证每个bean的配置。但是,在实际创建bean之前,不会设置bean属性本身。在创建容器时,将创建单例作用域的bean并将其设置为预实例化(默认值)。作用域是在Bean作用域中定义的。否则,仅在请求bean时才创建它。当bean的依赖项及其依赖项的依赖项(等等)被创建和分配时,bean的创建可能会创建一个bean图。注意,这些依赖项之间的解析不匹配可能会出现得很晚——也就是说,在首次创建受影响的bean时。
循环依赖
如果主要使用构造函数注入,可能会创建无法解析的循环依赖场景。
例如:类A需要一个类B通过构造函数注入的实例,而类B需要一个类A通过构造函数注入的实例。如果您将bean配置为类A和B相互注入,Spring IoC容器将在运行时检测这个循环引用,并抛出一个BeanCurrentlyInCreationException异常。
一种可能的解决方案是编辑某些类的源代码,由setter而不是构造函数来配置。另外,避免构造函数注入,只使用setter注入。换句话说,尽管不建议这样做,但您可以使用setter注入配置循环依赖项。
与典型的情况(没有循环依赖关系)不同,bean a和bean B之间的循环依赖关系迫使一个bean在完全初始化之前被注入到另一个bean中(典型的先有鸡还是先有蛋的场景)。
您通常可以相信Spring会做正确的事情。它在容器装载时检测配置问题,例如对不存在的bean的引用和循环依赖关系。在实际创建bean时,Spring会尽可能晚地设置属性和解析依赖项。这意味着,如果在创建对象或其依赖项时出现问题(例如,bean由于缺少或无效的属性而抛出异常),在请求对象时,已正确加载的Spring容器稍后可以生成异常。一些配置问题的潜在延迟可见性是ApplicationContext实现默认预实例化单例bean的原因。在实际需要这些bean之前花费一些前期时间和内存来创建它们,您将在创建ApplicationContext时发现配置问题,而不是稍后。您仍然可以覆盖这个默认行为,这样单例bean就可以延迟初始化,而不是预先实例化。
如果不存在循环依赖项,那么当一个或多个协作bean被注入一个依赖bean时,每个协作bean在被注入依赖bean之前都要完全配置好。这意味着,如果bean A依赖于bean B,那么Spring IoC容器将在调用bean A的setter方法之前完全配置bean B。换句话说,bean实例化(如果它不是一个单例预先实例化),其设置依赖项,相关的生命周期方法(如InitializingBean init方法或配置回调方法)调用。换句话说,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"/>
下面的例子展示了相应的ExampleBean类:
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;
}
}
在前面的示例中,声明了setter来匹配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"/>
下面的例子展示了相应的ExampleBean类:
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"/>
下面的例子展示了相应的ExampleBean类:
public class ExampleBean {
// a private constructor
private ExampleBean(...) {
...
}
// a static factory method; the arguments to this method can be
// considered the dependencies of the bean that is returned,
// regardless of how those arguments are actually used.
public static ExampleBean createInstance (
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
ExampleBean eb = new ExampleBean (...);
// some other operations...
return eb;
}
}
静态工厂方法的参数由<constructor-arg/>
元素提供,与实际使用构造函数的情况完全相同。工厂方法返回的类的类型不必与包含静态工厂方法的类的类型相同(尽管在本例中是相同的)。一个实例(非静态)工厂方法可以以一种基本相同的方式使用(除了使用factory-bean属性而不是class属性),所以我们在这里不讨论这些细节。
详细介绍依赖项和配置
如前一节所述,可以将bean属性和构造函数参数定义为对其他托管bean(协作者)的引用或内联定义的值。Spring基于xml的配置元数据支持其<property/>
和<constructor-arg/>
元素中的子元素类型。
直接值(基本类型、字符串等)
<property/>
元素的值属性将属性或构造函数参数指定为人类可读的字符串表示形式。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-namespace进行更简洁的XML配置:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
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>
前面的XML更简洁。但是,拼写错误是在运行时而不是在设计时发现的,除非您使用一个IDE(如IntelliJ IDEA或Eclipse的Spring工具)来支持在创建bean定义时自动完成属性。强烈建议这种IDE协助。
您还可以配置java.util。属性实例,如下:
<bean id="mappings"
class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<!-- 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容器将<value/>
元素中的文本转换为java.util.Properties实例,通过使用JavaBeans的PropertyEditor机制。这是一个很好的快捷方式,也是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,则此类型错误和由此产生的异常可能只有在部署容器之后很久才会被发现。
在4.0 bean XSD中不再支持idref元素的本地属性,因为它不再提供常规bean引用之上的值。升级到4.0模式时,将现有的idref本地引用更改为idref bean。
<idref/>
元素带来价值的一个常见地方(至少在Spring 2.0之前的版本中)是在ProxyFactoryBean bean定义中的AOP拦截器配置中。在指定拦截器名称时使用<idref/>
元素可以防止对拦截器ID的拼写错误。
对其他bean的引用(协作者)
ref元素是<constructor-arg/>
或<property/>
定义元素中的最后一个元素。在这里,将bean的指定属性的值设置为对容器管理的另一个bean(合作者)的引用。被引用的bean是要设置其属性的bean的依赖项,在设置属性之前,根据需要对其进行初始化。(如果合作者是一个单例bean,那么它可能已经被容器初始化了。)所有引用最终都是对另一个对象的引用。范围确定和验证取决于是否通过bean或父属性指定另一个对象的ID或名称。
通过<ref/>
标记的bean属性指定目标bean是最常见的形式,并且允许在同一个容器或父容器中创建对任何bean的引用,而不管它是否在同一个XML文件中。bean属性的值可以与目标bean的id属性相同,也可以与目标bean的name属性中的值相同。下面的例子展示了如何使用ref元素:
<ref bean="someBean"/>
通过parent属性指定目标bean将创建对当前容器的父容器中的bean的引用。父属性的值可以与目标bean的id属性相同,也可以与目标bean的name属性中的一个值相同。目标bean必须位于当前bean的父容器中。您应该主要在容器层次结构中使用此bean引用变体,并且希望将现有的bean与具有与父bean相同名称的代理包装在父容器中。下面的两个清单展示了如何使用父属性:
<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
<!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
</property>
<!-- insert other configuration and dependencies as required here -->
</bean>
在4.0 bean XSD中不再支持ref元素的本地属性,因为它不再提供常规bean引用之上的值。升级到4.0模式时,将现有的ref本地引用更改为ref bean。
内部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中。
作为一种特殊情况,可以从自定义范围接收销毁回调—例如,对于单例bean中包含的请求范围的内部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的键或值或set的值也可以是以下任何元素:
bean | ref | idref | list | set | map | props | value | null
集合合并
Spring容器还支持合并集合。应用程序开发人员可以定义父元素<list/>
、<map/>
、<set/>
或<props/>
元素,并拥有子元素<list/>
、<map/>
、<set/>
或<props/>
元素从父集合继承和覆盖值。也就是说,子集合的值是父集合和子集合的元素合并的结果,子集合的元素覆盖父集合中指定的值。
关于合并的这一节将讨论父-子bean机制。不熟悉父bean和子bean定义的读者可能希望在继续之前阅读相关部分。
下面的例子演示了集合合并:
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.com</prop>
<prop key="support">support@example.com</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<!-- the merge is specified on the child collection definition -->
<props merge="true">
<prop key="sales">sales@example.com</prop>
<prop key="support">support@example.co.uk</prop>
</props>
</property>
</bean>
<beans>
注意,在子bean定义的adminEmails属性的<props/>
元素上使用了merge=true属性。当容器解析并实例化子bean时,得到的实例有一个adminEmails Properties 集合,该集合包含将子bean的adminEmails集合与父bean的adminEmails集合合并的结果。下面的清单显示了结果:
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk
子Properties集合的值集继承父<props/>
的所有属性元素,而support值的子值覆盖父集合中的值。
这种合并行为同样适用于<list/>
、<map/>
和<set/>
集合类型。在<list/>
元素的特定情况下,将维护与列表集合类型(即ordered值集合的概念)相关联的语义。父列表的值位于子列表的所有值之前。对于Map、Set和Properties集合类型,不存在排序。因此,对于容器内部使用的关联的Map、Set和Properties实现类型下的集合类型,没有有效的排序语义。
集合合并的限制
您不能合并不同的集合类型(例如Map和List)。如果您确实尝试这样做,将抛出一个适当的异常。merge属性必须在较低的继承的子定义上指定。在父集合定义上指定merge属性是冗余的,并且不会导致所需的合并。
强类型集合
通过在Java 5中引入泛型类型,您可以使用强类型集合。也就是说,可以声明一个Collection类型,使它只能包含(例如)String元素。如果您使用Spring来依赖地将强类型Collection注入到bean中,那么您可以利用Spring的类型转换支持,以便在将强类型Collection实例的元素添加到Collection之前将其转换为适当的类型。下面的Java类和bean定义说明了如何做到这一点:
public class SomeClass {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
<beans>
<bean id="something" class="x.y.SomeClass">
<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字符串值和空字符串值
Spring将属性的空参数等作为空字符串处理。以下基于xml的配置元数据片段将email属性设置为空字符串值("")。
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
上面的例子相当于下面的Java代码:
exampleBean.setEmail("");
<null/>
元素处理空值。下面的清单显示了一个例子:
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
上述配置相当于以下Java代码:
exampleBean.setEmail(null);
使用p-namespace的XML快捷方式
p-namespace允许您使用bean元素的属性(而不是嵌套的<property/>
元素)来描述协作bean的属性值,或者两者都使用。
Spring支持带有名称空间的可扩展配置格式,这些名称空间基于XML模式定义。本章讨论的bean配置格式是在XML模式文档中定义的。但是,p-namespace并没有在XSD文件中定义,它只存在于Spring的核心中。
下面的示例显示了解析为相同结果的两个XML片段(第一个使用标准XML格式,第二个使用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="classic" class="com.example.ExampleBean">
<property name="email" value="someone@somewhere.com"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="someone@somewhere.com"/>
</beans>
该示例显示了bean定义中名为email的p-namespace属性。这告诉Spring包含一个属性声明。如前所述,p-namespace没有模式定义,因此可以将属性名设置为属性名。
下一个例子包括两个以上的bean定义,它们都有对另一个bean的引用:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
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-namespace的属性值,而且还使用一种特殊格式来声明属性引用。第一个bean定义使用<property name="spouse" ref="jane"/>
来创建一个从bean john到bean jane的引用,而第二个bean定义使用p:spouse-ref="jane"作为一个属性来完成完全相同的工作。在本例中,spouse是属性名,而-ref部分指出这不是一个直接的值,而是对另一个bean的引用。
p-namespace不如标准XML格式灵活。例如,声明属性引用的格式与以Ref结尾的属性冲突,而标准XML格式则不冲突。我们建议您仔细选择您的方法,并将其传达给您的团队成员,以避免生成同时使用这三种方法的XML文档。
使用c-namespace的XML快捷方式
与p-namespace的XML快捷方式类似,在Spring 3.1中引入的c-namespace允许配置构造函数参数的内联属性,而不是嵌套的构造函数-参数元素。
下面的例子使用c:命名空间来做与基于构造函数的依赖注入相同的事情:
<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="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<!-- traditional declaration with optional argument names -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="something@somewhere.com"/>
</bean>
<!-- c-namespace declaration with argument names -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>
</beans>
c: 命名空间使用与p: 相同的约定(bean引用的尾随-ref)来根据构造函数参数的名称设置参数。类似地,即使没有在XSD模式中定义它(它存在于Spring核心中),也需要在XML文件中声明它。
对于构造函数参数名不可用的罕见情况(通常是在编译字节码时没有调试信息的情况下),可以使用回退到参数索引,如下所示:
<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
c:_2="something@somewhere.com"/>
由于XML语法的原因,索引表示法要求前面有_,因为XML属性名不能以数字开头(尽管一些ide允许这样做)。对于
<constructor-arg>
元素也可以使用相应的索引表示法,但不常用,因为在这里声明的简单顺序通常就足够了。
在实践中,构造函数解析机制在匹配参数方面非常有效,因此,除非确实需要,否则我们建议在整个配置过程中使用名称符号。
复合属性名
在设置bean属性时,可以使用复合或嵌套属性名,只要路径的所有组件(最后的属性名除外)都不为null。考虑以下bean定义:
<bean id="something" class="things.ThingOne">
<property name="fred.bob.sammy" value="123" />
</bean>
something bean有一个fred属性,它有一个bob属性,它有一个sammy属性,而最后那个sammy属性的值被设置为123。为了使其工作,在构造bean之后,something的fred属性和fred的bob属性不能为null。否则,将抛出NullPointerException。
使用depends-on
如果一个bean是另一个bean的依赖项,这通常意味着将一个bean设置为另一个bean的属性。通常使用基于xml的配置元数据中的<ref/>
元素来完成此任务。然而,有时候bean之间的依赖关系不那么直接。例如,当需要触发类中的静态初始化器时,例如注册数据库驱动程序时。依赖属性可以显式地强制在使用此元素的bean初始化之前对一个或多个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" />
depends-on属性既可以指定一个初始化时间依赖项,仅在单例bean的情况下,也可以指定一个对应的销毁时间依赖项。与给定bean定义依赖关系的依赖bean首先被销毁,先于给定bean本身被销毁。因此,依赖也可以控制关机顺序。
延迟初始化的bean
默认情况下,ApplicationContext实现将急切地创建和配置所有的单例bean作为初始化过程的一部分。通常,这种预实例化是可取的,因为配置或周围环境中的错误会立即被发现,而不是几小时甚至几天之后。当此行为不可取时,您可以通过将bean定义标记为延迟初始化来防止单例bean的预实例化。延迟初始化的bean告诉IoC容器在第一次请求时创建bean实例,而不是在启动时。
在XML中,这种行为是由<bean/>
元素上的lazy-init属性控制的,如下面的示例所示:
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>
当前面的配置被ApplicationContext使用时,当ApplicationContext启动时,lazy bean不会被急切地预先实例化,反之则not.lazy bean被急切地预先实例化。
然而,当延迟初始化的bean是未延迟初始化的单例bean的依赖项时,ApplicationContext在启动时创建延迟初始化的bean,因为它必须满足单例的依赖项。延迟初始化的bean被注入到一个没有延迟初始化的单例bean中。
您还可以使用<beans/>
元素上的default-lazy-init属性来控制容器级别的延迟初始化,如下面的示例所示:
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
自动装配的合作者
Spring容器可以自动创建协作bean之间的关系。通过检查ApplicationContext的内容,您可以让Spring为您的bean自动解析协作者(其他bean)。自动装配有以下优点:
- 自动装配可以大大减少指定属性或构造函数参数的需要。(本章其他地方讨论的其他机制,如bean模板,在这方面也很有价值。)
- 自动装配可以随着对象的演化更新配置。例如,如果需要向类添加依赖项,则可以自动满足该依赖项,而不需要修改配置。因此,自动装配在开发过程中特别有用,当代码库变得更加稳定时,自动装配可以避免切换到显式连接的选项。
在使用基于xml的配置元数据(请参阅依赖项注入)时,可以使用<bean/>
元素的autowire属性为bean定义指定autowire模式。自动装配功能有四种模式。您可以为每个bean指定自动装配,因此可以选择要自动装配哪些bean。下表描述了四种自动装配模式:
模式 | 解释 |
---|---|
no | (默认)没有自动装配。Bean引用必须由ref元素定义。对于较大的部署,不建议更改默认设置,因为显式地指定协作者可以提供更好的控制和清晰度。在某种程度上,它记录了一个系统的结构。 |
byName | 通过属性名自动装配。Spring寻找与需要自动装配的属性同名的bean。例如,如果一个bean定义被按名称设置为自动装配,并且它包含一个主属性(也就是说,它有一个setMaster(…)方法),Spring会查找一个名为master的bean定义并使用它来设置属性。 |
byType | 如果容器中恰好存在该属性类型的一个bean,则允许自动获取该属性。如果存在多个异常,则抛出一个致命异常,这表明您不能为该bean使用byType自动装配。如果没有匹配的bean,则什么也不会发生(属性没有设置)。 |
constructor | 类似于byType,但适用于构造函数参数。如果容器中没有一个构造函数参数类型的bean,则会引发致命错误。 |
使用byType或constructor自动装配模式,您可以连接数组和类型化集合。在这种情况下,将提供容器中与预期类型匹配的所有autowire候选对象,以满足依赖性。如果期望的键类型是String,则可以自动装配强类型Map实例。自动装配Map实例的值由所有匹配期望类型的bean实例组成,Map实例的键包含相应的bean名称。
自动装配的局限性和缺点
自动装配在跨项目一致使用时效果最好。如果通常不使用自动装配,那么开发人员使用它来连接一个或两个bean定义可能会感到困惑。
考虑自动装配的局限性和缺点:
- property和constructor-arg设置中的显式依赖项总是覆盖自动装配。您不能自动生成简单的属性,如基本类型、Strings和Classes(以及这些简单属性的数组)。这种限制是由设计造成的。
- 自动装配不如显式连接精确。尽管如前面的表中所述,Spring小心地避免猜测,以免产生可能产生意外结果的歧义。spring管理对象之间的关系不再明确地记录下来。
- 装配信息可能对从Spring容器生成文档的工具不可用。
- 容器中的多个bean定义可能与要自动装配的setter方法或构造函数参数指定的类型相匹配。对于数组、集合或Map实例,这不一定是个问题。然而,对于期望单个值的依赖项,这种模糊性不是任意解决的。如果没有唯一的bean定义可用,则抛出异常。
在后一种情况下,你有几个选择:
- 放弃自动装配,支持显式连接。
- 通过将bean定义的autowire-candidate属性设置为false来避免自动装配,如下一节所述。
- 通过将其
<bean/>
元素的主属性设置为true,将单个bean定义指定为主候选。 - 实现基于注释的配置提供的更细粒度的控制,如基于注释的容器配置中所述。
从自动装配中排除一个Bean
在每个bean的基础上,可以将bean排除在自动装配之外。在Spring的XML格式中,将<bean/>
元素的autowire-candidate属性设置为false。容器使特定的bean定义对自动装配基础设施(包括诸如@Autowired之类的注释样式配置)不可用。
autowire-candidate属性被设计为只影响基于类型的自动装配。它不会影响按名称显示的引用,即使指定的bean没有标记为autowire候选bean,也会解析这些引用。因此,如果名字匹配,自动装配仍然会注入一个bean。
您还可以根据bean名称的模式匹配来限制自动装配候选对象。顶级<beans/>
元素在其default-autowire-candidate属性中接受一个或多个模式。例如,要将autowire候选状态限制为名称以Repository结尾的任何bean,请提供一个值*Repository。要提供多个模式,请在逗号分隔的列表中定义它们。bean定义的autowire-candidate属性的显式值true或false总是优先。对于这样的bean,不适用模式匹配规则。
这些技术对于那些您不希望通过自动装配将其注入到其他bean中的bean非常有用。这并不意味着不能通过使用自动装配来配置被排除的bean。相反,bean本身不是自动装配其他bean的候选对象。
方法注入
在大多数应用程序场景中,容器中的大多数bean都是单例的。当一个单例bean需要与另一个单例bean协作,或者一个非单例bean需要与另一个非单例bean协作时,通常通过将一个bean定义为另一个bean的属性来处理依赖性。当bean的生命周期不同时,就会出现问题。假设单例bean A需要使用非单例(原型)bean B,可能是在A的每个方法调用上。容器只创建一次单例bean A,因此只有一次机会来设置属性。容器不能每次需要bean B的新实例时都向bean A提供一个。
解决的办法是放弃一些控制反转。您可以通过实现ApplicationContextAware接口,并在每次bean A需要时调用容器的getBean(“B”)来请求(通常是一个新的)bean B实例,从而使bean A知道容器。下面的例子展示了这种方法:
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
前一种方法是不可取的,因为业务代码知道Spring框架并与之耦合。方法注入是Spring IoC容器的一个比较高级的特性,它允许您干净地处理这个用例。
您可以在这篇博客中了解更多关于方法注入的动机。
查找方法注入
查找方法注入是容器覆盖容器管理bean上的方法并返回容器中另一个命名bean的查找结果的能力。查找通常涉及到一个原型bean,如前一节描述的场景所示。Spring框架通过使用来自CGLIB库的字节码生成来动态生成覆盖该方法的子类,从而实现了这种方法注入。
要使这个动态子类工作,Spring bean容器子类的类不能是final,要覆盖的方法也不能是final。
对具有抽象方法的类进行单元测试需要您自己对该类进行子类化,并提供抽象方法的存根实现。
组件扫描也需要具体的方法,这需要具体的类来获取。
进一步的关键限制是,查找方法不能与工厂方法一起工作,特别是不能与配置类中的@Bean方法一起工作,因为在这种情况下,容器不负责创建实例,因此不能动态地创建运行时生成的子类。
在前面代码片段中的CommandManager类的情况下,Spring容器动态地覆盖createCommand()方法的实现。CommandManager类没有任何Spring依赖项,正如重新处理的示例所示:
package fiona.apple;
// no more Spring imports!
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
在包含要注入的方法的client类中(本例中的CommandManager),要注入的方法需要以下形式的签名:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果方法是抽象的,则动态生成的子类实现该方法。否则,动态生成的子类将覆盖在原始类中定义的具体方法。考虑下面的例子:
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="myCommand"/>
</bean>
标识为commandManager的bean在需要myCommand bean的新实例时调用自己的createCommand()方法。如果实际需要的话,您必须小心地将myCommand bean部署为原型。如果它是单例的,那么每次都会返回相同的myCommand bean实例。
或者,在基于注释的组件模型中,您可以通过@Lookup注释声明一个查找方法,如下面的示例所示:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup("myCommand")
protected abstract Command createCommand();
}
或者,更具体地说,您可以依赖于根据声明的查找方法返回类型解析目标bean:
public abstract class CommandManager {
public Object process(Object commandState) {
MyCommand command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract MyCommand createCommand();
}
请注意,您通常应该使用具体的存根实现来声明此类带注释的查找方法,以便使它们与Spring的组件扫描规则兼容,其中抽象类在缺省情况下被忽略。此限制不适用于显式注册或显式导入的bean类。
访问范围不同的目标bean的另一种方法是ObjectFactory/ Provider注入点。将作用域bean视为依赖项。
您还可以(在org.springframework .beans.factory.config包中)找到ServiceLocatorFactoryBean是有用的。
任意的方法替换
与查找方法注入相比,一种不太有用的方法注入形式是能够用另一种方法实现替换托管bean中的任意方法。您可以安全地跳过本节的其余部分,直到您真正需要此功能为止。
对于基于xml的配置元数据,您可以使用replace-method元素将已部署bean的现有方法实现替换为其他方法实现。考虑下面的类,它有一个我们想要覆盖的叫做computeValue的方法:
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
实现org.springframe .bean .factory.support的类。MethodReplacer接口提供了新的方法定义,如下例所示:
/**
* meant to be used to override the existing computeValue(String)
* implementation in MyValueCalculator
*/
public class ReplacementComputeValue implements MethodReplacer {
public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
// get the input value, work with it, and return a computed result
String input = (String) args[0];
...
return ...;
}
}
部署原始类并指定方法覆盖的bean定义将类似于以下示例:
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
<!-- arbitrary method replacement -->
<replaced-method name="computeValue" replacer="replacementComputeValue">
<arg-type>String</arg-type>
</replaced-method>
</bean>
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
您可以在<replacedmethod/>
元素中使用一个或多个<argtype/>
元素来指示被覆盖的方法的方法签名。只有在方法重载且类中存在多个变量时,才需要对参数进行签名。为了方便起见,参数的类型字符串可以是完全限定类型名称的子字符串。例如,以下所有匹配java.lang.String:
java.lang.String
String
Str
因为参数的数量通常足以区分每一个可能的选择,所以这个快捷方式可以节省大量的输入,因为它允许您只输入与参数类型匹配的最短字符串。
bean范围
当您创建一个bean定义时,您将创建一个用于创建由该bean定义定义的类的实际实例的方法。bean定义是菜谱的想法很重要,因为它意味着,与类一样,您可以从一个菜谱创建多个对象实例。
您不仅可以控制要插入到由特定bean定义创建的对象中的各种依赖项和配置值,还可以控制由特定bean定义创建的对象的范围。这种方法强大而灵活,因为您可以选择通过配置创建的对象的范围,而不必在Java类级别上考虑对象的范围。可以将bean定义为部署在多种作用域中的一种。Spring框架支持6种作用域,其中4种只有在使用web-aware的ApplicationContext时才可用。您还可以创建自定义范围。
下表描述了支持的范围:
范围 | 描述 |
---|---|
singleton | (默认)为每个Spring IoC容器将单个bean定义作用于单个对象实例。 |
prototype | 将单个bean定义作用于任意数量的对象实例。 |
request | 将单个bean定义的范围限定为单个HTTP请求的生命周期。也就是说,每个HTTP请求都有自己的bean实例,这些实例是在单个bean定义的基础上创建的。仅在可web-aware的Spring应用程序上下文中有效。 |
session | 将单个bean定义的范围限定为HTTP会话的生命周期。仅在可web-aware的Spring应用程序上下文中有效。 |
application | 将单个bean定义作用于ServletContext的生命周期。仅在可web-aware的Spring应用程序上下文中有效。 |
websocket | 将单个bean定义作用于WebSocket的生命周期。仅在可web-aware的Spring应用程序上下文中有效。 |
从Spring 3.0开始,线程作用域是可用的,但默认情况下不注册。有关更多信息,请参阅SimpleThreadScope的文档。有关如何注册此或任何其他自定义范围的说明,请参阅使用自定义范围。
单例的范围
只管理一个单例bean的一个共享实例,所有对具有与该bean定义匹配的ID或ID的bean的请求都会导致Spring容器返回该特定bean实例。
换句话说,当您定义一个bean definition并将其定义为一个单例对象时,Spring IoC容器将仅创建该bean definition定义的对象的一个实例。此单一实例存储在此类单例bean的缓存中,该指定bean的所有后续请求和引用都将返回缓存的对象。下图显示了单例范围的工作方式:
Spring的单例bean概念与四人组(GoF)模式书中定义的单例模式不同。GoF单例对象对对象的作用域进行硬编码,这样每个类装入器只能创建一个特定类的实例。Spring单例的范围最好描述为每个容器和每个bean。这意味着,如果您在单个Spring容器中为特定类定义一个bean,那么Spring容器将创建由该bean定义定义的类的一个且仅一个实例。单例范围是Spring的默认范围。要在XML中将bean定义为单例,您可以定义一个bean,如下面的例子所示:
<bean id="accountService" class="com.something.DefaultAccountService"/>
<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
原型范围
bean部署的非单例原型范围导致在每次发出对特定bean的请求时都会创建一个新的bean实例。也就是说,bean被注入到另一个bean中,或者您通过容器上的getBean()方法调用请求它。通常,您应该为所有有状态bean使用原型范围,为无状态bean使用单例范围。
下图说明了Spring原型的作用域:
(通常不将数据访问对象配置为原型,因为典型的DAO不包含任何会话状态。我们更容易重用单例图的核心。)
下面的例子在XML中将bean定义为原型:
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
与其他作用域不同,Spring不管理原型bean的完整生命周期。容器实例化、配置或以其他方式组装原型对象并将其交给客户端,而不需要该原型实例的进一步记录。因此,尽管初始化生命周期回调方法在所有对象上都被调用,而与范围无关,但是在原型的情况下,配置的销毁生命周期回调不会被调用。客户机代码必须清理原型作用域的对象,并释放原型bean持有的昂贵资源。为了让Spring容器释放原型作用域bean所持有的资源,可以尝试使用一个自定义bean后处理器,它持有一个需要清理的bean引用。
在某些方面,Spring容器在原型作用域bean方面的角色可以替代Java new操作符。所有超过那个点的生命周期管理都必须由客户端处理。(有关Spring容器中bean生命周期的详细信息,请参阅生命周期回调。)
具有原型bean依赖项的单例bean
当您使用依赖于原型bean的单例作用域bean时,请注意依赖项是在实例化时解析的。因此,如果您依赖地将一个原型作用域的bean注入到一个单例作用域的bean中,那么一个新的原型bean将被实例化,然后依赖地注入到单例bean中。prototype实例是惟一提供给单例作用域bean的实例。
但是,假设您希望单例作用域bean在运行时重复获取原型作用域bean的新实例。您不能依赖地将一个原型作用域的bean注入到您的单例bean中,因为这种注入只发生一次,当Spring容器实例化单例bean并解析和注入它的依赖项时。如果您需要在运行时多次使用原型bean的新实例,请参阅方法注入
请求、会话、应用程序和WebSocket作用域
只有在使用web-aware的Spring ApplicationContext实现(如XmlWebApplicationContext)时,请求、会话、应用程序和websocket作用域才可用。如果您将这些作用域与常规的Spring IoC容器(如ClassPathXmlApplicationContext)一起使用,则会抛出一个IllegalStateException,它会报错一个未知的bean作用域。
最初的网络配置
为了在请求、会话、应用程序和websocket级别(web范围的bean)上支持bean的作用域,需要在定义bean之前进行一些小的初始配置。(标准范围:单例和原型不需要这个初始设置。)
如何完成这个初始设置取决于特定的Servlet环境。
如果您在Spring Web MVC中访问作用域bean,实际上,在由Spring DispatcherServlet处理的请求中,不需要特殊的设置。DispatcherServlet已经公开了所有相关状态。
如果您使用Servlet 2.5 web容器,并在Spring的DispatcherServlet之外处理请求(例如,在使用JSF或Struts时),那么您需要注册org.springframework.web.context.request.RequestContextListener ServletRequestListener。对于Servlet 3.0+,这可以通过使用WebApplicationInitializer接口以编程方式实现。或者,对于较旧的容器,将以下声明添加到web应用程序的web.xml文件中:
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
...
</web-app>
DispatcherServlet、RequestContextListener和RequestContextFilter都做完全相同的事情,即将HTTP请求对象绑定到服务该请求的线程。这使得在请求和会话范围内的bean可以在调用链的更底层使用。
请求范围
请考虑以下bean定义的XML配置:
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
通过为每个HTTP请求使用LoginAction bean定义,Spring容器创建了LoginAction bean的新实例。也就是说,loginAction bean的作用域在HTTP请求级别。您可以随意更改创建的实例的内部状态,因为从相同的loginAction bean定义创建的其他实例在状态中看不到这些更改。它们是特定于个人请求的。当请求完成处理时,作用域为请求的bean被丢弃。
在使用注释驱动的组件或Java配置时,可以使用@RequestScope注释将组件分配给请求范围。下面的例子演示了如何做到这一点:
@RequestScope
@Component
public class LoginAction {
// ...
}
会话范围
请考虑以下bean定义的XML配置:
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
通过为单个HTTP Session的生命周期使用UserPreferences bean定义,Spring容器创建了UserPreferences bean的一个新实例。换句话说,userPreferences bean有效地限定在HTTP Session级别的范围内。与使用请求范围的bean一样,您可以根据需要更改创建的实例的内部状态, 知道其他也使用相同userPreferences bean定义创建的实例的HTTP会话实例不会在状态中看到这些更改,因为它们是特定于单个HTTP会话的。当HTTP Session最终被丢弃时,作用域为该特定HTTP Session的bean也被丢弃。
在使用注释驱动的组件或Java配置时,可以使用@SessionScope注释将组件分配给会话范围。
@SessionScope
@Component
public class UserPreferences {
// ...
}
应用范围
请考虑以下bean定义的XML配置:
<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
通过为整个web应用程序使用一次AppPreferences bean定义,Spring容器创建了AppPreferences bean的一个新实例。也就是说,appPreferences bean的作用域在ServletContext级别,并存储为一个常规的ServletContext属性。这有点类似于Spring单例bean,但在两个重要方面有所不同: 它是每个ServletContext的单例对象,而不是每个Spring ‘ApplicationContext’(在任何给定的web应用程序中可能有多个),它实际上是公开的,因此作为ServletContext属性可见。
在使用注释驱动的组件或Java配置时,可以使用@ApplicationScope注释将组件分配给应用程序范围。下面的例子演示了如何做到这一点:
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
将限定作用域的bean作为依赖项
Spring IoC容器不仅管理对象(bean)的实例化,还管理协作者(或依赖项)的连接。如果您想将(例如)一个HTTP请求作用域的bean注入到另一个更长的作用域的bean中,您可以选择注入一个AOP代理来代替作用域的bean。也就是说,您需要注入一个代理对象,它与作用域对象公开相同的公共接口,但也可以从相关作用域(如HTTP请求)检索实际目标对象,并将方法调用委托给实际对象。
你也可以在定义为单例的bean之间使用
<aop:scoped-proxy/>
,然后引用通过一个可序列化的中间代理,因此能够在反序列化时重新获得目标单例bean。
当对范围原型的bean声明<aop:scoped-proxy/>
时,共享代理上的每个方法调用都会导致创建一个新的目标实例,然后将调用转发给该实例。
而且,作用域代理不是以生命周期安全的方式从较短的作用域访问bean的惟一方法。你也可以声明你的注射点(也就是构造函数或setter参数或autowired的字段)作为ObjectFactory<MyTargetBean>
,允许getObject()调用来检索当前实例对需求每次需要——没有分别持有实例或存储它。
作为扩展的变体,您可以声明ObjectProvider<MyTargetBean>
,它提供了几个额外的访问变体,包括getIfAvailable和getIfUnique。
该方法的JSR-330变体称为Provider,它与Provider<MyTargetBean>
声明一起使用,并在每次检索尝试时调用相应的get()调用。有关JSR-330的更多细节,请参见这里。
下面例子中的配置只有一行,但是理解它背后的“为什么”和“如何”是很重要的:
<?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.something.UserPreferences" scope="session">
<!-- instructs the container to proxy the surrounding bean -->
<aop:scoped-proxy/>
</bean>
<!-- a singleton-scoped bean injected with a proxy to the above bean -->
<bean id="userService" class="com.something.SimpleUserService">
<!-- a reference to the proxied userPreferences bean -->
<property name="userPreferences" ref="userPreferences"/>
</bean>
</beans>
定义代理的行。
要创建这样的代理,需要将一个子<aop:scoped-proxy/>
元素插入到有作用域的bean定义中(参见选择要创建的代理类型和基于XML模式的配置)。为什么在请求、会话和自定义范围级别定义作用域的bean需要<aop:scoped-proxy/>
元素?考虑一下下面的单例bean定义,并将它与您需要为前面提到的作用域定义的内容进行对比(请注意,下面的userPreferences bean定义是不完整的):
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
在前面的示例中,单例bean (userManager)被注入了对HTTP会话范围的bean的引用(userPreferences)。这里的要点是userManager bean是单例的:它仅针对每个容器实例化一次,并且它的依赖项(在本例中只有一个,即userPreferences bean)也只注入一次。这意味着userManager bean只对完全相同的userPreferences对象(即它最初被注入的对象)进行操作。
这不是您在将一个较短的作用域bean注入到一个较长的作用域bean时想要的行为(例如,将一个HTTP Session作用域的协作bean作为依赖项注入到单例bean中)。相反,您需要一个单一的userManager对象,并且对于HTTP Session的生存期,您需要一个特定于HTTP Session的userPreferences对象。因此,容器创建一个对象,该对象公开与UserPreferences类完全相同的公共接口(理想情况下是UserPreferences实例的对象),该对象可以从作用域机制(HTTP请求、Session,等等)获取真正的UserPreferences对象。容器将此代理对象注入userManager bean,该bean不知道此UserPreferences引用是代理。在本例中,当UserManager实例调用依赖注入的UserPreferences对象上的方法时,它实际上是在调用代理上的方法。然后代理从HTTP Session(在本例中)获取实际的UserPreferences对象,并将方法调用委托给检索到的实际UserPreferences对象。
因此,在将请求和会话范围的bean注入到协作对象中时,您需要以下(正确和完整的)配置,如下面的示例所示:
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
<aop:scoped-proxy/>
</bean>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
选择要创建的代理的类型
默认情况下,当Spring容器为用<aop:scoped-proxy/>
元素标记的bean创建代理时,将创建一个基于cglib的类代理。
CGLIB代理只拦截公共方法调用!不要在这样的代理上调用非公共方法。它们没有被委托给实际作用域的目标对象。
或者,您可以配置Spring容器,为这种作用域bean创建标准的基于JDK接口的代理,方法是为<aop:scoped-proxy/>
元素的proxy-target-class属性的值指定false。使用基于JDK接口的代理意味着您不需要在应用程序类路径中添加其他库来影响这种代理。但是,这也意味着作用域bean的类必须实现至少一个接口,并且所有注入作用域bean的合作者必须通过它的一个接口引用bean。下面的例子展示了一个基于接口的代理:
<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.stuff.DefaultUserPreferences" scope="session">
<aop:scoped-proxy proxy-target-class="false"/>
</bean>
<bean id="userManager" class="com.stuff.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
有关选择基于类或基于接口的代理的详细信息,请参阅代理机制。
自定义范围
bean作用域机制是可扩展的。您可以定义自己的作用域,甚至可以重新定义现有的作用域,尽管后者被认为是不好的实践,并且您不能覆盖内置的单例和原型作用域。
创建自定义范围
要将您的自定义范围集成到Spring容器中,您需要实现org.springframework.bean .factory.config.Scope接口,将在本节中描述。要了解如何实现您自己的作用域,请参阅Spring框架本身提供的Scope实现和Scope javadoc,这将更详细地解释您需要实现的方法。
Scope接口有四种方法来从作用域获取对象,从作用域删除对象,然后销毁对象。
例如,会话范围实现将返回会话范围的bean(如果该bean不存在,则该方法将在将其绑定到会话以供将来引用后返回该bean的新实例)。下面的方法从底层范围返回对象:
Object get(String name, ObjectFactory<?> objectFactory)
例如,会话范围实现从基础会话中删除会话范围的bean。应该返回该对象,但是如果没有找到具有指定名称的对象,则可以返回null。下面的方法将对象从底层范围中移除:
Object remove(String name)
以下方法注册当范围被销毁或范围内的指定对象被销毁时应该执行的回调:
void registerDestructionCallback(String name, Runnable destructionCallback)
有关销毁回调的更多信息,请参阅javadoc或Spring作用域实现。
下面的方法获取底层作用域的对话标识符:
String getConversationId()
这个标识符对于每个范围都是不同的。对于会话范围的实现,此标识符可以是会话标识符。
使用自定义范围
在编写和测试一个或多个自定义Scope实现之后,您需要让Spring容器知道您的新范围。下面的方法是向Spring容器注册新Scope的中心方法:
void registerScope(String scopeName, Scope scope);
此方法在ConfigurableBeanFactory接口上声明,该接口可通过Spring附带的大多数具体ApplicationContext实现上的BeanFactory属性获得。
registerScope(…)方法的第一个参数是与范围关联的惟一名称。Spring容器中此类名称的例子有singleton和prototype。registerScope(…)方法的第二个参数是您希望注册和使用的自定义Scope实现的实际实例。
假设您编写了自定义Scope实现,然后在下一个示例中注册它。
下一个例子使用了SimpleThreadScope,它包含在Spring中,但默认情况下没有注册。对于您自己的自定义Scope实现,指令将是相同的。
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
然后,您可以创建遵循自定义Scope的作用域规则的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="thing2" class="x.y.Thing2" scope="thread">
<property name="name" value="Rick"/>
<aop:scoped-proxy/>
</bean>
<bean id="thing1" class="x.y.Thing1">
<property name="thing2" ref="thing2"/>
</bean>
</beans>
在FactoryBean实现中放置
<aop:scoped-proxy/>
时,受作用域限制的是工厂bean本身,而不是从getObject()返回的对象。
自定义Bean的性质
Spring框架提供了许多接口,您可以使用它们来定制bean的性质。本节将它们分组如下:
生命周期回调
为了与容器对bean生命周期的管理进行交互,您可以实现Spring的InitializingBean和DisposableBean接口。容器为前者调用afterPropertiesSet(),为后者调用destroy(),以便bean在初始化和销毁bean时执行某些操作。
JSR-250 @PostConstruct和@PreDestroy注释通常被认为是在现代Spring应用程序中接收生命周期回调的最佳实践。使用这些注释意味着您的bean没有耦合到特定于spring的接口。有关详细信息,请参见使用@PostConstruct和@PreDestroy。
如果您不希望使用JSR-250注释,但仍然希望消除耦合,那么可以考虑init-method和destroy-method bean定义元数据。
在内部,Spring框架使用BeanPostProcessor实现来处理它能找到并调用适当方法的任何回调接口。如果您需要自定义特性或Spring默认不提供的其他生命周期行为,您可以自己实现BeanPostProcessor。有关更多信息,请参见容器扩展点。
除了初始化和销毁回调之外,spring管理的对象还可以实现Lifecycle接口,以便这些对象可以参与启动和关闭过程,这是由容器自身的生命周期驱动的。
生命周期回调接口将在本节中描述。
初始化回调
org.springframework.beans.factory.InitializingBean接口允许bean在容器设置了bean上所有必需的属性之后执行初始化工作。InitializingBean接口指定了一个方法:
void afterPropertiesSet() throws Exception;
.我们建议您不要使用InitializingBean接口,因为它会不必要地将代码耦合到Spring。另外,我们建议使用@PostConstruct注释或指定POJO初始化方法。对于基于xml的配置元数据,您可以使用init-method属性来指定具有void无参数签名的方法的名称。使用Java配置,您可以使用@Bean的initMethod属性。参见接收生命周期回调。考虑下面的例子:
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {
public void init() {
// do some initialization work
}
}
上面的例子与下面的例子(包含两个清单)的效果几乎完全相同:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {
@Override
public void afterPropertiesSet() {
// do some initialization work
}
}
然而,前面两个示例中的第一个并没有将代码耦合到Spring。
销毁回调
实现了org.springframework.beans.factory.DisposableBean接口后,当包含它的容器被销毁时,bean可以获得回调。DisposableBean接口指定一个方法:
void destroy() throws Exception;
我们建议您不要使用DisposableBean回调接口,因为它不必要地将代码与Spring绑定在一起。另外,我们建议使用@PreDestroy注释或指定bean定义支持的泛型方法。使用基于xml的配置元数据,您可以在<bean/>
上使用destroy-method属性。使用Java配置,您可以使用@Bean的destroyMethod属性。参见接收生命周期回调。考虑以下定义:
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {
public void cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
前面的定义与下面的定义几乎具有完全相同的效果:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {
@Override
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
然而,前面两个定义中的第一个并没有将代码耦合到Spring。
您可以为
<bean>
元素的destroy-method属性指定一个特殊的(推断)值,该值指示Spring自动检测特定bean类上的公共close或shutdown方法。(任何实现java.lang.AutoCloseable或java.io.Closeable因此将匹配。)您还可以在<beans>
元素的default-destroy-method属性上设置这个特殊的(推断出来的)值,以便将此行为应用于整个bean集(请参阅缺省初始化和销毁方法)。注意,这是Java配置的默认行为。
默认初始化和销毁方法
当您编写不使用特定于spring的InitializingBean和DisposableBean回调接口的初始化和销毁方法回调时,您通常会编写具有init()、initialize()、dispose()等名称的方法。理想情况下,这样的生命周期回调方法的名称在整个项目中是标准化的,这样所有开发人员都可以使用相同的方法名称并确保一致性。
您可以将Spring容器配置为“查找”指定的初始化并销毁每个bean上的回调方法名。这意味着,作为应用程序开发人员,您可以编写应用程序类并使用名为init()的初始化回调,而不必为每个bean定义配置init-method="init"属性。在创建bean时,Spring IoC容器调用该方法(并根据前面描述的标准生命周期回调契约)。该特性还强制对初始化和销毁方法回调使用一致的命名约定。
假设您的初始化回调方法命名为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.");
}
}
}
然后你可以在一个类似如下的bean中使用这个类:
<beans default-init-method="init">
<bean id="blogService" class="com.something.DefaultBlogService">
<property name="blogDao" ref="blogDao" />
</bean>
</beans>
顶级<beans/>
元素属性上的default-init-method属性的存在导致Spring IoC容器将bean类上一个名为init的方法识别为初始化方法回调。在创建和组装bean时,如果bean类有这样的方法,则在适当的时候调用它。
您可以通过在顶级<beans/>
元素上使用default-destroy-method属性来配置destroy方法回调(即在XML中)。
现有的bean类已经有了根据约定命名的回调方法,您可以通过使用<bean/>
本身的init-method和destroy-method属性指定(在XML中)方法名来覆盖默认值。
Spring容器保证在向bean提供所有依赖项后立即调用已配置的初始化回调。因此,在原始bean引用上调用初始化回调,这意味着AOP拦截器等还没有应用到bean上。首先完全创建一个目标bean,然后应用带有拦截器链的AOP代理(例如)。如果目标bean和代理是单独定义的,那么您的代码甚至可以绕过代理与原始目标bean进行交互。因此,将拦截器应用于init方法将是不一致的,因为这样做将把目标bean的生命周期耦合到它的代理或拦截器,并在代码直接与原始目标bean交互时留下奇怪的语义。
结合生命周期机制
从spring2.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管理对象都可以实现生命周期接口。然后,当ApplicationContext本身接收到启动和停止信号(例如,对于运行时的停止/重新启动场景)时,它将这些调用级联到该上下文中定义的所有生命周期实现。它通过委托给一个LifecycleProcessor来做到这一点,如下面的清单所示:
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
注意,LifecycleProcessor本身就是Lifecycle接口的扩展。它还添加了另外两种方法来对刷新和关闭的上下文做出响应。
请注意,常规的org.springframe .context.Lifecycle接口是显式启动和停止通知的普通契约,并不意味着在上下文刷新时自动启动。对于特定bean的自动启动(包括启动阶段)的细粒度控制,请考虑实现org.springframe .context.SmartLifecycle代替。
另外,请注意停止通知不能保证在销毁之前发出。在常规关闭时,所有Lifecycle bean在传播常规销毁回调之前首先收到一个停止通知。但是,在上下文的生存期或中止的刷新尝试中进行热刷新时,只调用destroy方法。
启动和关闭调用的顺序可能很重要。如果任何两个对象之间存在“依赖关系”,依赖方在其依赖项之后开始,在其依赖项之前停止。然而,有时,直接依赖关系是未知的。您可能只知道某种类型的对象应该先于另一种类型的对象启动。在这些情况下,SmartLifecycle接口定义了另一个选项,即在其超级接口上定义的getPhase()方法。下面的清单显示了阶段接口的定义:
public interface Phased {
int getPhase();
}
下面的清单显示了SmartLifecycle接口的定义:
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
启动时,具有最低相位的对象首先启动。停止时,执行相反的顺序。因此,一个实现SmartLifecycle的对象,它的getPhase()方法返回Integer.MIN_VALUE将是第一个开始和最后一个停止的值。在频谱的另一端,相位值为整数MAX_VALUE将指示该对象应该最后启动并首先停止(可能是因为它依赖于要运行的其他进程)。在考虑phase值时,同样重要的是要知道,对于任何没有实现SmartLifecycle的“正常”生命周期对象,默认的phase是0。因此,任何负相位值都表示一个对象应该在那些标准组件之前启动(并在它们之后停止)。对于任何正相位值,情况正好相反。
SmartLifecycle定义的stop方法接受回调。任何实现都必须在该实现的关闭过程完成后调用该回调的run()方法。这在必要时支持异步关闭,因为LifecycleProcessor接口的默认实现DefaultLifecycleProcessor会等待每个阶段中的对象组的超时值来调用回调。默认的每个阶段超时时间是30秒。您可以通过在上下文中定义一个名为lifecycleProcessor的bean来覆盖默认的lifecycle processor实例。如果您只想修改超时,定义以下内容就足够了:
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
如前所述,LifecycleProcessor接口还定义了用于刷新和关闭上下文的回调方法。后者驱动关闭进程,就好像已经显式地调用了stop(),但它是在上下文关闭时发生的。另一方面,“refresh”回调启用了SmartLifecycle bean的另一个特性。当上下文被刷新时(在所有对象被实例化和初始化之后),该回调被调用。此时,默认的生命周期处理器将检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值。如果为真,则在该点启动该对象,而不是等待上下文的显式调用或其自身的start()方法(与上下文刷新不同,对于标准上下文实现,上下文启动不会自动发生)。正如前面所述,阶段值和任何“依赖”关系决定启动顺序。
在非web应用程序中优雅地关闭Spring IoC容器
本节仅适用于非web应用程序。Spring的基于web的ApplicationContext实现已经有了适当的代码,可以在相关web应用程序关闭时优雅地关闭Spring IoC容器。
如果您在非web应用程序环境(例如,在富客户机桌面环境中)中使用Spring的IoC容器,请向JVM注册一个关闭挂钩。这样做可以确保优雅地关闭并调用单例bean上的相关销毁方法,以便释放所有资源。您仍然必须正确配置和实现这些销毁回调。
要注册一个关闭钩子,请调用在ConfigurableApplicationContext接口上声明的registerShutdownHook()方法,如下面的示例所示:
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Boot {
public static void main(final String[] args) throws Exception {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
// add a shutdown hook for the above context...
ctx.registerShutdownHook();
// app runs here...
// main method exits, hook is called prior to the app shutting down...
}
}
ApplicationContextAware和BeanNameAware
当ApplicationContext创建一个实现org.springframework.context.ApplicationContextAware接口的对象实例时,实例提供了该ApplicationContext的引用。下面的清单显示了applicationcontext - ware接口的定义:
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
因此,bean可以通过编程方式操作创建它们的ApplicationContext,方法是通过ApplicationContext接口,或者通过将引用转换为该接口的一个已知子类(例如ConfigurableApplicationContext,它公开了额外的功能)。一种用途是通过编程检索其他bean。有时这种能力是有用的。但是,一般来说,您应该避免使用它,因为它将代码与Spring耦合在一起,并且不遵循控制反转的风格,即将协作者作为属性提供给bean。ApplicationContext的其他方法提供对文件资源的访问、发布应用程序事件和访问MessageSource。这些附加功能在ApplicationContext的附加功能中进行了描述。
自动装配是获取ApplicationContext引用的另一种选择。传统的constructor和byType自动装配模式(如自动装配协作者中所述)可以分别为构造函数参数或setter方法参数提供ApplicationContext类型的依赖项。为了获得更大的灵活性,包括自动装配字段和多个参数方法的能力,可以使用基于注释的自动装配特性。如果你这样做了,ApplicationContext就会被自动拖放到一个字段、构造函数参数或者方法参数中,如果这个字段、构造函数或者方法带有@Autowired注解,那么这个参数就会期望ApplicationContext类型。更多信息,请参见使用@Autowired。
当ApplicationContext创建一个实现org.springframework.bean.factory.BeanNameAware接口的类时。该类提供了对其关联对象定义中定义的名称的引用。下面的清单显示了BeanNameAware接口的定义:
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
回调是在填充普通bean属性之后,但在初始化回调(如InitializingBean、afterPropertiesSet或自定义init-method)之前调用的。
其他Aware接口
除了ApplicationContextAware和BeanNameAware(前面讨论过)之外,Spring还提供了广泛的Aware回调接口,让bean向容器表明它们需要某种基础设施依赖关系。通常,名称表示依赖项类型。下表总结了最重要的Aware接口:
名称 | 依赖注入 | …中阐述 |
---|---|---|
ApplicationContextAware | 声明ApplicationContext 。 | ApplicationContextAware和BeanNameAware |
ApplicationEventPublisherAware | 封装的ApplicationContext的事件发布者。 | ApplicationContext的附加功能 |
BeanClassLoaderAware | 类装入器,用于装入bean类。 | 实例化bean |
BeanFactoryAware | 声明BeanFactory。 | ApplicationContextAware和BeanNameAware |
BeanNameAware | 声明bean的名称。 | ApplicationContextAware和BeanNameAware |
BootstrapContextAware | 容器运行其中的资源适配器BootstrapContext。通常只在JCA-aware的ApplicationContext实例中可用。 | JCA CCI |
LoadTimeWeaverAware | 为在加载时处理类定义而定义的weaver。 | 在Spring框架中使用AspectJ进行加载时编织 |
MessageSourceAware | 解析消息的配置策略(支持参数化和国际化)。 | ApplicationContext的附加功能 |
NotificationPublisherAware | Spring JMX通知发布程序。 | 通知 |
ResourceLoaderAware | 为低级访问资源而配置的加载程序。 | 资源 |
ServletConfigAware | 当前servlet配置容器运行。仅在可感知web的Spring应用程序上下文中有效。 | Spring MVC |
ServletContextAware | 容器在当前ServletContext中运行。仅在可感知web的Spring应用程序上下文中有效。 | Spring MVC |
再次注意,使用这些接口将您的代码绑定到Spring API,并且不遵循控制反转样式。因此,我们建议将它们用于需要对容器进行编程访问的基础设施bean。
Bean定义继承
bean定义可以包含很多配置信息,包括构造函数参数、属性值和特定于容器的信息,比如初始化方法、静态工厂方法名等等。子bean定义从父定义继承配置数据。子定义可以根据需要覆盖某些值或添加其他值。使用父bean和子bean定义可以节省大量输入。实际上,这是模板的一种形式。
如果您以编程方式使用ApplicationContext接口,则子bean定义由ChildBeanDefinition类表示。大多数用户在这个级别上不使用它们。相反,它们在类(如ClassPathXmlApplicationContext)中声明式地配置bean定义。当您使用基于xml的配置元数据时,您可以通过使用父属性来指示子bean定义,并将父bean指定为该属性的值。下面的例子演示了如何做到这一点:
<bean id="inheritedTestBean" abstract="true"
class="org.springframework.beans.TestBean">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="inheritsWithDifferentClass"
class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBean" init-method="initialize">
<property name="name" value="override"/>
<!-- the age property value of 1 will be inherited from parent -->
</bean>
注意父属性。
如果没有指定任何bean,则子bean定义使用来自父定义的bean类,但也可以覆盖它。在后一种情况下,子bean类必须与父类兼容(也就是说,它必须接受父类的属性值)。
子bean定义继承父bean的作用域、构造函数参数值、属性值和方法覆盖,并具有添加新值的选项。您指定的任何范围、初始化方法、销毁方法或静态工厂方法设置都将覆盖相应的父设置。
其余的设置总是取自子定义:依赖、自动装配模式、依赖项检查、单例和惰性初始化。
前面的示例通过使用抽象属性显式地将父bean定义标记为抽象。如果父定义没有指定类,则需要显式地将父bean定义标记为抽象,如下面的示例所示:
<bean id="inheritedTestBeanWithoutClass" abstract="true">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBeanWithoutClass" init-method="initialize">
<property name="name" value="override"/>
<!-- age will inherit the value of 1 from the parent bean definition-->
</bean>
父bean不能单独实例化,因为它是不完整的,而且它也被显式地标记为抽象的。当定义是抽象的时,它只能作为作为子定义的父定义的纯模板bean定义使用。试单独使用这样一个抽象的父bean,方法是将它引用为另一个bean的ref属性,或者使用父bean ID执行显式的getBean()调用,这会返回一个错误。类似地,容器的内部预实例化esingletons()方法忽略定义为抽象的bean定义。
默认情况下,ApplicationContext会预先实例化所有的单例对象。因此,它是重要的(至少对单例bean),如果你有一个(父)bean定义你只打算使用作为模板,这个定义指定了一个类,您必须确保设置abstract属性为true,否则应用程序上下文会(试图)pre-instantiate抽象的bean。
容器扩展点
通常,应用程序开发人员不需要继承ApplicationContext实现类。相反,可以通过插入特殊集成接口的实现来扩展Spring IoC容器。下面几节将介绍这些集成接口。
通过使用BeanPostProcessor定制bean
BeanPostProcessor接口定义了回调方法,您可以实现这些回调方法来提供您自己的(或覆盖容器的默认)实例化逻辑、依赖项解析逻辑等等。如果希望在Spring容器完成实例化、配置和初始化bean之后实现一些自定义逻辑,可以插入一个或多个自定义BeanPostProcessor实现。
您可以配置多个BeanPostProcessor实例,并且可以通过设置order属性来控制这些BeanPostProcessor实例的执行顺序。只有在BeanPostProcessor实现Ordered接口时才能设置此属性。如果您编写自己的BeanPostProcessor,您也应该考虑实现Ordered接口。有关详细信息,请参见BeanPostProcessor和Ordered接口的javadoc。请参阅关于BeanPostProcessor实例的程序性注册的说明。
BeanPostProcessor实例操作bean(或对象)实例。也就是说,Spring IoC容器实例化一个bean实例,然后BeanPostProcessor实例执行它们的工作。
BeanPostProcessor实例的作用域是每个容器。这仅在使用容器层次结构时才相关。如果您在一个容器中定义一个BeanPostProcessor,那么它只对该容器中的bean进行后处理。换句话说,在一个容器中定义的bean不会由在另一个容器中定义的BeanPostProcessor进行后处理,即使两个容器都是相同层次结构的一部分。
要更改实际的bean定义(即定义bean的蓝图),您需要使用BeanFactoryPostProcessor,如用BeanFactoryPostProcessor自定义配置元数据中所述。
要更改实际的bean定义(即定义bean的蓝图),您需要使用BeanFactoryPostProcessor,如用BeanFactoryPostProcessor自定义配置元数据中所述。
org.springframework.beans.factory.config.BeanPostProcessor接口正好由两个回调方法组成。当这样一个类被容器注册为后处理程序时,对于容器创建的每个bean实例,后处理程序从容器中获取回调,在调用容器初始化方法(如InitializingBean.afterPropertiesSet()或任何声明的init方法)之前,以及在任何bean初始化回调之后。后处理器可以对bean实例采取任何操作,包括完全忽略回调。bean后处理器通常检查回调接口,或者使用代理包装bean。为了提供代理包装逻辑,一些Spring AOP基础设施类被实现为bean后处理器。
ApplicationContext自动检测在实现BeanPostProcessor接口的配置元数据中定义的任何bean。ApplicationContext将这些bean注册为后处理器,以便以后在创建bean时调用它们。Bean后置处理器可以与任何其他Bean以相同的方式部署在容器中。
注意,当在配置类上使用@Bean工厂方法声明BeanPostProcessor时,工厂方法的返回类型应该是实现类本身,或者至少是org.springframework.bean .factory.config.BeanPostProcessor接口,清楚地指示该bean的后处理器特性。否则,ApplicationContext不能在完全创建它之前通过类型自动检测它。由于为了应用于上下文中其他bean的初始化,需要尽早实例化BeanPostProcessor,所以这种早期类型检测非常重要。
以编程方式注册BeanPostProcessor实例
虽然推荐的BeanPostProcessor注册方法是通过ApplicationContext自动检测(如前所述),但是您可以通过使用addBeanPostProcessor方法以编程方式针对ConfigurableBeanFactory注册它们。当您需要在注册之前计算条件逻辑,甚至在层次结构的上下文中复制bean post处理器时,这可能很有用。但是,请注意,以编程方式添加的BeanPostProcessor实例并不遵循Ordered接口。在这里,注册的顺序决定了执行的顺序。还要注意,以编程方式注册的BeanPostProcessor实例总是在通过自动检测注册的实例之前处理,而不考虑任何显式的顺序。
BeanPostProcessor实例和AOP自动代理
实现BeanPostProcessor接口的类是特殊的,容器以不同的方式对待它们。它们直接引用的所有BeanPostProcessor实例和bean在启动时实例化,作为ApplicationContext的特殊启动阶段的一部分。接下来,以排序的方式注册所有BeanPostProcessor实例,并将其应用于容器中所有后续的bean。因为AOP自动代理是作为一个BeanPostProcessor本身来实现的,所以无论是BeanPostProcessor实例还是它们直接引用的bean都不适合自动代理,因此,没有将方面编织到它们之中。
对于任何这样的bean,您应该看到一条信息日志消息:Bean someBean不适合被所有BeanPostProcessor接口处理(例如:不适合自动代理)。
如果通过使用autowiring或@Resource(可能会回到autowiring)将bean连接到BeanPostProcessor中,Spring可能会在搜索类型匹配依赖项候选项时访问意外的bean,因此,使它们不适合自动代理或其他类型的bean后处理。例如,如果您有一个带有@Resource注释的依赖项,其中字段或setter名称与bean的声明名称不直接对应,并且没有使用name属性,那么Spring将访问其他bean,以便根据类型匹配它们。
下面的示例演示如何在ApplicationContext中编写、注册和使用BeanPostProcessor实例。
例子:Hello World, BeanPostProcessor风格
第一个例子演示了基本用法。该示例显示了一个自定义BeanPostProcessor实现,该实现在容器创建每个bean时调用toString()方法,并将结果字符串打印到系统控制台。
下面的清单显示了自定义BeanPostProcessor实现类定义:
package scripting;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
// simply return the instantiated bean as-is
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean; // we could potentially return any object reference here...
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("Bean '" + beanName + "' created : " + bean.toString());
return bean;
}
}
下面的bean元素使用了InstantiationTracingBeanPostProcessor:
<?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>
注意,InstantiationTracingBeanPostProcessor是如何定义的。它甚至没有名称,因为它是一个bean,所以可以像其他bean一样依赖注入。(前面的配置还定义了一个由Groovy脚本支持的bean。有关Spring动态语言支持的详细信息,请参阅“动态语言支持”一章。
以下Java应用程序运行上述代码和配置:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;
public final class Boot {
public static void main(final String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
Messenger messenger = ctx.getBean("messenger", Messenger.class);
System.out.println(messenger);
}
}
前一个应用程序的输出类似如下:
Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961
例子:RequiredAnnotationBeanPostProcessor
将回调接口或注释与自定义BeanPostProcessor实现结合使用是扩展Spring IoC容器的常见方法。一个例子是Spring的RequiredAnnotationBeanPostProcessor——这是一个随Spring发行版一起发布的BeanPostProcessor实现,它确保使用(任意)注释标记的bean上的JavaBean属性实际上(配置为)被注入了一个值。
使用BeanFactoryPostProcessor自定义配置元数据
我们查看的下一个扩展点是org.springframe .bean .factory.config.BeanFactoryPostProcessor。此接口的语义与BeanPostProcessor的语义相似,但有一个主要区别:BeanFactoryPostProcessor操作bean配置元数据。也就是说,Spring IoC容器允许BeanFactoryPostProcessor读取配置元数据,并可能在容器实例化除BeanFactoryPostProcessor实例之外的任何bean之前更改它。
您可以配置多个BeanFactoryPostProcessor实例,并且可以通过设置order属性来控制这些BeanFactoryPostProcessor实例的运行顺序。但是,只有在BeanFactoryPostProcessor实现Ordered接口时才能设置此属性。如果您编写自己的BeanFactoryPostProcessor,您也应该考虑实现Ordered接口。有关更多细节,请参见BeanFactoryPostProcessor和Ordered接口的javadoc。
如果您想要更改实际的bean实例(即,从配置元数据创建的对象),那么您需要使用BeanPostProcessor(在前面通过使用BeanPostProcessor定制bean中描述过)。虽然在BeanFactoryPostProcessor中使用bean实例在技术上是可行的(例如,通过使用BeanFactory.getBean()),这样做会导致过早的bean实例化,违反标准容器生命周期。这可能会导致负面的副作用,比如绕过bean的后处理。
另外,BeanFactoryPostProcessor实例的作用域是每个容器。这仅在使用容器层次结构时才相关。如果您在一个容器中定义一个BeanFactoryPostProcessor,那么它只应用于该容器中的bean定义。一个容器中的Bean定义不会由另一个容器中的BeanFactoryPostProcessor实例进行后处理,即使两个容器都是相同层次结构的一部分。
当在ApplicationContext中声明bean工厂后处理器时,它将自动执行,以便将更改应用于定义容器的配置元数据。Spring包含许多预定义的bean工厂后处理程序,如PropertyOverrideConfigurer和PropertySourcesPlaceholderConfigurer。您还可以使用自定义BeanFactoryPostProcessor—例如,注册自定义属性编辑器。
ApplicationContext自动检测部署到其中的实现BeanFactoryPostProcessor接口的任何bean。它在适当的时候使用这些bean作为bean factory的后处理器。您可以像部署任何其他bean一样部署这些后处理器bean。
与BeanPostProcessor一样,您通常不希望将BeanFactoryPostProcessor配置为延迟初始化。如果没有其他bean引用Bean(Factory)PostProcessor,则根本不会实例化该后处理器。因此,将其标记为延迟初始化将被忽略,即使您在
<beans />
元素的声明中将default-lazy-init属性设置为true, Bean(Factory)PostProcessor也将被急切地实例化。
例如:类名替换PropertySourcesPlaceholderConfigurer
通过使用标准的Java属性格式,您可以使用PropertySourcesPlaceholderConfigurer将属性值从bean定义外部化到一个单独的文件中。这样做使部署应用程序的人员能够自定义特定于环境的属性,如数据库url和密码,而不需要修改主XML定义文件或容器文件的复杂性或风险。
考虑以下基于xml的配置元数据片段,其中定义了一个具有占位符值的数据源:
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<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>
该示例显示了从外部属性文件配置的属性。在运行时,PropertySourcesPlaceholderConfigurer应用于替换数据源的某些属性的元数据。要替换的值被指定为表单${property-name}的占位符,它遵循Ant和log4j以及JSP EL样式。
实际值来自另一个标准Java属性格式的文件:
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
因此,${jdbc.username}字符串在运行时被替换为值’sa’,相同的情况也适用于其他与属性文件中的键匹配的占位符值。PropertySourcesPlaceholderConfigurer检查bean定义的大多数属性和属性中的占位符。此外,您还可以自定义占位符前缀和后缀。
通过Spring 2.5中引入的context命名空间,您可以使用专用的配置元素来配置属性占位符。您可以在location属性中以逗号分隔的列表的形式提供一个或多个位置,如下面的示例所示:
<context:property-placeholder location="classpath:com/something/jdbc.properties"/>
PropertySourcesPlaceholderConfigurer不仅在您指定的Properties文件中查找属性。默认情况下,如果在指定的属性文件中找不到属性,它会检查Spring Environment属性和常规Java System属性。
您可以使用PropertySourcesPlaceholderConfigurer来替换类名,这在您必须在运行时选择特定的实现类时非常有用。下面的例子演示了如何做到这一点:
<bean class="org.springframework.beans.factory.config.PropertySourcesPlaceholderConfigurer">
<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时,也就是在non-lazy-init bean的ApplicationContext的preInstantiateSingletons()阶段,bean的解析将失败。
例子:PropertyOverrideConfigurer
另一个bean工厂后处理器PropertyOverrideConfigurer类似于PropertySourcesPlaceholderConfigurer,但与后者不同,原始定义可以有缺省值,也可以没有bean属性的值。如果覆盖的Properties文件没有某个bean属性的条目,则使用默认的上下文定义。
注意,bean定义不知道被覆盖,所以从XML定义文件中不能立即看出使用了覆盖配置程序。如果有多个PropertyOverrideConfigurer实例为同一个bean属性定义不同的值,由于覆盖机制,最后一个实例胜出。
beanName.property=value
下面的清单显示了该格式的一个示例:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
此示例文件可与容器定义一起使用,容器定义包含一个名为dataSource的bean,该bean具有驱动程序和url属性。
也支持复合属性名,只要路径的每个组件(被覆盖的最后一个属性除外)都是非空的(可能是由构造函数初始化的)。在下面的例子中,tom bean的fred属性的bob属性的sammy属性被设置为标量值123:
tom.fred.bob.sammy=123
指定的重写值总是文字值。它们不会被转换成bean引用。当XML bean定义中的原始值指定一个bean引用时,也适用这种约定。
通过在Spring 2.5中引入context命名空间,可以使用专用的配置元素配置属性覆盖,如下面的示例所示:
<context:property-override location="classpath:override.properties"/>
使用FactoryBean自定义实例化逻辑
您可以实现org.springframe .bean .factory.FactoryBean接口用于本身就是工厂的对象。
FactoryBean接口是一个可插入Spring IoC容器实例化逻辑的点。如果您有复杂的初始化代码,用Java表示比(可能的)冗长的XML更好,那么您可以创建自己的FactoryBean,在该类中编写复杂的初始化,然后将定制的FactoryBean插入容器中。
FactoryBean接口提供了三种方法:
- Object getObject(): 返回工厂创建的对象的实例。实例可以共享,这取决于这个工厂返回的是单例还是原型。
- boolean isSingleton(): 如果这个FactoryBean返回单例,则返回true,否则返回false。
- Class getObjectType(): 返回getObject()方法返回的对象类型,如果类型事先不知道,则返回null。
FactoryBean概念和接口在Spring框架的许多地方都使用。FactoryBean接口的50多个实现随Spring本身一起发布。
当您需要向容器请求实际的FactoryBean实例本身而不是它所生成的bean时,在调用ApplicationContext的getBean()方法时,在bean的id前面加上与符号(&)。因此,对于id为myBean的给定FactoryBean,在容器上调用getBean(“myBean”)将返回FactoryBean的产品,而调用getBean(“&myBean”)将返回FactoryBean实例本身。
基于注解的容器配置
对于配置Spring,注释是否优于XML?
基于注释的配置的引入提出了一个问题,即这种方法是否比XML“更好”。简而言之,答案是“视情况而定”。长篇大论的回答是,每种方法都有其优点和缺点,通常由开发人员决定哪种策略更适合他们。由于它们的定义方式,注释在其声明中提供了大量上下文,从而使配置更短、更简洁。然而,XML擅长在不接触源代码或不重新编译它们的情况下连接组件。一些开发人员喜欢将连接放在接近源的地方,而另一些人则认为带注释的类不再是pojo,而且配置变得分散,更难控制。
无论选择什么,Spring都可以同时容纳两种风格,甚至将它们混合在一起。值得指出的是,通过它的JavaConfig选项,Spring允许以一种非侵入性的方式使用注释,而不涉及目标组件源代码,并且,在工具方面,所有的配置风格都得到了Eclipse的Spring工具的支持。
XML设置的另一种替代方法是基于注释的配置,它依赖于将组件连接起来的字节码元数据,而不是尖括号声明。开发人员不使用XML来描述bean连接,而是通过使用相关类、方法或字段声明上的注释将配置移动到组件类本身。如示例中所述:RequiredAnnotationBeanPostProcessor,将BeanPostProcessor与注释结合使用是扩展Spring IoC容器的一种常见方法。例如,Spring 2.0引入了使用@Required注释强制执行所需属性的可能性。Spring 2.5使得采用相同的通用方法来驱动Spring的依赖项注入成为可能。本质上,@Autowired注解提供了与Autowiring合作者描述的相同的功能,但是更细粒度的控制和更广泛的适用性。Spring 2.5还增加了对JSR-250注释的支持,比如@PostConstruct和@PreDestroy。Spring 3.0增加了对JSR-330 (Java依赖注入)注释的支持,包含在javax.inject包中,如@Inject和@Named。有关这些注释的详细信息可以在相关部分找到。
注释注入在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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
(隐式注册的后处理器包括AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、PersistenceAnnotationBeanPostProcessor和前面提到的RequiredAnnotationBeanPostProcessor。)
<context:annotation-config/>
只在定义它的应用程序上下文中查找bean上的注释。这意味着,如果你把<context:annotation-config/>
放在一个DispatcherServlet的WebApplicationContext中,它只会检查你的控制器中的@Autowired bean,而不会检查你的服务。有关更多信息,请参见DispatcherServlet。
@Required
@Required注释应用于bean属性设置器方法,如下面的示例所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
该注释指出,必须在配置时通过bean定义中的显式属性值或通过自动装配填充受影响的bean属性。如果未填充受影响的bean属性,容器将抛出异常。这允许立即执行和显式失败,避免了以后出现NullPointerException实例或类似的情况。我们仍然建议将断言放入bean类本身(例如,放入init方法)。即使在容器外部使用类,这样做也会加强那些必需的引用和值。
从Spring Framework 5.1开始,@Required注释被正式弃用,支持为所需的设置使用构造函数注入(或InitializingBean.afterPropertiesSet()的自定义实现以及bean属性设置器方法)。
使用@Autowired
在本节包含的示例中,JSR 330的@Inject注释可以代替Spring的@Autowired注释。详情请看这里。
你可以将@Autowired注解应用到构造函数上,如下图所示:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
从Spring Framework 4.3开始,如果目标bean只定义一个构造函数,那么就不再需要在这样的构造函数上使用@Autowired注解。但是,如果有多个构造函数可用,那么必须至少有一个用@Autowired来注释,以便指示容器使用哪个构造函数。
您还可以将@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注释的注入点的类型一致声明的。否则,注入可能会由于运行时“没有找到类型匹配”错误而失败。
对于通过类路径扫描找到的xml定义的bean或组件类,容器通常预先知道具体的类型。但是,对于@Bean工厂方法,您需要确保声明的返回类型具有足够的表达能力。对于实现多个接口的组件或可能由其实现类型引用的组件,请考虑在工厂方法上声明最特定的返回类型(至少与引用bean的注入点所需的返回类型一样具体)。
您还可以指示Spring通过向需要该类型数组的字段或方法添加@Autowired注释来从ApplicationContext中提供特定类型的所有bean,如下例所示:
public class MovieRecommender {
@Autowired
private MovieCatalog[] movieCatalogs;
// ...
}
类型化集合也是如此,如下例所示:
public class MovieRecommender {
private Set<MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
您的目标bean可以实现org.springframework.core.Ordered接口或可以使用@Order或标准的@Priority注释,如果希望数组或列表中的项按特定顺序排序。否则,它们的顺序将遵循容器中相应的目标bean定义的注册顺序。
您可以在目标类级别和@Bean方法上声明@Order注释,可能是针对单个bean定义(如果多个定义使用相同的bean类)。@Order值可能会影响注入点的优先级,但是要注意它们不会影响单例启动顺序,这是一个由依赖关系和@DependsOn声明决定的正交关系。
注意,在@Bean级别上不能使用标准的javax.annotation.Priority注释,因为它不能在方法上声明。它的语义可以通过@Order值与针对每种类型的单个bean上的@Primary组合来建模。
即使是类型化的Map实例,只要期望的键类型是String,也可以自动生成。映射值包含预期类型的所有bean,键包含相应的bean名称,如下例所示:
public class MovieRecommender {
private Map<String, MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
默认情况下,当给定注入点没有匹配的候选bean可用时,自动装配将失败。对于声明的数组、集合或映射,至少需要一个匹配的元素。
默认行为是将带注释的方法和字段视为指示所需的依赖项。您可以改变这个行为,如下面的例子所示,使框架能够跳过一个不可满足的注入点,通过标记它为非必需的(例如,通过设置@Autowired中的required属性为false):
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired(required = false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
如果一个非必需的方法的依赖项(或者它的一个依赖项,在有多个参数的情况下)不可用,那么这个方法将不会被调用。在这种情况下,完全不会填充非必需字段,而是保留其默认值。
注入构造函数和工厂方法参数是一种特殊情况,因为@Autowired中的required属性有一些不同的含义,因为Spring的构造函数解析算法可能会处理多个构造函数。默认情况下,构造函数和工厂方法参数是有效需要的,但是在单一构造函数场景中有一些特殊的规则,例如,如果没有匹配的bean可用,则多元素注入点(数组、集合、映射)解析为空实例。这就允许了一种通用的实现模式,在这种模式中,所有依赖项都可以在一个唯一的多参数构造函数中声明——例如,在没有@Autowired注解的情况下声明为一个公共构造函数。
任何给定bean类只有一个构造函数可以声明@Autowired,并将required属性设置为true,这表明构造函数在用作Spring bean时是自动装配的。因此,如果required属性的默认值为true,那么只能使用@Autowired来注释单个构造函数。如果有多个构造函数声明注释,那么它们都必须声明required=false,以便被认为是自动装配的候选对象(类似于XML中的autowire=constructor)。将选择具有最多依赖项的构造函数,这些依赖项可以通过匹配Spring容器中的bean来满足。如果没有一个候选者可以满足,那么将使用主/默认构造函数(如果存在)。如果一个类一开始只声明一个构造函数,那么它总是会被使用,即使没有注释。注意,带注释的构造函数不一定是公共的。
在setter方法上,建议使用@Autowired的required属性,而不建议使用@Required注释。将required属性设置为false表示该属性对于自动装配目的不是必需的,如果不能自动装配,则忽略该属性。另一方面,@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)。可空)或只是利用Kotlin内置的空安全支持:
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() {
}
// ...
}
@Autowired、@Inject、@Value和@Resource注释由Spring BeanPostProcessor实现处理。这意味着您不能在自己的BeanPostProcessor或BeanFactoryPostProcessor类型(如果有的话)中应用这些注释。必须使用XML或Spring @Bean方法显式地“连接”这些类型。
使用@Primary微调基于注释的自动装配
由于按类型自动装配可能会产生多个候选对象,因此通常需要对选择过程有更多的控制。实现此目的的一种方法是使用Spring的@Primary注释。@Primary表示,当多个bean是要自动生成单值依赖项的候选bean时,应该优先考虑特定的bean。如果在候选bean中只存在一个主bean,那么它就成为autowired值。
考虑一下下面的配置,它将firstMovieCatalog定义为主MovieCatalog:
@Configuration
public class MovieConfiguration {
@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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog" primary="true">
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
使用限定符微调基于注释的自动装配
@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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog">
<qualifier value="main"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<qualifier value="action"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
具有main限定符值的bean与构造函数参数连接,构造函数参数使用相同的值进行限定。
带有action限定符值的bean与构造函数参数连接,构造函数参数使用相同的值进行限定。
对于回退匹配,bean名称被认为是默认的限定符值。因此,您可以使用main的id来定义bean,而不是使用嵌套的qualifier元素,从而得到相同的匹配结果。然而,尽管您可以使用这个约定来通过名称引用特定的bean,但是@Autowired基本上是关于带有可选语义限定符的类型驱动注入的。这意味着限定符值,即使使用了bean名称回退,在类型匹配集内也总是具有收缩语义。它们在语义上不表示对惟一bean id的引用。好的限定符值是main或EMEA或persistent,它们表示独立于bean id的特定组件的特征,对于匿名bean定义(如前面示例中的定义),可以自动生成这些特征。
正如前面所讨论的,限定符也适用于类型化集合—例如,Set<MovieCatalog>
。在这种情况下,根据声明的限定符,所有匹配的bean都作为一个集合注入。这意味着限定符不必是唯一的。相反,它们构成了过滤标准。例如,您可以使用相同的限定符值“action”定义多个MovieCatalog bean,所有这些都被注入到一个使用@Qualifier(“action”)注释的Set<MovieCatalog>
。
在类型匹配的候选对象中,让qualifier值对目标bean名进行选择,不需要在注入点使用@Qualifier注释。如果没有其他解析指示器(例如限定词或主标记),对于非唯一依赖情况,Spring将针对目标bean名称匹配注入点名称(即字段名或参数名),并选择同名的候选项(如果有的话)。
也就是说,如果您打算通过名称来表示注释驱动的注入,那么不要主要使用@Autowired,即使它能够通过bean名称在类型匹配的候选项中进行选择。相反,使用JSR-250 @Resource注释,它的语义定义是通过特定的名称来标识特定的目标组件,声明的类型与匹配过程无关。@Autowired具有相当不同的语义:在根据类型选择候选bean之后,指定的String限定符值只在那些类型选择的候选者中被考虑(例如,将一个account限定符与标记有相同限定符标签的bean相匹配)。
对于本身定义为集合、Map或数组类型的bean, @Resource是一个很好的解决方案,它通过惟一名称引用特定的集合或数组bean。也就是说,从4.3开始,集合,您可以通过Spring的@Autowired类型匹配算法来匹配Map和数组类型,只要元素类型信息保存在@Bean返回类型签名或集合继承层次结构中。在这种情况下,可以使用限定符值在相同类型的集合中进行选择,如前一段所述。
在4.3中,@Autowired还考虑了注入的自我引用(也就是说,返回到当前注入的bean的引用)。注意,自注入是一种退路。对其他组件的常规依赖始终具有优先级。从这个意义上说,自我推荐不参与常规的候选人选择,因此尤其不是主要的。相反,它们的优先级总是最低的。在实践中,你应该把自我引用作为最后的手段(例如,通过bean的事务代理调用同一实例上的其他方法)。在这种情况下,考虑将受影响的方法分解到一个单独的委托bean中。或者,您可以使用@Resource,它可以通过惟一的名称将代理获取到当前bean。
尝试将来自@Bean方法的结果注入到相同的配置类中也是一种有效的自引用场景。要么在方法签名中实际需要的地方(与configuration类中的autowired字段相反)惰性地解析这些引用,要么将受影响的@Bean方法声明为静态的,将它们与包含它们的configuration类实例及其生命周期解耦。否则,只在回退阶段考虑这些bean,而选择其他配置类上的匹配bean作为主要候选(如果可用)。
@Autowired适用于字段、构造函数和多参数方法,允许在参数级别通过限定符注释来缩小范围。相反,@Resource只支持带有单个参数的字段和bean属性设置器方法。因此,如果您的注入目标是一个构造函数或一个多参数方法,那么您应该坚持使用限定符。
您可以创建自己的自定义限定符注释。为此,定义一个注释并在定义中提供@Qualifier注释,如下面的示例所示:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
String value();
}
然后你可以在自动装配的字段和参数上提供自定义限定符,如下面的例子所示:
public class MovieRecommender {
@Autowired
@Genre("Action")
private MovieCatalog actionCatalog;
private MovieCatalog comedyCatalog;
@Autowired
public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
this.comedyCatalog = comedyCatalog;
}
// ...
}
接下来,您可以提供候选bean定义的信息。您可以添加<qualifier/>
标记作为<bean/>
标记的子元素,然后指定type和value来匹配您的自定义qualifier注释。类型与注释的完全限定类名匹配。另外,为了方便起见,如果不存在名称冲突的风险,您可以使用简短的类名。下面的例子演示了这两种方法:
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://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>
在类路径扫描和托管组件中,您可以看到一个基于注释的替代方法,它可以在XML中提供限定符元数据。具体地说,请参阅使用注释提供限定符元数据。
在某些情况下,使用没有值的注释可能就足够了。当注释服务于更一般的用途,并且可以跨多个不同类型的依赖项应用时,这可能很有用。例如,您可以提供一个离线目录,当没有可用的Internet连接时,可以搜索该目录。首先,定义简单的注释,如下例所示:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
然后将注释添加到要自动装配的字段或属性中,如下例所示:
public class MovieRecommender {
@Autowired
@Offline
private MovieCatalog offlineCatalog;
// ...
}
这一行添加了@Offline注释。
现在bean定义只需要一个限定符type,如下面的例子所示:
<bean class="example.SimpleMovieCatalog">
<qualifier type="Offline"/>
<!-- inject any dependencies required by this bean -->
</bean>
此元素指定限定符。
您还可以定义自定义限定符注释,除简单值属性外,它还接受命名属性。如果在要自动装配的字段或参数上指定了多个属性值,则bean定义必须匹配所有这些属性值,才能被视为自动装配候选。以下面的注释定义为例:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
String genre();
Format format();
}
在这种情况下,格式是enum,定义如下:
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元属性来代替<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://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>
使用泛型作为自动装配限定符
除了@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:
// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
private List<Store<Integer>> s;
使用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通过以下方式确定autowire候选对象:
- 每个bean定义的autowire-candidate值
<beans/>
元素上可用的任何default-autowire-candidate模式- @Qualifier注释的存在,以及在CustomAutowireConfigurer中注册的任何自定义注释
当多个bean符合autowire候选条件时,“主bean”的确定如下:如果候选bean中的一个bean定义的primary设置为true,则选择它。
使用@Resource注入
Spring还通过在字段或bean属性设置器方法上使用JSR-250 @Resource注释(javax.annotation.Resource)来支持注入。这是Java EE中的一种常见模式:例如,在jsf管理的bean和JAX-WS端点中。Spring也支持Spring管理对象的这种模式。
@Resource接受name属性。默认情况下,Spring将该值解释为要注入的bean名。换句话说,它遵循姓名语义,如下例所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder")
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
这一行注入一个@Resource。
如果没有显式指定名称,则默认名称派生自字段名或setter方法。对于字段,它采用字段名。对于setter方法,它采用bean属性名。下面的例子将把名为movieFinder的bean注入到它的setter方法中:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
注释提供的名称由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;
public MovieRecommender() {
}
// ...
}
context字段是基于已知的可解析依赖项类型:ApplicationContext注入的。
使用@Value
@Value通常用于注入外化属性:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name}") String catalog) {
this.catalog = catalog;
}
}
配置如下:
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }
和以下application.properties文件:
catalog.name=MovieCatalog
在这种情况下,catalog参数和字段将等于MovieCatalog值。
Spring提供了一个默认的宽松嵌入式值解析器。它将尝试解析属性值,如果不能解析,属性名(例如${catalog.name})将作为值注入。如果你想对不存在的值保持严格的控制,你应该声明一个PropertySourcesPlaceholderConfigurer bean,如下面的例子所示:
@Configuration
public class AppConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
使用JavaConfig配置PropertySourcesPlaceholderConfigurer时,@Bean方法必须是静态的。
如果无法解析任何${}占位符,则使用上述配置可确保Spring初始化失败。也可以使用setPlaceholderPrefix、setPlaceholderSuffix或setValueSeparator等方法来定制占位符。
默认情况下,Spring引导配置一个PropertySourcesPlaceholderConfigurer bean,它将从application.properties和application.yml文件获取属性。
Spring提供的内置转换器支持允许自动处理简单的类型转换(例如,转换为整数或int)。多个逗号分隔的值可以自动转换为字符串数组,而不需要额外的工作。
可以提供一个默认值如下:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
this.catalog = catalog;
}
}
Spring BeanPostProcessor在后台使用一个ConversionService来处理将@Value中的字符串值转换为目标类型的过程。如果你想为自己的自定义类型提供转换支持,你可以提供自己的ConversionService bean实例,如下例所示:
@Configuration
public class AppConfig {
@Bean
public ConversionService conversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addConverter(new MyCustomConverter());
return conversionService;
}
}
当@Value包含SpEL表达式时,该值将在运行时动态计算,如下例所示:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
this.catalog = catalog;
}
}
SpEL还支持使用更复杂的数据结构:
@Component
public class MovieRecommender {
private final Map<String, Integer> countOfMoviesPerCatalog;
public MovieRecommender(
@Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
}
}
使用@PostConstruct和@PreDestroy
CommonAnnotationBeanPostProcessor不仅可以识别@Resource注释,还可以识别JSR-250生命周期注释:javax.annotation.PostConstruct和javax.annotation.PreDestroy。在Spring 2.5中引入了对这些注释的支持,为初始化回调和销毁回调中描述的生命周期回调机制提供了一种替代方法。如果CommonAnnotationBeanPostProcessor是在Spring ApplicationContext中注册的,那么在生命周期的同一点上,就会调用携带这些注释之一的方法,即对应的Spring生命周期接口方法或显式声明的回调方法。在下面的例子中,缓存在初始化时被预填充,在销毁时被清除:
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
public void clearMovieCache() {
// clears the movie cache upon destruction...
}
}
有关组合各种生命周期机制的效果的详细信息,请参阅组合生命周期机制。
与@Resource一样,@PostConstruct和@PreDestroy注释类型也是JDK 6到8的标准Java库的一部分。但是,整个javax.annotation包在JDK 9中与核心Java模块分离,最终在JDK 11中被删除。如果需要,现在需要通过Maven Central获得javax.annotation-api工件,只需将其像其他库一样添加到应用程序的类路径中即可。
类路径扫描和托管组件
本章的大多数示例使用XML指定配置元数据,这些元数据在Spring容器中生成每个bean定义。上一节(基于注释的容器配置)演示了如何通过源代码级注释提供大量配置元数据。然而,即使在这些示例中,“基础”bean定义也是在XML文件中显式定义的,而注释只驱动依赖项注入。本节描述通过扫描类路径隐式检测候选组件的选项。候选组件是根据筛选条件匹配的类,并且具有注册到容器中的相应bean定义。这样就不需要使用XML来执行bean注册。相反,您可以使用注释(例如,@Component)、AspectJ类型表达式或您自己的自定义筛选条件来选择哪些类具有向容器注册的bean定义。
从Spring 3.0开始,Spring JavaConfig项目提供的许多特性都是Spring核心框架的一部分。这允许您使用Java而不是使用传统的XML文件定义bean。查看@Configuration、@Bean、@Import和@DependsOn注释,了解如何使用这些新特性。
@Component和更多的原型注释
@Repository注释是任何满足存储库角色或构造型(也称为数据访问对象或DAO)的类的标记。该标记的用途之一是异常的自动翻译,如异常翻译中所述。
Spring提供了进一步的构造型注解:@Component, @Service,和@Controller。@Component是任何spring管理组件的通用构造型。@Component是任何spring管理组件的通用构造型。@Repository、@Service和@Controller是@Component对更具体用例(分别在持久性、服务和表示层)的专门化。因此,您可以使用@Component来注释您的组件类,但是,通过使用@Repository、@Service或@Controller来注释它们,您的类更适合通过工具进行处理或与方面相关联。例如,这些构造型注释是切入点的理想目标。在Spring框架的未来版本中,@Repository、@Service和@Controller也可以包含额外的语义。因此,如果您在使用@Component或@Service作为服务层之间进行选择,那么@Service显然是更好的选择。类似地,如前所述,@Repository已经被支持作为持久层中自动异常转换的标记。
使用元注释和复合注释
Spring提供的许多注释可以在您自己的代码中用作元注释。元注释是可以应用于其他注释的注释。例如,前面提到的@Service注释是使用@Component进行元注释的,如下例所示:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
// ...
}
组件导致@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 {
// ...
}
有关更多细节,请参见Spring注释编程模型wiki页面。
自动检测类并注册Bean定义
Spring可以自动检测构造型类,并在ApplicationContext中注册相应的BeanDefinition实例。例如,以下两个类可以进行这种自动检测:
@Service
public class SimpleMovieLister {
private MovieFinder movieFinder;
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
@Repository
public class JpaMovieFinder implements MovieFinder {
// implementation elided for clarity
}
要自动检测这些类并注册相应的bean,您需要将@ComponentScan添加到您的@Configuration类中,其中basePackages属性是这两个类的公共父包。(或者,您可以指定一个逗号、分号或空格分隔的列表,其中包含每个类的父包。)
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
为了简单起见,前面的示例可以使用注释的值属性(即@ComponentScan(“org.example”))。
以下替代方法使用XML:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.example"/>
</beans>
<context:component-scan>
的使用隐含地启用了<context:annotation-config>
的功能。当使用<context:component-scan>
时,通常不需要包含<context:annotation-config>
元素。
扫描类路径包需要在类路径中存在相应的目录条目。当您使用Ant构建JAR时,请确保您没有激活JAR任务的files-only开关。而且,在某些环境中,类路径目录可能不会根据安全策略公开——例如,JDK 1.7.0_45或更高版本上的独立应用程序(这需要在清单中设置“托管库”——请参见https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。
在JDK 9的模块路径(Jigsaw)上,Spring的类路径扫描通常按预期工作。但是,请确保您的组件类是在您的module-info描述符中导出的。如果您希望Spring调用您的类的非公共成员,请确保它们是“打开的”(也就是说,它们在您的module-info描述符中使用了一个opens声明,而不是一个exports声明)。
此外,在使用组件扫描元素时,AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor都是隐式包含的。这意味着这两个组件是自动检测和连接在一起的——所有这些都不需要XML中提供的任何bean配置元数据。
您可以通过包含带false值的annotation-config属性来禁用AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor的注册。
使用过滤器自定义扫描
默认情况下,使用@Component、@Repository、@Service、@Controller、@Configuration注释的类,或者本身使用@Component注释的自定义注释是唯一检测到的候选组件。但是,您可以通过应用自定义过滤器来修改和扩展此行为。将它们作为@ComponentScan注释的includeFilters或excludeFilters属性添加(或作为<context:include-filter />
或<context:component-scan>
元素在XML配置中的<context:component-scan>
元素的<context:exclude-filter />
子元素添加)。每个筛选器元素都需要类型和表达式属性。下表描述了过滤选项:
过滤器种类 | 示例表达式 | 描述 |
---|---|---|
annotation (default) | org.example.SomeAnnotation | 在目标组件的类型级别上呈现或元呈现的注释。 |
assignable | org.example.SomeClass | 目标组件可分配给(扩展或实现)的类(或接口)。 |
aspectj | org.example…*Service+ | 要由目标组件匹配的AspectJ类型表达式。 |
regex | org.example.Default.* | 由目标组件的类名匹配的正则表达式。 |
custom | org.example.MyTypeFilter | org.springframework.core.type.TypeFilter接口的自定义实现。 |
下面的示例显示了忽略所有@Repository注释而使用“存根”存储库的配置:
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
...
}
下面的清单显示了等价的XML:
<beans>
<context:component-scan base-package="org.example">
<context:include-filter type="regex"
expression=".*Stub.*Repository"/>
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
</beans>
您还可以通过在注释上设置useDefaultFilters=false或提供use-default-filters="false"作为
<component-scan/>
元素的属性来禁用默认过滤器。这有效地禁用了对带有@Component、@Repository、@Service、@Controller、@RestController或@Configuration的注释类或元注释类的自动检测。
在组件中定义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和自定义限定符注释。
除了用于组件初始化之外,您还可以将@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最有意义。对于其他作用域,工厂方法只会看到在给定作用域中触发创建新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类中的对应方法不同。不同之处在于@Component类没有使用CGLIB来拦截方法和字段的调用。CGLIB代理是在@Configuration类中调用@Bean方法中的方法或字段来创建对协作对象的bean元数据引用的方法。这些方法不是用普通的Java语义调用的,而是通过容器来提供通常的Spring bean的生命周期管理和代理,即使是通过对@Bean方法的编程调用来引用其他bean时也是如此。相反,在普通的@Component类中调用@Bean方法中的方法或字段具有标准的Java语义,没有特殊的CGLIB处理或应用其他约束。
您可以将@Bean方法声明为静态方法,这样就可以调用它们,而不需要创建包含它们的配置类作为实例。这在定义后处理器bean(例如,类型为BeanFactoryPostProcessor或BeanPostProcessor)时特别有意义,因为这样的bean在容器生命周期的早期初始化,并且应该避免在那时触发配置的其他部分。
由于技术限制:CGLIB子类只能覆盖非静态方法,所以对静态@Bean方法的调用不会被容器截获,甚至在@Configuration类中也不会被截获(如本节前面所述)。因此,直接调用另一个@Bean方法具有标准的Java语义,从而直接从工厂方法本身返回独立的实例。
@Bean方法的Java语言可见性对Spring容器中的结果bean定义没有直接影响。您可以自由地声明您认为适合于非@Configuration类的工厂方法,也可以在任何地方声明静态方法。然而,@Configuration类中的常规@Bean方法需要被覆盖——也就是说,它们不能被声明为私有或final。
@Bean方法还可以在给定组件或配置类的基类上发现,也可以在组件或配置类实现的接口中声明的Java 8缺省方法上发现。这为组合复杂的配置安排提供了很大的灵活性,从Spring 4.2开始,甚至可以通过Java 8的默认方法进行多重继承。
最后,一个类可能包含同一个bean的多个@Bean方法,这是根据运行时可用的依赖关系安排使用的多个工厂方法。这与在其他配置场景中选择“最贪婪”的构造函数或工厂方法的算法是相同的:在构建时选择具有最大数量可满足依赖关系的变体,类似于容器在多个@Autowired构造函数之间进行选择。
命名自动检测的组件
当一个组件作为扫描过程的一部分被自动检测时,它的bean名称由该扫描程序所知道的BeanNameGenerator策略生成。默认情况下,任何包含名称value的Spring构造型注释(@Component、@Repository、@Service和@Controller)都将该名称提供给相应的bean定义。
如果这样的注释不包含名称value或任何其他检测到的组件(例如由自定义过滤器发现的组件),则默认bean名称生成器将返回未大写的非限定类名。例如,如果检测到以下组件类,其名称将是myMovieLister和movieFinderImpl:
@Service("myMovieLister")
public class SimpleMovieLister {
// ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
如果不希望依赖默认的bean命名策略,可以提供自定义bean命名策略。首先,实现BeanNameGenerator接口,并确保包含一个默认的无参数构造函数。然后,在配置扫描程序时提供完全限定的类名,如下面的注释和bean定义示例所示。
如果由于多个自动检测到的组件具有相同的非限定类名而导致命名冲突(即,类具有相同的名称,但驻留在不同的包中),您可能需要配置一个BeanNameGenerator,该生成器默认为生成的bean名称的完全限定类名。从Spring Framework 5.2.3开始,FullyQualifiedAnnotationBeanNameGenerator位于包org.springframework.context.annotation可以用于这样的目的。
@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>
作为一般规则,当其他组件可能显式引用它时,考虑使用注释指定名称。另一方面,只要容器负责连接,自动生成的名称就足够了。
为自动检测的组件提供范围
与通常的spring管理组件一样,自动检测组件的默认和最常见的范围是singleton。但是,有时您需要一个可以由@Scope注释指定的不同范围。您可以在注释中提供作用域的名称,如下面的示例所示:
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
@Scope注释仅在具体的bean类(用于带注释的组件)或工厂方法(用于@Bean方法)上内省。与XML bean定义相反,没有bean定义继承的概念,类级别的继承层次结构与元数据目的无关。
有关特定于web的作用域(如Spring上下文中的“request”或“session”)的详细信息,请参阅请求、会话、应用程序和WebSocket作用域。与为这些作用域预先构建的注释一样,您也可以通过使用Spring的元注释方法来编写自己的作用域注释:例如,使用@Scope(“prototype”)进行自定义注释元注释,也可以声明自定义作用域代理模式。
为了提供范围解析的自定义策略,而不是依赖于基于注释的方法,您可以实现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作为依赖项中描述。为此,在组件扫描元素上提供了作用域代理属性。这三个可能的值是:no、interface和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>
提供带有注释的限定符元数据
在使用限定符微调基于注释的自动装配中讨论了@Qualifier注释。本节中的示例演示了如何使用@Qualifier注释和自定义qualifier注释在解析autowire候选对象时提供细粒度控制。因为这些示例是基于XML bean定义的,所以通过使用XML中bean元素的qualifier或meta子元素在候选bean定义上提供qualifier元数据。当依赖类路径扫描来自动检测组件时,您可以在候选类上提供带有类型级注释的限定符元数据。以下三个例子演示了这种技术:
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
// ...
}
与大多数基于注释的替代方法一样,请记住,注释元数据绑定到类定义本身,而XML的使用允许相同类型的多个bean在其限定符元数据中提供变体,因为该元数据是按实例而不是按类提供的。
生成候选组件的索引
虽然类路径扫描非常快,但是可以通过在编译时创建静态候选列表来提高大型应用程序的启动性能。在这种模式下,所有作为组件扫描目标的模块都必须使用这种机制。
现有的@ComponentScan或<context:component-scan指令必须保持不变,以便请求上下文扫描某些包中的候选者。当ApplicationContext检测到这样的索引时,它会自动使用它,而不是扫描类路径。
要生成索引,需要向每个模块添加一个附加依赖项,其中包含的组件是组件扫描指令的目标。下面的示例展示了如何使用Maven实现这一点:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.2.5.RELEASE</version>
<optional>true</optional>
</dependency>
</dependencies>
在Gradle 4.5及更早版本中,依赖项应该在compileOnly配置中声明,如下例所示:
dependencies {
compileOnly "org.springframework:spring-context-indexer:5.2.5.RELEASE"
}
在Gradle 4.6及更高版本中,依赖项应该在annotationProcessor配置中声明,如下面的例子所示:
dependencies {
annotationProcessor "org.springframework:spring-context-indexer:{spring-version}"
}
该过程生成一个包含在jar文件中的META-INF/spring.components文件。
在IDE中使用这种模式时,必须将spring-context-indexer注册为注释处理器,以确保在更新候选组件时索引是最新的。
当在类路径中找到一个META-INF/spring.components时,将自动启用该索引。如果某个索引对于某些库(或用例)是部分可用的,但是不能为整个应用程序构建,那么可以通过设置spring.index退回到常规的类路径安排(就好像根本没有索引一样)。忽略true,不管是作为系统属性还是在spring中。类路径根目录下的属性文件。
使用JSR 330标准注释
从Spring 3.0开始,Spring提供了对JSR-330标准注释(依赖项注入)的支持。这些注释的扫描方式与Spring注释相同。要使用它们,您需要在类路径中有相关的jar。
如果使用Maven,则使用javax.inject工件可以在标准的Maven存储库中找到(https://repo1.maven.org/maven2/javax/inject/javax.inject/1/)。您可以将以下依赖项添加到您的文件pom.xml:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
具有@Inject和@Named的依赖项注入
您可以使用@javax.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) {
// ...
}
}
@Named和@ManagedBean:与@Component注释等价的标准
您可以使用@javax.inject.Named或javax.annotation.ManagedBean代替@Component,如下例所示:
import javax.inject.Inject;
import javax.inject.Named;
@Named("movieListener") // @ManagedBean("movieListener") could be used as well
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
使用@Component而不指定组件的名称是很常见的。@Named也可以以类似的方式使用,如下面的例子所示:
import javax.inject.Inject;
import javax.inject.Named;
@Named
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
当您使用@Named或@ManagedBean时,您可以使用与使用Spring注释完全相同的方式来使用组件扫描,如下面的示例所示:
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
与@Component不同,JSR-330 @Named和JSR-250 ManagedBean注释是不可组合的。您应该使用Spring的原型模型来构建定制的组件注释。
JSR-330标准注释的局限性
当您使用标准注释时,您应该知道一些重要的特性是不可用的,如下表所示:
Spring | javax.inject.* | javax.inject限制/注释 |
---|---|---|
@Autowired | @Inject | @Inject没有“required”属性。可以与Java 8的Optional一起使用。 |
@Component | @Named / @ManagedBean | JSR-330不提供可组合模型,只提供了一种识别命名组件的方法。 |
@Scope(“singleton”) | @Singleton | JSR-330默认范围类似于Spring的原型。但是,为了使它与Spring的一般默认值保持一致,在Spring容器中声明的JSR-330 bean在默认情况下是单例的。为了使用单例之外的范围,您应该使用Spring的@Scope注释。javax.inject还提供了一个@Scope注释。尽管如此,本教程仅用于创建您自己的注释。 |
@Qualifier | @Qualifier / @Named | javax.inject.Qualifier只是用于构建自定义限定符的元注释。具体的String限定符(比如Spring的@Qualifier和一个值)可以通过javax.inject.Named来关联。 |
@Value | - | 没有等价 |
@Required | - | 没有等价 |
@Lazy | - | 没有等价 |
ObjectFactory | Provider | javax.inject.Provider是Spring的ObjectFactory的直接替代方法,只是使用了更短的get()方法名。它还可以与Spring的@Autowired结合使用,也可以与没有注释的构造函数和setter方法结合使用。 |
基于java的容器配置
本节介绍如何在Java代码中使用注释来配置Spring容器。它包括下列主题:
- 基本概念:@Bean和@Configuration
- 通过使用注释AnnotationConfigApplicationContext实例化Spring容器
- 使用@Bean注释
- 使用@Configuration注释
- 编写基于java的配置
- Bean定义概要文件
- PropertySource抽象
- 使用@PropertySource
- 语句中的占位符解析
基本概念:@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模式?
当@Bean方法在没有使用@Configuration注释的类中声明时,它们被称为在“lite”模式中进行处理。在@Component或普通旧类中声明的Bean方法被认为是“精简的”,包含类的主要用途不同,而@Bean方法在这里是一种附加功能。例如,服务组件可以通过每个适用组件类上的附加@Bean方法向容器公开管理视图。在这些场景中,@Bean方法是一种通用的工厂方法机制。
与完整的@Configuration不同,lite @Bean方法不能声明bean之间的依赖关系。相反,它们对包含它们的组件的内部状态以及可能声明的参数(可选)进行操作。因此,这样的@Bean方法不应该调用其他@Bean方法。每个这样的方法实际上只是一个特定bean引用的工厂方法,没有任何特殊的运行时语义。这里的积极的副作用是不需要在运行时应用CGLIB子类,所以在类设计方面没有限制(也就是说,包含的类可能是final等等)。
在常见的场景中,@Bean方法是在@Configuration类中声明的,以确保总是使用“full”模式,从而将交叉方法引用重定向到容器的生命周期管理。这可以防止通过常规Java调用意外调用相同的@Bean方法,这有助于减少在“lite”模式下操作时难以跟踪的细微bug。
下面几节将深入讨论@Bean和@Configuration注释。但是,我们首先介绍了通过基于java的配置创建spring容器的各种方法。
通过使用注释AnnotationConfigApplicationContext实例化Spring容器
下面几节将介绍Spring 3.0中引入的注释AnnotationConfigApplicationContext。这个通用的ApplicationContext实现不仅可以接受@Configuration类作为输入,还可以接受普通的@Component类和使用JSR-330元数据注释的类。
当@Configuration类作为输入提供时,@Configuration类本身注册为bean定义,类中所有声明的@Bean方法也注册为bean定义。
当提供@Component和JSR-330类时,它们被注册为bean定义,并假设DI元数据(如@Autowired或@Inject)在这些类中使用。
简单构建
与在实例化ClassPathXmlApplicationContext时将Spring XML文件用作输入非常相似,您可以在实例化注释AnnotationConfigApplicationContext时将@Configuration类用作输入。这使得Spring容器可以完全不使用xml,如下面的例子所示:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
如前所述,AnnotationConfigApplicationContext不仅限于使用@Configuration类。任何@Component或JSR-330注释类都可以作为输入提供给构造函数,如下例所示:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
前面的示例假设MyServiceImpl、Dependency1和Dependency2使用了Spring依赖项注入注释,比如@Autowired。
通过使用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")
public class AppConfig {
...
}
该注释支持组件扫描。
有经验的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进行元注释的,因此它们是组件扫描的候选对象。在前面的例子中,假设AppConfig是在com.acme包(或下面的任何包)中声明的,它在调用scan()期间被获取。在refresh()中,它的所有@Bean方法都将作为bean定义在容器中进行处理和注册。
支持带有注释AnnotationConfigWebApplicationContext的Web应用程序
AnnotationConfigApplicationContext的WebApplicationContext变体可以与AnnotationConfigWebApplicationContext一起使用。您可以在配置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>
使用@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
您还可以使用接口(或基类)返回类型声明@Bean方法,如下面的示例所示:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
但是,这将高级类型预测的可见性限制为指定的接口类型(TransferService)。然后,容器只知道一次完整类型(TransferServiceImpl),受影响的单例bean就被实例化了。非惰性单例bean根据它们的声明顺序被实例化,因此您可能会看到不同的类型匹配结果,这取决于另一个组件何时试图通过未声明的类型进行匹配(例如@Autowired TransferServiceImpl,它只在transferService bean实例化之后才会解析)。
如果始终通过声明的服务接口引用类型,则@Bean返回类型可以安全地加入该设计决策。但是,对于实现多个接口的组件或可能由其实现类型引用的组件,声明最特定的返回类型是比较安全的(至少与引用bean的注入点所要求的一样具体)。
Bean的依赖关系
@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注释了解更多细节。
常规的Spring生命周期回调也得到了完全的支持。如果一个bean实现了InitializingBean、处置bean或生命周期,容器将调用它们各自的方法。
标准的*Aware接口集(如BeanFactoryAware、BeanNameAware、MessageSourceAware、ApplicationContextAware等)也得到了完全支持。
@Bean注释支持指定任意的初始化和销毁回调方法,很像Spring XML在bean元素上的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();
}
}
默认情况下,使用具有公共close或shutdown方法的Java配置定义的bean将被自动登记到销毁回调中。如果您有一个公共close或shutdown方法,并且您不希望在容器关闭时调用它,那么您可以向bean定义中添加@Bean(destroyMethod="")来禁用默认(推断)模式。
在缺省情况下,您可能希望对使用JNDI获得的资源这样做,因为它的生命周期是在应用程序之外管理的。特别要注意的是,一定要始终对一个DataSource这样做,因为在Java EE应用服务器上这样做是有问题的。
下面的例子展示了如何防止一个数据源的自动销毁回调:
@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
return (DataSource) jndiTemplate.lookup("MyDS");
}
@Bean方法,您通常使用程序化的JNDI查找,通过使用Spring的JndiTemplate或JndiLocatorDelegate助手或直接使用JNDI InitialContext但不是JndiObjectFactoryBean变体(这将迫使你声明返回类型作为FactoryBean类型,而不是实际的目标类型,因此很难使用交叉引用调用@Bean方法,打算在其他参考所提供的资源)。
对于上例中的BeanOne,在构造期间直接调用init()方法同样有效,如下例所示:
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
BeanOne beanOne = new BeanOne();
beanOne.init();
return beanOne;
}
// ...
}
您直接在Java中工作时,您可以对对象做任何您喜欢的事情,而不总是需要依赖容器生命周期。
指定Bean范围
Spring包含@Scope注释,因此您可以指定bean的范围。
使用@Scope注释
您可以指定使用@Bean注释定义的bean应该具有特定的范围。您可以使用Bean作用域部分中指定的任何标准作用域。
默认的范围是singleton,但是你可以用@Scope注释来覆盖它,如下面的例子所示:
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
@Scope和scoped-proxy
Spring通过作用域代理提供了处理作用域依赖项的方便方法。使用XML配置时创建这样一个代理的最简单方法是<aop:scoped-proxy/>
元素。用@Scope注释在Java中配置bean可以提供与proxyMode属性相同的支持。默认值是no proxy (ScopedProxyMode. no),但是您可以指定ScopedProxyMode.TARGET_CLASS或ScopedProxyMode.INTERFACES。
如果您使用Java将XML参考文档中的作用域代理示例(请参阅作用域代理)移植到我们的@Bean,它类似于以下内容:
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
public UserPreferences userPreferences() {
return new UserPreferences();
}
@Bean
public Service userService() {
UserService service = new SimpleUserService();
// a reference to the proxied userPreferences bean
service.setUserPreferences(userPreferences());
return service;
}
定制Bean命名
默认情况下,配置类使用@Bean方法的名称作为结果bean的名称。但是,可以使用name属性覆盖此功能,如下面的示例所示:
@Configuration
public class AppConfig {
@Bean(name = "myThing")
public Thing thing() {
return new Thing();
}
}
Bean别名
正如在bean的命名中所讨论的,有时希望为单个bean赋予多个名称,否则称为bean别名。@Bean注释的name属性为此接受一个字符串数组。下面的例子展示了如何为一个bean设置多个别名:
@Configuration
public class AppConfig {
@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
Bean描述
有时,提供bean的更详细的文本描述是有帮助的。当出于监控目的而公开bean(可能通过JMX)时,这一点特别有用。
要向@Bean添加描述,可以使用@Description注释,如下面的示例所示:
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Thing thing() {
return new Thing();
}
}
使用@Configuration注释
@Configuration是一个类级注释,指示对象是bean定义的源。@Configuration类通过公共的@Bean注释方法声明bean。在@Configuration类上调用@Bean方法也可以用来定义bean之间的依赖关系。参见基本概念:@Bean和@Configuration以获得一般介绍。
注入Inter-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的引用。
只有在@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 createCommand()
// 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();
}
}
在clientService1()中调用一次clientDao(),在clientService2()中调用一次。因为这个方法创建了一个新的ClientDaoImpl实例并返回它,所以您通常会希望有两个实例(每个服务一个)。这肯定会有问题:在Spring中,实例化的bean默认有一个单例范围。这就是神奇之处:所有@Configuration类都是在启动时用CGLIB子类化的。在子类中,子方法在调用父方法并创建新实例之前,首先检查容器是否有缓存的(作用域限定的)bean。
根据bean的范围,行为可能会有所不同。我们这里说的是单例。
从Spring 3.2开始,不再需要将CGLIB添加到类路径中,因为CGLIB类已经在org.springframework.cglib下重新打包了并直接包含在spring-core JAR中。
由于CGLIB在启动时动态添加特性,所以有一些限制。特别是,配置类不能是final。但是,从4.3开始,配置类上可以使用任何构造函数,包括使用@Autowired或单个非默认构造函数声明进行默认注入。
如果希望避免cglib强加的限制,可以考虑在非@Configuration类上声明@Bean方法(例如,在普通的@Component类上声明)。然后不会拦截@Bean方法之间的交叉方法调用,因此必须完全依赖于构造函数或方法级别的依赖注入。
组合基于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类。
从Spring Framework 4.2开始,@Import也支持对常规组件类的引用,类似于AnnotationConfigApplicationContext.register方法。如果您想避免组件扫描,这是特别有用的,通过使用一些配置类作为入口点来显式定义所有组件。
有条件地包含@Configuration类或@Bean方法
根据某些任意的系统状态,有条件地启用或禁用一个完整的@Configuration类,甚至单个的@Bean方法,这通常是很有用的。一个常见的例子是,只有在Spring环境中启用了特定的配置文件时,才使用@Profile注释来激活Bean(有关详细信息,请参阅Bean定义配置文件)。
@Profile注释实际上是通过使用一个灵活得多的名为@Conditional的注释来实现的。@Conditional注释表示在注册@Bean之前应该咨询的特定的org.springframework.context.annotation.Condition实现。
Condition接口的实现提供了一个返回true或false的matches(…)方法。例如,下面的清单显示了用于@Profile的实际Condition实现:
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// Read the @Profile annotation attributes
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
有关更多细节,请参见@Conditional javadoc。
结合Java和XML配置
Spring的@Configuration类支持的目标不是100%完全替代Spring XML。一些工具(如Spring XML namespaces)仍然是配置容器的理想方法。在XML方便或必要的情况下,你有一个选择:要么在容器实例化在一个“以XML为中心”的方式使用,例如,ClassPathXmlApplicationContext或实例化它“以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: annotated -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());
}
}
下面的示例显示了示例系统-test-config.xml文件的一部分:
<beans>
<!-- enable processing of annotations such as @Autowired and @Configuration -->
<context:annotation-config/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="com.acme.AppConfig"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
下面的示例显示了一种可能的jdbc.properties文件:
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
在system-test-config.xml文件中,AppConfig
<bean/>
没有声明id元素。虽然这样做是可以接受的,但这是不必要的,因为没有其他bean引用它,而且不太可能通过名称显式地从容器中获取它。类似地,DataSource bean只根据类型进行自动装配,因此并不严格要求显式bean id。
使用<context:component-scan/>
来获取@Configuration类
因为@Configuration使用@Component进行元注释,所以带有@Configuration注释的类自动成为组件扫描的候选类。使用与前面示例相同的场景,我们可以重新定义system-test-config.xml,以利用组件扫描。注意,在本例中,我们不需要显式地声明<context: annotated -config/>
,因为<context:component-scan/>
启用了相同的功能。
下面的示例显示了修改后的system-test-config.xml文件:
<beans>
<!-- picks up and registers AppConfig as a bean definition -->
<context:component-scan base-package="com.acme"/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
以配置类为中心使用XML和@ImportResource
在@Configuration类是配置容器的主要机制的应用程序中,仍然可能需要至少使用一些XML。在这些场景中,您可以使用@ImportResource并只定义您需要的XML。这样做实现了一种“以java为中心”的方法来配置容器,并将XML保持在最低限度。下面的示例(包括一个配置类、一个定义bean的XML文件、一个属性文件和主类)展示了如何使用@ImportResource注释来实现“以java为中心”的配置,该配置根据需要使用XML:
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource() {
return new DriverManagerDataSource(url, username, password);
}
}
properties-config.xml
<beans>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
Environment抽象
Environment接口是集成在容器中的抽象,它对应用程序环境的两个关键方面建模:profiles和properties。
profile文件是一组命名的逻辑bean定义,只有在给定的配置文件处于活动状态时才向容器注册。可以将bean分配给profile文件,不管它是用XML定义的还是用注释定义的。与profile文件相关的Environment对象的作用是确定哪些profile文件(如果有的话)当前是活动的,以及哪些profile文件(如果有的话)在默认情况下应该是活动的。
属性在几乎所有的应用程序中都扮演着重要的角色,它们可能来自各种各样的来源:属性文件、JVM系统属性、系统环境变量、JNDI、servlet上下文参数、特别属性对象、Map对象等等。与属性相关的Environment对象的作用是为用户提供一个方便的服务接口,用于配置属性源并从中解析属性。
Bean定义概要文件
Bean定义配置文件在核心容器中提供了一种机制,允许在不同的环境中注册不同的Bean。“环境”这个词对不同的用户可能意味着不同的东西,这个特性可以帮助许多用例,包括:
- 在开发中处理内存中的数据源,而在QA或生产中从JNDI查找相同的数据源。
- 仅在将应用程序部署到性能环境中时注册监控基础设施。
- 为客户A和客户B的部署注册定制的bean实现。
考虑实际应用程序中需要DataSource的第一个用例。在测试环境中,配置可能类似于以下内容:
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build();
}
现在考虑如何将此应用程序部署到QA或生产环境中,假设应用程序的数据源是在生产应用程序服务器的JNDI目录中注册的。我们的数据源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注释允许您指出一个组件有资格注册。使用我们前面的例子,我们可以重写数据源配置如下:
@Configuration
@Profile("development")
public class StandaloneDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
@Configuration
@Profile("production")
public class JndiDataConfig {
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
@Bean方法,如前所述,您通常选择使用程序化的JNDI查找,通过使用Spring的JndiTemplate / JndiLocatorDelegate助手或直接使用JNDI InitialContext JndiObjectFactoryBean变体,但不是早些时候显示这将迫使你声明返回类型作为FactoryBean类型。
profile字符串可以包含简单的profile名称(例如,production)或profile表达式。profile表达式允许表达更复杂的profile逻辑(例如,production & us-east)。profile表达式支持以下操作符:
- !:profile的逻辑非
- &:profile的逻辑与
- |:profile的逻辑或
如果不使用括号,就不能混合使用&和|操作符。例如,production & us-east | eu-central不是一个有效的表达式。它必须表示为production & (us-east | eu-central)。
您可以使用@Profile作为元注释,以创建自定义组合注释。下面的例子定义了一个自定义的@Production注释,你可以用它来代替@Profile(“production”):
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
如果@Configuration类被标记为@Profile,那么与该类关联的所有@Bean方法和@Import注释都将被绕过,除非一个或多个指定的配置文件处于活动状态。如果@Component或@Configuration类被标记为@Profile({“p1”, “p2”}),则除非配置文件’p1’或’p2’已被激活,否则不会注册或处理该类。如果给定的配置文件以NOT操作符(!)为前缀,则只有在配置文件不活动时才注册带注释的元素。例如,给定@Profile({“p1”, “!p2”}),如果配置文件’p1’是活动的,或者配置文件’p2’不是活动的,就会发生注册。
@Profile也可以在方法级声明,只包含配置类的一个特定bean(例如,一个特定bean的可选变体),如下例所示:
@Configuration
public class AppConfig {
@Bean("dataSource")
@Profile("development")
public DataSource standaloneDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
@Bean("dataSource")
@Profile("production")
public DataSource jndiDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
- standaloneDataSource方法仅在development配置文件中可用。
- jndiDataSource方法仅在production配置文件中可用。
对于@Bean方法上的@Profile,可能会应用一个特殊的场景:对于相同Java方法名的重载@Bean方法(类似于构造函数重载),需要在所有重载方法上一致地声明@Profile条件。如果条件不一致,则只有重载方法中第一个声明的条件重要。因此,不能使用@Profile来选择带有特定参数签名的重载方法。同一bean的所有工厂方法之间的解析在创建时遵循Spring的构造函数解析算法。
如果希望定义具有不同概要条件的可选bean,可以使用@Bean name属性来使用指向相同bean名称的不同Java方法名称,如前面的示例所示。如果参数签名都是相同的(例如,所有的变体都有无参数工厂方法),那么这是在有效的Java类中表示这种安排的惟一方法(因为一个特定的名称和参数签名只能有一个方法)。
XML Bean定义配置文件
XML对应项是<beans>
元素的配置文件属性。我们前面的示例配置可以在两个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被限制为只允许文件中的最后一个元素。这应该有助于提供灵活性,而不会在XML文件中引起混乱。
XML对等物不支持前面描述的profile表达式。但是,可以使用
!
操作符来否定一个配置文件。也可以通过嵌套配置文件来应用逻辑“和”,如下面的例子所示:
<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提供的环境API以编程方式激活配置文件。下面的例子演示了如何做到这一点:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
此外,您还可以通过spring.profiles.active属性声明性地激活配置文件,可以通过系统环境变量、JVM系统属性、web.xml中的servlet上下文参数,或者甚至作为JNDI中的一个条目(参见PropertySource抽象)来指定。在集成测试中,可以通过使用spring-test模块中的@ActiveProfiles注释来声明活动概要文件(请参阅环境概要文件的上下文配置)。
请注意,配置文件不是一个“非此即彼”的命题。您可以同时激活多个配置文件。通过编程,您可以向setActiveProfiles()方法提供多个配置文件名称,该方法接受String…可变参数,下面的例子激活多个配置文件:
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
声明地,spring.profiles.active可以接受逗号分隔的配置文件名称列表,如下例所示:
-Dspring.profiles.active="profile1,profile2"
缺省的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()来更改默认配置文件的名称,或者通过声明的方式,通过使用spring.profiles.default属性来更改名称。
PropertySource抽象
Spring的Environment抽象在可配置的属性源层次结构上提供搜索操作。考虑以下清单:
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())。
这些默认属性源用于StandardEnvironment,用于独立应用程序。StandardServletEnvironment使用额外的默认属性源来填充,包括servlet配置和servlet上下文参数。它可以选择性地启用JndiPropertySource。有关详细信息,请参阅javadoc。
具体来说,当您使用StandardEnvironment时,如果运行时出现my-property系统属性或my-property环境变量,则对env.containsProperty(“my-property”)的调用将返回true。
执行的搜索是分层的。默认情况下,系统属性优先于环境变量。因此,如果在调用env.getProperty(“my-property”)期间,碰巧在两个地方都设置了my-property属性,则系统属性值将“胜出”并返回。注意,属性值并没有被合并,而是被前面的条目完全覆盖。
对于一个常见的StandardServletEnvironment,完整的层次结构如下,最高优先级的条目位于顶部:
ServletConfig参数(如果适用—例如,在DispatcherServlet上下文的情况下)
ServletContext参数(web.xml上下文参数项)
JNDI环境变量(java:comp/env/ 入口)
JVM系统属性(-D命令行参数)
JVM系统环境(操作系统环境变量)
最重要的是,整个机制是可配置的。也许您有一个自定义的属性源,希望集成到这个搜索中。为此,实现并实例化您自己的PropertySource,并将其添加到当前环境的PropertySource集。下面的例子演示了如何做到这一点:
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
在前面的代码中,MyPropertySource以搜索中的最高优先级添加。如果它包含一个my-property属性,那么将检测并返回该属性,以支持任何其他PropertySource中的任何my-property属性。MutablePropertySources API公开了许多方法,这些方法允许对属性源集进行精确操作。
使用@PropertySource
@PropertySource注释为向Spring的环境添加PropertySource提供了一种方便的声明性机制。
给定一个名为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。
根据Java 8的约定,@PropertySource注释是可重复的。但是,所有这些@PropertySource注释都需要在相同的级别上声明,可以直接在configuration类上声明,也可以作为相同自定义注释中的元注释声明。不建议混合使用直接注释和元注释,因为直接注释可以有效地覆盖元注释。
语句中的占位符解析
从历史上看,元素中占位符的值只能根据JVM系统属性或环境变量来解析。现在情况已经不同了。因为Environment抽象是在整个容器中集成的,所以很容易通过它来路由占位符的解析。这意味着您可以以任何您喜欢的方式配置解析过程。您可以更改通过系统属性和环境变量进行搜索的优先级,或者完全删除它们。您还可以适当地将自己的属性源添加到组合中。
具体来说,下面的语句不管在哪里定义customer属性,只要它在Environment中可用,都是有效的:
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
</beans>
注册一个LoadTimeWeaver
Spring使用LoadTimeWeaver在类加载到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类转换所必需的。有关更多细节,请咨询LocalContainerEntityManagerFactoryBean javadoc。有关AspectJ加载时编织的更多信息,请参阅Spring框架中AspectJ的加载时编织。
ApplicationContext的附加功能
正如在介绍一章中所讨论的,org.springframe.beans.factory包提供了管理和操作bean的基本功能,包括以编程的方式。org.springframework.context包添加了ApplicationContext接口,该接口扩展了BeanFactory接口,此外还扩展了其他接口,以更面向应用程序框架的风格提供附加功能。许多人以一种完全声明式的方式使用ApplicationContext,甚至不以编程方式创建它,而是依赖于诸如ContextLoader之类的支持类来自动实例化ApplicationContext,将其作为Java EE web应用程序正常启动过程的一部分。
为了以更面向框架的方式增强BeanFactory功能,上下文包还提供了以下功能:
- 通过MessageSource接口访问i18n风格的消息。
- 通过ResourceLoader接口访问资源,例如url和文件。
- 事件发布,即通过使用ApplicationEventPublisher接口实现ApplicationListener接口的bean。
- 通过HierarchicalBeanFactory接口加载多个(分层的)上下文,使每个上下文都集中于一个特定的层,例如应用程序的web层。
使用MessageSource国际化
ApplicationContext接口扩展了名为MessageSource的接口,因此提供了国际化(“i18n”)功能。Spring还提供了HierarchicalMessageSource接口,可以层次化地解析消息。这些接口一起提供了Spring影响消息解析的基础。这些接口上定义的方法包括:
- 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作为消息源。如果ApplicationContext找不到任何消息源,则实例化一个空的DelegatingMessageSource,以便能够接受对上述定义的方法的调用。
Spring提供了两种MessageSource实现,ResourceBundleMessageSource和StaticMessageSource。两者都实现了层次化的messagesource,以便进行嵌套消息传递。StaticMessageSource很少使用,但提供了向源添加消息的编程方法。下面的例子显示了ResourceBundleMessageSource:
<beans>
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>format</value>
<value>exceptions</value>
<value>windows</value>
</list>
</property>
</bean>
</beans>
该示例假设您的类路径中定义了三个名为format、exceptions和windows的资源包。任何解析消息的请求都是通过通过ResourceBundle对象解析消息的jdk标准方式处理的。在本例中,假设上述两个资源包文件的内容如下:
# in format.properties
message=Alligators rock!
# in exceptions.properties
argument.required=The {0} argument is required.
下一个示例显示了一个执行MessageSource功能的程序。请记住,所有ApplicationContext实现也是MessageSource实现,因此可以转换为MessageSource接口。
public static void main(String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
System.out.println(message);
}
以上程序的输出结果如下:
Alligators rock!
总之,MessageSource是在一个名为bean.xml的文件中定义的,它存在于类路径的根目录中。messageSource bean定义通过其basenames属性引用许多资源包。在列表中传递给basenames属性的三个文件作为文件存在于类路径的根目录中,分别称为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", Locale.ENGLISH);
System.out.println(message);
}
}
调用execute()方法的结果如下:
The userDao argument is required.
关于国际化(“i18n”),Spring的各种MessageSource实现遵循与标准JDK ResourceBundle相同的语言环境解析和回退规则。简而言之,继续前面定义的示例messageSource,如果您希望根据英国(en-GB)地区解析消息,您将分别创建名为format_en_GB.properties,exceptions_en_GB.properties和windows_en_GB.properties的文件。
通常,地区解析由应用程序的周围环境管理。在下面的示例中,手动指定解析(英国)消息的区域设置:
# in exceptions_en_GB.properties
argument.required=Ebagum lad, the ''{0}'' argument is required, I say, required.
public static void main(final String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("argument.required",
new Object [] {"userDao"}, "Required", Locale.UK);
System.out.println(message);
}
以上程序运行的结果输出如下:
Ebagum lad, the 'userDao' argument is required, I say, required.
您还可以使用MessageSourceAware接口来获取对任何已定义的MessageSource的引用。在创建和配置bean时,在实现MessageSourceAware接口的ApplicationContext中定义的任何bean都将被注入应用程序上下文的MessageSource。
作为ResourceBundleMessageSource的替代,Spring提供了一个ReloadableResourceBundleMessageSource类。此变体支持相同的包文件格式,但比基于标准JDK的ResourceBundleMessageSource实现更灵活。特别是,它允许从任何Spring资源位置读取文件(不仅仅是从类路径),并支持热重载bundle属性文件(同时有效地在两者之间缓存它们)。有关详细信息,请参见ReloadableResourceBundleMessageSource javadoc。
标准和自定义事件
ApplicationContext中的事件处理是通过ApplicationEvent类和ApplicationListener接口提供的。如果将实现ApplicationListener接口的bean部署到上下文中,每当将ApplicationEvent发布到ApplicationContext时,都会通知该bean。本质上,这是标准的观察者设计模式。
从Spring 4.2开始,事件基础设施得到了显著的改进,并提供了[基于注释的模型](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#context-functionality-events-annotation)以及发布任意事件的能力(即不需要从ApplicationEvent扩展的对象)。当这样一个对象被发布时,我们将它包装成一个事件。
下表描述了Spring提供的标准事件:
事件 | 解释 |
---|---|
ContextRefreshedEvent | 在初始化或刷新ApplicationContext时发布(例如,通过使用ConfigurableApplicationContext接口上的refresh()方法)。在这里,initialized意味着加载所有的bean,检测并激活后处理器bean,预实例化单例对象,并且ApplicationContext对象已经准备好可以使用了。只要上下文没有关闭,就可以多次触发刷新,前提是所选的ApplicationContext实际上支持这样的热刷新。例如,XmlWebApplicationContext支持热刷新,但是GenericApplicationContext不支持。 |
ContextStartedEvent | 通过在ConfigurableApplicationContext接口上使用start()方法启动ApplicationContext时发布。这里,“启动”意味着所有生命周期bean都接收一个显式的启动信号。通常,此信号用于在显式停止之后重新启动bean,但也可以用于启动尚未配置为自动启动的组件(例如,尚未在初始化时启动的组件)。 |
ContextStoppedEvent | 通过在ConfigurableApplicationContext接口上使用stop()方法停止ApplicationContext时发布。这里,“停止”意味着所有生命周期bean都接收一个显式的停止信号。可以通过start()调用重新启动已停止的上下文。 |
ContextClosedEvent | 通过在ConfigurableApplicationContext接口上使用close()方法或通过JVM关闭挂钩关闭ApplicationContext时发布。这里,“关闭”意味着所有的单例bean都将被销毁。一旦上下文关闭,它就会到达生命的终点,无法刷新或重新启动。 |
RequestHandledEvent | 一个特定于web的事件,通知所有bean HTTP请求已得到服务。此事件在请求完成后发布。此事件仅适用于使用Spring的DispatcherServlet的web应用程序。 |
ServletRequestHandledEvent | RequestHandledEvent的子类,用于添加特定于servlet的上下文信息。 |
您还可以创建和发布自己的自定义事件。下面的例子展示了一个简单的类,它扩展了Spring的ApplicationEvent基类:
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()方法将阻塞,直到所有侦听器都完成对事件的处理。这种同步和单线程方法的一个优点是,当侦听器接收到事件时,如果事务上下文可用,它将在发布者的事务上下文中进行操作。如果需要另一种事件发布策略,请参阅Spring的ApplicationEventMulticaster接口的javadoc和配置选项的SimpleApplicationEventMulticaster实现。
下面的例子展示了用于注册和配置上述每个类的bean定义:
<bean id="emailService" class="example.EmailService">
<property name="blackList">
<list>
<value>known.spammer@example.org</value>
<value>known.hacker@example.org</value>
<value>john.doe@example.org</value>
</list>
</property>
</bean>
<bean id="blackListNotifier" class="example.BlackListNotifier">
<property name="notificationAddress" value="blacklist@example.org"/>
</bean>
总之,当调用emailService bean的sendEmail()方法时,如果有任何应该列入黑名单的电子邮件消息,则会发布BlackListEvent类型的自定义事件。blackListNotifier bean注册为ApplicationListener并接收BlackListEvent,此时它可以通知相关方。
Spring的事件机制是为同一应用程序上下文中的Spring bean之间的简单通信而设计的。然而,对于更复杂的企业集成需求,单独维护的Spring integration项目提供了对构建轻量级、面向模式、事件驱动的体系结构的完整支持,这些体系结构构建于著名的Spring编程模型之上。
基于注解的事件监听器
从Spring 4.2开始,您可以使用@EventListener注释在托管bean的任何公共方法上注册事件监听器。黑名单通知可以重写如下:
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表达式的注释的条件属性来添加额外的运行时筛选,该注释属性应该与实际调用特定事件的方法相匹配。
下面的例子展示了如何重写我们的通知程序,只有在事件的内容属性等于my-event时才能被调用:
@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlackListEvent(BlackListEvent blEvent) {
// notify appropriate parties via notificationAddress...
}
每个SpEL表达式根据专用上下文计算。下表列出了对上下文可用的项,以便您可以将它们用于条件事件处理:
名称 | 位置 | 描述 | 例子 |
---|---|---|---|
事件 | 根对象 | 实际的ApplicationEvent。 | #root.event or event |
参数数组 | 根对象 | 用于调用方法的参数(作为对象数组)。 | #root.args或args;args[0]来访问第一个参数,等等。 |
参数名称 | 上下文 | 方法参数的名称。如果由于某种原因,这些名称不可用(例如,因为编译后的字节码中没有调试信息),那么还可以使用#a<#arg>语法使用单个参数,其中<#arg>表示参数索引(从0开始)。 | #blEvent或#a0(也可以使用#p0或#p<#arg>参数表示法作为别名) |
请注意#root.event使您能够访问底层事件,即使您的方法签名实际上引用了已发布的任意对象。
如果你需要发布一个事件作为处理另一个事件的结果,你可以改变方法签名来返回应该发布的事件,如下面的例子所示:
@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
异步侦听器不支持此功能。
这个新方法为上面方法处理的每个黑名单事件发布一个新的ListUpdateEvent。如果需要发布多个事件,则可以返回一个事件Collection。
异步的监听
如果希望特定的侦听器异步处理事件,可以重用常规的@Async支持。下面的例子演示了如何做到这一点:
@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
// BlackListEvent is processed in a separate thread
}
在使用异步事件时要注意以下限制:
- 如果异步事件侦听器抛出异常,则不会将其传播到调用者。有关更多细节,请参见AsyncUncaughtExceptionHandler。
- 异步事件监听器方法不能通过返回值来发布后续事件。如果您需要作为处理的结果发布另一个事件,请注入ApplicationEventPublisher来手动发布事件。
有序的监听
如果需要在调用另一个侦听器之前调用一个侦听器,可以将@Order注释添加到方法声明中,如下面的示例所示:
@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
泛型事件
还可以使用泛型进一步定义事件的结构。考虑使用EntityCreatedEvent<T>
,其中T是所创建的实际实体的类型。例如,您可以创建以下侦听器定义来仅接收一个人的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()));
}
}
这不仅适用于ApplicationEvent,也适用于作为事件发送的任意对象。
方便地访问底层资源
为了优化应用程序上下文的使用和理解,您应该熟悉Spring的Resource抽象,如资源中所述。
应用程序上下文是ResourceLoader,可用于加载Resource对象。Resource本质上是JDK java.net.URL类的一个功能更丰富的版本。实际上,Resource的实现在适当的地方封装了java.net.URL的一个实例。Resource可以以透明的方式从几乎任何位置获取底层资源,包括类路径、文件系统位置、任何可以用标准URL描述的位置,以及其他一些变体。如果资源位置字符串是没有任何特殊前缀的简单路径,那么这些资源的来源是特定的,并且适合于实际的应用程序上下文类型。
您可以配置部署到应用程序上下文中的bean,以实现特殊的回调接口ResourceLoaderAware,以便在初始化时自动回调,并将应用程序上下文本身作为ResourceLoader传递进来。您还可以公开Resource类型的属性,用于访问静态资源。它们像其他属性一样被注入其中。您可以将这些Resource属性指定为简单的String路径,并依赖于部署bean时从这些文本字符串到实际Resource对象的自动转换。
提供给ApplicationContext构造函数的位置路径实际上是资源字符串,并且以简单的形式根据特定的上下文实现进行适当的处理。例如,ClassPathXmlApplicationContext将简单的位置路径作为classpath位置。您还可以使用带有特殊前缀的位置路径(资源字符串)来强制从类路径或URL加载定义,而不管实际的上下文类型如何。
方便的应用程序上下文实例化的Web应用程序
您可以通过使用(例如)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作为默认值。当参数存在时,侦听器使用预定义的分隔符(逗号、分号和空格)分隔String,并将这些值用作搜索应用程序上下文的位置。还支持ant样式的路径模式。例如/WEB-INF/*Context.xml(用于所有名称以Context.xml结尾并驻留在WEB-INF目录中的文件)和/WEB-INF/**/*Context.xml(用于WEB-INF的任何子目录中的所有此类文件)。
将Spring ApplicationContext部署为Java EE RAR文件
可以将Spring ApplicationContext部署为RAR文件,将上下文及其所需的所有bean类和库jar封装在Java EE RAR部署单元中。这相当于引导一个独立的ApplicationContext(仅在Java EE环境中托管),使其能够访问Java EE服务器设施。RAR部署是部署无头WAR文件的一种更自然的替代方案—实际上,没有任何HTTP入口点的WAR文件仅用于在Java EE环境中引导Spring ApplicationContext。
RAR部署非常适合不需要HTTP入口点,而只由消息端点和计划的作业组成的应用程序上下文。在这种上下文中,bean可以使用应用服务器资源,如JTA事务管理器和JNDI绑定的JDBC数据源实例和JMS ConnectionFactory实例,还可以向平台的JMX服务器注册——所有这些都是通过Spring的标准事务管理和JNDI和JMX支持设施实现的。应用程序组件还可以通过Spring的TaskExecutor抽象与应用服务器的JCA WorkManager交互。
有关RAR部署中涉及的配置细节,请参阅SpringContextResourceAdapter类的javadoc。
对于一个简单的部署一个Spring ApplicationContext作为一个Java EE RAR文件:
- 将所有应用程序类打包到一个RAR文件中(这是一个标准JAR文件,具有不同的文件扩展名)。将所有需要的库jar添加到RAR归档的根目录中。添加一个META-INF/ra.xml部署描述符(如SpringContextResourceAdapter的javadoc中所示)和相应的Spring XML bean定义文件(通常是META-INF/applicationContext.xml)。
- 将生成的RAR文件放入应用服务器的部署目录。
这种RAR部署单元通常是自包含的。它们不向外界公开组件,甚至不向同一应用程序的其他模块公开。与基于rar的ApplicationContext的交互通常通过与其他模块共享的JMS目的地进行。例如,基于rar的ApplicationContext还可以调度一些作业或对文件系统中的新文件(或类似的东西)做出响应。如果它需要允许来自外部的同步访问,它可以(例如)导出RMI端点,这些端点可以由同一台机器上的其他应用程序模块使用。
BeanFactory
BeanFactory
API为Spring的IoC功能提供了底层基础。它的特定契约主要用于与Spring的其他部分和相关第三方框架的集成,它的DefaultListableBeanFactory
实现是更高级别的GenericApplicationContext
容器中的一个关键委托。
BeanFactory
和相关接口(如BeanFactoryAware
、InitializingBean
、DisposableBean
)是其他框架组件的重要集成点。通过不需要任何注释甚至反射,它们允许容器及其组件之间非常有效的交互。应用程序级bean可能使用相同的回调接口,但通常更喜欢通过注释或编程配置进行声明性依赖项注入。
请注意,核心BeanFactory
API级别及其DefaultListableBeanFactory
实现不会对配置格式或要使用的任何组件注释做任何假设。所有这些风格都是通过扩展(如XmlBeanDefinitionReader
和AutowiredAnnotationBeanPostProcessor
)来实现的,并作为核心元数据表示对共享的BeanDefinition
对象进行操作。这就是Spring的容器如此灵活和可扩展的本质。
BeanFactory还是ApplicationContext ?
本节解释BeanFactory
和ApplicationContext
容器级别之间的差异,以及对引导的影响。
除非有充分的理由,否则应该使用ApplicationContext
,将GenericApplicationContext
及其子类AnnotationConfigApplicationContext
作为自定义引导的公共实现。这些是Spring核心容器的主要入口点,用于所有常见目的:加载配置文件、触发类路径扫描、以编程方式注册bean定义和带注释的类,以及(从5.0开始)注册功能性bean定义。
因为ApplicationContext
包含了BeanFactory
的所有功能,所以通常推荐使用它而不是简单的BeanFactory
,除非需要对bean处理进行完全控制。在ApplicationContext
(比如GenericApplicationContext
实现)的几种检测到bean按照惯例(也就是说,由bean名称或bean类型——特别是,后处理器),而一个普通的DefaultListableBeanFactory
对于任何特殊的bean都是不可知的。
对于许多扩展的容器特性,例如注释处理和AOP代理,BeanPostProcessor扩展点是必不可少的。如果只使用普通的DefaultListableBeanFactory
,那么默认情况下不会检测到并激活这样的后处理器。这种情况可能令人困惑,因为您的bean配置实际上没有任何错误。相反,在这种情况下,需要通过额外的设置完全引导容器。
下表列出了BeanFactory
和ApplicationContext
接口和实现提供的特性。
特征 | BeanFactory | ApplicationContext |
---|---|---|
Bean实例化/装配 | Yes | Yes |
集成的生命周期管理 | 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"));
// bring in some property values from a Properties file
PropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));
// now actually do the replacement
cfg.postProcessBeanFactory(factory);
在这两种情况下,显式注册步骤都不方便,这就是为什么在spring支持的应用程序中,各种ApplicationContext
变体比普通的DefaultListableBeanFactory
更受欢迎的原因,特别是在典型的企业设置中,依赖于BeanFactoryPostProcessor
和BeanPostProcessor
实例来扩展容器功能时。
一个
AnnotationConfigApplicationContext
拥有所有已注册的公共注释后置处理器,并且可以通过配置注释(如@EnableTransactionManagement
)在幕后引入额外的处理器。在Spring的基于注释的配置模型的抽象级别上,bean后处理器的概念变成了纯粹的内部容器细节。