官方文档:https://docs.spring.io/spring/docs/4.3.21.RELEASE/spring-framework-reference/htmlsingle/#beans
本章介绍了Spring框架实现的控制反转(IoC)[1]原理。IoC也称为依赖注入(dependency injection, DI)。它是一个过程,对象定义它们的依赖项,也就是说,它们使用的其他对象,仅通过构造函数参数、工厂方法的参数或对象实例在构造或从工厂方法返回后设置的属性。然后容器在创建bean时注入这些依赖项。这个过程从根本上说是bean本身的逆过程,也就是名称控制反转(IoC),它通过使用类的直接构造或一种机制(如服务定位器模式)来控制依赖项的实例化或位置。
org.springframework.beans和org.springframework.context上下文包是Spring Framework IoC容器的基础。BeanFactory接口提供了一种高级配置机制,能够管理任何类型的对象。ApplicationContext是BeanFactory的一个子接口。它增加了与Spring AOP特性的更容易集成;消息资源处理(用于国际化)、事件发布;以及特定于应用层的上下文,如用于web应用程序的WebApplicationContext。
简而言之,BeanFactory提供了配置框架和基本功能,ApplicationContext添加了更多特定于企业的功能。ApplicationContext是BeanFactory的一个完整超集,在本章中只用于描述Spring的IoC容器。有关使用BeanFactory而不是ApplicationContext的更多信息,请参阅第7.16节“BeanFactory”。
在Spring中,构成应用程序主干并由Spring IoC容器管理的对象称为bean。bean是由Spring IoC容器实例化、组装和以其他方式管理的对象。否则,bean只是应用程序中的许多对象之一。bean及其之间的依赖关系反映在容器使用的配置元数据中。
接口org.springframework.context.ApplicationContext表示Spring IoC容器,并负责实例化、配置和组装上述bean。容器通过读取配置元数据获取有关实例化、配置和组装哪些对象的说明。配置元数据用XML、Java注释或Java代码表示。它允许您表达组成应用程序的对象以及这些对象之间丰富的相互依赖关系。
随Spring一起提供了ApplicationContext接口的几个实现。在独立应用程序中,通常创建ClassPathXmlApplicationContext或FileSystemXmlApplicationContext的实例。虽然XML是定义配置元数据的传统格式,但您可以指示容器使用Java注释或代码作为元数据格式,方法是提供少量XML配置,以声明方式支持这些附加元数据格式。
在大多数应用程序场景中,不需要显式用户代码实例化Spring IoC容器的一个或多个实例。例如,在web应用程序场景中,web中的8行(或更多)简单的web描述符XML样板行。应用程序的xml文件通常就足够了(参见7.15.4节,“web应用程序的方便的ApplicationContext实例化”)。如果您使用的是Spring工具套件eclipse支持的开发环境,那么只需单击鼠标或敲击键盘,就可以轻松创建这个样板配置。
下图是Spring如何工作的高级视图。您的应用程序类与配置元数据相结合,这样在创建并初始化ApplicationContext之后,您就拥有了一个完全配置并可执行的系统或应用程序。
配置元数据:
正如前面的图所示,Spring IoC容器使用一种配置元数据形式;这个配置元数据表示您作为应用程序开发人员如何告诉Spring容器实例化、配置和组装应用程序中的对象。
配置元数据通常以简单直观的XML格式提供,本章的大部分内容都使用这种格式来传递Spring IoC容器的关键概念和特性。
基于xml的元数据不是唯一允许的配置元数据形式。Spring IoC容器本身与实际写入配置元数据的格式完全脱钩。现在,许多开发人员为他们的Spring应用程序选择基于java的配置。
有关在Spring容器中使用其他形式元数据的信息,请参见:
Spring配置由容器必须管理的至少一个bean定义组成,通常包括多个bean定义。基于xml的配置元数据显示这些bean配置为顶级的
这些bean定义与组成应用程序的实际对象相对应。通常,您定义服务层对象、数据访问对象(DAOs)、表示对象(如Struts操作实例)、基础设施对象(如Hibernate SessionFactories)、JMS队列等等。通常不需要在容器中配置细粒度的域对象,因为创建和加载域对象通常是dao和业务逻辑的责任。但是,您可以使用Spring与AspectJ的集成来配置在IoC容器控制之外创建的对象。参见使用AspectJ使用Spring依赖注入域对象。
下面的例子展示了基于xml的配置元数据的基本结构:
id属性是用于标识单个bean定义的字符串。class属性定义bean的类型并使用完全限定的类名。id属性的值引用协作对象。本例中没有显示用于引用协作对象的XML;有关更多信息,请参见依赖项。
实例化一个容器:
实例化Spring IoC容器很简单。提供给ApplicationContext构造函数的位置路径或路径实际上是资源字符串,允许容器从各种外部资源(如本地文件系统)、Java类路径等装载配置元数据。
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
在学习了Spring的IoC容器之后,您可能希望更多地了解Spring的资源抽象,如第8章“资源”所述,它提供了一种方便的机制,可以从URI语法中定义的位置读取输入流。特别是,资源路径用于构建应用程序上下文,如8.7节“应用程序上下文和资源路径”所述。
下面的例子显示了服务层对象(services.xml)配置文件:
下面的示例显示了数据访问对象dao。xml文件:
在前面的示例中,服务层由类PetStoreServiceImpl和两个JpaAccountDao和JpaItemDao类型的数据访问对象(基于JPA对象/关系映射标准)组成。属性名元素引用JavaBean属性的名称,ref元素引用另一个bean定义的名称。id和ref元素之间的链接表示协作对象之间的依赖关系。有关配置对象依赖项的详细信息,请参见依赖项。
组合基于xml的配置元数据:
让bean定义跨越多个XML文件是很有用的。通常,每个XML配置文件代表体系结构中的逻辑层或模块。
您可以使用应用程序上下文构造函数从所有这些XML片段加载bean定义。此构造函数接受多个资源位置,如前一节所示。或者,使用
在前面的示例中,外部bean定义是从三个文件加载的:服务。xml, messageSource.xml和themeSource.xml。所有位置路径都相对于执行导入的定义文件,所以是服务。xml必须与执行导入的文件位于相同的目录或类路径位置,而messageSource必须位于相同的目录或类路径位置。xml和themeSource。xml必须位于导入文件位置以下的资源位置。如您所见,前导斜杠将被忽略,但是考虑到这些路径是相对的,最好不要使用斜杠。根据Spring模式,导入的文件的内容,包括顶级的
可以,但不建议,引用文件在父目录使用相对"../”路径。这样做会对当前应用程序之外的文件产生依赖关系。特别是对于“classpath:”url(例如“classpath:../services.xml”),不建议使用这种引用,因为在这种情况下,运行时解析过程选择“最近的”classpath根目录,然后查看其父目录。类路径配置更改可能导致选择不同的错误目录。
您总是可以使用完全限定的资源位置而不是相对路径:例如,“file:C:/config/services”。xml”或“类路径:/配置/ services . xml”。但是,请注意您正在将应用程序的配置与特定的绝对位置耦合。对于这样的绝对位置,通常最好保持间接,例如,通过在运行时根据JVM系统属性解析的“${…}”占位符。
导入指令是bean名称空间本身提供的特性。除了普通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
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 userList = service.getUsernameList();
使用Groovy配置,引导看起来非常相似,只是不同的上下文实现类是Groovy敏感的(但也理解XML bean定义):
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");
最灵活的变体是GenericApplicationContext与reader委托结合使用,例如XML文件的XmlBeanDefinitionReader:
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
或者使用Groovy文件的GroovyBeanDefinitionReader:
GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();
这些阅读器委托可以在相同的ApplicationContext上混合和匹配,如果需要,可以从不同的配置源读取bean定义。
然后可以使用getBean检索bean的实例。ApplicationContext接口还有一些用于检索bean的其他方法,但理想情况下,应用程序代码不应该使用它们。实际上,您的应用程序代码应该完全不需要调用getBean()方法,因此完全不需要依赖Spring api。例如,Spring与web框架的集成为各种web框架组件(如控制器和jsf管理的bean)提供依赖注入,允许您通过元数据(例如自动连接注释)声明对特定bean的依赖。
Bean概述:
Spring IoC容器管理一个或多个bean。这些bean是用您提供给容器的配置元数据创建的,例如,以XML
在容器本身内,这些bean定义表示为BeanDefinition对象,其中包含(除其他信息外)以下元数据:
这个元数据转换为组成每个bean定义的一组属性。
Property | Explained in… |
---|---|
class |
Section 7.3.2, “Instantiating beans” |
name |
Section 7.3.1, “Naming beans” |
scope |
Section 7.5, “Bean scopes” |
constructor arguments |
Section 7.4.1, “Dependency Injection” |
properties |
Section 7.4.1, “Dependency Injection” |
autowiring mode |
Section 7.4.5, “Autowiring collaborators” |
lazy-initialization mode |
Section 7.4.4, “Lazy-initialized beans” |
initialization method |
the section called “Initialization callbacks” |
destruction method |
the section called “Destruction callbacks” |
除了包含关于如何创建特定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”、“fooService”等),但也可能包含特殊字符。如果希望向bean引入其他别名,还可以在name属性中指定它们,用逗号(、)、分号(;)或空格分隔。作为一个历史说明,在Spring 3.1之前的版本中,id属性被定义为xsd: id类型,它限制了可能的字符。从3.1开始,它被定义为xsd:string类型。请注意,bean id的惟一性仍然由容器强制执行,但不再由XML解析器强制执行。
您不需要为bean提供名称或id。如果没有显式地提供名称或id,容器将为该bean生成惟一的名称。但是,如果希望通过使用ref元素或服务定位符样式查找按名称引用该bean,则必须提供名称。不提供名称的动机与使用内部bean和自动连接协作者有关。
BEAN命名约定:
约定是在命名bean时使用标准Java约定作为实例字段名。也就是说,bean名称以小写字母开头,从那时起采用驼峰式大小写。这些名称的示例是(没有引号)“accountManager”、“accountService”、“userDao”、“loginController”等等。
命名bean始终如一地使您的配置更易于阅读和理解,如果您使用Spring AOP,那么在将通知应用到一组名称相关的bean时,它将提供很大帮助。
通过在类路径中扫描组件,Spring按照上面的规则为未命名组件生成bean名称:本质上,使用简单的类名并将其初始字符转换为小写。但是,在(不寻常的)特殊情况下,当有多个字符且第一个和第二个字符都是大写字符时,原始的大小写将得到保留。这些规则与java.beans.Introspector.decapitalize(这里使用的是哪个Spring)定义的规则相同。
在bean定义之外对bean进行别名:
在bean定义本身中,通过使用id属性指定的最多一个名称和name属性中任意数量的其他名称的组合,可以为bean提供多个名称。这些名称可以是相同bean的等价别名,在某些情况下非常有用,例如允许应用程序中的每个组件通过使用特定于该组件本身的bean名称来引用公共依赖项。
但是,指定bean实际定义的所有别名并不总是足够的。有时需要为在别处定义的bean引入别名。在大型系统中,配置通常在每个子系统之间进行分割,每个子系统都有自己的一组对象定义。在基于xml的配置元数据中,可以使用
在这种情况下,在使用这个别名定义之后,名为fromName(在同一个容器中)的bean也可以称为toName。
例如,子系统A的配置元数据可以通过子系统-数据源的名称引用数据源。子系统B的配置元数据可以通过子系统B数据源的名称引用数据源。在组合使用这两个子系统的主应用程序时,主应用程序通过myApp-dataSource的名称引用数据源。要使所有三个名称引用相同的对象,可以向配置元数据添加以下别名定义:
现在,每个组件和主应用程序都可以通过惟一的名称引用数据源,并且保证该名称不会与任何其他定义冲突(有效地创建名称空间),但是它们引用的是同一个bean。
JAVA 配置:
如果使用Java-configuration,可以使用@Bean注释提供别名,详细信息请参阅7.12.3节“使用@Bean注释”。
实例化BEAN:
bean定义本质上是创建一个或多个对象的方法。容器在被请求时查看命名bean的配方,并使用该bean定义封装的配置元数据来创建(或获取)实际对象。
如果使用基于xml的配置元数据,则指定要在
内部类的名字。如果希望为静态嵌套类配置bean定义,则必须使用嵌套类的二进制名称。
例如,如果在com中有一个名为Foo的类。例包,这个Foo类有一个叫做Bar的静态嵌套类,bean定义上的class属性的值是…
com.example.Foo$Bar
注意名称中使用$字符将嵌套类名与外部类名分隔开。
用构造函数实例化:
当您使用构造函数方法创建bean时,所有常规类都可由Spring使用并与Spring兼容。也就是说,被开发的类不需要实现任何特定的接口或以特定的方式编码。简单地指定bean类就足够了。但是,根据您对特定bean使用的IoC类型,您可能需要一个默认(空)构造函数。
Spring IoC容器实际上可以管理您希望它管理的任何类;它并不局限于管理真正的javabean。大多数Spring用户更喜欢实际的javabean,它只有一个默认的(无参数的)构造函数,以及根据容器中的属性建模的适当的setter和getter。您还可以在容器中拥有更多奇异的非bean样式的类。例如,如果您需要使用完全不符合JavaBean规范的遗留连接池,Spring也可以管理它。
使用基于xml的配置元数据,您可以指定您的bean类如下:
有关向构造函数提供参数(如果需要)和在构造对象之后设置对象实例属性的机制的详细信息,请参见注入依赖项。
使用静态工厂方法实例化:
在定义使用静态工厂方法创建的bean时,使用class属性指定包含静态工厂方法的类,并使用名为factory-method的属性指定工厂方法本身的名称。您应该能够调用这个方法(使用后面描述的可选参数)并返回一个活动对象,该对象随后将被视为通过构造函数创建的。这种bean定义的一个用途是在遗留代码中调用静态工厂。
下面的bean定义指定将通过调用工厂方法创建bean。定义不指定返回对象的类型(类),只指定包含工厂方法的类。在本例中,createInstance()方法必须是静态方法。
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
有关向工厂方法提供(可选)参数和在从工厂返回对象后设置对象实例属性的机制的详细信息,请参阅依赖项和配置的详细信息。
使用实例工厂方法实例化:
与通过静态工厂方法实例化类似,使用实例工厂方法实例化从容器调用现有bean的非静态方法来创建新bean。要使用这种机制,请保留class属性为空,并在factory-bean属性中指定当前(或父/祖先)容器中bean的名称,该容器包含要调用以创建对象的实例方法。使用factory-method属性设置工厂方法本身的名称。
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
一个工厂类还可以包含多个工厂方法,如下所示:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private static AccountService accountService = new AccountServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
public AccountService createAccountServiceInstance() {
return accountService;
}
}
这种方法表明工厂bean本身可以通过依赖注入(DI)进行管理和配置。请参阅依赖项和配置的详细信息。
在Spring文档中,工厂bean指的是在Spring容器中配置的bean,它将通过实例或静态工厂方法创建对象。相比之下,FactoryBean(注意大小写)引用一个特定于spring的FactoryBean。
依赖关系:
典型的企业应用程序不包含单个对象(或Spring术语中的bean)。即使是最简单的应用程序,也有一些对象一起工作,以表示最终用户认为是一致的应用程序。下一节将解释如何从定义许多独立的bean定义到完全实现的应用程序,在该应用程序中,对象协作以实现目标。
依赖注入:
依赖注入(Dependency injection, DI)是这样一个过程:对象通过构造函数参数、工厂方法的参数或在从工厂方法构造或返回对象实例之后在对象实例上设置的属性来定义它们的依赖关系,即它们处理的其他对象。然后容器在创建bean时注入这些依赖项。这个过程从根本上说是bean本身的逆过程,也就是名称控制反转(IoC),它通过使用类的直接构造或服务定位器模式来控制依赖项的实例化或位置。
使用DI原则的代码更清晰,当对象具有依赖关系时,解耦更有效。对象不查找它的依赖项,也不知道依赖项的位置或类。因此,您的类变得更容易测试,特别是当依赖于接口或抽象基类时,这些类允许在单元测试中使用存根或模拟实现。
依赖注入主要有两种变体,一种是基于构造函数的依赖注入,另一种是基于集合的依赖注入。
基于构造方法的依赖注入:
基于构造函数的DI是通过容器调用带有许多参数的构造函数来完成的,每个参数代表一个依赖项。调用带有特定参数的静态工厂方法来构造bean几乎是等价的,本讨论将参数处理为构造函数和静态工厂方法。下面的例子显示了一个只能依赖于构造函数注入的类。注意,这个类没有什么特别之处,它是一个POJO,不依赖于容器特定的接口、基类或注释。
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...
}
构造函数参数解析:
使用参数类型进行构造函数参数解析匹配。如果在bean定义的构造函数参数中不存在潜在的歧义,那么在bean定义中定义构造函数参数的顺序就是在实例化bean时将这些参数提供给适当构造函数的顺序。考虑以下类:
package x.y;
public class Foo {
public Foo(Bar bar, Baz baz) {
// ...
}
}
假设Bar和Baz类不是通过继承关联的,因此不存在潜在的歧义。因此,下面的配置工作正常,您不需要在
当引用另一个bean时,类型是已知的,并且可以进行匹配(就像前面示例中的情况一样)。当使用简单类型(如
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属性显式指定构造函数参数的类型,容器可以使用与简单类型匹配的类型。例如:
使用index属性显式指定构造函数参数的索引。例如:
除了解决多个简单值的歧义之外,指定索引还可以解决构造函数具有相同类型的两个参数的歧义。注意,索引是基于0的。
您还可以使用构造函数参数名来消除值的歧义:
请记住,要使此工作开箱即用,必须在启用调试标志的情况下编译代码,以便Spring可以从构造函数中查找参数名。如果不能使用调试标志编译代码(或者不想编译),可以使用@ConstructorProperties JDK注释显式地命名构造函数参数。然后,示例类必须如下所示:
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
基于Setter的依赖注入:
基于setter的DI是在调用无参数构造函数或无参数静态工厂方法来实例化bean之后,通过容器调用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支持基于构造器和基于集合的DI。在通过构造函数方法注入了一些依赖项之后,它还支持基于setter的DI。您可以以BeanDefinition的形式配置依赖项,并将其与PropertyEditor实例一起使用,将属性从一种格式转换为另一种格式。然而,大多数Spring用户并不直接使用这些类(即而是使用XML bean定义、带注释的组件(例如,,或基于java的@Configuration类中的@Bean方法。然后在内部将这些源转换为BeanDefinition实例,并用于加载整个Spring IoC容器实例。
基于构造器还是基于Setter的DI?
因为您可以混合基于构造函数和基于集合的DI,所以对于强制依赖项使用构造函数,对于可选依赖项使用setter方法或配置方法,这是一个很好的经验法则。注意,可以使用setter方法上的@Required注释使属性成为必需的依赖项。
Spring团队通常提倡构造函数注入,因为它允许将应用程序组件实现为不可变的对象,并确保所需的依赖关系不是null。此外,构造注入组件总是以完全初始化的状态返回给客户端(调用)代码。附带说明一下,大量构造函数参数是一种糟糕的代码味道,这意味着类可能有太多的责任,应该进行重构以更好地解决关注点的适当分离。
Setter注入主要应该只用于可选的依赖项,这些依赖项可以在类中分配合理的默认值。否则,必须在代码使用依赖项的任何地方执行非空检查。setter注入的一个优点是,setter方法使该类的对象能够在以后进行重新配置或重新注入。因此,通过JMX mbean进行管理是setter注入的一个引人注目的用例。
使用对特定类最有意义的DI样式。有时,在处理您没有源代码的第三方类时,会为您做出选择。例如,如果第三方类不公开任何setter方法,那么构造函数注入可能是惟一可用的DI形式。
依赖性解析过程:
容器按照以下方式执行bean依赖项解析:
Spring容器在创建容器时验证每个bean的配置。但是,在实际创建bean之前,不会设置bean属性本身。当创建容器时,将创建单列作用域并设置为预实例化(默认)的bean。作用域在7.5节“Bean作用域”中定义。否则,只有在请求bean时才创建它。创建一个bean可能会导致创建一个bean图,因为bean的依赖项及其依赖项的依赖项(等等)是创建和分配的。请注意,这些依赖项之间的解析不匹配可能很晚才出现,即在第一次创建受影响的bean时。
循环依赖:
如果您主要使用构造函数注入,则有可能创建无法解决的循环依赖项场景。
例如:类A通过构造函数注入需要类B的实例,类B通过构造函数注入需要类A的实例。如果为要相互注入的类A和B配置bean, Spring IoC容器将在运行时检测到此循环引用,并抛出BeanCurrentlyInCreationException异常。
一种可能的解决方案是编辑由设置器而不是构造器配置的一些类的源代码。或者,避免构造函数注入,只使用setter注入。换句话说,虽然不建议使用setter注入配置循环依赖项。
与典型的情况(没有循环依赖关系)不同,bean a和bean B之间的循环依赖关系强制在完全初始化之前将其中一个bean注入另一个bean(典型的鸡/蛋场景)。
您通常可以相信Spring会做正确的事情。它在容器装载时检测配置问题,例如对不存在的bean和循环依赖项的引用。Spring在bean实际创建时尽可能晚地设置属性并解决依赖项。这意味着,当您请求对象时,如果存在创建该对象或其依赖项之一的问题,那么正确加载的Spring容器稍后可以生成异常。例如,bean由于缺少或无效属性而引发异常。某些配置问题的这种潜在的延迟可见性就是为什么默认情况下ApplicationContext实现会预实例化单例bean。在实际需要这些bean之前,您需要花费一些前期时间和内存来创建它们,而在创建ApplicationContext时,您会发现配置问题,而不是在稍后。您仍然可以覆盖这个默认行为,以便单例bean将延迟初始化,而不是预先实例化。
如果不存在循环依赖关系,当一个或多个协作bean被注入到依赖bean中时,每个协作bean在被注入到依赖bean之前都是完全配置的。这意味着如果bean A依赖bean B,Spring IoC容器调用beanA的setter方法之前完全配置bean B。换句话说,bean被实例化(如果不是一个单例预先实例化),然后其依赖项被设置,再然后相关的生命周期方法(如InitializingBean init方法或配置回调方法)被调用。
依赖注入的例子:
下面的示例将基于xml的配置元数据用于基于set的DI。Spring XML配置文件的一小部分指定了一些bean定义:
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:
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没有使用构造函数,而是被告知调用一个静态工厂方法来返回对象的实例:
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;
}
}
静态工厂方法的参数通过
详细的依赖关系和配置:
正如在前一节中提到的,您可以将bean属性和构造函数参数定义为对其他托管bean(协作者)的引用,或者定义为内联定义的值。为此,Spring的基于xml的配置元数据支持其
下面的示例使用p名称空间进行更简洁的XML配置。
前面的XML更简洁;但是,打字错误是在运行时发现的,而不是在设计时发现的,除非您使用IntelliJ IDEA之类的IDE,或者在创建bean定义时支持自动完成属性的Spring Tool Suite (STS)。强烈建议这种IDE协助。
您还可以配置java.util.Properties实例:
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
Spring容器将
idref元素:
idref元素只是将容器中另一个bean的id (string value - not a reference)传递给
上面的bean定义代码片段是完全等价的(在运行时),以下片段:
第一种形式优于第二种形式,因为使用idref标记允许容器在部署时验证引用的命名bean是否确实存在。在第二个变体中,对传递给客户机bean的targetName属性的值不执行验证。只有在实际实例化客户机bean时才会发现拼写错误(很可能会导致致命的结果)。如果客户端bean是原型bean,则只有在部署容器很久之后才会发现此错误和由此产生的异常。
4.0 bean xsd不再支持idref元素上的本地属性,因为它不再提供超过常规bean引用的值。在升级到4.0模式时,只需更改对idref bean的现有idref本地引用。
在ProxyFactoryBean bean定义中的AOP拦截器配置中有一个常见的地方(至少在Spring 2.0之前的版本中是这样)是
对其他bean(协作者)的引用:
ref元素是
通过标记的bean属性指定目标bean是最通用的形式,并且允许创建对相同容器或父容器中的任何bean的引用,而不管它是否位于相同的XML文件中。bean属性的值可以与目标bean的id属性相同,也可以与目标bean的name属性中的值相同。
通过父属性指定目标bean将创建对当前容器的父容器中的bean的引用。父属性的值可以与目标bean的id属性相同,也可以与目标bean的name属性中的值相同,目标bean必须位于当前bean的父容器中。当您拥有容器的层次结构,并且希望将现有的bean与与父bean同名的代理一起包装在父容器中时,您将主要使用此bean引用变体。
class="org.springframework.aop.framework.ProxyFactoryBean">
ref元素上的本地属性在4.0 bean xsd中不再受支持,因为它不再提供超过常规bean引用的值。在升级到4.0模式时,只需将现有的ref本地引用更改为ref bean。
内部BEAN:
内部bean定义不需要定义id或名称;如果指定,容器不会使用这样的值作为标识符。容器还忽略了创建时的作用域标志:内部bean总是匿名的,它们总是用外部bean创建的。除了将内部bean注入到封闭的bean之外,不可能将内部bean注入到协作bean中,也不可能单独访问它们。
作为特例,可以接收来自自定义范围的破坏回调,例如请求范围内内在bean包含在一个单例bean:内心的bean实例的创建将绑定到包含bean,但破坏回调允许它参与请求范围的生命周期。这种情况并不常见;内部bean通常只是简单地共享其包含bean的范围。
集合:
在、
[email protected]
[email protected]
[email protected]
just some string
map键值或值,或集合值的值,也可以是以下任何元素之一:
bean | ref | idref | list | set | map | props | value | null
集合合并:
Spring容器还支持集合的合并。应用程序开发人员可以定义父样式的、、
、、
关于合并的这一部分将讨论父-子bean机制。不熟悉父bean和子bean定义的读者可能希望在继续之前阅读相关部分。
下面的例子演示了集合合并:
[email protected]
[email protected]
[email protected]
[email protected]
注意,子bean定义的adminemail属性的
[email protected]
[email protected]
[email protected]
子属性集合的值集继承父
这种合并行为类似于、和
元素的特定情况下,维护与list集合类型相关联的语义,即值的有序集合的概念;父列表的值先于所有子列表的值。对于映射、集合和属性集合类型,不存在排序。因此,对于容器内部使用的关联映射、集合和属性实现类型下面的集合类型,没有有效的排序语义。
集合合并的限制:
您不能合并不同的集合类型(例如映射和列表),如果您尝试合并,则会引发适当的异常。merge属性必须在较低的继承的子定义上指定;在父集合定义上指定merge属性是多余的,不会导致所需的合并。
强类型集合:
随着Java 5中泛型类型的引入,您可以使用强类型集合。也就是说,可以声明集合类型,使其只能包含字符串元素(例如)。如果您使用Spring依赖于将强类型集合注入bean,则可以利用Spring的类型转换支持,以便在将强类型集合实例的元素添加到集合之前将其转换为适当的类型。
public class Foo {
private Map accounts;
public void setAccounts(Map accounts) {
this.accounts = accounts;
}
}
当foo bean的accounts属性准备注入时,关于强类型映射
空字符串值
Spring将属性等的空参数视为空字符串。以下基于xml的配置元数据片段将email属性设置为空字符串值(“”)。
前面的例子等价于下面的Java代码:
exampleBean.setEmail("");
上述配置相当于以下Java代码:
exampleBean.setEmail(null);
具有p名称空间的XML快捷方式
p-namespace允许您使用bean元素的属性,而不是嵌套的
Spring支持具有名称空间的可扩展配置格式,名称空间基于XML模式定义。本章讨论的bean配置格式是在XML模式文档中定义的。但是,p名称空间不是在XSD文件中定义的,它只存在于Spring的核心中。
下面的示例显示了两个XML片段,它们解析得到相同的结果:第一个使用标准XML格式,第二个使用p名称空间。
该示例显示了bean定义中p名称空间中名为email的属性。这告诉Spring包含一个属性声明。正如前面提到的,p-namespace没有模式定义,因此可以将属性的名称设置为属性名。
下一个示例包含另外两个bean定义,它们都对另一个bean有引用:
如您所见,此示例不仅包含使用p名称空间的属性值,而且还使用特殊格式声明属性引用。第一个bean定义使用
p名称空间没有标准XML格式灵活。例如,声明属性引用的格式与以Ref结尾的属性冲突,而标准XML格式则不冲突。我们建议您仔细选择您的方法,并与团队成员进行沟通,以避免同时生成使用这三种方法的XML文档。
使用c名称空间的XML快捷方式
与名为“带有p-namespace的XML快捷方式”一节类似,在Spring 3.1中新引入的c-namespace允许使用内联属性配置构造函数参数,而不是使用嵌套的构造函数-arg元素。
让我们回顾一下c: namespace中“基于构造器的依赖注入”一节中的示例:
命名空间使用与p: one (bean引用的末尾-ref)相同的约定,用于按名称设置构造函数参数。同样,即使没有在XSD模式中定义它(但是它存在于Spring核心中),也需要声明它。
对于极少数构造函数参数名不可用的情况(通常如果字节码是在没有调试信息的情况下编译的),可以使用回退参数索引:
由于XML语法的原因,索引表示法需要有前导_,因为XML属性名不能以数字开头(即使一些IDE允许)。
实际上,构造函数解析机制在匹配参数方面非常有效,因此,除非确实需要,否则我们建议在配置中使用名称表示法。
复合属性名
在设置bean属性时,可以使用复合或嵌套属性名,只要路径的所有组件(除了最终属性名之外)都不是null。考虑以下bean定义。
foo bean有一个fred属性,它有一个bob属性,它有一个sammy属性,最后一个sammy属性被设置为123。为了使其工作,foo的fred属性和fred的bob属性在构造bean或抛出NullPointerException之后不能为null。
使用depends-on
如果一个bean是另一个bean的依赖项,通常意味着一个bean被设置为另一个bean的属性。通常,您可以使用基于xml的配置元数据中的元素来完成此任务。然而,有时bean之间的依赖关系不太直接;例如,需要触发类中的静态初始化器,例如数据库驱动程序注册。依赖属性可以显式强制在使用此元素的bean初始化之前初始化一个或多个bean。下面的例子使用depends-on属性来表示对单个bean的依赖关系:
要表示对多个bean的依赖关系,请提供一个bean名称列表作为depends-on属性的值,其中逗号、空格和分号用作有效的分隔符:
bean定义中的depends-on属性既可以指定初始化时依赖项,也可以指定相应的销毁时依赖项(在单例bean的情况下)。定义与给定bean的依赖关系的依赖bean首先被销毁,然后销毁给定bean本身。因此,依赖也可以控制关机顺序。
延迟初始化的BEAN
默认情况下,ApplicationContext实现作为初始化过程的一部分急切地创建和配置所有单例bean。通常,这种预实例化是可取的,因为配置或周围环境中的错误是立即发现的,而不是几小时甚至几天之后。当不需要这种行为时,可以将bean定义标记为延迟初始化,从而防止单例bean的预实例化。延迟初始化的bean告诉IoC容器在首次请求bean实例时创建它,而不是在启动时。
在XML中,这种行为由
当前面的配置被ApplicationContext使用时,当ApplicationContext启动时,名为lazy的bean不会急切地预先实例化,相反的,不是延迟bean被急切地预先实例化。
但是,当一个延迟初始化bean是一个没有延迟初始化的单例bean的依赖项时,ApplicationContext在启动时创建这个延迟初始化bean,因为它必须满足单例bean的依赖项。延迟初始化的bean被注入到其他没有延迟初始化的单例bean中。
您还可以通过在
自动装配的合作者
Spring容器可以自动连接协作bean之间的关系。通过检查ApplicationContext的内容,您可以允许Spring为您的bean自动解析协作者(其他bean)。自动布线有以下优点:
在使用基于xml的配置元数据[2]时,可以使用
Table 7.2. Autowiring modes
Mode | Explanation |
---|---|
no |
(默认)没有自动装配。Bean引用必须通过ref元素定义。对于较大的部署不建议更改默认设置,因为显式地指定协作者可以提供更大的控制和清晰度。在某种程度上,它记录了系统的结构。 |
byName |
按属性名自动连接。Spring寻找与需要自动连接的属性名称相同的bean。例如,如果一个bean定义被设置为按名称自动连接,并且它包含一个主属性(也就是说,它有一个setMaster(..)方法),Spring将查找一个名为master的bean定义,并使用它来设置该属性。 |
byType |
如果容器中恰好存在属性类型的bean,则允许自动连接属性。如果存在一个以上的异常,就会抛出一个致命的异常,这表明您可能不会对该bean使用byType自动连接。如果没有匹配的bean,则什么也不会发生;属性未设置。 |
constructor |
类似于byType,但适用于构造函数参数。如果容器中没有构造函数参数类型的bean,则会引发致命错误。 |
使用byType或构造函数自动连接模式,您可以连接数组和类型集合。在这种情况下,将提供容器中与预期类型匹配的所有自动连接候选项,以满足依赖性。如果所需的键类型是字符串,则可以自动连接强类型映射。自动连接映射值将由所有匹配预期类型的bean实例组成,映射键将包含相应的bean名称。
您可以将自动连接行为与依赖项检查结合起来,依赖项检查是在自动连接完成之后执行的。
autowiring的限制和缺点
自动连接在整个项目中一致使用时效果最好。如果一般不使用自动连接,那么开发人员可能会混淆使用它只连接一个或两个bean定义。
考虑autowiring的限制和缺点:
在后一种情况下,您有几个选项:
将bean排除在autowiring之外
在每个bean的基础上,您可以从自动连接中排除一个bean。在Spring的XML格式中,将
autowire-candidate属性被设计成只影响基于类型的自动连接。它不影响按名称显式引用,即使指定的bean没有标记为自动连接候选,也会解析该引用。因此,如果名称匹配,则按名称自动连接将注入bean。
您还可以基于对bean名称的模式匹配来限制自动连接候选项。顶级的
这些技术对于您永远不希望通过自动连接将其注入到其他bean中的bean非常有用。这并不意味着不能使用自动连接来配置被排除的bean本身。相反,bean本身不适合自动连接其他bean。
方法注入
在大多数应用程序场景中,容器中的大多数bean都是单例的。当单例bean需要与另一个单例bean协作,或者非单例bean需要与另一个非单例bean协作时,通常通过将一个bean定义为另一个bean的属性来处理依赖关系。当bean的生命周期不同时,就会出现问题。假设单例bean A需要使用非单例(原型)bean B,也许在对A的每个方法调用中都是这样。容器只创建单例bean A一次,因此只有一次机会来设置属性。容器不能每次都向bean A提供bean B的新实例。
解决办法是放弃一些反向控制。您可以通过实现ApplicationContextAware接口使bean A知道容器,并在每次bean A需要bean B实例时对容器进行getBean(“B”)调用来请求(通常是新的)bean B实例。下面是这种方法的一个例子:
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
前面的内容并不可取,因为业务代码知道Spring框架并与之耦合。方法注入是Spring IoC容器的一个高级特性,它允许以一种干净的方式处理这个用例。
查找方法注入
查找方法注入是容器重写容器管理bean上的方法的能力,以返回容器中另一个命名bean的查找结果。查找通常涉及一个原型bean,就像前一节描述的场景一样。Spring框架通过使用来自CGLIB库的字节码生成来动态生成覆盖该方法的子类来实现此方法注入。
查看前面代码片段中的CommandManager类,您会看到Spring容器将动态覆盖createCommand()方法的实现。CommandManager类将不会有任何Spring依赖项,这可以在重做的示例中看到:
package fiona.apple;
// no more Spring imports!
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
在包含要注入的方法的客户端类中(在本例中为CommandManager),要注入的方法需要以下形式的签名:
[abstract] theMethodName(no-arguments);
如果方法是抽象的,则动态生成的子类实现该方法。否则,动态生成的子类将覆盖在原始类中定义的具体方法。例如:
被标识为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.springframe .beans.factory中找到ServiceLocatorFactoryBean。配置包)将被使用。
任意的方法替换
方法注入的一种不如查找方法注入有用的形式是,可以用另一种方法实现替换托管bean中的任意方法。在真正需要该功能之前,用户可以安全地跳过本节的其余部分。
对于基于xml的配置元数据,您可以使用replacsed -method元素为已部署的bean用另一个方法实现替换现有的方法实现。考虑下面的类,它具有我们想要覆盖的computeValue方法:
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
/**
* 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定义如下:
String
您可以在< replacsed -method/>元素中使用一个或多个包含的< ag -type/>元素来指示被覆盖的方法的方法签名。只有在方法重载且类中存在多个变量时,参数的签名才是必要的。为了方便起见,参数的类型字符串可以是完全限定类型名称的子字符串。例如,以下所有匹配java.lang.String:
java.lang.String
String
Str
由于参数的数量通常足以区分每一个可能的选择,这个快捷方式可以节省大量的输入,只允许您键入与参数类型匹配的最短字符串。
当您创建bean定义时,您将创建用于创建由该bean定义定义的类的实际实例的配方。bean定义是配方的想法很重要,因为这意味着,与类一样,您可以从单个配方创建多个对象实例。
您不仅可以控制要插入到从特定bean定义创建的对象中的各种依赖项和配置值,还可以控制从特定bean定义创建的对象的范围。这种方法功能强大且灵活,因为您可以选择通过配置创建的对象的范围,而不必在Java类级别上烘烤对象的范围。可以将bean定义为部署在许多范围中的一种:开箱即用,Spring框架支持7个范围,其中5个范围只有在使用web敏感的ApplicationContext时才可用。
开箱即用支持以下作用域。您还可以创建自定义范围。
Table 7.3. Bean scopes
Scope | Description |
---|---|
singleton |
(默认情况下)为每个Spring IoC容器将单个bean定义作用域为单个对象实例。 |
prototype |
将单个bean定义作用域到任意数量的对象实例。 |
request |
将单个bean定义作用于单个HTTP请求的生命周期;也就是说,每个HTTP请求都有自己的bean实例,该实例是从单个bean定义的后面创建的。仅在web感知的Spring ApplicationContext上下文中有效。 |
session |
将单个bean定义限定为HTTP会话的生命周期。仅在web感知的Spring ApplicationContext上下文中有效。 |
globalSession |
将单个bean定义限定为全局HTTP会话的生命周期。通常只有在Portlet上下文中使用时才有效。仅在web感知的Spring ApplicationContext上下文中有效。 |
application |
将单个bean定义作用于servlet上下文的生命周期。仅在web感知的Spring ApplicationContext上下文中有效。 |
websocket |
将单个bean定义定义为WebSocket的生命周期。仅在web感知的Spring ApplicationContext上下文中有效。 |
在Spring 3.0中,线程作用域是可用的,但是在默认情况下没有注册。有关更多信息,请参阅SimpleThreadScope文档。有关如何注册此自定义范围或任何其他自定义范围的说明,请参阅“使用自定义范围”一节。
The singleton scope
只有一个单例bean的共享实例是受管理的,所有对具有id或与该bean定义匹配的id的bean的请求都会导致Spring容器返回一个特定的bean实例。
换句话说,当您定义一个bean定义并且它的作用域是单例的时候,Spring IoC容器正好创建由该bean定义定义的对象的一个实例。这个实例存储在这样的单例bean的缓存中,对于该命名bean的所有后续请求和引用都返回缓存的对象。
Spring的单例bean概念不同于四人组(GoF)模式书中定义的单例模式。单例对象的GoF对对象的作用域进行了硬编码,以便在每个类加载器中创建一个且只有一个特定类的实例。Spring singleton的作用域最好描述为每个容器和每个bean。这意味着,如果在单个Spring容器中为特定的类定义一个bean,那么Spring容器将创建由该bean定义定义的类的一个且仅一个实例。singleton作用域是Spring中的默认作用域。要在XML中将bean定义为单例,可以这样编写:
每次对特定bean发出请求时,bean部署的非单例原型作用域都会创建一个新的bean实例。也就是说,将bean注入到另一个bean中,或者通过容器上的getBean()方法调用请求它。作为规则,对所有有状态bean使用prototype作用域,对无状态bean使用singleton作用域。
下图演示了Spring原型作用域。数据访问对象(DAO)通常不配置为原型,因为典型的DAO不包含任何会话状态;对于作者来说,重用单例图的核心更加容易。
下面的例子将bean定义为XML中的原型:
与其他作用域不同,Spring不管理原型bean的完整生命周期:容器实例化、配置和组装原型对象,并将其交给客户机,而没有原型实例的进一步记录。因此,尽管初始化生命周期回调方法在所有对象上都被调用,而不管范围如何,在原型的情况下,配置的销毁生命周期回调不会被调用。客户端代码必须清理原型范围内的对象,并释放原型bean所持有的昂贵资源。要让Spring容器释放原型作用域bean所持有的资源,请尝试使用自定义bean后处理程序,该后处理程序保存对需要清理的bean的引用。
在某些方面,Spring容器在原型作用域bean中的角色是Java new操作符的替代。所有超过这一点的生命周期管理都必须由客户端处理。(有关Spring容器中bean的生命周期的详细信息,请参见7.6.1节“生命周期回调”)。
具有原型bean依赖关系的单例bean
当您使用依赖于原型bean的单列作用域bean时,请注意依赖关系是在实例化时解析的。因此,如果您依赖于将原型作用域bean注入到单实例作用域bean中,那么将实例化一个新的原型bean,然后将依赖注入到单例bean中。原型实例是唯一一个提供给单列作用域bean的实例。
但是,假设您希望单列作用域bean在运行时重复获取原型作用域bean的新实例。您不能将依赖项作用域的原型bean注入到单例bean中,因为该注入只在Spring容器实例化单例bean并解析和注入其依赖项时发生一次。如果您在运行时不止一次需要一个原型bean的新实例,请参见7.4.6节“方法注入”
请求、会话、globalSession、应用程序和websocket作用域只有在使用web感知的Spring ApplicationContext实现(如XmlWebApplicationContext)时才可用。如果将这些作用域与常规的Spring IoC容器(如ClassPathXmlApplicationContext)一起使用,就会抛出一个IllegalStateException,抱怨未知的bean作用域。
初始WEB配置
要在请求、会话、globalSession、应用程序和websocket级别(web作用域bean)上支持bean的作用域,需要在定义bean之前进行一些较小的初始配置。(标准作用域、单例和原型不需要这个初始设置。)
如何完成这个初始设置取决于您的特定Servlet环境。
如果您在Spring Web MVC中访问作用域bean,那么实际上,在由Spring DispatcherServlet或DispatcherPortlet处理的请求中,就不需要特殊的设置:DispatcherServlet和DispatcherPortlet已经公开了所有相关的状态。
如果使用Servlet 2.5 web容器,在Spring的DispatcherServlet之外处理请求(例如,在使用JSF或Struts时),则需要注册org.springframe .web.context.request。RequestContextListener ServletRequestListener。对于Servlet 3.0+,这可以通过WebApplicationInitializer接口以编程方式完成。或者,对于旧容器,将以下声明添加到web应用程序的web中。xml文件:
...
org.springframework.web.context.request.RequestContextListener
...
或者,如果您的侦听器设置有问题,可以考虑使用Spring的RequestContextFilter。过滤器映射取决于周围的web应用程序配置,因此您必须根据需要更改它。
...
requestContextFilter
org.springframework.web.filter.RequestContextFilter
requestContextFilter
/*
...
DispatcherServlet、RequestContextListener和RequestContextFilter都做完全相同的事情,即将HTTP请求对象绑定到服务该请求的线程。这使得请求作用域和会话作用域的bean在调用链的更下游可用。
Request scope
考虑以下用于bean定义的XML配置:
Spring容器为每个HTTP请求使用LoginAction bean定义来创建LoginAction bean的新实例。也就是说,loginAction bean的作用域是在HTTP请求级别。您可以随心所欲地更改创建的实例的内部状态,因为从相同loginAction bean定义创建的其他实例不会看到这些状态更改;它们是特定于单个请求的。当请求完成处理时,将丢弃该请求的作用域bean。
当使用注解驱动的组件或Java配置时,@RequestScope注解可用于将组件分配给请求范围。
@RequestScope
@Component
public class LoginAction {
// ...
}
Session scope
考虑以下用于bean定义的XML配置:
Spring容器通过在单个HTTP会话的生命周期中使用UserPreferences bean定义来创建UserPreferences bean的新实例。换句话说,userPreferences bean有效地限定在HTTP会话级别。与请求范围内bean一样,你可以改变内部状态的实例创建尽可能多的你想要的,知道其他HTTP会话实例也使用相同的实例创建userPreferences bean定义看不到这些变化状态,因为他们是特定于一个单独的HTTP会话。当HTTP会话最终被丢弃时,作用域为该特定HTTP会话的bean也将被丢弃。
当使用注解驱动组件或Java配置时,@SessionScope注解可用于将组件分配给会话范围。
@SessionScope
@Component
public class UserPreferences {
// ...
}
Global session scope
globalSession作用域类似于标准HTTP会话作用域(如上所述),并且仅适用于基于portlet的web应用程序的上下文中。portlet规范定义了组成单个portlet web应用程序的所有portlet之间共享的全局会话的概念。在globalSession作用域中定义的bean被限定(或绑定)到全局portlet会话的生命周期。
如果编写一个基于servlet的标准web应用程序,并将一个或多个bean定义为具有globalSession作用域,则使用标准HTTP会话作用域,并且不会引发错误。
Application scope
Spring容器通过对整个web应用程序一次性使用AppPreferences bean定义来创建AppPreferences bean的新实例。也就是说,appPreferences bean的作用域在ServletContext级别,存储为一个常规的ServletContext属性。这有点类似于Spring单例bean,但在两个重要方面有所不同:它是每个servlet上下文的单例,而不是每个Spring的“ApplicationContext”(在任何给定的web应用程序中都可能有多个),而且它实际上是作为servlet上下文属性公开的,因此是可见的。
当使用注解驱动组件或Java配置时,@ApplicationScope注解可用于将组件分配给应用程序范围。
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
Scoped beans as dependencies
Spring IoC容器不仅管理对象(bean)的实例化,而且还管理协作者(或依赖项)的连接。如果您想将(例如)HTTP请求作用域bean注入到另一个更长的作用域bean中,您可以选择将AOP代理注入到作用域bean中。也就是说,您需要注入一个代理对象,该代理对象公开与作用域对象相同的公共接口,但也可以从相关范围(例如HTTP请求)检索实际目标对象,并将方法调用委托给实际对象。
您还可以在具有单例作用域的bean之间使用
当对范围原型bean声明
而且,作用域代理并不是以生命周期安全的方式从较短的作用域访问bean的唯一方法。您还可以简单地将注入点(即构造函数/setter参数或自动连接字段)声明为ObjectFactory
作为扩展的变体,您可以声明ObjectProvider
它的JSR-330变体称为提供者,与提供者
下面示例中的配置只有一行,但是理解其背后的“为什么”和“如何”非常重要。
要创建这样的代理,您需要将一个子
在前面的例子中,单例bean userManager被注入了对HTTP会话作用域bean userPreferences的引用。这里的重点是userManager bean是一个单例:每个容器只实例化它一次,它的依赖项(在本例中只有一个,userPreferences bean)也只注入一次。这意味着userManager bean将只操作完全相同的userPreferences对象,即它最初注入的对象。
当您将一个较短的作用域bean注入到一个较长的作用域bean中时,这不是您想要的行为,例如将一个HTTP会话作用域协作bean作为依赖项注入到单例bean中。相反,您需要一个userManager对象,并且在HTTP会话的生命周期中,您需要一个特定于上述HTTP会话的userPreferences对象。因此,容器创建的对象公开了与UserPreferences类(理想情况下是UserPreferences实例的对象)完全相同的公共接口,后者可以从范围机制(HTTP请求、会话等)获取真正的UserPreferences对象。容器将此代理对象注入userManager bean,该bean不知道此UserPreferences引用是代理。在本例中,当UserManager实例调用依赖注入的UserPreferences对象上的方法时,它实际上是在调用代理上的方法。然后代理从(在本例中)HTTP会话中获取真正的UserPreferences对象,并将方法调用委托给检索到的真正UserPreferences对象。
因此,在将请求作用域bean、会话作用域bean和全局会话作用域bean注入协作对象时,需要进行以下正确和完整的配置:
Choosing the type of proxy to create
默认情况下,当Spring容器为标记为
CGLIB代理只拦截公共方法调用!不要在这样的代理上调用非公共方法;它们不会被委托给实际作用域的目标对象。
或者,您可以通过为
有关选择基于类或基于接口的代理的详细信息,请参见第11.6节“代理机制”。
自定义作用域:
bean作用域机制是可扩展的;您可以定义自己的作用域,甚至重新定义现有的作用域,尽管后者被认为是不好的实践,而且您不能覆盖内置的单例和原型作用域。
生命周期回调:
要与容器对bean生命周期的管理进行交互,可以实现Spring InitializingBean和DisposableBean接口。容器为前者调用afterPropertiesSet(),为后者调用destroy(),以允许bean在初始化和销毁bean时执行某些操作。
JSR-250 @PostConstruct和@PreDestroy注释通常被认为是在现代Spring应用程序中接收生命周期回调的最佳实践。使用这些注释意味着您的bean没有耦合到特定于Spring的接口。有关详细信息,请参见第7.9.8节“@PostConstruct和@PreDestroy”。
如果您不想使用JSR-250注释,但仍然希望消除耦合,请考虑使用init-method和destroy-method对象定义元数据。
在内部,Spring框架使用BeanPostProcessor实现来处理它可以找到并调用适当方法的任何回调接口。如果您需要定制特性或Spring不提供的其他生命周期行为,您可以自己实现BeanPostProcessor。有关更多信息,请参见7.8节“容器扩展点”。
除了初始化和销毁回调之外,spring管理的对象还可以实现生命周期接口,以便这些对象可以参与由容器自己的生命周期驱动的启动和关闭过程。
本节将描述生命周期回调接口。
初始化回调
org.springframework.beans.factory.InitializingBean接口允许bean在容器设置了bean上所有必要的属性之后执行初始化工作。InitializingBean接口指定了一个方法:
void afterPropertiesSet() throws Exception;
建议不要使用InitializingBean接口,因为它不必要地将代码耦合到Spring。或者,使用@PostConstruct注解或指定POJO初始化方法。对于基于xml的配置元数据,可以使用init-method属性指定具有空无参数签名的方法的名称。使用Java config,您可以使用@Bean的initMethod属性,请参阅“接收生命周期回调”一节。例如:
public class ExampleBean {
public void init() {
// do some initialization work
}
}
和……完全一样。
public class AnotherExampleBean implements InitializingBean {
public void afterPropertiesSet() {
// do some initialization work
}
}
但是没有将代码与Spring绑定。
销毁回调
实施org.springframework.beans.factory.dispose sablebean接口允许bean在包含它的容器被销毁时获得回调。DisposableBean接口指定了一个方法:
void destroy() throws Exception;
建议不要使用DisposableBean回调接口,因为它不必要地将代码耦合到Spring。或者,使用@PreDestroy注释或指定bean定义支持的泛型方法。对于基于xml的配置元数据,可以在
public class ExampleBean {
public void cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
完全相同:
public class AnotherExampleBean implements DisposableBean {
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
但是没有将代码与Spring绑定。
可以为
默认初始化和销毁方法
当您编写初始化和销毁不使用特定于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类已经有了与约定不一致的回调方法,那么可以使用
Spring容器保证在向bean提供所有依赖项之后立即调用配置的初始化回调。因此,在原始bean引用上调用初始化回调,这意味着AOP拦截器等等还没有应用到bean。首先完全创建目标bean,然后应用带有拦截器链的AOP代理(例如)。如果目标bean和代理是单独定义的,那么您的代码甚至可以绕过代理与原始目标bean交互。因此,将拦截器应用于init方法是不一致的,因为这样做会将目标bean的生命周期与其代理/拦截器耦合起来,并在代码直接与原始目标bean交互时留下奇怪的语义。
结合生命周期机制
从spring2.5开始,您有三个选项来控制bean的生命周期行为:初始化bean和可丢弃bean回调接口;自定义init()和destroy()方法;以及@PostConstruct和@PreDestroy注释。您可以组合这些机制来控制给定的bean。
如果为一个bean配置了多个生命周期机制,并且每个机制都配置了不同的方法名,那么每个配置的方法将按照下面列出的顺序执行。但是,如果为这些生命周期机制中的一个以上配置了相同的方法名(例如,初始化方法的init()),则该方法将执行一次,如上一节所述。
为同一个bean配置的具有不同初始化方法的多个生命周期机制调用如下:
@PostConstruct
afterPropertiesSet()
as defined by the InitializingBean
callback interfaceinit()
method销毁方法的调用顺序相同:
@PreDestroy
destroy()
as defined by the DisposableBean
callback interfacedestroy()
method启动和关闭的回调
生命周期接口定义了任何具有自己生命周期需求的对象的基本方法(例如,启动和停止一些后台过程):
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
任何spring管理的对象都可以实现该接口。然后,当ApplicationContext本身接收到启动和停止信号时,例如对于运行时的停止/重启场景,它将把这些调用级联到该上下文中定义的所有生命周期实现。它通过委托给一个生命周期处理器来做到这一点:
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
请注意,LifecycleProcessor本身就是生命周期接口的扩展。它还添加了另外两个方法来响应正在刷新和关闭的上下文。
请注意常规的org.springframe .context。Lifecycle接口只是显式启动/停止通知的普通契约,并不意味着在上下文刷新时自动启动。考虑实施org.springframework.context。SmartLifecycle用于对特定bean(包括启动阶段)的自动启动进行细粒度控制。另外,请注意,停止通知不能保证在销毁之前到来:在常规关闭时,所有生命周期bean都将在传播常规销毁回调之前首先收到停止通知;但是,在上下文生命周期内的热刷新或在失败的刷新尝试时,只调用destroy方法。
启动和关闭调用的顺序可能很重要。如果任意两个对象之间存在“依赖于”关系,依赖方将在依赖之后启动,在依赖之前停止。然而,有时直接依赖关系是未知的。您可能只知道某种类型的对象应该在另一种类型的对象之前启动。在这些情况下,SmartLifecycle接口定义了另一个选项,即在其超级接口上定义的getPhase()方法phase。
public interface Phased {
int getPhase();
}
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
启动时,相位最低的对象先启动,停止时,顺序相反。因此,一个实现SmartLifecycle的对象,它的getPhase()方法返回Integer。MIN_VALUE将是最先启动和最后停止的。在光谱的另一端,相位值为整数。MAX_VALUE将指示对象应该最后启动,然后首先停止(可能是因为它依赖于正在运行的其他进程)。在考虑阶段值时,同样重要的是要知道,对于任何不实现SmartLifecycle的“正常”生命周期对象,默认阶段都是0。因此,任何负相位值都表示对象应该在这些标准组件之前启动(在它们之后停止),反之亦然。
正如您所看到的,SmartLifecycle定义的stop方法接受回调。任何实现都必须在该实现的关闭过程完成后调用该回调的run()方法。由于LifecycleProcessor接口的默认实现DefaultLifecycleProcessor将等待每个阶段中的对象组的超时值来调用该回调,因此在需要时启用异步关闭。每个阶段的默认超时时间是30秒。您可以通过在上下文中定义名为“lifecycleProcessor”的bean来覆盖默认的生命周期处理器实例。如果您只想修改超时,那么定义以下内容就足够了:
如前所述,LifecycleProcessor接口还定义了用于刷新和关闭上下文的回调方法。后者将驱动关机进程,就像显式调用stop()一样,但它将在上下文关闭时发生。另一方面,“刷新”回调启用了SmartLifecycle bean的另一个特性。当上下文被刷新(在所有对象实例化和初始化之后)时,将调用该回调,此时默认的生命周期处理器将检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值。如果“true”,则该对象将在该点启动,而不是等待上下文的或其自身的start()方法的显式调用(与上下文刷新不同,对于标准上下文实现,上下文启动不会自动发生)。“阶段”值以及任何“依赖”关系将以与上述相同的方式确定启动顺序。
在非web应用程序中优雅地关闭Spring IoC容器
本节仅适用于非web应用程序。Spring的基于web的ApplicationContext实现已经有了适当的代码,可以在关闭相关web应用程序时优雅地关闭Spring IoC容器。
如果您在非web应用程序环境中使用Spring的IoC容器;例如,在富客户机桌面环境中;您向JVM注册了一个关闭钩子。这样做可以确保适当的关闭,并调用singleton bean上的相关销毁方法,以便释放所有资源。当然,您仍然必须正确配置和实现这些销毁回调。
要注册一个关闭钩子,可以调用registerShutdownHook()方法,该方法是在ConfigurableApplicationContext接口上声明的:
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...
}
}
当ApplicationContext创建实现org.springframework.context.ApplicationContextAware接口的实例时。该实例提供了对该ApplicationContext的引用。
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
因此,bean可以通过ApplicationContext接口,或通过将引用转换为该接口的一个已知子类(如ConfigurableApplicationContext),以编程方式操作创建它们的ApplicationContext,该子类公开了额外的功能。一种用途是对其他bean进行编程式检索。有时这种能力是有用的;但是,一般情况下您应该避免使用它,因为它将代码耦合到Spring,而不遵循控制反转样式,在这种样式中协作者作为属性提供给bean。ApplicationContext的其他方法提供对文件资源、发布应用程序事件和访问MessageSource的访问。这些附加特性在第7.15节“ApplicationContext的附加功能”中进行了描述。
从spring2.5开始,自动连接是获取对ApplicationContext引用的另一种选择。“传统”构造函数和byType自动连接模式(如7.4.5节所述,“自动连接协作者”)可以分别为构造函数参数或setter方法参数提供类型ApplicationContext的依赖关系。要获得更大的灵活性,包括自动连接字段和多个参数方法的能力,请使用新的基于注释的自动连接特性。如果您这样做了,那么ApplicationContext将自动连接到一个字段、构造函数参数或方法参数中,如果所讨论的字段、构造函数或方法携带@Autowired注释,则该字段、构造函数或方法参数将预期为ApplicationContext类型。有关更多信息,请参见7.9.2节“@Autowired”。
当ApplicationContext创建实现org.springframework.beans.factory.BeanNameAware接口的类时。这个类提供了对其关联对象定义中定义的名称的引用。
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
回调是在填充普通bean属性之后调用的,但是在初始化回调之前调用,例如InitializingBean afterPropertiesSet或定制的init-method。
除了上面讨论的ApplicationContextAware和BeanNameAware之外,Spring还提供了一系列可感知的接口,这些接口允许bean向容器表明它们需要某种基础设施依赖。下面总结了最重要的感知接口——作为一般规则,名称很好地说明了依赖类型:
Table 7.4. Aware interfaces
Name | Injected Dependency | Explained in… |
---|---|---|
|
声明 |
Section 7.6.2, “ApplicationContextAware and BeanNameAware” |
|
封装的ApplicationContext的事件发布者 | Section 7.15, “Additional capabilities of the ApplicationContext” |
|
类加载器,用于加载bean类。 | Section 7.3.2, “Instantiating beans” |
|
声明 |
Section 7.6.2, “ApplicationContextAware and BeanNameAware” |
|
声明bean的名称 | Section 7.6.2, “ApplicationContextAware and BeanNameAware” |
|
资源适配器BootstrapContext引导容器运行。通常只能在JCA感知的applicationcontext中使用 | Chapter 32, JCA CCI |
|
为在加载时处理类定义而定义的weaver | Section 11.8.4, “Load-time weaving with AspectJ in the Spring Framework” |
|
用于解析消息的配置策略(支持参数化和国际化) | Section 7.15, “Additional capabilities of the ApplicationContext” |
|
Spring JMX notification publisher |
Section 31.7, “Notifications” |
|
Current |
Chapter 25, Portlet MVC Framework |
|
Current |
Chapter 25, Portlet MVC Framework |
|
Configured loader for low-level access to resources |
Chapter 8, Resources |
|
当前容器运行的ServletConfig。仅在web感知的Spring ApplicationContext中有效 | Chapter 22, Web MVC framework |
|
容器运行的当前ServletContext。仅在web感知的Spring ApplicationContext中有效 | Chapter 22, Web MVC framework |
再次注意,这些接口的使用将您的代码与Spring API绑定在一起,并且不遵循控制风格的反转。因此,对于需要对容器进行编程访问的基础设施bean,建议使用它们。
bean定义可以包含许多配置信息,包括构造函数参数、属性值和容器特定的信息,如初始化方法、静态工厂方法名称等。子bean定义从父定义继承配置数据。子定义可以根据需要覆盖某些值,或者添加其他值。使用父bean和子bean定义可以节省很多输入。实际上,这是一种模板形式。
如果以编程方式使用ApplicationContext接口,则子bean定义由ChildBeanDefinition类表示。大多数用户并不在这个级别上使用它们,而是在ClassPathXmlApplicationContext之类的环境中声明地配置bean定义。当您使用基于xml的配置元数据时,您可以通过使用父属性来指示子bean定义,并将父bean指定为该属性的值。
如果没有指定任何类,则子bean定义使用父定义中的bean类,但也可以覆盖它。在后一种情况下,子bean类必须与父类兼容,也就是说,它必须接受父类的属性值。
子bean定义从父bean继承范围、构造函数参数值、属性值和方法覆盖,并具有添加新值的选项。您指定的任何范围、初始化方法、销毁方法和/或静态工厂方法设置都将覆盖相应的父设置。
其余的设置总是从子定义中获取:依赖项、自动连接模式、依赖项检查、单例、惰性初始化。
前面的示例通过使用抽象属性显式地将父bean定义标记为抽象。如果父定义没有指定类,则需要显式地将父bean定义标记为抽象,如下所示:
父bean不能单独实例化,因为它是不完整的,而且它也被显式地标记为抽象的。当定义是像这样抽象时,它只能作为作为子定义的父定义的纯模板bean定义使用。试图单独使用这样的抽象父bean,将其引用为另一个bean的ref属性,或者使用父bean id执行显式getBean()调用,将返回错误。类似地,容器的内部预实例化ingletons()方法忽略定义为抽象的bean定义。
默认情况下,ApplicationContext预实例化所有的单例。因此,它是重要的(至少对单例bean),如果你有一个(父)bean定义你只打算使用作为模板,这个定义指定了一个类,您必须确保设置抽象属性为true,否则应用程序上下文会(试图)pre-instantiate抽象的bean。
通常,应用程序开发人员不需要子类化ApplicationContext实现类。相反,可以通过插入特殊集成接口的实现来扩展Spring IoC容器。下面几节将描述这些集成接口。
使用BeanPostProcessor自定义bean
BeanPostProcessor接口定义了回调方法,您可以实现这些方法来提供自己的(或覆盖容器的默认)实例化逻辑、依赖解析逻辑等等。如果希望在Spring容器完成实例化、配置和初始化bean之后实现一些定制逻辑,可以插入一个或多个BeanPostProcessor实现。
您可以配置多个BeanPostProcessor实例,并且可以通过设置order属性来控制这些BeanPostProcessor执行的顺序。只有当BeanPostProcessor实现了有序接口时,才能设置此属性;如果您编写自己的BeanPostProcessor,您也应该考虑实现有序接口。要了解更多细节,请参考BeanPostProcessor和有序接口的javadoc。还请参阅下面关于beanpostprocessor程序注册的说明。
beanpostprocessor操作bean(或对象)实例;也就是说,Spring IoC容器实例化一个bean实例,然后beanpostprocessor完成它们的工作。
bean后处理器的作用域是每个容器。只有在使用容器层次结构时,这才是相关的。如果在一个容器中定义BeanPostProcessor,它将只对该容器中的bean进行后处理。换句话说,在一个容器中定义的bean不会由在另一个容器中定义的BeanPostProcessor进行后处理,即使这两个容器都是同一层次结构的一部分。
更改实际bean定义(即相反,您需要使用第7.8.2节中描述的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.springframe.beans.factory.config.BeanPostProcessor接口,清楚地指示该bean的后处理器性质。否则,ApplicationContext将无法在完全创建它之前按类型自动检测它。由于BeanPostProcessor需要尽早实例化,以便应用于上下文中其他bean的初始化,因此这种早期类型检测非常关键。
虽然BeanPostProcessor注册的推荐方法是通过ApplicationContext自动检测(如上所述),但是也可以使用addBeanPostProcessor方法以编程方式对ConfigurableBeanFactory进行注册。当需要在注册之前计算条件逻辑时,甚至在层次结构中跨上下文复制bean post处理器时,这一点都非常有用。但是请注意,以编程方式添加的beanpostprocessor不尊重有序接口。在这里,注册的顺序决定了执行的顺序。还要注意,以编程方式注册的beanpostprocessor总是在那些通过自动检测注册的处理器之前进行处理,而不管显式排序如何。
实现BeanPostProcessor接口的类是特殊的,容器以不同的方式处理它们。它们直接引用的所有beanpostprocessor和bean都是在启动时实例化的,这是ApplicationContext的特殊启动阶段的一部分。接下来,所有beanpostprocessor都以排序的方式注册,并应用于容器中的所有其他bean。因为AOP的自动代理是作为BeanPostProcessor本身实现的,所以无论是BeanPostProcessor还是它们直接引用的bean都没有资格进行自动代理,因此没有将方面编入其中。
对于任何这样的bean,您都应该看到一条信息日志消息:“bean foo不适合由所有BeanPostProcessor接口处理(例如:不适合自动代理)”。
请注意,如果您使用autowiring或@Resource将bean连接到BeanPostProcessor中(这可能会回到autowiring),那么在搜索类型匹配依赖项候选项时,Spring可能会访问意外的bean,从而使它们没有资格进行自动代理或其他类型的bean后处理。例如,如果您有一个用@Resource标注的依赖项,其中字段/setter名称与声明的bean名称不直接对应,并且没有使用name属性,那么Spring将访问其他bean来按类型匹配它们。
下面的示例演示如何在ApplicationContext中编写、注册和使用beanpostprocessor。
Example: Hello World, BeanPostProcessor-style
第一个例子说明了基本用法。该示例显示了一个自定义BeanPostProcessor实现,该实现在容器创建每个bean时调用该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;
}
}
请注意,InstantiationTracingBeanPostProcessor是如何简单定义的。它甚至没有名称,而且因为它是bean,所以可以像其他bean一样依赖注入。(前面的配置还定义了一个由Groovy脚本支持的bean。Spring动态语言支持在第35章“动态语言支持”中有详细介绍。
下面的简单Java应用程序执行前面的代码和配置:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;
public final class Boot {
public static void main(final String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
Messenger messenger = (Messenger) ctx.getBean("messenger");
System.out.println(messenger);
}
}
前一个应用程序的输出如下:
Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961
Example: The RequiredAnnotationBeanPostProcessor
将回调接口或注解与自定义BeanPostProcessor实现结合使用是扩展Spring IoC容器的常见方法。一个例子是Spring的RequiredAnnotationBeanPostProcessor——一个随Spring发行版一起发布的BeanPostProcessor实现,它确保用(任意)注解标记的bean上的JavaBean属性实际上(被配置为)依赖注入一个值。
使用BeanFactoryPostProcessor自定义配置元数据
下一个扩展点是org.springframe.beans.factory.config.beanfactorypostprocessor。此接口的语义与BeanPostProcessor的语义相似,但有一个主要区别:BeanFactoryPostProcessor操作bean配置元数据;也就是说,Spring IoC容器允许BeanFactoryPostProcessor读取配置元数据并在容器实例化除BeanFactoryPostProcessor之外的任何bean之前更改它。
您可以配置多个beanfactorypostprocessor,并且可以通过设置order属性来控制这些beanfactorypostprocessor执行的顺序。但是,只有当BeanFactoryPostProcessor实现了有序接口时,才能设置此属性。如果您编写自己的BeanFactoryPostProcessor,您也应该考虑实现有序接口。有关更多细节,请参考BeanFactoryPostProcessor和Ordered接口的javadoc。
如果您想要更改实际的bean实例(例如,从配置元数据创建的对象),然后您需要使用BeanPostProcessor(在上面的7.8.1节“使用BeanPostProcessor自定义bean”中进行了描述)。
虽然在技术上可以在BeanFactoryPostProcessor中使用bean实例,例如,使用BeanFactory.getBean(),这样做会导致bean过早实例化,违反标准容器生命周期。这可能会导致诸如绕过bean后处理之类的负面副作用。另外,beanfactorypostprocessor对每个容器都有作用域。只有在使用容器层次结构时,这才是相关的。如果在一个容器中定义BeanFactoryPostProcessor,它将只应用于该容器中的bean定义。一个容器中的Bean定义不会被另一个容器中的beanfactorypostprocessor后处理,即使这两个容器是同一层次结构的一部分。
bean factory后处理器在ApplicationContext中声明时自动执行,以便对定义容器的配置元数据应用更改。Spring包含许多预定义的bean工厂后处理器,例如PropertyOverrideConfigurer和PropertyPlaceholderConfigurer。例如,还可以使用自定义BeanFactoryPostProcessor来注册自定义属性编辑器。
ApplicationContext自动检测部署到其中实现BeanFactoryPostProcessor接口的任何bean。它在适当的时候将这些bean用作bean工厂的后处理器。您可以像部署其他bean一样部署这些后处理器bean。
与beanpostprocessor一样,您通常不希望为延迟初始化配置beanfactorypostprocessor。如果没有其他bean引用bean(工厂)后处理器,则该后处理器将完全不会实例化。因此,将它标记为延迟初始化将被忽略,Bean(工厂)后处理器将被急切地实例化,即使您在声明
示例:类名替换PropertyPlaceholderConfigurer
使用PropertyPlaceholderConfigurer将属性值从bean定义外部化到使用标准Java属性格式的单独文件中。这样做可以让部署应用程序的人员自定义特定于环境的属性,如数据库url和密码,而无需修改主要XML定义文件或容器文件的复杂性或风险。
考虑以下基于xml的配置元数据片段,其中定义了具有占位符值的数据源。该示例显示了从外部属性文件配置的属性。在运行时,PropertyPlaceholderConfigurer应用于将替换数据源某些属性的元数据。要替换的值指定为表单${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”替换,这同样适用于与属性文件中的键匹配的其他占位符值。PropertyPlaceholderConfigurer检查bean定义的大多数属性和属性中的占位符。此外,可以自定义占位符前缀和后缀。
使用Spring 2.5中引入的上下文名称空间,可以使用专用的配置元素配置属性占位符。可以在location属性中以逗号分隔的列表的形式提供一个或多个位置。
PropertyPlaceholderConfigurer不仅在指定的属性文件中查找属性。默认情况下,如果无法在指定的属性文件中找到属性,它还会检查Java系统属性。您可以使用以下三个受支持的整数值之一来设置配置程序的systemPropertiesMode属性,从而自定义此行为:
有关更多信息,请参阅PropertyPlaceholderConfigurer javadoc。
您可以使用PropertyPlaceholderConfigurer来替换类名,当您必须在运行时选择特定的实现类时,这种方法有时非常有用。例如:
classpath:com/foo/strategy.properties
custom.strategy.class=com.foo.DefaultStrategy
如果在运行时无法将类解析为有效的类,则在即将创建bean时,即在ApplicationContext的application -init bean的预实例化阶段,bean的解析将失败。
Example: the PropertyOverrideConfigurer
另一个bean工厂后处理器PropertyOverrideConfigurer类似于PropertyPlaceholderConfigurer,但与后者不同,原始定义可以为bean属性设置默认值,也可以完全不设置值。如果覆盖的属性文件没有特定bean属性的条目,则使用默认上下文定义。
请注意,bean定义并不知道被覆盖,因此从XML定义文件中不能立即看出正在使用覆盖配置程序。在多个PropertyOverrideConfigurer实例为同一个bean属性定义不同值的情况下,由于覆盖机制,最后一个实例获胜。
属性文件配置行采用这种格式:
beanName.property=value
For example:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
这个示例文件可以与容器定义一起使用,容器定义包含一个名为dataSource的bean,该bean具有驱动程序和url属性。
还支持复合属性名,只要路径的每个组件(最终被覆盖的属性除外)已经是非空的(假定由构造函数初始化)。在这个例子中…
foo.fred.bob.sammy=123
foo bean的fred属性的bob属性的sammy属性被设置为标量值123。
指定的重写值总是文本值;它们不会被转换为bean引用。当XML bean定义中的原始值指定bean引用时,也适用此约定。
使用Spring 2.5中引入的上下文名称空间,可以使用专用的配置元素配置属性覆盖:
使用FactoryBean自定义实例化逻辑
实现org.springframework.beans.factory.FactoryBean接口的对象本身就是工厂。
FactoryBean接口是可插入Spring IoC容器实例化逻辑的一个点。如果您有复杂的初始化代码,这些代码可以更好地用Java表示,而不是(潜在地)冗长的XML,那么您可以创建自己的FactoryBean,在该类中编写复杂的初始化,然后将定制的FactoryBean插入容器中。
FactoryBean接口提供了三种方法:
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允许以非侵入式的方式使用注释,而无需接触目标组件源代码,而且在工具方面,Spring工具套件支持所有配置样式。
基于注解的配置提供了一种替代XML设置的方法,它依赖字节码元数据来连接组件,而不是使用尖括号声明。开发人员没有使用XML来描述bean连接,而是通过对相关的类、方法或字段声明使用注解将配置转移到组件类本身。正如在“示例:RequiredAnnotationBeanPostProcessor”一节中提到的,将BeanPostProcessor与注释结合使用是扩展Spring IoC容器的一种常见方法。例如,Spring 2.0引入了使用@Required注释强制执行所需属性的可能性。Spring 2.5使使用相同的通用方法来驱动Spring的依赖注入成为可能。本质上,@Autowired注释提供了与第7.4.5节“Autowiring collaborator”中描述的相同的功能,但是具有更细粒度的控制和更广泛的适用性。Spring 2.5还添加了对JSR-250注释(如@PostConstruct和@PreDestroy)的支持。Spring 3.0增加了对JSR-330 (Java依赖注入)注释的支持,这些注释包含在javax中。注入包,例如@Inject和@Named。有关这些注释的详细信息可以在相关部分找到。
注解注入是在XML注入之前执行的,因此对于通过这两种方法连接的属性,后一种配置将覆盖前者。
与往常一样,您可以将它们注册为单独的bean定义,但是也可以通过在基于xml的Spring配置中包含以下标记来隐式注册它们(请注意上下文名称空间的包含):
隐式注册的后处理器包括AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、PersistenceAnnotationBeanPostProcessor,以及前面提到的RequiredAnnotationBeanPostProcessor。
@Required注释应用于bean属性设置方法,如下面的示例所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
此注释仅指示必须在配置时填充受影响的bean属性,方法是通过bean定义中的显式属性值或通过自动连接。如果没有填充受影响的bean属性,容器将抛出异常;这允许出现紧急的和显式的失败,避免以后出现nullpointerexception或类似的异常。仍然建议您将断言放入bean类本身,例如,放入init方法。即使在容器外部使用类时,这样做也会强制执行那些必需的引用和值。
在下面的示例中,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注释应用于“传统”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)是由您为@ autowiredannotedinjection点使用的类型一致声明的。否则,注入可能会由于在运行时没有找到类型匹配而失败。
对于通过类路径扫描找到的xml定义的bean或组件类,容器通常预先知道具体类型。但是,对于@Bean工厂方法,您需要确保声明的返回类型具有足够的表达能力。对于实现多个接口的组件或可能由其实现类型引用的组件,请考虑在工厂方法上声明最特定的返回类型(至少与引用bean的注入点所需的相同)。
还可以提供一个特定类型的所有bean的ApplicationContext通过添加注释字段或方法,预计一个数组的类型:
public class MovieRecommender {
@Autowired
private MovieCatalog[] movieCatalogs;
// ...
}
这同样适用于类型化集合:
public class MovieRecommender {
private Set movieCatalogs;
@Autowired
public void setMovieCatalogs(Set movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
您的目标bean可以实现org.springframework.core.Ordered。如果希望数组或列表中的项按特定顺序排序,请使用@Order或标准@Priority注释。否则,它们的顺序将遵循容器中相应目标bean定义的注册顺序。
@Order注释可以在目标类级别声明,也可以在@Bean方法上声明,每个bean定义可能非常独立(对于具有相同bean类的多个定义)。@Order值可能会影响注入点的优先级,但是请注意它们不会影响单例启动顺序,单例启动顺序是由依赖关系和@DependsOn声明确定的正交关系。请注意标准的javax.annotation.Priority优先级注释在@Bean级别不可用,因为它不能在方法上声明。它的语义可以通过每个类型上的单个bean上的@Order值与@Primary值组合来建模。
即使类型映射也可以自动连接,只要预期的键类型是字符串。映射值将包含所有期望类型的bean,键将包含相应的bean名称:
public class MovieRecommender {
private Map movieCatalogs;
@Autowired
public void setMovieCatalogs(Map movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
默认情况下,当没有候选bean可用时,自动连接将失败;默认行为是将带注解的方法、构造函数和字段视为指示所需依赖项。此行为可以更改,如下所示。
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired(required = false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
每个类只能标记一个带注释的构造函数,但是可以标记多个不需要的构造函数。在这种情况下,每个构造函数都被认为是候选函数之一,Spring使用最贪婪的构造函数,它的依赖关系可以得到满足,也就是参数数量最多的构造函数。
@Autowired的required属性比@Required注释更受推荐。required属性表示属性不需要自动连接,如果属性不能自动连接,则忽略该属性。另一方面,@Required更强大,因为它强制使用容器支持的任何方法设置的属性。如果没有注入值,则会引发相应的异常。
或者,您可以通过Java 8的Java .util. optional来表示特定依赖项的非必需性质:
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(Optional movieFinder) {
...
}
}
您还可以将@Autowired用于众所周知的可解析依赖项的接口:BeanFactory、ApplicationContext、Environment、ResourceLoader、ApplicationEventPublisher和MessageSource。这些接口及其扩展接口(如ConfigurableApplicationContext或ResourcePatternResolver)是自动解析的,不需要特殊的设置。
public class MovieRecommender {
@Autowired
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
@Autowired、@Inject、@Resource和@Value注释由Spring BeanPostProcessor实现处理,这意味着您不能在自己的BeanPostProcessor或BeanFactoryPostProcessor类型(如果有的话)中应用这些注释。这些类型必须通过XML或使用Spring @Bean方法显式地“连接”起来。
使用@Primary微调基于注释的自动连接
由于按类型自动连接可能会导致多个候选项,因此通常需要对选择过程进行更多的控制。实现这一点的一种方法是使用Spring的@Primary注释。@Primary表示当多个bean是自动连接到单值依赖项的候选bean时,应该优先选择某个bean。如果候选bean中恰好存在一个“主”bean,它将是自动连接的值。
假设我们有以下配置,它将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定义如下所示。
使用限定符微调基于注释的自动连接
@Primary是一种有效的方法,当可以确定一个主候选时,可以根据类型对多个实例使用自动连接。当需要对选择过程进行更多控制时,可以使用Spring的@Qualifier注释。您可以将限定符值与特定参数关联,缩小类型匹配集,以便为每个参数选择特定的bean。在最简单的情况下,这可以是一个简单的描述性值:
public class MovieRecommender {
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
// ...
}
@Qualifier注释也可以在单个构造函数参数或方法参数上指定:
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(@Qualifier("main")MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
相应的bean定义如下所示。具有限定符值“main”的bean与具有相同值限定的构造函数参数连接。
限定符也适用于类型化集合,如前所述,用于设置
在类型匹配候选项中,让限定符值根据目标bean名进行选择,甚至不需要在注入点上使用@Qualifier注释。如果没有其他解析指示器(例如限定符或主标记),对于非惟一依赖情况,Spring将把注入点名称(即字段名称或参数名称)与目标bean名称匹配,并选择相同名称的候选项(如果有的话)。也就是说,如果您打算按名称表示注释驱动的注入,则不要主要使用@Autowired,即使能够在类型匹配候选项中按bean名称选择。相反,应该使用JSR-250 @Resource注释,该注释在语义上定义为通过惟一的名称标识特定的目标组件,声明的类型与匹配过程无关。@Autowired具有完全不同的语义:在按类型选择候选bean之后,指定的字符串限定符值将仅在这些类型选择的候选bean中考虑,例如,将“account”限定符与标有相同限定符标签的bean进行匹配。对于本身定义为集合/映射或数组类型的bean, @Resource是一个很好的解决方案,它通过惟一的名称引用特定的集合或数组bean。也就是说,从4.3开始,只要元素类型信息保存在@Bean返回类型签名或集合继承层次结构中,集合/映射和数组类型也可以通过Spring的@Autowired类型匹配算法进行匹配。在这种情况下,可以使用限定符值在相同类型的集合中进行选择,如上一段所述。
在4.3中,@Autowired还考虑了注入的自引用,即对当前注入的bean的引用。注意,自我注入是一种回退;对其他组件的常规依赖总是具有优先级。从这个意义上说,自我参照不参与定期的候选人选择,因此,尤其不是主要的;相反,它们总是以最低优先级结束。在实践中,只使用self引用作为最后的手段,例如,通过bean的事务代理调用同一个实例上的其他方法:在这种场景中,考虑将受影响的方法分解为单独的委托bean。或者,使用@Resource,它可以通过其唯一的名称获得一个代理返回到当前bean。
@Autowired应用于字段、构造函数和多参数方法,允许在参数级别通过限定符注释进行缩小。相比之下,@Resource只支持具有单个参数的字段和bean属性设置器方法。因此,如果注入目标是构造函数或多参数方法,请使用限定符。
您可以创建自己的自定义限定符注释。只需定义注释并在定义中提供@Qualifier注释:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
String value();
}
然后您可以在自动连接的字段和参数上提供自定义限定符:
public class MovieRecommender {
@Autowired
@Genre("Action")
private MovieCatalog actionCatalog;
private MovieCatalog comedyCatalog;
@Autowired
public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
this.comedyCatalog = comedyCatalog;
}
// ...
}
接下来,为候选bean定义提供信息。您可以添加
在第7.10节“类路径扫描和托管组件”中,您将看到一个基于注释的替代方案,可以提供XML中的限定符元数据。具体来说,请参见第7.10.8节“使用注释提供限定符元数据”。
在某些情况下,使用不带值的注释就足够了。当注释服务于更通用的目的,并且可以跨几个不同类型的依赖项应用时,这可能非常有用。例如,您可以提供脱机目录,当没有Internet连接可用时将搜索该目录。首先定义简单的注释:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
然后将注释添加到要自动连接的字段或属性:
public class MovieRecommender {
@Autowired
@Offline
private MovieCatalog offlineCatalog;
// ...
}
现在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
}
要自动连接的字段使用自定义限定符进行注释,并包含两个属性的值:类型和格式。
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注释之外,还可以使用Java泛型类型作为一种隐式的限定形式。例如,假设您有以下配置:
@Configuration
public class MyConfiguration {
@Bean
public StringStore stringStore() {
return new StringStore();
}
@Bean
public IntegerStore integerStore() {
return new IntegerStore();
}
}
假设上述bean实现了一个泛型接口,即Store
@Autowired
private Store s1; // qualifier, injects the stringStore bean
@Autowired
private Store s2; // qualifier, injects the integerStore bean
通用限定符也适用于自动连接列表、映射和数组:
// Inject all Store beans as long as they have an generic
// Store beans will not appear in this list
@Autowired
private List> s;
CustomAutowireConfigurer是一个BeanFactoryPostProcessor,它允许您注册自己的自定义限定符注释类型,即使它们没有使用Spring的@Qualifier注释。
example.CustomQualifier
AutowireCandidateResolver 确定autowire 候选通过:
当多个bean符合自动连接候选项的条件时,“主”bean的确定如下:如果候选项中恰好有一个bean定义的主属性设置为true,那么它将被选中。
Spring还支持在字段或bean属性设置器方法上使用JSR-250 @Resource注释进行注入。这是Java EE 5和6中的常见模式,例如JSF 1.2托管bean或JAX-WS 2.0端点。对于Spring管理的对象,Spring也支持这种模式。
@Resource接受name属性,默认情况下Spring将该值解释为要注入的bean名。换句话说,它遵循按名语义,如本例所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder")
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
如果没有显式指定名称,则默认名称来自字段名或setter方法。对于字段,它采用字段名;对于setter方法,它采用bean属性名。因此,下面的例子将把名称为“movieFinder”的bean注入到它的setter方法中:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
由CommonAnnotationBeanPostProcessor知道的ApplicationContext将注释提供的名称解析为bean名称。如果显式配置Spring的SimpleJndiBeanFactory,则可以通过JNDI解析这些名称。但是,建议您依赖于默认行为,并简单地使用Spring的JNDI查找功能来保持间接级别。
在没有指定显式名称的@Resource使用的唯一情况下(类似于@Autowired), @Resource找到一个主类型匹配,而不是一个特定的命名bean,并解析众所周知的可解析依赖项:BeanFactory、ApplicationContext、ResourceLoader、ApplicationEventPublisher和MessageSource接口。
因此,在下面的示例中,customerPreferenceDao字段首先查找名为customerPreferenceDao的bean,然后返回与customerPreferenceDao类型匹配的主类型。“上下文”字段是基于已知的可解析依赖项类型ApplicationContext注入的。
public class MovieRecommender {
@Resource
private CustomerPreferenceDao customerPreferenceDao;
@Resource
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
CommonAnnotationBeanPostProcessor不仅识别@Resource注释,还识别JSR-250生命周期注释。在Spring 2.5中引入的对这些注释的支持提供了另一种方法,可以替代在初始化回调和销毁回调中描述的方法。如果CommonAnnotationBeanPostProcessor是在Spring ApplicationContext中注册的,那么在生命周期的同一时刻,将调用一个带有这些注释之一的方法,该方法与相应的Spring lifecycle interface方法或显式声明的回调方法相同。在下面的示例中,缓存将在初始化时预填充,并在销毁时清除。
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
public void clearMovieCache() {
// clears the movie cache upon destruction...
}
}
类路径扫描和托管组件
本章中的大多数示例使用XML指定在Spring容器中生成每个bean定义的配置元数据。上一节(7.9节,“基于注释的容器配置”)演示了如何通过源代码级注释提供大量配置元数据。然而,即使在这些示例中,“基本”bean定义也是在XML文件中显式定义的,而注释只驱动依赖项注入。本节描述一个通过扫描类路径隐式检测候选组件的选项。候选组件是与筛选器标准匹配的类,并且在容器中注册了相应的bean定义。这消除了使用XML执行bean注册的需要;相反,您可以使用注释(例如@Component)、AspectJ类型表达式或您自己的自定义筛选条件来选择哪些类将bean定义注册到容器中。
从Spring 3.0开始,Spring JavaConfig项目提供的许多特性都是核心Spring框架的一部分。这允许您使用Java定义bean,而不是使用传统的XML文件。查看@Configuration、@Bean、@Import和@DependsOn注释,了解如何使用这些新特性。
@Component 和进一步的构造型注解
@Repository注释是实现存储库角色或原型(也称为数据访问对象或DAO)的任何类的标记。该标记的使用之一是异常的自动转换,如20.2.2节“异常转换”所述。
Spring提供了进一步的构造型注解:@Component、@Service和@Controller。@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 // Spring will see this and treat @Service in the same way as @Component
public @interface Service {
// ....
}
还可以组合元注释来创建复合注释。例如,来自Spring MVC的@RestController注释由@Controller和@ResponseBody组成。
此外,组合注释可以选择从元注释中重新声明属性,以允许用户自定义。当您只想公开元注释属性的一个子集时,这一点尤其有用。例如,Spring的@SessionScope注释将作用域名称硬编码为session,但仍然允许定制proxyMode。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {
/**
* Alias for {@link Scope#proxyMode}.
* Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
*/
@AliasFor(annotation = Scope.class)
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
@SessionScope可以在不声明代理模式的情况下使用,如下所示:
@Service
@SessionScope
public class SessionScopedService {
// ...
}
或为proxyMode赋值如下:
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
// ...
}
自动检测类并注册bean定义
Spring可以自动检测原型类,并在ApplicationContext中注册相应的bean定义。例如,以下两个类有资格进行这种自动检测:
@Service
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
@Repository
public class JpaMovieFinder implements MovieFinder {
// implementation elided for clarity
}
要自动检测这些类并注册相应的bean,需要将@ComponentScan添加到@Configuration类中,其中basePackages属性是这两个类的公共父包。(也可以指定逗号/分号/空格分隔的列表,其中包含每个类的父包)。
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
...
}
为了简单起见,上面可能使用了注释的值属性,即@ComponentScan(“org.example”)
此外,在使用组件扫描元素时,AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor都是隐式包含的。这意味着这两个组件被自动检测并连接在一起——所有这些组件都不需要XML中提供的任何bean配置元数据。
您可以禁用AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor的注册,方法是将annotation-config属性的值设为false。
使用过滤器自定义扫描
默认情况下,使用@Component、@Repository、@Service、@Controller注释的类,或者本身使用@Component注释的自定义注释,是唯一检测到的候选组件。但是,您可以简单地通过应用自定义筛选器来修改和扩展此行为。添加它们作为@ComponentScan注释的includeFilters或exclude defilters参数(或者作为组件扫描元素的include-filter或exclude-filter子元素)。每个筛选器元素都需要类型和表达式属性。下表描述了过滤选项。
Table 7.5. Filter Types
Filter Type | Example Expression | Description |
---|---|---|
annotation (default) |
|
在目标组件中出现在类型级别的注释。 |
assignable |
|
目标组件可分配给(扩展/实现)的类(或接口)。 |
aspectj |
|
要由目标组件匹配的AspectJ类型表达式。 |
regex |
|
由目标组件类名匹配的正则表达式。 |
custom |
|
自定义的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 {
...
}
您还可以通过在注释上设置useDefaultFilters=false或提供use-default-filters="false"作为
在组件中定义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);
}
该示例将字符串方法参数country自动连接到另一个名为privateInstance的bean上的age属性的值。Spring表达式语言元素通过符号#{< Expression >}定义属性的值。对于@Value注释,表达式解析器被预先配置为在解析表达式文本时查找bean名。
在Spring Framework 4.3中,为了访问触发当前bean创建的请求注入点,还可以声明类型为InjectionPoint(或其更特定的子类DependencyDescriptor)的工厂方法参数。注意,这只适用于bean实例的实际创建,而不适用于现有实例的注入。因此,这个特性对于原型范围的bean最有意义。对于其他范围,工厂方法只会看到触发给定范围内新bean实例创建的注入点:例如,触发懒单例bean创建的依赖项。在这种场景中,使用提供的注入点元数据时要注意语义。
@Component
public class FactoryMethodComponent {
@Bean @Scope("prototype")
public TestBean prototypeInstance(InjectionPoint injectionPoint) {
return new TestBean("prototypeInstance for " + injectionPoint.getMember());
}
}
常规Spring组件中的@Bean方法与Spring @Configuration类中的对应方法处理方式不同。不同之处在于@Component类没有使用CGLIB进行增强,以拦截方法和字段的调用。CGLIB代理是在@Configuration类中的@Bean方法中调用方法或字段来创建对协作对象的bean元数据引用的方法;这些方法不是用普通的Java语义调用的,而是通过容器来提供通常的Spring bean生命周期管理和代理,甚至在通过对@Bean方法的编程调用引用其他bean时也是如此。相反,在普通的@Component类中调用@Bean方法中的方法或字段具有标准的Java语义,不应用特殊的CGLIB处理或其他约束。
您可以将@Bean方法声明为静态的,允许调用它们,而不需要将它们的包含配置类创建为实例。这在定义后处理器bean时特别有意义,例如,类型为BeanFactoryPostProcessor或BeanPostProcessor,因为此类bean将在容器生命周期的早期初始化,并且应该避免在那时触发配置的其他部分。
请注意,对静态@Bean方法的调用永远不会被容器拦截,即使在@Configuration类中也不会(参见上面)。这是由于技术上的限制:CGLIB子类化只能覆盖非静态方法。因此,对另一个@Bean方法的直接调用将具有标准的Java语义,从而直接从工厂方法本身返回独立的实例。
@Bean方法的Java语言可见性不会立即影响Spring容器中生成的bean定义。您可以自由声明您认为适合于非@ configuration类和任何地方的静态方法的工厂方法。然而,@Configuration类中的常规@Bean方法需要可重写,也就是说,它们不能声明为私有或final。
@Bean方法还可以在给定组件或配置类的基类上发现,也可以在由组件或配置类实现的接口中声明的Java 8默认方法上发现。这在组合复杂配置安排方面提供了很大的灵活性,甚至可以通过自Spring 4.2以来的Java 8默认方法进行多重继承。
最后,请注意,单个类可以为同一个bean保存多个@Bean方法,作为根据运行时可用依赖关系使用的多个工厂方法的安排。这与在其他配置场景中选择“最贪婪”的构造函数或工厂方法是相同的算法:在构建时选择可满足依赖项数量最多的变体,类似于容器在多个@Autowired构造函数之间进行选择的方式。
给组件命名
当组件作为扫描过程的一部分被自动检测时,它的bean名由扫描程序所知道的BeanNameGenerator策略生成。默认情况下,任何包含名称值的Spring构造型注释(@Component、@Repository、@Service和@Controller)都会将该名称提供给相应的bean定义。
如果这样的注释不包含名称值或任何其他检测到的组件(例如自定义筛选器发现的组件),则默认bean名称生成器将返回未大写的非限定类名。例如,如果检测到以下组件类,则名称为myMovieLister和movieFinderImpl:
@Service("myMovieLister")
public class SimpleMovieLister {
// ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
如果不希望依赖缺省bean命名策略,可以提供自定义bean命名策略。首先,实现BeanNameGenerator接口,并确保包含默认的无参数构造函数。然后,在配置扫描器时提供完全限定的类名:
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
...
}
作为一般规则,当其他组件可能显式引用该名称时,请考虑使用注释指定该名称。另一方面,只要容器负责连接,自动生成的名称就足够了。
为自动检测组件提供作用域
与一般的spring管理组件一样,自动检测组件的默认和最常见的作用域是单例的。但是,有时您需要一个不同的范围,可以通过@Scope注释指定。只需在注释中提供范围的名称:
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
@Scope注释只在具体的bean类(用于带注释的组件)或工厂方法(用于@Bean方法)上进行反省。与XML bean定义不同,没有bean定义继承的概念,类级别的继承层次结构与元数据目的无关。
基于java的容器配置
Spring新的Java-configuration支持中的核心构件是@ configuration注释类和@ bean注释方法。
@Bean注释用于指示方法实例化、配置和初始化由Spring IoC容器管理的新对象。对于那些熟悉Spring的
用@Configuration注释类表明它的主要目的是作为bean定义的源。此外,@Configuration类允许通过简单地调用同一类中的其他@Bean方法来定义bean之间的依赖关系。最简单的@Configuration类如下:
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
上面的AppConfig类相当于下面的Spring
@Bean和@Configuration注释将在下面的部分中深入讨论。但是,首先,我们将介绍使用基于java的配置创建spring容器的各种方法。
使用AnnotationConfigApplicationContext实例化Spring容器
下面的部分将记录Spring的AnnotationConfigApplicationContext,这是Spring 3.0中的新内容。这个通用的ApplicationContext实现不仅能够接受@Configuration类作为输入,还能够接受普通的@Component类和用JSR-330元数据注释的类。
当@Configuration类作为输入提供时,@Configuration类本身注册为bean定义,类中所有声明的@Bean方法也注册为bean定义。
当提供@Component和JSR-330类时,它们被注册为bean定义,并且假定在需要时在这些类中使用DI元数据,例如@Autowired或@Inject。
就像在实例化ClassPathXmlApplicationContext时使用Spring XML文件作为输入一样,@Configuration类在实例化AnnotationConfigApplicationContext时也可以作为输入。这允许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 {
...
}
在上面的例子中,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应用程序
注释configapplicationcontext的WebApplicationContext变体可以与AnnotationConfigWebApplicationContext一起使用。这个实现可以在配置Spring ContextLoaderListener servlet侦听器、Spring MVC DispatcherServlet等时使用。配置典型Spring MVC web应用程序的xml片段。注意contextClass context-param和init-param的使用:
contextClass
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
contextConfigLocation
com.acme.AppConfig
org.springframework.web.context.ContextLoaderListener
dispatcher
org.springframework.web.servlet.DispatcherServlet
contextClass
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
contextConfigLocation
com.acme.web.MvcConfig
dispatcher
/app/*
@Bean是一个方法级注释,是XML
您可以在@ configuration注释的类中使用@Bean注释,也可以在@ component注释的类中使用@Bean注释。
要声明一个bean,只需用@Bean注释注释一个方法。您可以使用此方法在指定为方法返回值的类型的ApplicationContext中注册bean定义。默认情况下,bean名称将与方法名称相同。下面是@Bean方法声明的一个简单示例:
@Configuration
public class AppConfig {
@Bean
public TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
前面的配置与下面的Spring XML完全等价:
这两个声明使一个名为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所需的依赖关系。例如,如果我们的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、DisposableBean或Lifecycle,则容器将调用它们各自的方法。
还完全支持BeanFactoryAware、BeanNameAware、MessageSourceAware、ApplicationContextAware等标准的*Aware接口集。
@Bean注释支持指定任意初始化和销毁回调方法,就像Spring XML在bean元素上的init-method和分解-method属性一样:
public class Foo {
public void init() {
// initialization logic
}
}
public class Bar {
public void cleanup() {
// destruction logic
}
}
@Configuration
public class AppConfig {
@Bean(initMethod = "init")
public Foo foo() {
return new Foo();
}
@Bean(destroyMethod = "cleanup")
public Bar bar() {
return new Bar();
}
}
默认情况下,使用具有公共关闭或关闭方法的Java config定义的bean将自动与销毁回调一起调用。如果您有一个公共关闭或关闭方法,并且您不希望在容器关闭时调用它,只需将@Bean(destroyMethod="")添加到bean定义中,以禁用默认(推断)模式。
在缺省情况下,您可能希望对通过JNDI获得的资源这样做,因为它的生命周期是在应用程序之外管理的。特别是,请确保总是对数据源执行此操作,因为众所周知,数据源在Java EE应用程序服务器上存在问题。
@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
return (DataSource) jndiTemplate.lookup("MyDS");
}
@ bean方法,你通常会选择使用程序化的JNDI查找:直接使用Spring的JndiTemplate / JndiLocatorDelegate助手或者JNDI InitialContext使用,但不是JndiObjectFactoryBean变体这将迫使你声明返回类型作为FactoryBean类型,而不是实际的目标类型,使其难以在其他@ bean中使用交叉引用调用方法,打算在这儿指的是所提供的资源。
当然,对于上面的Foo,在构造过程中直接调用init()方法同样有效:
@Configuration
public class AppConfig {
@Bean
public Foo foo() {
Foo foo = new Foo();
foo.init();
return foo;
}
// ...
}
当您直接使用Java时,您可以对您的对象做任何您喜欢的事情,而不总是需要依赖容器生命周期!
指定bean范围
使用@Scope注解
您可以指定使用@Bean注释定义的bean应该具有特定的范围。您可以使用Bean作用域部分中指定的任何标准作用域
默认的作用域是单例的,但是你可以用@Scope注释覆盖它:
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
@Scope and scoped-proxy
Spring提供了一种通过作用域代理处理作用域依赖项的便捷方法。在使用XML配置时创建这样一个代理的最简单方法是
如果您使用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 = "myFoo")
public Foo foo() {
return new Foo();
}
}
BEAN别名
正如在7.3.1节“命名bean”中所讨论的,有时需要为单个bean提供多个名称,或者称为bean别名。@Bean注释的name属性接受用于此目的的字符串数组。
@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 Foo foo() {
return new Foo();
}
}
使用@Configuration注解
@Configuration是一个类级别的注释,表示对象是bean定义的源。配置类通过public @Bean注释的方法声明bean。对@Configuration类上的@Bean方法的调用也可以用来定义bean之间的依赖关系。有关一般介绍,请参见第7.12.1节“基本概念:@Bean和@Configuration”。
当@Beans相互依赖时,表示这种依赖就像让一个bean方法调用另一个bean方法一样简单:
@Configuration
public class AppConfig {
@Bean
public Foo foo() {
return new Foo(bar());
}
@Bean
public Bar bar() {
return new Bar();
}
}
在上面的例子中,foo bean通过构造函数注入接收对bar的引用。
这种声明bean间依赖关系的方法只有在@Bean方法在@Configuration类中声明时才有效。不能使用普通的@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-configuration支持,您可以创建CommandManager的子类,其中抽象的createCommand()方法被重写,以便查找新的(原型)命令对象:
@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
AsyncCommand command = new AsyncCommand();
// inject dependencies here as required
return command;
}
@Bean
public CommandManager commandManager() {
// return new anonymous implementation of CommandManager with command() overridden
// to return a new prototype Command object
return new CommandManager() {
protected Command createCommand() {
return asyncCommand();
}
}
}
关于基于java的配置如何在内部工作的进一步信息
下面的例子显示了一个@Bean注解的方法被调用了两次:
@Configuration
public class AppConfig {
@Bean
public ClientService clientService1() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientService clientService2() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientDao clientDao() {
return new ClientDaoImpl();
}
}
clientDao()在clientService1()中调用一次,在clientService2()中调用一次。由于这个方法创建了ClientDaoImpl的一个新实例并返回它,所以您通常希望有两个实例(每个服务一个实例)。这肯定会有问题:在Spring中,实例化的bean默认情况下具有单例作用域。这就是神奇之处:所有@Configuration类都在启动时使用CGLIB子类化。在子类中,子方法在调用父方法并创建新实例之前,首先检查容器是否有任何缓存(作用域)bean。请注意,从Spring 3.2开始,不再需要将CGLIB添加到类路径中,因为CGLIB类已经在org.springframework下重新打包。cglib,并直接包含在spring-core JAR中。
根据bean的范围,行为可能有所不同。这里我们讨论的是单例。
由于CGLIB在启动时动态地添加特性,因此有一些限制,特别是配置类不能是最终的。但是,从4.3开始,配置类上允许使用任何构造函数,包括使用@Autowired或为默认注入声明一个非默认构造函数。
如果您希望避免任何cglib强加的限制,可以考虑在非@配置类上声明@Bean方法,例如在普通的@Component类上声明@Bean方法。那时@Bean方法之间的跨方法调用不会被截获,所以您必须在构造函数或方法级别上完全依赖依赖注入。
编写基于java的配置
使用@Import注解
正如在Spring XML文件中使用
@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。类和ConfigB。类实例化上下文时,只需要显式地提供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。注册方法。如果您希望避免组件扫描,那么这一点尤其有用,可以使用一些配置类作为显式定义所有组件的入口点。
对导入的@Bean定义注入依赖项
上面的例子有效,但过于简单。在大多数实际场景中,bean将跨配置类彼此依赖。在使用XML时,这本身不是问题,因为不涉及编译器,而且可以简单地声明ref="someBean",并相信Spring会在容器初始化期间解决这个问题。当然,当使用@Configuration类时,Java编译器会对配置模型施加约束,因为对其他bean的引用必须是有效的Java语法。
@Configuration
public class ServiceConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
@Bean
public AccountRepository accountRepository(DataSource dataSource) {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
还有另一种方法可以达到同样的效果。记住,@Configuration类最终只是容器中的另一个bean:这意味着它们可以像任何其他bean一样利用@Autowired和@Value注入等等。
确保以这种方式注入的依赖项是最简单的类型。@Configuration类在上下文初始化期间很早就被处理了,强制以这种方式注入依赖项可能会导致意外的早期初始化。只要可能,就像上面的例子一样,使用基于参数的注入。
另外,通过@Bean特别小心BeanPostProcessor和BeanFactoryPostProcessor定义。这些方法通常应该声明为静态@Bean方法,而不是触发它们包含的配置类的实例化。否则,@Autowired和@Value将无法处理配置类本身,因为它作为bean实例创建得太早了。
@Configuration
public class ServiceConfig {
@Autowired
private AccountRepository accountRepository;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
private final DataSource dataSource;
@Autowired
public RepositoryConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
@Configuration类中的构造函数注入仅在Spring Framework 4.3中受支持。还要注意,如果目标bean只定义一个构造函数,则不需要指定@Autowired;在上面的例子中,@Autowired在RepositoryConfig构造函数上是不必要的。
在上面的场景中,使用@Autowired工作得很好,并提供了所需的模块化,但是确定在哪里声明autowired bean定义仍然有些含糊。例如,作为一个关注ServiceConfig的开发人员,您如何确切地知道@Autowired AccountRepository bean在哪里声明?它在代码中不是显式的,这可能没有问题。请记住,Spring工具套件提供了一些工具,这些工具可以显示如何将所有内容连接起来——这可能就是您所需要的全部。而且,Java IDE可以很容易地找到AccountRepository类型的所有声明和使用,并将快速显示返回该类型的@Bean方法的位置。
如果这种模糊性是不可接受的,并且您希望在IDE中实现从@Configuration类到另一个@Configuration类的直接导航,请考虑自动连接配置类本身:
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
// navigate 'through' the config class to the @Bean method!
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
在上述情况下,定义AccountRepository的位置是完全明确的。然而,ServiceConfig现在与RepositoryConfig紧密耦合;这是一种折衷。通过使用基于接口或基于抽象类的@Configuration类,可以稍微缓解这种紧密耦合。考虑以下:
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
@Configuration
public interface RepositoryConfig {
@Bean
AccountRepository accountRepository();
}
@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(...);
}
}
@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the concrete config!
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
现在ServiceConfig与具体的DefaultRepositoryConfig松散耦合,内置IDE工具仍然很有用:开发人员很容易获得存储库配置实现的类型层次结构。这样,导航@Configuration类及其依赖项与导航基于接口的代码的通常过程没有什么不同。
如果你想影响某些BEAN的启动创建顺序,考虑将其中一些声明为@Lazy(用于创建在第一次访问,而不是在启动时)或@DependsOn在某些其他bean(确保特定的其他bean创建当前bean之前,超出后者的直接依赖关系暗示)。
有条件地包括@Configuration类或@Bean方法
根据一些任意的系统状态,有条件地启用或禁用一个完整的@Configuration类,甚至单个@Bean方法,通常是有用的。一个常见的例子是,只有在Spring环境中启用了特定的概要文件时,才使用@Profile注释激活Bean(有关详细信息,请参阅7.13.1节“Bean定义概要文件”)。
@Profile注释实际上是使用名为@ condition的更灵活的注释实现的。@ condition注释指示特定的org.springframework.context.annotation.Condition在注册@Bean之前应该咨询的条件实现。
条件接口的实现只是提供一个返回true或false的matches(…)方法。例如,@Profile的实际实现是这样的:
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() != null) {
// Read the @Profile annotation attributes
MultiValueMap 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;
}
See the @Conditional
javadocs for more detail.
@Configuration class-centric use of XML with @ImportResource
在以@Configuration类作为配置容器的主要机制的应用程序中,仍然可能需要至少使用一些XML。在这些场景中,只需使用@ImportResource并定义所需的XML即可。这样做可以实现一种“以java为中心”的容器配置方法,并将XML保持在最低限度。
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource() {
return new DriverManagerDataSource(url, username, password);
}
}
properties-config.xml
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 抽象
环境是集成在容器中的抽象,它为应用程序环境的两个关键方面建模:概要文件和属性。
概要文件是一组命名的、逻辑的bean定义,只有在给定的概要文件是活动的情况下才注册到容器中。可以将bean分配给配置文件,无论是在XML中定义的还是通过注释定义的。与概要文件相关的环境对象的作用是确定哪些概要文件(如果有)当前是活动的,以及哪些概要文件(如果有)在缺省情况下应该是活动的。
属性在几乎所有的应用程序中都扮演着重要的角色,并且可能来自各种来源:属性文件、JVM系统属性、系统环境变量、JNDI、servlet上下文参数、特殊属性对象、映射等等。与属性相关的环境对象的作用是为用户提供一个方便的服务接口来配置属性源并从中解析属性。
Bean定义配置文件
Bean定义概要文件是核心容器中的一种机制,它允许在不同环境中注册不同的Bean。环境这个词对不同的用户有不同的含义,这个特性可以帮助许多用例,包括:
让我们考虑需要数据源的实际应用程序中的第一个用例。在测试环境中,配置可能是这样的:
@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用户已经设计了许多方法来实现这一点,通常依赖于系统环境变量和包含${占位符}令牌的XML
如果我们一般化上述特定于环境的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 helper,要么使用上面所示的JNDI InitialContext使用,但不使用JndiObjectFactoryBean变体,后者将强制您将返回类型声明为FactoryBean类型。
@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");
}
}
对于@Bean方法上的@Profile,可以应用一种特殊的场景:对于具有相同Java方法名称的重载@Bean方法(类似于重载构造函数),需要在所有重载方法上一致声明@Profile条件。如果条件不一致,则重载方法中只有第一个声明的条件才是重要的。因此,不能使用@Profile选择具有特定参数签名的重载方法;同一bean的所有工厂方法之间的解析在创建时遵循Spring的构造函数解析算法。
如果您希望定义具有不同概要条件的可选bean,请使用不同的Java方法名,通过@Bean name属性指向相同的bean名,如上例所示。如果参数签名都是相同的(例如,所有的变体都有no-arg工厂方法),那么这是在一个有效的Java类中表示这种安排的唯一方法(因为只能有一个具有特定名称和参数签名的方法)。
激活一个profile
现在我们已经更新了配置,仍然需要指示Spring哪个配置文件是活动的。如果我们现在启动示例应用程序,就会看到抛出NoSuchBeanDefinitionException异常,因为容器无法找到名为dataSource的Spring bean。
激活概要文件可以通过几种方式来完成,但是最直接的方法是针对环境API通过ApplicationContext进行编程:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
此外,概要文件也可以通过spring.概要文件声明激活。可以通过系统环境变量、JVM系统属性、web中的servlet上下文参数指定的活动属性。xml,甚至作为JNDI中的一个条目(请参阅7.13.2节“PropertySource抽象”)。在集成测试中,可以通过spring-test模块中的@ActiveProfiles注释声明活动概要文件(请参阅“带有环境概要文件的上下文配置”一节)。
注意,配置文件不是一个“非此即彼”的命题;可以同时激活多个配置文件。通过编程,只需为setActiveProfiles()方法提供多个配置文件名称,该方法接受String…varargs:
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
声明,spring.profiles.active可以接受逗号分隔的配置文件名称列表:
-Dspring.profiles.active="profile1,profile2"
Default profile
默认配置文件表示默认启用的配置文件。考虑以下:
@Configuration
@Profile("default")
public class DefaultDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build();
}
}
如果没有配置文件是活动的,将创建上面的数据源;这可以看作是为一个或多个bean提供默认定义的一种方法。如果启用了任何配置文件,默认配置文件将不适用。
可以使用环境上的setDefaultProfiles()更改默认概要文件的名称,也可以使用spring.profiles.default属性进行声明。
Spring的环境抽象在可配置的属性源层次结构上提供了搜索操作。为作全面解释,请考虑以下各点:
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsFoo = env.containsProperty("foo");
System.out.println("Does my environment contain the 'foo' property? " + containsFoo);
在上面的代码片段中,我们看到了一种高级方式,它询问Spring是否为当前环境定义了foo属性。为了回答这个问题,环境对象对一组PropertySource对象执行搜索。PropertySource是对任何键-值对源的简单抽象,Spring的标准环境配置有两个PropertySource对象——一个表示JVM系统属性集(a la System.getProperties()),另一个表示系统环境变量集(a la System.getenv())。
这些默认属性源用于标准环境,用于独立应用程序。StandardServletEnvironment中填充了其他默认属性源,包括servlet配置和servlet上下文参数。类似地,StandardPortletEnvironment可以访问portlet配置和portlet上下文参数作为属性源。两者都可以选择启用JndiPropertySource。有关详细信息,请参见javadoc。
具体来说,当使用标准环境时,如果在运行时出现foo系统属性或foo环境变量,那么对env.containsProperty(“foo”)的调用将返回true。
执行的搜索是分层的。默认情况下,系统属性优先于环境变量,因此,如果在调用env.getProperty(“foo”)的过程中恰好在这两个地方设置了foo属性,那么系统属性值将“胜出”,并优先于环境变量返回。注意,属性值不会被合并,而是被前面的条目完全覆盖。
对于一个通用的StandardServletEnvironment,完整的层次结构如下所示,其中最高优先级的条目位于顶部:
最重要的是,整个机制是可配置的。也许您有一个想要集成到这个搜索中的自定义属性源。没问题——只需实现并实例化自己的PropertySource,并将其添加到当前环境的PropertySource集合中即可:
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
在上面的代码中,MyPropertySource在搜索中以最高的优先级添加。如果它包含一个foo属性,它将在任何其他PropertySource中的任何foo属性之前被检测并返回。MutablePropertySources API公开了一些方法,这些方法允许对属性源集进行精确操作。
@PropertySource注解为向Spring环境添加PropertySource提供了一种方便的声明性机制。
给定一个包含键/值对testbean.name=myTestBean的文件“app.properties”,下面的@Configuration类使用@PropertySource的方式是,对testBean.getName()的调用将返回“myTestBean”。
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
@PropertySource资源位置中出现的任何${…}占位符都将根据已在环境中注册的属性源集进行解析。例如:
@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
我的假设”。占位符”出现在已经注册的属性源中,例如系统属性或环境变量,占位符将被解析为相应的值。如果不是,那么“default/path”将被用作默认值。如果没有指定默认值,并且无法解析属性,则会抛出IllegalArgumentException。
@PropertySource注释根据Java 8约定是可重复的。但是,所有这些@PropertySource注释都需要在同一级别声明:要么直接在configuration类上声明,要么作为同一定制注释中的元注释声明。不建议混合直接注释和元注释,因为直接注释将有效地覆盖元注释。
语句中的占位符解析
历史上,元素中占位符的值只能根据JVM系统属性或环境变量进行解析。情况不再是这样了。由于环境抽象集成在整个容器中,因此很容易通过它路由占位符解析。这意味着您可以以您喜欢的任何方式配置解析过程:更改搜索系统属性和环境变量的优先级,或者完全删除它们;根据需要向组合中添加您自己的属性源。
具体地说,下面的语句不管在哪里定义客户属性都有效,只要它在环境中可用:
注册一个LoadTimeWeaver
当类加载到Java虚拟机(JVM)中时,Spring使用LoadTimeWeaver动态转换类。
要启用加载时编织,请将@EnableLoadTimeWeaving添加到您的@Configuration类中:
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
对于XML配置,也可以使用上下文:load-time-weaver元素:
为ApplicationContext配置之后。该ApplicationContext中的任何bean都可以实现LoadTimeWeaverAware,从而接收对加载时编织器实例的引用。这与Spring的JPA支持相结合特别有用,在JPA类转换中可能需要加载时编织。有关更多细节,请参阅LocalContainerEntityManagerFactoryBean javadoc。有关AspectJ加载时编织的更多信息,请参见11.8.4节,“在Spring框架中使用AspectJ进行加载时编织”。
ApplicationContext的附加功能
正如在介绍一章中所讨论的,org.springframework.beans.factory包提供管理和操作bean的基本功能,包括以编程的方式。org.springframework.context包添加了ApplicationContext接口,该接口扩展了BeanFactory接口,此外还扩展了其他接口,以更面向应用程序框架的风格提供附加功能。许多人以完全声明的方式使用ApplicationContext,甚至没有以编程方式创建它,而是依赖ContextLoader等支持类来自动实例化ApplicationContext,这是Java EE web应用程序的正常启动过程的一部分。
为了以更面向框架的风格增强BeanFactory功能,上下文包还提供了以下功能:
使用MessageSource国际化
ApplicationContext接口扩展了名为MessageSource的接口,因此提供了国际化(i18n)功能。Spring还提供了分层次的接口HierarchicalMessageSource,它可以分层次地解析消息。这些接口共同提供了Spring影响消息解析的基础。这些接口上定义的方法包括:
当装载ApplicationContext时,它自动搜索上下文中定义的MessageSource bean。bean必须具有名称messageSource。如果找到这样的bean,则对前面方法的所有调用都委托给消息源。如果没有找到消息源,ApplicationContext将尝试找到包含同名bean的父bean。如果是,则使用该bean作为MessageSource。如果ApplicationContext找不到任何消息源,就实例化一个空的DelegatingMessageSource,以便能够接受对上面定义的方法的调用。
Spring提供了两个MessageSource实现,ResourceBundleMessageSource和StaticMessageSource。它们都实现了HierarchicalMessageSource来执行嵌套消息传递。StaticMessageSource很少使用,但它提供了向源添加消息的编程方法。ResourceBundleMessageSource如下例所示:
format
exceptions
windows
在这个示例中,假设您在类路径中定义了三个资源包,分别名为format、exceptions和windows。任何解析消息的请求都将以JDK标准的方式处理,即通过resourcebundle解析消息。就本例而言,假设上面两个资源包文件的内容是……
# in format.properties
message=Alligators rock!
# in exceptions.properties
argument.required=The {0} argument is required.
下一个示例中显示了一个执行MessageSource功能的程序。请记住,所有ApplicationContext实现也是MessageSource实现,因此可以转换到MessageSource接口。
public static void main(String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("message", null, "Default", null);
System.out.println(message);
}
上述程序的输出结果将是……
Alligators rock!
综上所述,MessageSource是在一个名为beans的文件中定义的。xml,它存在于类路径的根目录中。messageSource bean定义通过其basenames属性引用了许多资源包。列表中传递给basenames属性的三个文件作为类路径根目录下的文件存在,称为format。属性,例外。属性,和windows。属性分别。
下一个示例显示传递给消息查找的参数;这些参数将转换为字符串并插入查找消息中的占位符。
public class Example {
private MessageSource messages;
public void setMessages(MessageSource messages) {
this.messages = messages;
}
public void execute() {
String message = this.messages.getMessage("argument.required",
new Object [] {"userDao"}, "Required", null);
System.out.println(message);
}
}
execute()方法调用的结果输出将是……
The userDao argument is required.
关于国际化(i18n), Spring的各种MessageSource实现遵循与标准JDK ResourceBundle相同的地区解析和回退规则。简而言之,继续前面定义的示例messageSource,如果您希望根据英国(en-GB)地区解析消息,您将创建名为format_en_GB的文件。属性,exceptions_en_GB。属性和windows_en_GB。属性分别。
通常,区域设置解析是由应用程序的周围环境管理的。在本例中,将根据(英国)消息解析的区域设置是手工指定的。
# 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的引用。在实现MessageSourceAware接口的ApplicationContext中定义的任何bean在创建和配置bean时都被注入应用程序上下文的MessageSource。
作为ResourceBundleMessageSource的替代,Spring提供了ReloadableResourceBundleMessageSource类。该变体支持相同的包文件格式,但比基于JDK的ResourceBundleMessageSource实现更灵活。特别是,它允许从任何Spring资源位置读取文件(不只是从类路径),并支持热重加载包属性文件(同时有效地在两者之间缓存它们)。有关详细信息,请查看ReloadableResourceBundleMessageSource javadoc。
标准和定制事件
ApplicationContext中的事件处理是通过ApplicationEvent类和ApplicationListener接口提供的。如果实现ApplicationListener接口的bean部署到上下文中,那么每次将ApplicationEvent发布到ApplicationContext时,都会通知该bean。本质上,这是标准的观察者设计模式。
从Spring 4.2开始,事件基础设施得到了显著改进,并提供了基于注释的模型以及发布任意事件的能力,这是一个不一定从ApplicationEvent扩展的对象。当这样一个对象被发布时,我们将它包装在一个事件中。
Spring提供了以下标准事件:
Table 7.7. Built-in Events
Event | Explanation |
---|---|
|
在初始化或刷新ApplicationContext时发布,例如,在ConfigurableApplicationContext接口上使用refresh()方法。这里的“初始化”意味着加载了所有bean,检测并激活了后处理bean,预实例化了单例,并且ApplicationContext对象已经准备好使用。只要上下文没有关闭,就可以多次触发刷新,前提是所选的ApplicationContext实际上支持这种“热”刷新。例如,XmlWebApplicationContext支持热刷新,但GenericApplicationContext不支持。 |
|
在启动ApplicationContext时发布,在ConfigurableApplicationContext接口上使用start()方法。这里的“Started”意味着所有生命周期bean都接收到显式的启动信号。通常,此信号用于在显式停止后重新启动bean,但它也可以用于启动尚未为autostart配置的组件,例如,尚未在初始化时启动的组件。 |
|
在停止ApplicationContext时发布,在ConfigurableApplicationContext接口上使用stop()方法。这里的“停止”意味着所有生命周期bean都会收到一个明确的停止信号。停止的上下文可以通过start()调用重新启动。 |
|
在关闭ApplicationContext时发布,在ConfigurableApplicationContext接口上使用close()方法。这里的“Closed”意味着所有单例bean都被销毁。封闭的环境达到其生命的终点;无法刷新或重新启动。 |
|
一个特定于web的事件,它告诉所有bean HTTP请求已经得到了服务。此事件在请求完成后发布。此事件仅适用于使用Spring的DispatcherServlet的web应用程序。 |
您还可以创建和发布自己的自定义事件。这个例子演示了一个扩展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 blackList;
private ApplicationEventPublisher publisher;
public void setBlackList(List 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 {
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()方法将阻塞,直到所有侦听器完成对事件的处理。这种同步和单线程方法的一个优点是,当侦听器接收到事件时,如果事务上下文可用,它将在发布者的事务上下文中操作。如果需要另一种事件发布策略,请参阅javadoc获取Spring的applicationeventmultiaster接口。
下面的示例显示了用于注册和配置上述每个类的bean定义:
综上所述,当emailService bean的sendEmail()方法被调用时,如果有任何电子邮件应该被列入黑名单,则会发布BlackListEvent类型的自定义事件。blackListNotifier bean注册为ApplicationListener,因此接收BlackListEvent,此时它可以通知适当的方。
Spring的事件处理机制是为相同应用程序上下文中Spring bean之间的简单通信而设计的。然而,对于更复杂的企业集成需求,单独维护的Spring integration项目为构建基于众所周知的Spring编程模型的轻量级的、面向模式的、事件驱动的体系结构提供了完整的支持。
基于注解的事件监听器
从Spring 4.2开始,可以通过EventListener注释在托管bean的任何公共方法上注册事件侦听器。BlackListNotifier可以重写如下:
public class BlackListNotifier {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
@EventListener
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
正如您在上面所看到的,方法签名再次声明它所侦听的事件类型,但是这次它使用了一个灵活的名称,并且没有实现特定的侦听器接口。只要实际事件类型在其实现层次结构中解析了泛型参数,就可以通过泛型缩小事件类型。
如果您的方法应该侦听多个事件,或者您希望在完全不带参数的情况下定义它,那么还可以在注释本身上指定事件类型:
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
...
}
还可以通过注释的condition属性添加额外的运行时筛选,该注释定义了应该匹配的SpEL表达式,以便为特定事件实际调用该方法。
例如,我们的通知符可以重写,只有当事件的内容属性等于foo时才会被调用:
@EventListener(condition = "#blEvent.content == 'foo'")
public void processBlackListEvent(BlackListEvent blEvent) {
// notify appropriate parties via notificationAddress...
}
每个SpEL表达式根据专用上下文计算。下表列出了上下文可用的项,以便可以将它们用于条件事件处理:
Table 7.8. Event SpEL available metadata
Name | Location | Description | Example |
---|---|---|---|
Event |
root object |
The actual |
|
Arguments array |
root object |
The arguments (as array) used for invoking the target |
|
Argument name |
evaluation context |
Name of any of the method arguments. If for some reason the names are not available (e.g. no debug information), the argument names are also available under the |
|
请注意 #root.event允许您访问底层事件,即使您的方法签名实际上引用了已发布的任意对象。
如果您在处理另一个事件时需要发布一个事件,只需更改方法签名以返回应该发布的事件,例如:
@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
异步侦听器不支持此功能。
这个新方法将为上面方法处理的每个BlackListEvent发布一个新的ListUpdateEvent。如果需要发布多个事件,只需返回一个事件集合即可。
异步的监听器
如果您想要一个特定的侦听器异步处理事件,只需重用常规的@Async支持:
@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
// BlackListEvent is processed in a separate thread
}
在使用异步事件时,请注意以下限制:
监听器排序
如果您需要在另一个侦听器之前调用侦听器,只需将@Order注释添加到方法声明中:
@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
通用的事件
您还可以使用泛型进一步定义事件的结构。考虑EntityCreatedEvent
@EventListener
public void onPersonCreated(EntityCreatedEvent event) {
...
}
由于类型擦除,只有当触发的事件解析事件侦听器筛选的通用参数(类似于class PersonCreatedEvent extends EntityCreatedEvent
在某些情况下,如果所有事件都遵循相同的结构(对于上面的事件应该如此),那么这可能会变得相当乏味。在这种情况下,您可以实现resolabletypeprovider来指导框架超越运行时环境所提供的:
public class EntityCreatedEvent extends ApplicationEvent implements ResolvableTypeProvider {
public EntityCreatedEvent(T entity) {
super(entity);
}
@Override
public ResolvableType getResolvableType() {
return ResolvableType.forClassWithGenerics(getClass(),
ResolvableType.forInstance(getSource()));
}
}
这不仅适用于ApplicationEvent,而且适用于作为事件发送的任何任意对象。
方便地访问低级资源
为了更好地使用和理解应用程序上下文,用户通常应该熟悉Spring的资源抽象,如第8章“资源”所述。
应用程序上下文是一个ResourceLoader,可用于加载资源。资源本质上是JDK类java.net.URL的更富特性的版本,实际上,资源的实现在适当的情况下包装了java.net.URL的实例。资源可以以透明的方式从几乎任何位置获得低级资源,包括从类路径、文件系统位置、任何可以用标准URL描述的位置,以及其他一些变体。如果资源位置字符串是一个没有任何特殊前缀的简单路径,那么这些资源的来源是特定的,并且适合实际的应用程序上下文类型。
您可以配置部署到应用程序上下文中的bean,以实现特殊的回调接口ResourceLoaderAware,该接口在初始化时自动调用,应用程序上下文本身作为ResourceLoader传入。还可以公开资源类型的属性,以用于访问静态资源;它们会像其他属性一样被注入。您可以将这些资源属性指定为简单的字符串路径,并依赖于上下文自动注册的特殊JavaBean PropertyEditor,以便在部署bean时将这些文本字符串转换为实际的资源对象。
提供给ApplicationContext构造函数的位置路径或路径实际上是资源字符串,并且以简单的形式适当地处理特定上下文实现。ClassPathXmlApplicationContext将一个简单的位置路径作为类路径位置。您还可以使用带有特殊前缀的位置路径(资源字符串)强制从类路径或URL加载定义,而不管实际的上下文类型如何。
方便的web应用程序的ApplicationContext实例化
您可以使用(例如)ContextLoader以声明方式创建ApplicationContext实例。当然,您也可以通过使用ApplicationContext实现之一以编程方式创建ApplicationContext实例。
您可以使用ContextLoaderListener注册一个ApplicationContext,如下所示:
contextConfigLocation
/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml
org.springframework.web.context.ContextLoaderListener
侦听器检查contextConfigLocation参数。如果参数不存在,侦听器将使用/WEB-INF/applicationContext。xml作为默认值。当参数存在时,侦听器使用预定义的分隔符(逗号、分号和空格)分隔字符串,并将值用作搜索应用程序上下文的位置。还支持ant样式的路径模式。例子/ web - inf / *上下文。所有名称以“上下文”结尾的文件的xml。,驻留在“WEB-INF”目录和/WEB-INF/**/*上下文中。所有这些文件在“WEB-INF”的任何子目录。
将Spring ApplicationContext部署为Java EE RAR文件
可以将Spring ApplicationContext部署为RAR文件,将上下文及其所需的所有bean类和库jar封装在Java EE RAR部署单元中。这相当于引导一个独立的ApplicationContext,它只是托管在Java EE环境中,能够访问Java EE服务器设施。RAR部署比部署无头WAR文件的场景更自然,实际上,WAR文件没有任何HTTP入口点,只用于在Java EE环境中引导Spring ApplicationContext。
RAR部署非常适合不需要HTTP入口点,而只包含消息端点和计划作业的应用程序上下文。在这样的上下文中,bean可以使用应用服务器资源,如JTA事务管理器和JNDI绑定的JDBC数据源以及JMS ConnectionFactory实例,还可以通过Spring的标准事务管理和JNDI及JMX支持工具在平台的JMX服务器上注册。应用程序组件还可以通过Spring的TaskExecutor抽象与应用服务器的JCA WorkManager交互。
查看springcontext tresourceadapter类的javadoc,了解RAR部署中涉及的配置细节。
对于将Spring ApplicationContext简单地部署为Java EE RAR文件:将所有应用程序类打包到RAR文件中,RAR文件是一个标准JAR文件,具有不同的文件扩展名。将所有必需的库jar添加到RAR归档的根目录中。添加一个“meta - inf / ra。部署描述符(如springcontext - tresourceadapter javadoc所示)和相应的Spring xml bean定义文件(通常是“META-INF/applicationContext.xml”),并将生成的RAR文件拖放到应用服务器的部署目录中。
这种RAR部署单元通常是自包含的;它们不向外部世界公开组件,甚至不向同一应用程序的其他模块公开组件。与基于rar的ApplicationContext的交互通常通过与其他模块共享的JMS目的地进行。例如,基于rar的ApplicationContext还可以调度一些作业,对文件系统中的新文件(或类似文件)作出响应。如果它需要允许从外部进行同步访问,它可以导出RMI端点,这些端点当然可以被同一台机器上的其他应用程序模块使用。
BeanFactory API为Spring的IoC功能提供了基础。它的特定契约主要用于与Spring的其他部分和相关第三方框架的集成,它的DefaultListableBeanFactory实现是更高级别GenericApplicationContext容器中的关键委托。
BeanFactory和相关接口(如BeanFactoryAware、InitializingBean、DisposableBean)是其他框架组件的重要集成点:它们不需要任何注释甚至反射,允许容器及其组件之间进行非常有效的交互。应用程序级bean可能使用相同的回调接口,但通常更喜欢声明性依赖项注入,要么通过注释,要么通过编程配置。
请注意,核心BeanFactory API级别及其DefaultListableBeanFactory实现并不假设要使用的配置格式或任何组件注释。所有这些风格都是通过扩展(如XmlBeanDefinitionReader和AutowiredAnnotationBeanPostProcessor)实现的,这些扩展操作作为核心元数据表示的共享BeanDefinition对象。这就是使Spring容器如此灵活和可扩展的本质。
下一节将解释BeanFactory和ApplicationContext容器级别之间的差异以及引导的含义。
除非有充分的理由,否则请使用ApplicationContext,将GenericApplicationContext及其子类AnnotationConfigApplicationContext作为自定义引导的常见实现。对于所有常见的目的,这些都是Spring核心容器的主要入口点:加载配置文件、触发类路径扫描、以编程方式注册bean定义和带注释的类。
因为ApplicationContext包含bean工厂的所有功能,所以一般建议在普通bean工厂上使用它,除非需要对bean处理进行完全控制的场景除外。在ApplicationContext(如GenericApplicationContext实现)中,几种bean将通过约定(即通过bean名称或bean类型)检测,特别是在后处理程序中,而普通的DefaultListableBeanFactory对任何特殊bean都是不可知的。
对于许多扩展容器特性,如注释处理和AOP代理,BeanPostProcessor扩展点是必不可少的。如果您只使用一个普通的DefaultListableBeanFactory,那么这种后处理器将不会在默认情况下被检测和激活。这种情况可能会令人混淆,因为您的bean配置实际上没有问题;在这种场景中,需要通过其他设置完全引导容器。
下表列出了BeanFactory和ApplicationContext接口和实现提供的特性。
Table 7.9. Feature Matrix
Feature | BeanFactory |
ApplicationContext |
---|---|---|
Bean instantiation/wiring |
Yes |
Yes |
Integrated lifecycle management |
No |
Yes |
Automatic |
No |
Yes |
Automatic |
No |
Yes |
Convenient |
No |
Yes |
Built-in |
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
PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));
// now actually do the replacement
cfg.postProcessBeanFactory(factory);
在这两种情况下,显式注册步骤都很不方便,这就是为什么在spring支持的应用程序中,各种ApplicationContext变体比普通的DefaultListableBeanFactory更受欢迎的原因,尤其是在典型企业设置中依赖于beanfactorypostprocessor和beanpostprocessor实现扩展容器功能时。
一个AnnotationConfigApplicationContext拥有所有注册的通用注释后处理器,并且可以通过配置注释(例如@EnableTransactionManagement)引入额外的处理器。在Spring基于注解的配置模型的抽象级别上,bean后处理器的概念仅仅是一个内部容器细节。
粘合代码和邪恶的单体
最好以依赖注入(dependency-injection, DI)风格编写大多数应用程序代码,在这种风格中,代码是由Spring IoC容器提供的,在创建时由容器提供它自己的依赖项,并且完全不知道容器。但是,对于有时需要将其他代码绑定在一起的代码的小粘合层,有时需要对Spring IoC容器进行单例(或准单例)样式的访问。例如,第三方代码可能尝试直接构造新对象(Class.forName()样式),而无法从Spring IoC容器中获得这些对象。如果由第三方代码构造对象是小存根或代理,然后使用一个单例风格访问Spring IoC容器实际对象来代表,然后控制反转仍实现了大部分的代码(对象的容器)。因此,大多数代码仍然不知道容器或它是如何被访问的,并且仍然与其他代码解耦,从而带来所有的好处。ejb还可以使用这种存根/代理方法将委托委托给从Spring IoC容器检索到的普通Java实现对象。虽然在理想情况下,Spring IoC容器本身不必是单例的,但是在内存使用或初始化时间(在Spring IoC容器中使用bean时,比如Hibernate SessionFactory)方面,让每个bean使用自己的非单例Spring IoC容器可能是不现实的。
在服务定位符样式中查找应用程序上下文有时是访问共享spring托管组件的惟一选项,例如在EJB 2.1环境中,或者当您希望跨WAR文件共享单个ApplicationContext作为webapplicationcontext的父组件时。在这种情况下,您应该考虑使用工具类ContextSingletonBeanFactoryLocator定位器,这是在Spring team博客条目中描述的。