Spring Framework 5.0.0.M4中文文档第3章
文章目录
- Part II. 核心技术
- 3. IoC容器
- 3.2 容器概述
- 3.2.1 配置元数据
- 3.2.2 实例化容器
- 3.2.3 使用容器
- 3.3 Bean概述
- 3.3.1 命名bean
- 3.3.2 实例化bean
- 3.4 依赖
- 3.4.1 依赖注入
- 3.4.2 依赖和配置的种种细节
- 3.4.3 Using depends-on
- 3.4.4 延迟初始化bean
- 3.4.5 自动装配协作者
- 3.4.6 Method injection
- 3.5 bean作用域
- 3.5.1 单例作用域
- 3.5.2 The prototype scope
- 3.5.3 Singleton beans 与prototype-bean依赖关系
- 3.5.4 Request, session, application, and WebSocket scopes
- 3.5.5 自定义作用域
- 3.6 Customizing the nature of a bean
- 3.6.1 生命周期回调函数
- 3.6.2 ApplicationContextAware和BeanNameAware
- 3.6.3 Other Aware interfaces
- 3.7 Spring Bean的继承
- 3.8 容器扩展点
- 3.8.1 使用BeanPostProcessor定制bean
- 3.8.2 使用BeanFactoryPostProcessor定制配置元数据
- 3.8.3 使用FactoryBean定制实例化逻辑
Part II. 核心技术
这部分参考文档涵盖了所有那些绝对必要的Spring框架的技术。
其中最重要的是Spring框架的控制反转(IoC)容器。对Spring框架的IoC容器的全面处理,紧跟其后是全面覆盖Spring的面向方面的编程(AOP)技术。
Spring框架有自己的AOP框架,它在概念上很容易理解,而且成功解决了Java企业级应用中AOP需求80%的核心要素。
Spring还提供了与AspectJ(目前是最丰富的 - 在功能方面 - 而且也无疑是Java企业领域最成熟的AOP实现 )的集成。
- 第3章,IoC容器
- 第4章,资源
- 第5章,验证,数据绑定和类型转换
- 第6章,Spring表达式语言(SpEL)
- 第7章,使用Spring的面向方面的编程
- 第8章,Spring AOP API
3. IoC容器
本章介绍了Spring框架实现的控制反转(IoC)[1]原理。 IoC也称为依赖注入(DI)。它是一个过程,对象通过构造函数参数,工厂方法的参数或在对象实例构造或从工厂方法返回后在对象实例上设置的属性来定义它们的依赖关系,即它们一起合作的其他对象。 然后容器在创建bean时注入那些依赖。这个过程基本上是相反的,因此称为控制反转(IoC),通过使用类的直接构造或诸如服务定位器模式的机制来控制其依赖性的实例化或位置的bean自身的名称。
org.springframework.beans和org.springframework.context包是Spring Framework的IoC容器的基础。 BeanFactory接口提供了一种能够管理任何类型的对象的高级配置机制。 ApplicationContext是BeanFactory的子接口。它增加了与Spring的AOP特性的更容易的集成到一起的实现;消息资源处理(用于国际化),事件发布;和应用程序层特定上下文(如WebApplicationContext)以用于Web应用程序。
简而言之,BeanFactory提供了配置框架和基本功能,ApplicationContext添加了更多的企业特定功能。 ApplicationContext是BeanFactory的完整超集,并且在本章中专门用于描述Spring的IoC容器。有关使用BeanFactory而不是ApplicationContext的更多信息,请参见第3.16节“BeanFactory”。
在Spring中,构成应用程序主干并由Spring IoC容器管理的对象称为bean。 bean是由Spring IoC容器实例化,组装和以其他方式管理的对象。此外,bean只是应用程序中许多对象之一。Bean及其之间的依赖关系被容器所使用的配置元数据所反射 。
3.2 容器概述
接口org.springframework.context.ApplicationContext表示Spring IoC容器,并负责实例化,配置和组合上述bean。容器通过读取配置元数据获取关于要实例化,配置和组合的对象的指令。配置元数据以XML,Java注释或Java代码表示。它允许你表达组成你的应用程序的对象和这些对象之间丰富的相互依赖。
ApplicationContext接口的几个实现是Spring提供的开箱即用的。在独立应用程序中,通常创建一个ClassPathXmlApplicationContext或FileSystemXmlApplicationContext的实例。尽管XML是定义配置元数据的传统格式,但您可以通过提供少量的XML配置来声明性地支持这些额外的元数据格式,从而指示容器使用Java注释或代码作为元数据格式。
在大多数应用场景中,不需要显式用户代码来实例化Spring IoC容器的一个或多个实例。例如,在Web应用程序场景中,应用程序的web.xml文件中的一个简单的样板网站的XML 描述符通常就足够了 (请参见第3.15.4节“便捷的 ApplicationContext 实例化 Web 应用程序 ”) 。如果您使用的是Eclipse工具套件Eclipse开发环境,那么只需点击鼠标或敲击即可轻松创建此样板配置。
下图是Spring如何工作的高级视图。您的应用程序类与配置元数据相结合,以便在创建和初始化ApplicationContext之后,您具有完全配置和可执行的系统或应用程序。
Figure 3.1.Spring IoC容器
3.2.1 配置元数据
如上图所示, Spring IoC容器使用一种形式的配置元数据;此配置元数据表示你作为应用程序开发人员如何告诉 Spring容器如何实例化,配置和组合应用程序中的对象。
配置元数据传统上以简单和直观的 XML格式提供,这是本章的大部分用来传达 Spring IoC容器的关键概念和特性。
基于XML的元数据不是配置元数据唯一允许的形式。 Spring IoC容器本身完全与实际写入配置元数据的格式解耦。现在,许多开发人员为他们的Spring应用程序选择基于Java的配置。
有关在Spring容器中使用其他形式的元数据的信息,请参阅:
- 基于注释的配置: Spring 2.5引入了对基于注解的配置元数据的支持。
- 基于Java的配置:从 Spring 3.0开始,Spring JavaConfig项目提供的许多功能成为Spring Framework核心的一部分。因此,您可以使用Java而不是XML文件来定义应用程序类外部的bean。要使用这些新功能,请参阅 @Configuration, @Bean, @Importand @DependsOn注释。
Spring配置包括容器必须管理的至少一个,通常多个bean定义。基于XML的配置元数据显示这些bean配置为顶级元素内的元素。 Java配置通常在 @Configurationclass中使用 @Bean注解方法。
这些bean定义对应于组成应用程序的实际对象。通常,您定义服务层对象,数据访问对象(DAO),演示对象(如Struts Action实例),基础结构对象(如Hibernate SessionFactories),JMS队列等。通常, 不会在容器中配置细粒度域对象,因为通常由DAO和业务逻辑负责创建和加载域对象。但是,您可以使用 Spring与 AspectJ的集成来配置在 IoC容器控制之外创建的对象。请参阅使用 AspectJ通过 Spring来依赖注入域对象。
以下示例显示了基于 XML的配置元数据的基本结构:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="..." class="..."><!-- collaborators and configuration for this bean go here --></bean><bean id="..." class="..."><!-- collaborators and configuration for this bean go here --></bean><!-- more bean definitions go here --></beans>
id属性是一个字符串,用于标识单个bean定义。 class属性定义bean的类型,并使用完全限定的类名。 id属性的值指协作对象(即容器内此bean的名称)。 在此示例中未显示用于引用协作对象的XML; 有关详细信息,请参阅依赖关系。
3.2.2 实例化容器
实例化Spring IoC容器很简单。 提供给一个ApplicationContext构造函数的位置路径实际上是允许容器从各种外部资源(如本地文件系统,从JavaCLASSPATH等)加载配置元数据的资源字符串。
ApplicationContext context =new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});
在你学习Spring的IoC容器之后,你可能想更多地了解Spring的Resource资源抽象,如第4章,资源,它提供了从URI语法中定义的位置读取InputStream的方便机制。 特别是,Resource路径用于构造应用程序上下文,如第4.7节“应用程序上下文和资源路径”
以下示例显示服务层对象(services.xml)配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!-- services --><bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl"><property name="accountDao" ref="accountDao"/><property name="itemDao" ref="itemDao"/><!-- additional collaborators and configuration for this bean go here --></bean><!-- more bean definitions for services go here --></beans>
以下示例显示数据访问对象daos.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="accountDao"class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao"><!-- additional collaborators and configuration for this bean go here --></bean><bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao"><!-- additional collaborators and configuration for this bean go here --></bean><!-- more bean definitions for data access objects go here --></beans>
在前面的例子中,服务层由类PetStoreServiceImpl和JpaAccountDao和JpaItemDao类型的两个数据访问对象(基于JPA对象/关系映射标准)组成。 property name元素是指JavaBean属性的名称,ref元素是指另一个bean定义的名称。 id和ref元素之间的链接表示协作对象之间的依赖关系。 有关配置对象依赖性的详细信息,请参阅 Dependencies.
撰写基于XML的配置元数据
使bean定义跨越多个XML文件可能很有用。 通常每个单独的XML配置文件表示您的架构中的逻辑层或模块。
您可以使用应用程序上下文构造函数从所有这些XML片段加载bean定义。 这个构造函数需要多个Resource位置,如上一节所示。 或者,使用一个或多个出现的``元素从其他文件加载bean定义。 例如:
<beans><import resource="services.xml"/><import resource="resources/messageSource.xml"/><import resource="/resources/themeSource.xml"/><bean id="bean1" class="..."/><bean id="bean2" class="..."/>
</beans>
在前面的示例中,外部bean定义从三个文件加载:services.xml,messageSource.xml和themeSource.xml。 所有位置路径都与执行导入的定义文件相关,因此services.xml必须与执行导入的文件位于相同的目录或类路径位置,而messageSource.xml和themeSource.xml必须在 位于导入文件位置下方的resources位置。 如你所见,前导斜杠被忽略,但是鉴于这些路径是相对的,所以最好不要使用斜杠。 被导入的文件的内容,包括顶层``元素,必须是根据Spring Schema的有效XML bean定义。
使用相对“…/”路径引用父目录中的文件是可能的,但不推荐。 这样做会对当前应用程序之外的文件创建依赖关系。 特别地,不推荐对“classpath:”URL(例如,“classpath:…/ services.xml”)使用此引用,其中运行时解析进程选择“最近”类路径根,然后查看其父目录。 类路径配置更改可能导致选择不同的不正确目录。您可以始终使用完全限定资源位置而不是相对路径:例如,“file:C:/config/services.xml”或“classpath:/ config / services.xml“。 但是,请注意,您正在将应用程序的配置耦合到特定的绝对位置。 通常优选为这样的绝对位置保留间接,例如通过在运行时针对JVM系统属性解析的“$ {…}”占位符。
3.2.3 使用容器
ApplicationContext是一个高级工厂的接口,能够维护不同bean及其依赖关系的注册表。 使用方法T getBean(String name,Class requiredType)你可以检索bean的实例。
ApplicationContext允许你读取bean定义并访问它们,如下所示:
// create and configure beans
ApplicationContext context =new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);// use configured instance
List<String> userList = service.getUsernameList();
你使用getBean()来检索bean的实例。 ApplicationContext接口有一些其他方法来检索bean,但理想情况下你的应用程序代码不应该使用它们。 事实上,你的应用程序代码应该根本没有调用getBean()方法,因此根本没有依赖于Spring API。 例如,Spring与Web框架的集成并为各种Web框架类(如控制器和JSF管理的bean)提供了依赖注入。
3.3 Bean概述
Spring IoC容器管理一个或多个 beans 。这些bean是使用您提供给容器的配置元数据创建的,例如,以XML“定义”的形式。
在容器本身内,这些bean定义表示为BeanDefinition对象,它包含(除其他信息之外)以下元数据:
- 包限定类名:通常是定义的bean的实际实现类。
- Bean行为配置元素,它说明了bean在容器中的行为(范围,生命周期回调等等)。
- 引用其他bean的bean需要做的工作;这些引用也称为协作者或依赖性。
- 在新创建的对象中设置的其他配置设置,例如,在管理连接池的bean中使用的连接数,或者池的大小限制。
此元数据转换为构成每个bean定义的一组属性。
Table 3.1. The bean definition
Property | Explained in… |
---|---|
class | Section 3.3.2, “Instantiating beans” |
name | Section 3.3.1, “Naming beans” |
scope | Section 3.5, “Bean scopes” |
constructor arguments | Section 3.4.1, “Dependency Injection” |
properties | Section 3.4.1, “Dependency Injection” |
autowiring mode | Section 3.4.5, “Autowiring collaborators” |
lazy-initialization mode | Section 3.4.4, “Lazy-initialized beans” |
initialization method | the section called “Initialization callbacks” |
destruction method | the section called “Destruction callbacks” |
除了包含如何创建特定bean的bean定义之外,ApplicationContext实现还允许注册由用户在容器外部创建的现有对象。 这是通过访问ApplicationContext的BeanFactory通过方法getBeanFactory()来实现的,它返回BeanFactory实现DefaultListableBeanFactory。 DefaultListableBeanFactory通过方法registerSingleton(…)和registerBeanDefinition(…)支持这种注册。 然而,典型的应用程序只能通过元数据定义的 bean 来定义。
Bean元数据和手动提供的单例实例需要尽早注册,以便容器在自动装配和其他自检步骤期间正确地进行推理。 虽然在某种程度上支持覆盖现有元数据和现有单例实例,但是在运行时(与动态访问工厂同时)对新bean的注册未被官方支持,并且可能导致并发访问异常和/或bean容器中的不一致状态 。
3.3.1 命名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生成一个唯一的名称。但是,如果你想通过名称引用那个bean,通过使用ref元素或Service Locator样式查找,您必须提供一个名称。不提供名称的动机与使用内部bean和自动装配协作者相关。
Bean命名约定
约定是在命名bean时使用标准Java约定作为实例字段名称。 也就是说,bean名称以小写字母开头,从那时开始是驼峰式的。 这样的名称的示例将是(无引号)accountManager,accountService,userDao,loginController等。
命名Bean一致地使您的配置更容易阅读和理解,如果您使用Spring AOP,它对按照名称相关的一组bean应用建议时会有很多帮助。
通过类路径中的组件扫描,Spring根据上面的规则生成未命名组件的bean名称:基本上,取简单的类名称并将其初始字符转换为小写。 然而,在(异常)特殊情况下,当存在多个字符并且第一和第二字符都是大写字母时,原始形式被保留。 这些是由java.beans.Introspector.decapitalize(Spring在这里使用)定义的相同规则
bean 的别名
在对 bean 定义时,可以通过使用由id属性指定指定一个唯一的名称外,为了提供多个名称,需要通过name 属性加以指定,所有这个名称都指向同一个bean,在某些情况下提供别名非常有用,例如为了让应用每一个组件都能更容易的对公共组件进行引用。
然而,指定bean实际定义的所有别名并不总是足够的。 有时需要为在其他地方定义的bean引入别名。 在大型系统中通常是这种情况,其中配置在每个子系统之间分割,每个子系统具有其自己的对象定义集合。 在基于XML的配置元数据中,您可以使用<alias/>
元素来实现这一点。
<alias name="fromName" alias="toName"/>
在这种情况下,名为fromName的同一容器中的bean也可以在使用此别名定义之后称为toName。
例如,子系统A的配置元数据可以通过名称“subsystemA-dataSource”引用DataSource。 子系统B的配置元数据可以通过名称“subsystemB-dataSource”引用DataSource。 当编译使用这两个子系统的主应用程序时,主应用程序通过名称myApp-dataSource引用DataSource。 要使所有三个名称引用添加到MyApp配置元数据中的同一对象,以下别名定义:
<alias name="subsystemA-dataSource" alias="subsystemB-dataSource"/>
<alias name="subsystemA-dataSource" alias="myApp-dataSource" />
现在每个组件和主应用程序可以通过唯一的名称引用dataSource,并且保证不与任何其他定义(有效地创建命名空间)冲突,但它们引用同一个bean。
基于 Java 的配置
如果你使用Java配置,@ Bean注解可以用来提供别名,详细信息请看Section 3.12.3, “Using the @Bean annotation”.
3.3.2 实例化bean
bean定义本质上是创建一个或多个对象的方法。容器在询问时查看命名bean的配方,并使用由该bean定义封装的配置元数据来创建(或获取)实际对象。
如果使用基于XML的配置元数据,则指定要在<bean />
元素的class属性中实例化的对象的类型(或类)。这个class属性,在内部是一个BeanDefinition实例的Class属性,通常是强制的。 (对于异常,请参见“使用实例工厂方法实例化”一节和第3.7节“Bean定义继承”.)使用Class属性有两种方法之一:
- 通常,在容器本身通过反射调用其构造函数直接创建bean的情况下指定要构造的bean类,某种程度上等同于使用“new”运算符的Java代码。
- 要指定包含将被调用来创建对象的“static”工厂方法的实际类,类中包含静态方法。从static工厂方法的调用返回的对象类型可以是完全相同的类或另一个类。
内部类名. 如果你想为一个static嵌套类配置bean定义,你必须使用嵌套类的 binary 名字。
例如,如果你在com.example包中有一个名为Foo的类,并且这个Foo类有一个叫Bar的static嵌套类,class的attribute的值 一个bean的定义是…
com.example.FooBar注意在名称中使用Bar 注意在名称中使用Bar注意在名称中使用字符来分隔嵌套类名和外部类名。
通过构造函数实例化
当你通过构造函数方法创建一个bean时,所有正常的类都可以被Spring使用并兼容。也就是说,正在开发的类不需要实现任何特定接口或者以特定方式编码。只需指定bean类就足够了。但是,根据您用于该特定bean的IoC的类型,您可能需要一个默认(空)构造函数。
Spring IoC容器可以管理你想要管理的虚拟任何类;它不限于管理真正的JavaBean。大多数Spring用户喜欢实际的JavaBeans只有一个默认的(无参数)构造函数和适当的setters和getter在容器中的属性之后建模。你也可以在你的容器中有更多异常的非bean风格的类。例如,如果您需要使用绝对不遵守JavaBean规范的旧连接池,Spring也可以管理它。
使用基于XML的配置元数据,您可以按如下所示指定bean类:
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
有关向构造函数指定参数(如果需要)和在构建对象后设置对象实例属性的机制的详细信息,请参见依赖注入.
使用静态工厂方法实例化
当定义一个使用静态工厂方法创建的bean时,除了需要指定 class 属性外,还需要通过 factory-method属性来指定创建 bean 实例的工厂方法。Spring将调用此方法(其可选参数接下来介绍)返回实例对象,就此而言,跟通过普通构造器创建类实例没什么两样。
以下bean定义指定将通过调用factory-method创建bean。 该定义不指定返回对象的类型(类),只指定包含工厂方法的类。 在这个例子中,createInstance()方法必须是 static 方法。
<bean id="clientService"class="examples.ClientService"factory-method="createInstance"/>
public class ClientService {private static ClientService clientService = new ClientService();private ClientService() {}public static ClientService createInstance() {return clientService;}
}
有关向工厂方法提供(可选)参数和在工厂返回对象后设置对象实例属性的机制的详细信息,请参阅依赖和配置详解.
使用实例工厂方法实例化
类似于通过静态工厂方法实例化,使用实例工厂方法的实例化从容器调用现有bean的非静态方法以创建新bean。 要使用此机制,将class属性保留为空,并在factory-bean属性中,指定当前(或父/祖先)容器中包含要调用的实例方法的bean的名称 创建对象。 使用factory-method属性设置工厂方法本身的名称。
<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator"><!-- inject any dependencies required by this locator bean -->
</bean><!-- the bean to be created via the factory bean -->
<bean id="clientService"factory-bean="serviceLocator"factory-method="createClientServiceInstance"/>
public class DefaultServiceLocator {private static ClientService clientService = new ClientServiceImpl();private DefaultServiceLocator() {}public ClientService createClientServiceInstance() {return clientService;}
}
一个工厂类也可以有多个工厂方法,如下代码所示:
<bean id="serviceLocator" class="examples.DefaultServiceLocator"><!-- inject any dependencies required by this locator bean -->
</bean><bean id="clientService"factory-bean="serviceLocator"factory-method="createClientServiceInstance"/><bean id="accountService"factory-bean="serviceLocator"factory-method="createAccountServiceInstance"/>
public class DefaultServiceLocator {private static ClientService clientService = new ClientServiceImpl();private static AccountService accountService = new AccountServiceImpl();private DefaultServiceLocator() {}public ClientService createClientServiceInstance() {return clientService;}public AccountService createAccountServiceInstance() {return accountService;}}
这个方法展示了工厂bean本身可以通过依赖注入(DI)来管理和配置。更多细节请看”依赖和配置”。
在Spring文档中, factory bean 是指在Spring容器中配置的bean,它将通过instance 或static 工厂方法创建对象。 相比之下,“FactoryBean”(注意大写)指的是Spring特有的FactoryBean.
3.4 依赖
典型的企业应用程序不只包括一个对象(或Spring语法中的bean)。 即使是最简单的应用程序也有一些对象,一起工作并最终呈现用户看到的一致性应用程序。 下一节将介绍如何从定义多个独立的bean定义到一个完全实现的应用程序,其中对象通过协作来实现一个目标。
3.4.1 依赖注入
依赖注入(DI)是一个过程,对象通过构造函数参数,工厂方法的参数或在构造对象实例后设置的属性来定义它们的依赖关系,即它们所处理的其他对象或从工厂方法返回。容器然后在创建bean时注入这些依赖。这个过程基本上是相反的,因此名称为 Inversion of Control (IoC),通过使用类的直接构造或 Service Locator 模式来控制其自身的依赖性来实例化或通过调用的bean自身的名称(比如xml里的那些配置,通过名字直接调用到)。
代码与DI原理更清洁,当对象提供其依赖性时,解耦更有效。该对象不查找其依赖关系,并且不知道依赖关系的位置或类。因此,您的类变得更容易测试,特别是当依赖关系在接口或抽象基类上时,这允许在单元测试中使用存根或模拟实现。
DI存在两个主要形式,基于构造函数的依赖注入 和基于Setter的依赖注入。
基于构造方法的依赖注入
基于构造函数的 DI由容器调用具有多个参数的构造函数完成,每个参数表示依赖。 调用具有特定参数的static工厂方法来构造bean几乎是等效的,并且这个结论同样对将参数传递给构造函数和静态工厂方法有效。 以下示例显示只能使用构造函数注入进行依赖关系注入的类。 注意,这个类没有什么特别的*,它是一个POJO没有依赖于容器特定的接口,基类或注释。
public class SimpleMovieLister {// the SimpleMovieLister has a dependency on a MovieFinderprivate MovieFinder movieFinder;// a constructor so that the Spring container can inject a MovieFinderpublic SimpleMovieLister(MovieFinder movieFinder) {this.movieFinder = movieFinder;}// business logic that actually uses the injected MovieFinder is omitted...}
基于Setter的依赖注入
基于Setter的 DI是通过在调用一个无参构造函数或无参数“static”工厂方法来实例化你的bean之后,通过容器调用你的bean上的setter方法来完成的。
以下示例显示只能使用纯setter注入进行依赖关系注入的类。 这个类是常规的Java。 它是一个POJO,没有依赖于容器特定的接口,基类或注释。
public class SimpleMovieLister {// the SimpleMovieLister has a dependency on the MovieFinderprivate MovieFinder movieFinder;// a setter method so that the Spring container can inject a MovieFinderpublic void setMovieFinder(MovieFinder movieFinder) {this.movieFinder = movieFinder;}// business logic that actually uses the injected MovieFinder is omitted...}
ApplicationContext支持其管理的bean的基于构造函数和基于setter的DI。 它还支持基于setter的DI,在一些依赖关系已经通过构造函数方法注入之后。 您以一个BeanDefinition的形式配置依赖关系,你可以与PropertyEditor实例结合使用将属性从一种格式转换为另一种格式。 然而,大多数Spring用户不直接使用这些类(即,以编程方式),而是使用XMLbean定义,或注解组件(即用@ Component,@ Controller等注解的类) @ Bean方法在基于Java的@ Configuration类中定义。 然后通过这些配置在内部转换为BeanDefinition`的实例,并用于加载整个Spring IoC容器实例。
基于构造函数或基于setter的DI?
因为你可以混合基于构造函数和基于setter的DI,使用构造函数强制依赖 和setter方法或可选依赖的配置方法是一个好的经验法则。请注意,在设置方法上使用 @Required 注解可以用于使属性成为必需的依赖关系。
Spring团队通常倡导构造函数注入,因为它能够将应用程序组件实现为 immutable对象,并确保所需的依赖项不是“null”。此外,构造器注入的组件总是返回到完全初始化状态下的客户端(调用)代码。需要多说一点,大量的构造函数参数是一个坏的代码气味,暗示该类可能有太多的责任,应该重构,以更好地解耦。
Setter注入应主要仅用于可以在类中分配合理默认值的可选依赖项。否则,必须在代码使用依赖性的任何地方执行非空检查。 setter注入的一个好处是setter方法使该类的对象可以重新配置或稍后重新注入。因此,通过JMX MBeans的管理是setter注入的一个让人眼前一亮的例子。
使用对特定类最有意义的DI样式。有时候,当你处理你没有源码的第三方类时,选择你所能做到的。例如,如果第三方类没有公开任何setter方法,则构造函数注入可能是DI的唯一可用形式。
依赖解析过程
容器对bean依赖性解析过程如下:
- 使用描述所有bean的配置元数据创建和初始化ApplicationContext。配置元数据可以通过XML,Java代码或注解指定。
- 对于每个bean,它的依赖关系以属性,构造函数参数或静态工厂方法的参数的形式表示,如果你使用而不是一个正常的构造函数。这些依赖关系提供给bean,实际创建bean时调用。
- 每个属性或构造函数参数是要设置的值的实际定义,或对容器中另一个bean的引用。
- 作为值的每个属性或构造函数参数从其指定的格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring可以将以字符串格式提供的值转换为所有内置类型,例如int,long,String,boolean等。
Spring容器在创建容器时验证每个bean的配置。但是,bean属性本身不会设置,直到实际创建的时候调用。在创建容器时创建单例范围并设置为预实例化的Bean(默认值)。范围在第3.5节 “Bean scopes”中定义。否则,仅当请求时才创建bean。创建bean可能导致创建bean的图形,因为bean的依赖关系及其依赖关系(依此类推)被创建和分配。注意,那些依赖关系之间的分辨率不匹配可能迟到,即首次创建受影响的bean时显示。
循环依赖
如果主要使用构造函数注入,可以创建一个不可解析的循环依赖场景。
例如:A类通过构造函数注入需要B类的实例,B类通过构造函数注入需要A类的实例。如果将A和B类的bean配置为彼此注入,则Spring IoC容器在运行时检测到此循环引用,并抛出一个BeanCurrentlyInCreationException。
一个可行的解决方案是编辑要由setter而不是构造函数配置的一些类的源代码。或者,避免构造函数注入,并仅使用setter注入。换句话说,虽然不推荐,可以使用setter注入配置循环依赖性。
与典型情况(没有循环依赖)不同,bean A和bean B之间的循环依赖性迫使一个bean在被完全初始化之前被注入另一个bean(一个经典的鸡/鸡蛋场景)。
你一般可以信任Spring做正确的事情。它在容器加载时检测配置问题,例如引用不存在的bean和循环依赖性。 Spring在实际创建bean时尽可能晚地设置属性并解析依赖关系。这意味着,如果在创建该对象或其某个依赖关系时出现问题,则在请求对象时,已正确加载的Spring容器可能稍后会生成异常。例如,bean由于缺少或无效的属性而抛出异常。这可能延迟一些配置问题的可见性是因为ApplicationContext实现这默认情况下预实例化单例bean。以实际需要之前创建这些bean的一些预先时间和内存为代价,您在创建ApplicationContext时不会发现配置问题。您仍然可以覆盖此默认行为,以便单例bean将延迟初始化,而不是预先实例化(其实就是懒加载,真正用到的时候才会发生,提高了系统的性能)。
如果不存在循环依赖性,则当一个或多个协作bean被注入到依赖bean中时,每个协作bean在被注入到依赖bean之前被完全配置。这意味着如果bean A对bean B有依赖性,则Spring IoC容器在调用bean A上的setter方法之前完全配置bean B.换句话说,bean被实例化(如果不是实例化的单例)设置相关性,并调用相关的生命周期方法(如配置的init方法或InitializingBean回调方法)。
依赖注入示例
The following example uses XML-based configuration metadata for setter-based DI. A small part of a Spring XML configuration file specifies some bean definitions: 以下示例使用基于XML的配置基于setter的DI。 Spring XML配置文件的一小部分指定了一些bean定义:
<bean id="exampleBean" class="examples.ExampleBean"><!-- setter injection using the nested ref element --><property name="beanOne"><ref bean="anotherExampleBean"/></property><!-- setter injection using the neater ref attribute --><property name="beanTwo" ref="yetAnotherBean"/><property name="integerProperty" value="1"/>
</bean><bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {private AnotherBean beanOne;private YetAnotherBean beanTwo;private int i;public void setBeanOne(AnotherBean beanOne) {this.beanOne = beanOne;}public void setBeanTwo(YetAnotherBean beanTwo) {this.beanTwo = beanTwo;}public void setIntegerProperty(int i) {this.i = i;}}
在前面的示例中,setters被声明为与XML文件中指定的属性匹配。 以下示例使用基于构造函数的DI:
<bean id="exampleBean" class="examples.ExampleBean"><!-- constructor injection using the nested ref element --><constructor-arg><ref bean="anotherExampleBean"/></constructor-arg><!-- constructor injection using the neater ref attribute --><constructor-arg ref="yetAnotherBean"/><constructor-arg type="int" value="1"/>
</bean><bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {private AnotherBean beanOne;private YetAnotherBean beanTwo;private int i;public ExampleBean(AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {this.beanOne = anotherBean;this.beanTwo = yetAnotherBean;this.i = i;}}
在bean定义中指定的构造函数参数将被用作ExampleBean构造函数的参数。
现在考虑这个例子的一个变体,在这里不使用构造函数,Spring被告知要调用一个static工厂方法来返回一个对象的实例:
<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance"><constructor-arg ref="anotherExampleBean"/><constructor-arg ref="yetAnotherBean"/><constructor-arg value="1"/>
</bean><bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {// a private constructorprivate 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;}}
static工厂方法的参数是通过未定义的<constructor-arg/>
元素提供的,完全和实际使用的构造函数一样。 由工厂方法返回的类的类型不必与包含“static”工厂方法的类的类型相同,尽管在本例中它是。 一个实例(非静态)工厂方法将以基本相同的方式使用(除了使用“factory-bean”属性而不是“class”属性),因此这里不再讨论细节。
3.4.2 依赖和配置的种种细节
如上一节所述,您可以将bean属性和构造函数参数定义为对其他托管bean(协作者)的引用,或者作为内联定义的值。 Spring通过配置基于XML的元数据来支持它的和元素中的子元素类型。
直值(基本类型,字符串等)
<property />
元素的value属性指定一个属性或构造函数参数作为一个人为可读的字符串表示。 Spring的转换服务用于将这些值从 一个String转换到属性或参数的实际类型。
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"><!-- results in a setDriverClassName(String) call --><property name="driverClassName" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mydb"/><property name="username" value="root"/><property name="password" value="masterkaoli"/>
</bean>
前面的XML更简洁; 但是,除非您使用IntelliJ IDEA或Spring Tool Suite (STS)等IDE,否则会在运行时而不是设计时间(也就是在敲代码的时候)发现打字错误,在创建bean定义时支持自动属性完成。 强烈推荐此类IDE帮助。
您还可以将java.util.Properties实例配置为:
<bean id="mappings"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"><!-- typed as a java.util.Properties --><property name="properties"><value>jdbc.driver.className=com.mysql.jdbc.Driverjdbc.url=jdbc:mysql://localhost:3306/mydb</value></property>
</bean>
Spring容器通过使用JavaBeans的PropertyEditor机制将<value />
元素内的文本转换为java.util.Properties实例。 这是一个很好的快捷方式,并且是Spring团队喜欢在“value”属性样式上使用嵌套的<value />
元素的几个地方之一。
idref元素
idref元素只是一种将容器中另一个bean的 id (字符串值 - 不是引用)传递给<constructor-arg />
或<property />
元素 。
<bean id="theTargetBean" class="..."/><bean id="theClientBean" class="..."><property name="targetName"><idref bean="theTargetBean" /></property>
</bean>
上面的bean定义代码片段与下面的代码片段完全相同(在运行时):
<bean id="theTargetBean" class="..." /><bean id="client" class="..."><property name="targetName" value="theTargetBean" />
</bean>
第一种形式优于第二种形式,因为使用idref标签允许容器在部署时验证被引用的命名bean实际存在。在第二个变体中,不对传递给client bean的targetName属性的值执行验证。当client bean实际被实例化时,只有发现了typos(而且可能是致命的结果)。如果client bean是一个prototype bean,这个打印错误所生成的异常只能在容器部署后很久才被发现。
idref元素上的local属性在4.0 bean xsd中不再支持,因为它不再提供超过正则bean引用的值。在升级到4.0模式时,只需将现有的“idref local”引用更改为“idref bean”。
<idref />
元素带来的一个常见的价值(至少在Spring 2.0之前的版本中)是在AOP拦截器 的配置中。在一个ProxyFactoryBean bean定义中,当指定拦截器名称时,使用元素可防止拼写拦截器ID。
引用其他bean(协作者)
ref元素是<constructor-arg/>
或<property/>
定义元素中的最后一个元素。在这里,你将bean的指定属性的值设置为对容器管理的另一个bean(协作者)的引用。引用的bean是将设置其属性的bean的依赖关系,并且在设置属性之前根据需要对其进行初始化。 (如果协作者是单例bean,它可能已经由容器初始化。)所有引用最终都是对另一个对象的引用。范围和验证取决于您是通过“bean”,“local”还是“parent”属性指定其他对象的id / name。
通过<ref/>
标签的bean属性指定目标bean是最通用的形式,允许创建对同一容器或父容器中的任何bean的引用,而不管它是否在同一个XML文件。 “bean”属性的值可以与目标bean的“id”属性相同,也可以与目标bean的“name”属性中的值之一相同。
<ref bean="someBean"/>
通过parent属性指定目标bean将创建对当前容器的父容器中的bean的引用。 “parent”属性的值可以与目标bean的“id”属性相同,也可以与目标bean的“name”属性中的一个值相同,并且目标bean必须位于父容器中 的当前一个。 你使用这个bean引用变体主要是当你有一个容器的层次结构,并且你想用一个与父bean同名的代理在一个父容器中包装一个现有的bean。
<!-- in the parent context -->
<bean id="accountService" class="com.foo.SimpleAccountService"><!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->class="org.springframework.aop.framework.ProxyFactoryBean"><property name="target"><ref parent="accountService"/> <!-- notice how we refer to the parent bean --></property><!-- insert other configuration and dependencies as required here -->
</bean>
'ref元素的local属性在4.0 beans xsd中不再支持,因为它不再提供超过正则范围bean引用的值。 在升级到4.0模式时,只需将现有的ref local引用更改为ref bean`
内部beans
元素内的<property />
或<constructor-arg />
元素里面定义了一个所谓的inner(内部)bean 。
<bean id="outer" class="..."><!-- instead of using a reference to a target bean, simply define the target bean inline --><property name="target"><bean class="com.example.Person"> <!-- this is the inner bean --><property name="name" value="Fiona Apple"/><property name="age" value="25"/></bean></property>
</bean>
内部bean定义不需要定义的id或名称; 如果指定,容器不使用这样的值作为标识符。 容器在创建时也忽略scope标志:内部bean 总是匿名的,它们总是用外部bean创建。 将内部bean注入到协作bean中而不是注入到封闭bean中或者独立访问它们是不可能的。
作为一种角落情况,可以从自定义范围接收销毁回调,例如。 对于包含在单例bean内的请求范围内部bean:内部bean实例的创建将绑定到其包含的bean,但销毁回调允许它参与请求范围的生命周期。 这不是一个常见的情况; 内部bean通常简单地共享它们被包含bean的范围。
集合
在<list/>
,<set/>
,<map/>
和<props/>
元素中,设置JavaCollection类型List,Set,Map和Properties的属性和参数。
<bean id="moreComplexObject" class="example.ComplexObject"><!-- results in a setAdminEmails(java.util.Properties) call --><property name="adminEmails"><props><prop key="administrator">administrator@example.org</prop><prop key="support">support@example.org</prop><prop key="development">development@example.org</prop></props></property><!-- results in a setSomeList(java.util.List) call --><property name="someList"><list><value>a list element followed by a reference</value><ref bean="myDataSource" /></list></property><!-- results in a setSomeMap(java.util.Map) call --><property name="someMap"><map><entry key="an entry" value="just some string"/><entry key ="a ref" value-ref="myDataSource"/></map></property><!-- results in a setSomeSet(java.util.Set) call --><property name="someSet"><set><value>just some string</value><ref bean="myDataSource" /></set></property>
</bean>
一个map键或值或set值也可以是以下任何元素:
bean | ref | idref | list | set | map | props | value | null
集合合并
Spring容器还支持集合的合并。 一个应用程序开发人员可以定义一个父类型 <list/>
,<set/>
,<map/>
或者<props/>
, <list/>
,<set/>
,<map/>
或<props/>
元素继承并覆盖父集合中的值。 也就是说,子集合的值是合并父和子集合的元素的结果,子集合元素覆盖父集合中指定的值。
*本节关于合并讨论了父子bean的机制。 不熟悉父和子bean定义的读者可能希望在继续之前阅读相关章节。
以下示例演示集合合并:
<beans><bean id="parent" abstract="true" class="example.ComplexObject"><property name="adminEmails"><props><prop key="administrator">administrator@example.com</prop><prop key="support">support@example.com</prop></props></property></bean><bean id="child" parent="parent"><property name="adminEmails"><!-- the merge is specified on the child collection definition --><props merge="true"><prop key="sales">sales@example.com</prop><prop key="support">support@example.co.uk</prop></props></property></bean>
<beans>
注意在child bean定义的adminEmails属性的元素上使用merge = true属性。 当child bean被容器解析和实例化时,生成的实例会有一个adminEmails``Properties集合,其中包含子集adminEmailscollection与父集合’adminEmails`集合的合并结果。
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk
子属性Properties集合的值集合从父<props/>
继承所有属性元素,support值的子值将覆盖父集合中的值。
这种合并行为类似地适用于<list/>
,<map/>
和<set/>
集合类型。 在<list/>
元素的特定情况下,与List集合类型相关联的语义,即ordered集合的值的概念被维护; 父级的值在所有子级列表的值之前。 在Map,Set和Propertie集合类型的情况下,不存在排序。 因此没有排序语义对集合类型有效,这些类型是容器在内部使用的相关联的Map,Set和Properties实现类型的基础。
集合合并的限制
你不能合并不同的集合类型(例如一个Map和一个List),如果你试图这样做,一个适当的Exception被抛出。 merge属性必须在下层,继承,子定义上指定; 在父集合定义上指定merge属性是多余的,并且不会导致所需的合并。
强类型集合
随着在Java 5中引入泛型类型,可以使用强类型集合。 也就是说,可以声明一个Collection类型,使得它只能包含String元素(例如)。 如果你使用Spring来将一个强类型的“Collection”依赖注入到一个bean中,你可以利用Spring的类型转换支持,这样强类型的“Collection”实例的元素被转换为适当的类型 被添加到Collection。
public class Foo {private Map<String, Float> accounts;public void setAccounts(Map<String, Float> accounts) {this.accounts = accounts;}
}
<beans><bean id="foo" class="x.y.Foo"><property name="accounts"><map><entry key="one" value="9.99"/><entry key="two" value="2.75"/><entry key="six" value="3.99"/></map></property></bean>
</beans>
当foo bean的accounts属性准备注入时,强类型Map的元素类型的泛型信息可以通过反射来获得。 因此,Spring的类型转换基础设施将各种值元素识别为“Float”类型,并且字符串值“9.99,2.75”和“3.99”被转换为实际的“Float”类型。
Null和空字符串
Spring将空参数的属性等作为空的字符串处理。 以下基于XML的配置元数据片段将email属性设置为空的“String”值(“”)。
<bean class="ExampleBean"><property name="email" value=""/>
</bean>
以上配置等同于以下Java代码:
exampleBean.setEmail("")
元素用来处理null值。 例如:
<bean class="ExampleBean"><property name="email"><null/></property>
</bean>
以上配置等同于以下Java代码:
exampleBean.setEmail(null)
XML使用p命名空间进行简化操作
p-namespace(命名空间p)使您能够使用bean元素的属性,而不是嵌套的元素来描述属性值和/或协作bean。
Spring支持基于XML的可扩展配置格式带命名空间 模式定义。 本章讨论的beans配置格式在XML Schema文档中定义。 但是,p命名空间不是在XSD文件中定义的,只存在于Spring的核心。
以下示例显示解析为相同结果的两个XML片段:第一个使用标准XML格式,第二个使用p命名空间。
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean name="classic" class="com.example.ExampleBean"><property name="email" value="foo@bar.com"/></bean><bean name="p-namespace" class="com.example.ExampleBean"p:email="foo@bar.com"/>
</beans>
该示例显示了bean定义中名为email的p-namespace(p命名空间)中的属性。 这告诉Spring包含一个属性声明。 如前所述,p命名空间没有模式定义,因此你可以设置属性的名字作为bean的property的名字。
下一个示例包括两个具有对另一个bean的引用的bean定义:
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean name="john-classic" class="com.example.Person"><property name="name" value="John Doe"/><property name="spouse" ref="jane"/></bean><bean name="john-modern"class="com.example.Person"p:name="John Doe"p:spouse-ref="jane"/><bean name="jane" class="com.example.Person"><property name="name" value="Jane Doe"/></bean>
</beans>
如您所见,此示例不仅包含使用 p-namespace(p命名空间)的属性值,还使用特殊格式声明属性引用。 第一个bean定义使用创建一个john bean 对jane bean的引用,第二个bean的定义使用了p:spouse-ref="jane"作为一个属性来做同样的事情。 在这种情况下,spouse是属性名,而-ref部分表示这不是一个直接的值,而是一个对另一个bean的引用。
p-namespace(p命名空间)不如标准XML格式灵活。 例如,声明属性引用的格式与以“Ref”结尾的属性冲突(声明属性的引用是以Ref结尾的,采用p命名空间将会产生冲突),而标准XML格式不会。 我们建议您仔细选择您的方法,并将其传达给您的团队成员,以避免生成同时使用所有这三种方法的XML文档。
XML使用c命名空间进行简化操作
类似于 “XML shortcut with the p-namespace”这一节,在Spring 3.1中新引入的 c-namespace 允许使用内联属性来配置构造函数参数,而不是嵌套constructor-argthe section called “XML shortcut with the p-namespace”元素。
让我们回顾一下“基于构造函数的依赖注入”一节中的例子并顺带使用c:命名空间进行改造:
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:c="http://www.springframework.org/schema/c"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="bar" class="x.y.Bar"/><bean id="baz" class="x.y.Baz"/><!-- traditional declaration --><bean id="foo" class="x.y.Foo"><constructor-arg ref="bar"/><constructor-arg ref="baz"/><constructor-arg value="foo@bar.com"/></bean><!-- c-namespace declaration --><bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/></beans>
c:命名空间使用与p:一样的约定(用于bean引用的尾部“-ref”),用于通过其名称设置构造函数参数。 同样,它需要被声明,即使它没有在XSD模式中定义(但它存在于Spring核心内部)。
对于少数情况下构造函数参数名称不可用(通常如果字节码没有调试信息编译),可以使用fallback到参数索引:
<!-- c-namespace index declaration -->
<bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>
由于XML语法,索引符号需要存在前导_,因为XML属性名称不能以数字开头(即使某些IDE允许它).
实际上,构造函数的解析机制在匹配参数方面是 相当高效的,除非一个真正需要,我们建议通过使用名称符号来进行你的配置。
组合属性名称
您可以在设置bean属性时使用复合或嵌套属性名称,只要路径的所有组件(最终属性名称除外)不为null。 考虑下面的bean定义:
<bean id="foo" class="foo.Bar"><property name="fred.bob.sammy" value="123" />
</bean>
foo bean有一个fred属性,fred又有一个bob属性,bob又有一个sammy属性,最后的sammy属性被设置为值123。 为了使这个工作,foo的fred属性和fred的bob属性在构造bean之后不能是null,否则NullPointerException会被抛出。
3.4.3 Using depends-on
如果bean是另一个的依赖,通常意味着一个bean被设置为另一个的属性。 通常你用element 在XML中配置依赖。 然而,有时bean之间的依赖不直接; 例如,类的静态块初始化,又比如数据库驱动程序注册。 depends-on属性可以在使用此元素的bean初始化之前明确强制一个或多个bean被初始化。 以下示例使用 depends-on属性来表示对单个bean的依赖关系:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
为了实现多个bean的依赖,你可以在depends-on中将指定的多个bean名字用分隔符进行分隔,分隔符可以是逗号,空格以及分号等。
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao"><property name="manager" ref="manager" />
</bean><bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
bean定义中的depends-on属性可以指定初始化时间依赖性,在singleton 的情况下只有bean,一个相应的销毁时间依赖。 在给定的bean本身被销毁之前,首先销毁定义与给定bean的依赖关系关系的依赖bean。 因此,依赖也可以控制销毁的顺序.
3.4.4 延迟初始化bean
默认情况下,ApplicationContext实现急切地创建和配置所有singleton bean作为初始化过程的一部分。 通常,这种预实例化是期望的,因为配置或周围环境中的错误被立即发现,而不是数小时或甚至数天之后。 当这种行为不是所期望的,你可以通过将bean定义标记为延迟初始化来阻止单例bean的预实例化。 一个延迟初始化的bean告诉IoC容器在第一次请求时而不是在启动时创建一个bean实例。
在XML中,此行为由元素上的lazy-init属性控制; 例如:
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>
当前面的配置被一个ApplicationContext消费时,名为lazy的bean在ApplicationContext启动时不会被预先实例化,而not.lazybean被预先实例化。
但是,当一个延迟初始化的bean是单例bean的依赖,而这个单例bean又不是 lazy初始化时,ApplicationContext在启动时创建延迟初始化的bean,因为它必须满足singleton的依赖。因此延迟加载的bean会被注入单例bean。
您还可以通过使用<bean />
元素上的default-lazy-init属性在容器级别控制延迟初始化; 例如:
<beans default-lazy-init="true"><!-- no beans will be pre-instantiated... -->
</beans>
3.4.5 自动装配协作者
Spring容器可以自动装配协作bean之间的关系。你可以允许Spring通过检查ApplicationContext的内容来自动为你的bean解析协作者(其他bean)。自动装配有以下优点:
- 自动装配可以显着减少指定属性或构造函数参数的需求.(其他机制,如bean模板在本章其他地方讨论 在这方面也很有价值。)
- 自动装配可以在您的对象发生变化时更新配置。例如,如果您需要向类添加依赖关系,则可以自动满足该依赖关系,而无需修改配置。因此,自动依赖在开发期间可以是特别有用的,当系统趋于稳定时改为显式装配。
当使用基于XML的配置元数据[2 ]时,您指定autowire“属性的bean定义的自动装配模式。自动装配功能有四种模式。您指定自动装配每个bean,因此可以选择哪些自动装配。
Table 3.2. Autowiring modes
Mode | 模式解释 |
---|---|
no | (默认)无自动装配。 Bean引用必须通过ref元素定义。 对于较大型部署,不建议更改默认设置,因为明确指定协作者会提供更好控制和清晰度。 在一定程度上,它记录了系统的结构. |
byName | 按属性名称自动装配。 Spring查找与需要自动注入的属性同名的bean。 例如,如果bean定义设置为autowire by name,并且它包含 master 属性(即它有一个 setMaster(…)方法),Spring会查找名为master的bean定义, 并使用它来设置属性. |
byType | 允许属性在属性类型中只有一个bean存在于容器中时自动连接。 如果存在多个,则会抛出致命异常,这表示您不能对该bean使用 byType autowiring。 如果没有匹配的bean,什么都不发生; 该属性未设置. |
constructor | 类似于 byType ,但适用于构造函数参数。如果在容器中没有找到与构造器参数类型一致的bean,则会抛出异常. |
使用 byType 或构造函数自动装配模式,可以应用于数组和类型集合。 在这种情况下,提供容器中所有匹配的自动装配对象将 被应用于满足各种依赖。 如果预期的键类型为“String”,则可以自动注入强类型的Map。一个自动装配的Map value值将包含与预期类型匹配的所有Bean实例。
你可以结合自动装配和依赖检查,后者将会在自动装配完成之后进行。
3.4.6 Method injection
在大多数应用场景中,容器中的大多数bean都是 singletons。 当单例bean需要与另一个单例bean协作或非单例bean需要与另一个非单例bean协作时,通常通过将一个bean定义为另一个的属性来处理依赖关系。不过对于具有不同生命周期的bean 来说这样做就会出现问题。 假设单例bean A需要使用非单例(原型)bean B,也许在A上的每个方法调用上。容器仅创建单例bean A一次,因此只有一次机会来设置属性。 这样就没办法 在需要的时候每次让容器为bean A提供一个新的bean B实例。
解决方案是放弃一些控制的反转。 您可以通过实现以下操作来让bean A知道容器 ApplicationContextAware接口,并通过对容器调用getBean(“B”)在每次bean A需要它时请求(一个通常是新的)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 CommandCommand command = createCommand();// set the state on the (hopefully brand new) Command instancecommand.setState(commandState);return command.execute();}protected Command createCommand() {// notice the Spring API dependency!return this.applicationContext.getBean("command", Command.class);}public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}
}
前面是不可取的,,因为业务代码和Spring框架产生的耦合。方法注入,作为Spring Ioc容器的高级特性,,允许以干净的方式处理这个用例。
你可以在这个博客查看关于方法注入的动机
Lookup method injection
查找方法注入具有使容器覆盖受容器管理的bean 上的方法的能力,从而可以返回容器中另一个命名bean的查找结果。 查找方法注入适用于原型bean,如前一节中所述的场景。 Spring框架通过使用从CGLIB库生成的字节码来动态生成覆盖该方法的子类来实现此方法注入。
为了使这个动态子类化起作用,Spring bean容器将继承的类不能是final,被重写的方法也不能是final。对一个具有abstract方法的类进行单元测试需要你为其写一个子类并提供该抽象方法的实现。Concrete(具体)方法对于组件扫描也是必要的,这需要具体的类来拾取。另一个关键的限制是,查找方法将不能使用工厂方法, 特别是在配置类中不使用@Bean方法,因为容器不负责在这种情况下创建实例,因此不能即时创建运行时生成的子类。
看看前面代码片段中的CommandManager类,你会看到Spring容器将动态覆盖createCommand()方法的实现。 你的CommandManager类不会有任何Spring依赖,在下面重写的例子中可以看出:
package fiona.apple;// no more Spring imports!public abstract class CommandManager {public Object process(Object commandState) {// grab a new instance of the appropriate Command interfaceCommand command = createCommand();// set the state on the (hopefully brand new) Command instancecommand.setState(commandState);return command.execute();}// okay... but where is the implementation of this method?protected abstract Command createCommand();
}
在包含要注入的方法(在这种情况下为“CommandManager”)的客户端类中,要注入的方法需要具有以下形式的签名:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果方法是“抽象的”,动态生成的子类实现该方法。 否则,动态生成的子类将覆盖原始类中定义的具体方法。 例如:
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype"><!-- inject dependencies here as required -->
</bean><!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager"><lookup-method name="createCommand" bean="myCommand"/>
</bean>
标识为 commandManager 的bean在需要myCommand bean的新实例时,将调用自己的createCommand()方法。 将myCommand bean 设置成prototype,如果这是实际上需要的话,一定要谨慎处理。 如果它是一个 singleton,那么每次将返回相同的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();}@Lookupprotected abstract MyCommand createCommand();
}
注意,您通常会使用具体的子类实现来声明这种带注解的查找方法,以使它们与Spring的组件扫描规则兼容,默认情况下会忽略抽象类。 此限制不适用于显式注册或显式导入的bean类的情况。
另一种访问不同范围的目标bean的方法是ObjectFactory/ Provider注入点。 查看the section called “Scoped beans as dependencies”。感兴趣的读者也可以找到ServiceLocatorFactoryBean(在org.springframework.beans.factory.config包中)来用一用。
任意方法替换
与查找方法注入相比,很少有用的方法注入形式是使用另一个方法实现替换托管bean中的任意方法的能力。 用户可以安全地跳过本节的其余部分,直到实际需要该功能。
使用基于XML的配置元数据,您可以使用“replaced-method”元素将现有的方法实现替换为另一个已部署的bean。 考虑下面的类,使用方法computeValue,我们要覆盖:
public class MyValueCalculator {public String computeValue(String input) {// some real code...}// some other methods...}
实现org.springframework.beans.factory.support.MethodReplacer接口的类提供了新的方法定义。
/*** meant to be used to override the existing computeValue(String)* implementation in MyValueCalculator*/
public class ReplacementComputeValue implements MethodReplacer {public Object reimplement(Object o, Method m, Object[] args) throws Throwable {// get the input value, work with it, and return a computed resultString input = (String) args[0];...return ...;}
}
下面的bean定义中指定了要配置的原始类和将要重写的方法:
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator"><!-- arbitrary method replacement --><replaced-method name="computeValue" replacer="replacementComputeValue"><arg-type>String</arg-type></replaced-method>
</bean><bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
您可以在元素中使用一个或多个包含的元素来指示要覆盖的方法的方法签名。 仅当方法重载并且类中存在多个变量时,参数的签名才是必需的。 为了方便起见,参数的类型字符串可以是完全限定类型名称的子字符串。 例如,以下所有匹配java.lang.String:
java.lang.String
String
Str
因为参数的数量通常足以区分每个可能的选择,这个简写方式可以节省大量的输入,允许你只需要输入最短字符串就可匹配参数类型。
3.5 bean作用域
当创建bean定义时,您将创建一个 recipe 用于创建由该bean所定义的类的实际实例。 bean定义是一个配方(模型)的想法很重要,因为它意味着,和一个类一样,您可以从单个配方(模型)创建多个对象实例。
你不仅可以控制要插入到从特定bean定义创建的对象的各种依赖性和配置值,还可以控制从特定bean定义创建的对象的 scope 。这种方法是强大和灵活的,因为您可以选择通过配置创建的对象的作用域,而无需在代码层面去控制。 bean可以被定义为部署在多个作用域之一:开箱即用,Spring框架支持六个作用域,其中五个作用域(这里就和之前的文档有区别了,可以做相应的对比)仅在使用Web感知的ApplicationContext时可用。
以下作用域是开箱即用的。您还可以创建自定义作用域
Table 3.3. Bean scopes
Scope | Description |
---|---|
singleton | 默认的。一个bean定义,在一个IoC容器内只会产生一个对象。 |
prototype | 一个bean定义会产生多个对象实例 |
request | 一个bean定义产生的bean生命周期为一个HTTP请求;也就是,每一个HTTP请求都会根据bean定义产生一个对象实例。该作用域只有在Spring web ApplicationContext 上下文环境中才有效。 |
session | 产生的bean生命周期在HTTP 会话期间。该作用域只有在Spring web ApplicationContext 上下文环境中才有效 |
application | 将单个bean定义范围限定为ServletContext的生命周期。 该作用域只有在Spring web ApplicationContext 上下文环境中才有效 |
websocket | 将单个bean定义范围限定为WebSocket的生命周期。该作用域只有在Spring web ApplicationContext 上下文环境中才有效 |
从Spring 3.0开始,线程范围可用,但默认情况下未注册。 有关更多信息,请参阅SimpleThreadScope的文档。 有关如何注册此或任何其他自定义范围的说明,请参阅使用自定义范围一节。
3.5.1 单例作用域
单例bean只会产生一个实例,并且所有对具有与该bean定义匹配的id或id的bean的请求Spring容器都只会返回一个实例。
换句话说,当你定义一个bean定义,并且作为一个singleton,Spring IoC容器创建该bean所定义的对象的一个实例。 这个单个实例存储在这样的单例bean的缓存中,所有后续的请求和引用返回缓存的这个对象。 singleton
Spring的单例bean的概念不同于Singleton模式,和在Gang of Four(GoF)模式的书中定义的不同的地方。 GoF Singleton硬编码对象的范围,使得每一个类加载器内会产生单例类的一个实例。 Spring单例的作用域恰如其名:一个容器对应一个bean 。 这意味着如果你在一个Spring容器中为一个特定的类定义了一个bean,那么Spring容器将创建一个实例,并且只有一个由该bean定义定义的类的实例。 singleton scope (单例作用域)是Spring 中的默认配置。 要将bean定义为XML中的单例,您可以编写,例如:
<bean id="accountService" class="com.foo.DefaultAccountService"/><!-- 和下面的写法等价,因为单例作用域是默认的 -->
<bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>
3.5.2 The prototype scope
设置bean作用域为prototype,就是非单例,bean部署的prototype scope导致每次对该特定bean的请求时都会创建新的bean实例。 也就是说,bean被注入到另一个bean中,或者通过容器上的getBean()方法调用来请求它。 通常,对所有有状态bean使用prototype scope,对无状态bean使用singleton scope。
下图说明了Spring prototype scope。 数据访问对象(DAO)通常不被配置为 prototype scope,因为通常DAO不持有任何会话状态; prototype
接下来看看如何在XML中定义prototype bean:
<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>
与其他作用域相比,Spring不管理prototype bean的完整生命周期:容器实例化,配置和以其他方式组装原型对象,并将其传递给客户端,没有原型实例的进一步记录。因此,尽管初始化生命周期回调方法在所有对象上被调用,不管范围如何,在prototype的情况下,配置的销毁生命周期回调被不调用。客户端代码必须清理prototype作用域对象,并释放prototype bean持有的昂贵资源。要使Spring容器释放prototype作用域的bean所拥有的资源,请尝试使用自定义bean post-processor,它持有需要被清理bean的引用。
在某些方面,Spring容器在prototype作用域bean中的作用是Java“new”运算符的替代。经由该点的所实例化的bean的所有生命周期管理必须由客户端处理。 (有关Spring容器中bean的生命周期的详细信息,请参见第3.6.1节“生命周期回调”。)
3.5.3 Singleton beans 与prototype-bean依赖关系
当单例类依赖了原型类,要知道依赖在单例类初始化的时候就已经注入好了。因此,如果您将一个原型范围的bean依赖注入到一个单例范围的bean中,一个新的原型bean被实例化,然后依赖注入到单例bean中。原型实例是曾经提供给单例作用域bean的唯一实例。
但是,假设您希望单例范围的bean在运行时重复获取原型作用域bean的新实例。您不能将原型作用域bean依赖注入到单例bean中,因为当Spring容器实例化单例bean并解析和注入其依赖性时,该注入只发生一次。如果您在运行时需要多次获取新的原型bean实例,请参见第3.4.6节“方法注入”
3.5.4 Request, session, application, and WebSocket scopes
request,session,global session作用域,只有在spring web ApplicationContext的实现中(比如XmlWebApplicationContext)才会起作用,若在常规Spring IoC容器中使用,比如ClassPathXmlApplicationContext中,就会收到一个异常IllegalStateException来告诉你不能识别的bean作用域
初始化web配置
为了支持request,session,application和websocket级别(web-scoped bean)的bean的作用域,在定义bean之前需要一些小的初始配置。 (Spring标准作用域,包括singleton和prototype,这个初始设置不是必须要配置的。)
如何配置要根据具体的Servlet环境
如果你在Spring Web MVC中访问作用域bean,实际上,在SpringDispatcherServlet处理的请求中,则不需要特殊的设置:DispatcherServlet已经暴露了所有相关的状态。
如果你使用一个Servlet 2.5 web容器,请求在Spring的dispatcherServlet之外处理(例如,当使用JSF或Struts时),你需要注册org.springframework.web.context.request.RequestContextListener``ServletRequestListener 。对于Servlet 3.0+,这可以通过WebApplicationInitializer接口以编程方式完成。或者,对于较旧的容器,将以下声明添加到Web应用程序的web.xml文件中:
<web-app>...<listener><listener-class>org.springframework.web.context.request.RequestContextListener</listener-class></listener>...
</web-app>
或者,如果你的listener设置有问题,请考虑使用Spring的RequestContextFilter。 过滤器映射取决于周围的Web应用程序配置,因此必须根据需要进行更改。
<web-app>...<filter><filter-name>requestContextFilter</filter-name><filter-class>org.springframework.web.filter.RequestContextFilter</filter-class></filter><filter-mapping><filter-name>requestContextFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>...
</web-app>
DispatcherServlet,RequestContextListener和RequestContextFilter都做同样的事情,即将HTTP请求对象绑定到正在为该请求提供服务的Thread线程中。 这使得相关bean可以共享一个相同请求和会话作用域,并在调用链中进一步可用。
Request scope
考虑下面这种bean定义:
<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>
Spring容器通过对每个HTTP请求使用loginAction bean定义来创建一个LoginAction bean的新实例。 也就是说,loginAction bean的作用域是在HTTP请求级别。 您可以根据需要更改创建的实例的内部状态,因为根据此loginAcitonbean定义创建的其他bean实例并不会看到这些状态的改变;他们为各自的request拥有。 当reqeust完成处理,request作用的bean就被丢弃了。
当使用注解驱动组件或Java Config时,@RequestScope注解可以用于将一个组件分配给request范围。
@RequestScope
@Component
public class LoginAction {// ...
}
Session scope
考虑下面这种bean的xml配置定义:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
Spring容器通过对单个HTTP会话的生命周期使用userPreferences bean定义来创建UserPreferences bean的新实例。 换句话说,userPreferences bean在HTTPSession级别有效地作用域。 和request-scopedbean相类似,可以改变bean实例的内部状态,不管bean创建了多少实例都可以,要知道,使用相同的userPreferences定义创建的其他的bean实例看不到这些状态的改变,因为他们都是为各自的HTTP Session服务的。 当HTTPSession最终被丢弃时,被限定为该特定HTTPSession的bean也被丢弃。
当使用注解驱动组件或Java Config时,@SessionScope注解可用于将一个组件分配给session范围。
@SessionScope
@Component
public class UserPreferences {// ...
}
Application scope
考虑下面这种bean定义:
<bean id="appPreferences" class="com.foo.AppPreferences" scope="application"/>
Spring容器通过对整个web应用程序使用appPreferences bean定义来创建一个AppPreferences bean的新实例。 也就是说,appPreferences bean是在ServletContext级别定义的,存储为一个常规的ServletContext属性。 这有点类似于Spring单例bean,但在两个重要方面有所不同:1、他是每一个ServeltContext一个实例,而不是SpringApplicationContext范围。2、它是直接暴露的,作为ServletContext属性,因此可见。
当使用注解驱动组件或Java Config时,@ApplicationScope注解可用于将一个组件分配给application作用域。
@ApplicationScope
@Component
public class AppPreferences {// ...
}
不同级别作用域bean之间依赖
Spring IoC容器不仅管理对象(bean)的实例化,还管理协作者(或依赖关系)。 如果要将(例如)一个HTTP请求作用域bean注入到较长期作用域的另一个bean中,您可以选择注入一个AOP代理来代替该作用域bean。 也就是说,您需要注入一个代理对象,该对象暴露与作用域对象相同的公共接口,但也可以从相关作用域(例如HTTP请求)查找实际目标对象,并将方法调用委托给真实对象。
你也可以在定义为“singleton”的bean之间使用
<aop:scoped-proxy/>
,然后通过引用一个可序列化的中间代理,因此能够在反序列化时重新获得目标单例bean。
当对prototype scope的bean声明<aop:scoped-proxy/>
时,共享代理上的每个方法调用都将导致创建一个新的目标实例,然后将该调用转发给该目标实例。
此外,scoped代理不是以生命周期安全的方式从较小作用域访问bean的唯一方法。您也可以简单地将注入点(即构造函数/ setter参数或自动注入字段)声明为ObjectFactory<MyTargetBean>
,允许一个getObject()调用在需要时根据所需查找当前实例 - 作为一个扩展的变体,你可以声明ObjectProvider,它提供了几个附加的访问变量,包括getIfAvailable和getIfUnique。这个JSR-330变体被称为Provider, Provider声明和对应的get()调用每次检索尝试。有关JSR-330整体的更多详细信息,请参见 here .
在下面的例子中的配置只有一行,但重要的是要了解背后的“为什么”以及“如何”.
NTOE:加入本人说明(当你访问userService可以不需要访问userPreferences,而你要访问userPreferences必须要先访问userService,使用了的AOP包装,在cglib生成userPreferences代理的时候先来一层AOP的包装)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!-- an HTTP Session-scoped bean exposed as a proxy --><bean id="userPreferences" class="com.foo.UserPreferences" scope="session"><!-- instructs the container to proxy the surrounding bean --><aop:scoped-proxy/></bean><!-- a singleton-scoped bean injected with a proxy to the above bean --><bean id="userService" class="com.foo.SimpleUserService"><!-- a reference to the proxied userPreferences bean --><property name="userPreferences" ref="userPreferences"/></bean>
</beans>
要创建这样的代理,您需要将一个子元素<aop:scoped-proxy/>
插入到一个有范围的bean定义中(参见选择要创建的代理类型和第38章,基于XML模式的配置部分) 为什么在request,session和自定义scope级别限定bean的定义需要<aop:scoped-proxy/>
元素? 让我们检查下面的单例bean定义,并将其与您需要为上述scope定义的内容进行对比(注意,下面的userPreferencesbean定义是不完整的)。
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/><bean id="userManager" class="com.foo.UserManager"><property name="userPreferences" ref="userPreferences"/>
</bean>
在前面的例子中,单例beanuserManager被注入对HTTPSession作用域的beanuserPreferences的引用。这里的要点是userManager bean是一个单例:它将被实例化每个容器一次,它的依赖(在这种情况下只有一个,userPreferences bean)也只注入一次。这意味着userManager bean将只对完全相同的userPreferences对象操作,也就是说,它最初注入的对象。
这是不将较短寿命的作用域bean注入到较长寿命的作用域bean时所需的行为,例如将一个HTTPSession作用域的协作bean作为依赖注入到单例bean中。相反,你需要一个单独的userManager对象,并且对于HTTPSession的生命周期,你需要一个特定于所述HTTP会话的userPreferences对象。因此,容器创建一个对象,暴露与UserPreferences类完全相同的公共接口(理想情况下,一个对象,是一个UserPreferences实例),它可以从作用域机制(HTTP请求, Session等)。容器将这个代理对象注入到userManager bean中,它不知道这个UserPreferences引用是一个代理。在这个例子中,当一个UserManager实例调用一个依赖注入UserPreferences对象的方法时,它实际上是在代理上调用一个方法。代理然后从(在这种情况下)HTTPSession提取真正的UserPreferences对象,并将方法调用委托给检索的真正的UserPreferences对象。
因此,当将request-和session-scoped bean注入协作对象时,您需要以下,正确和完整的配置:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"><aop:scoped-proxy/>
</bean><bean id="userManager" class="com.foo.UserManager"><property name="userPreferences" ref="userPreferences"/>
</bean>
选择创建代理类型
默认情况下,当Spring容器为标记了<aop:scoped-proxy/>
元素的bean创建一个代理时,将创建一个基于CGLIB的类代理。
CGLIB代理只拦截公共方法调用! 不要在这样的代理上调用非公共方法; 它们不会被委派给实际的作用域目标对象.
或者,您可以将Spring容器配置成(分开念)为这些作用域bean创建标准的基于JDK接口的代理,通过为<aop:scoped-proxy/>
的“proxy-target-class”属性的值指定false元素。 使用基于JDK接口的代理意味着在应用程序类路径中不需要其他库来实现此类代理。 然而,这也意味着作用域bean的类必须实现至少一个接口,并且注入bean作用域的所有协作者必须通过它的一个接口引用该bean。
<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.foo.DefaultUserPreferences" scope="session"><aop:scoped-proxy proxy-target-class="false"/>
</bean><bean id="userManager" class="com.foo.UserManager"><property name="userPreferences" ref="userPreferences"/>
</bean>
有关选择基于类或基于接口的代理的更多详细信息,请参见第7.6节“代理机制”.
3.5.5 自定义作用域
bean作用域机制是可扩展的; 你可以定义自己的作用域,甚至重新定义现有的作用域,虽然后者被认为是不推荐的做法,并且,你不能覆盖内置的singleton和prototype作用域。
创建自定义作用域
要将自定义作用域集成到Spring容器中,您需要实现本部分中描述的org.springframework.beans.factory.config.Scope接口。 有关如何实现自己的作用域的想法,请参阅Spring框架本身和Scope javadocs,它解释了您需要更详细实现的方法。
Scope接口有四种方法来从作用域中获取对象,将它们从作用域中删除,并允许它们被销毁。
下面的方```法作用是返回作用域中对象。比如,session作用域的实现,该方法返回session-scoped会话作用域bean(若不存在,方法创建该bean的实例,并绑定到session会话中,用于引用,然后返回该对象)
Object get(String name, ObjectFactory objectFactory)
下面的方法作用是从作用域中删除对象。以session作用域实现为例,方法内删除对象后,会返回该对象,但是若找不到指定对象,则会返回null
Object remove(String name)
下面的方法作用是注册销毁回调函数,销毁是指对象销毁或者是作用域内对象销毁。销毁回调的详情请参看javadocs或者Spring 作用域实现。
void registerDestructionCallback(String name, Runnable destructionCallback)
下面的方法,用于获取作用域会话标识。每个作用域的标识都不一样。比如,session作用域的实现中,标识就是session标识(应该是指sessionId)
String getConversationId()
使用自定义作用域
在你编写和测试一个或多个自定义Scope实现之后,你需要让Spring容器意识到你的新作用域。 下面的方法是使用Spring容器注册一个新的Scope的核心方法:
void registerScope(String scopeName, Scope scope);
这个方法在ConfigurableBeanFactory接口上声明,在大多数具体的ApplicationContext实现中都可以使用,它通过BeanFactory属性与Spring一起提供。
registerScope(…)方法的第一个参数是与作用域相关联的唯一名称; Spring容器本身中这样的名称的例子是singleton和prototype。registerScope(…)方法的第二个参数是你想要注册和使用的自定义Scope实现的一个实际实例。
假设你编写了你的自定义Scope实现,然后像下面那样注册它。
下面的示例使用Spring Simple中包含的SimpleThreadScope,但默认情况下未注册。 这些指令对于您自己的自定义Scope实现是一样的。
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
然后,创建符合您的自定义Scope的作用域规则的bean定义:
<bean id="..." class="..." scope="thread">
使用自定义Scope 实现,你不限于作用域的编程(即代码)注册。 你也可以使用CustomScopeConfigurer类来声明性地注册Scope:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><bean class="org.springframework.beans.factory.config.CustomScopeConfigurer"><property name="scopes"><map><entry key="thread"><bean class="org.springframework.context.support.SimpleThreadScope"/></entry></map></property></bean><bean id="bar" class="x.y.Bar" scope="thread"><property name="name" value="Rick"/><aop:scoped-proxy/></bean><bean id="foo" class="x.y.Foo"><property name="bar" ref="bar"/></bean></beans>
当你在FactoryBean实现中放置
<aop:scoped-proxy/>
时,它是工厂bean本身的作用域,而不是从 getObject()返回的对象。
3.6 Customizing the nature of a bean
3.6.1 生命周期回调函数
要与容器的bean生命周期管理进行交互,可以实现SpringInitializingBean和DisposableBean接口。 容器为前者调用beforePropertiesSet(),后者调用destroy(),以允许bean在初始化和销毁bean时执行某些操作。
JSR-250@PostConstruct和@PreDestroy注解通常被认为是在现代Spring应用程序中接收生命周期回调的最佳实践。 使用这些注解意味着你的bean不耦合到Spring特定的接口。 有关详细信息,请参见第3.9.8节@PostConstruct和@PreDestroy。如果你不想使用JSR-250注解,但是你仍然希望删除耦合考虑使用init-method和destroy-method对象定义元数据.
在内部,Spring框架使用BeanPostProcessor实现来处理它可以找到并调用适当方法的任何回调接口。 如果你需要自定义特性或其他生命周期行为但Spring不提供开箱即用的,你可以自己实现一个BeanPostProcessor。 有关详细信息,请参见第3.8节容器扩展点.
除了初始化和销毁回调之外,Spring管理的对象还可以实现Lifecycle接口,使得这些对象可以参与由容器自身生命周期驱动的启动和关闭过程。
本节介绍了生命周期回调接口。
初始化回调
org.springframework.beans.factory.InitializingBean
接口允许bean在容器已经设置好bean的所有必需属性之后执行初始化工作。 InitializingBean接口指定了一个单一的方法:
void afterPropertiesSet() throws Exception;
建议不要使用InitializingBean接口,因为它不必要地将代码耦合到Spring。 或者,使用@PostConstruct注解或指定POJO初始化方法。 在基于XML的配置元数据的情况下,你使用init-method属性来指定具有void无参数签名的方法的名称。 使用Java配置,你使用@Bean的initMethod属性,参见接收生命周期回调一节. 例如,以下:
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {public void init() {// do some initialization work}}…和下面作用是完全一样的…
```xml
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {public void afterPropertiesSet() {// do some initialization work}}
但不会将代码耦合到Spring。
销毁回调
实现org.springframework.beans.factory.DisposableBean接口允许bean在包含它的容器被销毁时获得回调。 DisposableBean接口指定了一个单一的方法:
void destroy() throws Exception;
建议你不要使用DisposableBean回调接口,因为它不必要地将代码耦合到Spring。 或者,使用@PreDestroy 注解或指定bean定义支持的通用方法。 使用基于XML的配置元数据,你在<bean />
上使用destroy-method属性。 使用Java配置,你使用@Bean的destroyMethod属性,参见接收生命周期回调一节. 例如,以下定义:
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {public void cleanup() {// do some destruction work (like releasing pooled connections)}}和下面作用是完全一样的:<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
```java
public class AnotherExampleBean implements DisposableBean {public void destroy() {// do some destruction work (like releasing pooled connections)}}
但不会将代码耦合到Spring。
一个
<bean>
元素的destroy-method属性可以被赋予一个特殊的(推断)值,该值指示Spring自动检测特定bean类上的一个publicclose或shutdown方法 实现java.lang.AutoCloseable或java.io.Closeable因此匹配)。 这个特殊的(推断)值也可以在<beans>
元素的default-destroy-method属性上设置,以将这个行为应用到整个bean集合中(参见默认初始化 销毁方法)注意,这是Java配置的默认行为.
默认的初始化函数和销毁函数
当你编写不使用Spring特定的InitializeBean和DisposableBean回调接口的初始化和销毁方法回调时,你通常使用诸如init(),initialize(),dispose(),等等。理想情况下,此类生命周期回调方法的名称在整个项目中标准化,以便所有开发人员使用相同的方法名称并确保一致性。
你可以配置Spring容器来查找命名初始化,并在每个 bean上销毁回调方法名。这意味着,作为应用程序开发人员,你可以编写应用程序类并使用称为init()的初始化回调,而无需为每个bean定义配置一个init-method ="init"属性。 Spring IoC容器在创建bean时(并且根据前面描述的标准生命周期回调契约)调用该方法。此功能还对初始化和destroy方法回调强制执行一致的命名约定。
假设你的初始化回调方法名为init(),而destroy回调方法名为destroy()。你的类将类似于以下示例中的类。
public class DefaultBlogService implements BlogService {private BlogDao blogDao;public void setBlogDao(BlogDao blogDao) {this.blogDao = blogDao;}// this is (unsurprisingly) the initialization callback methodpublic void init() {if (this.blogDao == null) {throw new IllegalStateException("The [blogDao] property must be set.");}}}<beans default-init-method="init"><bean id="blogService" class="com.foo.DefaultBlogService"><property name="blogDao" ref="blogDao" /></bean></beans>
在顶层<beans/>
元素属性上的default-init-method属性的存在导致Spring IoC容器识别一个名为init在 bean中的方法作为初始化方法回调。当bean被创建和组装时,如果bean类有这样的方法,它会在适当的时间被调用。
你可以使用顶层的<bean/>
元素上的default-destroy-method属性来类似地配置destroy方法回调(在XML中)。
如果现有的bean类已经具有与常规方式不同的回调方法,则可以使用init-method和destroy-method属性指定(在XML中)方法名称来覆盖默认值<bean/>
本身。
Spring容器保证在提供了所有依赖关系的bean之后立即调用配置的初始化回调。因此,对原始bean引用调用初始化回调,这意味着AOP拦截器等尚未应用于bean。目标bean是完全创建的首先,然后一个AOP代理(例如)应用它的拦截器链。如果目标bean和代理是分开定义的,你的代码甚至可以与原始目标bean交互,绕过代理。因此,将拦截器应用于init方法是不一致的,因为这样做会将目标bean的生命周期与其代理/拦截器耦合,并在代码直接与原始目标bean交互时留下奇怪的语义。
结合生命周期机制
从Spring 2.5开始,你有三个选项来控制bean生命周期行为 InitializingBean 和DisposableBean 回调接口; 自定义init()和destroy()方法; 和@PostConstruct和@PreDestroy注解。 你可以组合这些机制来控制给定的bean。
如果为bean配置了多个生命周期机制,并且每个机制都配置了不同的方法名称,则按照下面列出的顺序执行每个配置的方法。 但是,如果配置了相同的方法名,例如,对于多个这些生命周期机制,初始化方法的init(),则该方法将执行一次,如前一节所述.
为同一个bean配置的多个生命周期机制,具有不同的初始化方法,调用顺序如下:
- 使用@PostConstruct注解的方法
- InitializingBean回调接口定义的afterPropertiesSet()
- 自定义配置的init()方法
销毁方法以相同的顺序调用:
- 用@PreDestroy注解的方法
- destroy()由DisposableBean回调接口定义
- 自定义配置的destroy()方法
启动和关闭回调
Lifecycle接口定义了任何具有自己生命周期需求的对象的基本方法(例如启动和停止一些后台进程):
public interface Lifecycle {void start();void stop();boolean isRunning();}
任何Spring管理的对象可以实现该接口。 然后,当ApplicationContext本身接收到开始和停止信号时,例如, 对于在运行时的停止/重新启动情形,它将级联这些调用到在该上下文中定义的所有Lifecycle实现。 它通过委托给一个LifecycleProcessor来实现:
public interface LifecycleProcessor extends Lifecycle {void onRefresh();void onClose();}
注意,LifecycleProcessor本身是Lifecycle接口的扩展。 它还添加了两个其他方法来对正在刷新和关闭的上下文做出反应。
注意,org.springframework.context.Lifecycle接口定义规则只是明确了启动/停止通知的定义,并不意味着在上下文刷新时自动启动。 考虑实现org.springframework.context.SmartLifecycle而不是细粒度控制特定bean的自动启动(包括启动阶段)。 此外,请注意,停止通知不能保证在销毁之前:在正常关闭时,所有Lifecycle bean将首先在传播一般销毁回调之前收到停止通知; 然而,在上下文的生命周期中的热刷新或中止的刷新尝试,只有destroy方法将被调用。
启动和关闭调用的顺序很重要。 如果任何两个对象之间存在依赖关系,则依赖方会在依赖启动之后启动,会在依赖停止之前停止。 然而,有时直接依赖性是未知的。 你可能只知道某种类型的对象应该在另一种类型的对象之前开始。 在这些情况下,SmartLifecycle接口定义了另一个选项,即在它的超级接口Phased上定义的getPhase()方法。
public interface Phased {int getPhase();}public interface SmartLifecycle extends Lifecycle, Phased {boolean isAutoStartup();void stop(Runnable callback);}
启动时,具有最低层次的对象首先开始,并且在停止时,遵循相反的顺序。因此,实现SmartLifecycle并且其getPhase()方法返回Integer.MIN_VALUE的对象将首先开始,最后停止。若是返回了Integer.MAX_VALUE,那么该方法最后启动最先停止(因为该对象依赖其他bean才能运行)。关于phase的值,常规的并未实现SmartLifecycle接口的Lifecycle对象,其值默认为0。因此,负phase值表示要在常规Lifecycle对象之前启动(在常规Lifecycyle对象之后停止),使用 正值则恰恰相反。
正如你可以看到由SmartLifecycle定义的stop方法接受一个回调。任何实现必须在该实现的关闭过程完成后调用该回调的run()方法。这使得能够在必要时实现异步关闭,因为LifecycleProcessorinterfaceDefaultLifecycleProcessor的默认实现将等待到每个阶段内的对象组的超时值以调用该回调。默认每阶段超时为30秒。你可以通过在上下文中定义名为lifecycleProcessor的bean来覆盖默认生命周期处理器实例。如果只想修改超时,则定义以下内容就足够了:
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor"><!-- timeout value in milliseconds --><property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
如上所述,LifecycleProcessor接口定义了用于刷新和关闭上下文的回调方法。后者将简单地驱动关闭进程,就像显式地调用了stop()一样,但是当上下文关闭时,它会发生。另一方面,刷新回调启用了SmartLifecyclebean的另一个功能。当上下文被刷新时(在所有对象被实例化和初始化之后),该回调将被调用,并且在那时,默认生命周期处理器将检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值。如果true,那么该对象将在该点开始,而不是等待上下文或其自己的start()方法的显式调用(这和容器刷新不同,标准的容器实现启动不会自动发生)。 phase值以及任何depends-on关系将以与上述相同的方式来确定启动顺序。
在非Web应用程序中正常关闭Spring IoC容器
本节仅适用于非Web应用程序。 Spring的基于Web的ApplicationContext实现已经有代码在相关Web应用程序关闭时优雅地关闭Spring IoC容器。
如果你在非Web应用程序环境中使用Spring的IoC容器; 例如,在富客户端桌面环境中; 你使用JVM注册关闭挂接。 这样做可确保正常关闭并调用单元bean上的相关销毁方法,以便释放所有资源。 当然,你仍然必须正确配置和实现这些destroy回调。
要注册一个 shutdown hook,你需要调用在ConfigurableApplicationContext接口上声明的registerShutdownHook()方法:
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public final class Boot {public static void main(final String[] args) throws Exception {ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext(new String []{"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...}
}
3.6.2 ApplicationContextAware和BeanNameAware
当一个ApplicationContext创建一个实现org.springframework.context.ApplicationContextAware接口的对象实例时,该实例提供了对ApplicationContext的引用。
public interface ApplicationContextAware {void setApplicationContext(ApplicationContext applicationContext) throws BeansException;}
因此,bean可以通过ApplicationContext接口,通过编程方式操作创建它们的ApplicationContext,或者通过转换对这个接口的已知子类的引用,例如ConfigurableApplicationContext,这暴露了额外的功能。一个用途是其他bean的程序检索。有时这种能力是有用的;然而,通常你应该避免它,因为它将代码耦合到Spring,并不遵循控制反转风格,其中协作者作为属性提供给bean。 ApplicationContext的其他方法提供对文件资源的访问,发布应用程序事件和访问MessageSource。这些附加功能在第3.15节,“ApplicationContext的附加功能”
从Spring 2.5开始,autowiring是另一个替代方法来获取对ApplicationContext的引用。 “传统的”构造函数和byType的自动装配模式(如第3.4.5节“自动装配协作者”中所述)可以分别为构造函数参数或setter方法参数提供类型ApplicationContext的依赖。为了更灵活,包括自动连接字段和多个参数方法的能力,使用新的基于注解的自动装配功能。如果你这样做,ApplicationContext被自动装配到一个字段,构造函数参数或方法参数中,如果相关的字段,构造函数或方法带有@ Autowired注解,那么应该是ApplicationContext类型。有关详细信息,请参见第3.9.2节“@Autowired”。
当一个ApplicationContext创建一个实现org.springframework.beans.factory.BeanNameAware接口的类时,该类将提供对其关联对象定义中定义的名称的引用。
public interface BeanNameAware {void setBeanName(String name) throws BeansException;}
回调在普通bean属性的设置之后但在初始化回调之前被调用(本人注:其实这里我也有点懵,估计是在后面例如里的方法之前优先调用),例如InitializingBean afterPropertiesSet 或一个自定义init方法。
3.6.3 Other Aware interfaces
除了上面讨论的ApplicationContextAware和BeanNameAware,Spring提供了一系列Aware接口,允许bean向容器指示他们需要一个基础结构(SpringAPI)依赖。 最重要的Aware接口总结如下 - 作为一般规则,看名字就能知道依赖类型:
Table 3.4. Aware interfaces
名称 | 注入依赖 | 详情… |
---|---|---|
ApplicationContextAware | 声明 ApplicationContext | Section 3.6.2, “ApplicationContextAware and BeanNameAware” |
ApplicationEventPublisherAware | Event publisher of the enclosing ApplicationContext | Section 3.15, “Additional Capabilities of the ApplicationContext” |
BeanClassLoaderAware | 加载bean的类加载器 | Section 3.3.2, “Instantiating beans” |
BeanFactoryAware | Declaring BeanFactory | Section 3.6.2, “ApplicationContextAware and BeanNameAware” |
BeanNameAware | Name of the declaring bean | Section 3.6.2, “ApplicationContextAware and BeanNameAware” |
BootstrapContextAware | Resource adapter BootstrapContext the container runs in. Typically available only in JCA aware ApplicationContexts | Chapter 28, JCA CCI |
LoadTimeWeaverAware | Defined weaver for processing class definition at load time | Section 7.8.4, “Load-time weaving with AspectJ in the Spring Framework” |
MessageSourceAware | 配置的解决消息的策略(支持参数化和国际化) | Section 3.15, “Additional Capabilities of the ApplicationContext” |
NotificationPublisherAware | Spring JMX notification publisher | Section 27.7, “Notifications” |
ResourceLoaderAware | Configured loader for low-level access to resources | Chapter 4, Resources |
ServletConfigAware | Current ServletConfig the container runs in. Valid only in a web-aware Spring ApplicationContext | Chapter 18, Web MVC framework |
ServletContextAware | Current ServletContext the container runs in. Valid only in a web-aware Spring ApplicationContext | Chapter 18, Web MVC framework |
再次注意,这些接口的使用将您的代码绑定到Spring API,而不遵循控制反转风格。 因此,建议除非有需求的基础bean才使用编程式访问容器。
3.7 Spring Bean的继承
bean定义可以包含大量配置信息,包括构造函数参数,属性值和特定于容器的信息,例如初始化方法,静态工厂方法名称等。 子bean定义从父定义继承配置数据。 子定义可以根据需要覆盖一些值,或添加其他值。 使用父和子bean定义可以节省大量的输入。 这是模板的一种形式,讲究的就是效率。
如果你以编程方式使用一个ApplicationContext接口,子bean定义由ChildBeanDefinition类来表示。 大多数用户不在这个级别上使用它们,而是在类ClassPathXmlApplicationContext中声明性地配置bean定义。 当您使用基于XML的配置元数据时,您通过使用parent属性指定子bean定义,并指定父bean作为此属性的值。
<bean id="inheritedTestBean" abstract="true"class="org.springframework.beans.TestBean"><property name="name" value="parent"/><property name="age" value="1"/>
</bean><bean id="inheritsWithDifferentClass"class="org.springframework.beans.DerivedTestBean"parent="inheritedTestBean" init-method="initialize"><property name="name" value="override"/><!-- the age property value of 1 will be inherited from parent -->
</bean>
子bean定义使用父定义中的bean类(如果没有指定),但也可以覆盖它。 在后一种情况下,子bean类必须与父兼容,也就是说,它必须接受父的属性值。
子bean定义从父级继承 作用域,构造函数参数值,属性值和方法覆盖,并具有添加新值的选项。 您指定的任何作用域,初始化方法,销毁方法和/或static工厂方法设置将覆盖相应的父设置。
剩下的设置是 always 取自子定义:depends on, autowire模式,依赖性检查, singleton , lazy init 。
前面的示例通过使用abstract属性将父bean定义显式标记为抽象。 如果父定义没有指定类,则需要将父bean定义明确标记为`abstract’,如下所示:
<bean id="inheritedTestBeanWithoutClass" abstract="true"><property name="name" value="parent"/><property name="age" value="1"/>
</bean><bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"parent="inheritedTestBeanWithoutClass" init-method="initialize"><property name="name" value="override"/><!-- age will inherit the value of 1 from the parent bean definition-->
</bean>
父bean不能自己实例化,因为它是不完整的,它也被显式标记为“抽象”。 当定义是这样的“抽象”时,它只能用作纯模板bean定义,用来作为子定义的父定义。 试图使用这样的抽象父bean,通过引用它作为另一个bean的ref属性或使用父bean id执行显式的getBean()调用,返回一个错误。 同样,容器的内部preInstantiateSingletons()方法忽略定义为抽象的bean定义。
ApplicationContext默认实例化所有单例。 因此,重要的是(至少对于单例bean)如果你有一个(父)bean定义,你打算只使用作为一个模板,并且这个定义指定一个类,你必须确保设置 abstract 属性 为 true ,否则应用程序上下文将实际(尝试)预实例化abstract bean。
3.8 容器扩展点
通常,应用程序开发人员不需要子类化ApplicationContext实现类。 相反,Spring IoC容器可以通过插入特殊集成接口的实现来扩展。 接下来的几个部分描述这些集成接口。
3.8.1 使用BeanPostProcessor定制bean
BeanPostProcessor接口定义了回调方法,你可以实现它来提供你自己的(或重载容器的默认)实例化逻辑,依赖解析逻辑等等。如果你想在Spring容器完成实例化,配置和初始化bean之后实现一些自定义逻辑,可以插入一个或多个BeanPostProcessor实现。
你可以配置多个BeanPostProcessor实例,你可以通过设置order属性来控制这些BeanPostProcessor的执行顺序。只有当BeanPostProcessor实现了“Ordered”接口时,才可以设置此属性;如果你写自己的BeanPostProcessor,你应该考虑实现“Ordered”接口。有关更多详细信息,请参阅“BeanPostProcessor”和“有序”接口的javadoc。另见下面关于BeanPostProcessor的程序化注册的注解注册Beanpost处理器)。
BeanPostProcessor操作bean(或对象)实例; 也就是说,Spring IoC容器实例化一个bean实例,然后BeanPostProcessor做他们的工作.BeanPostProcessor的作用域是每个容器。 这仅在使用容器层次结构时才会用到这个。 如果你在一个容器中定义一个BeanPostProcessor,它将只对该容器中的bean进行后处理。 换句话说,在一个容器中定义的bean不会被另一个容器中定义的BeanPostProcessor进行后处理,即使两个容器都是同一层次结构的一部分。要改变实际的bean定义(即blueprint定义bean,译者注:应该是指各种配置元数据,比如xml、注解等),你需要使用一个BeanFactoryPostProcessor,如第3.8.2节“使用BeanFactoryPostProcessor定制配置元数据”。
org.springframework.beans.factory.config.BeanPostProcessor接口恰好包含两个回调方法。当这样的类在容器内注册为post-processor,对于由容器创建的每个bean实例,容器创建所有bean,在容器初始化方法(比如InitializingBean的afterProperieSet()方法和其他所有的声明的init方法)和所有bean 初始化回调之前,运行post-processor回调。post-processor可以对bean实例采取任何操作,包括完全忽略回调。 bean post-processor通常检查回调接口或者可以用代理包装bean。一些Spring AOP基础结构类被实现为bean post-processor,以便提供代理包装逻辑。
一个ApplicationContext 自动检测在配置元数据中定义的实现BeanPostProcessor接口的任何bean。 ApplicationContext将这些bean注册为后处理器,以便稍后在创建bean时调用它们。 Bean后处理器可以像任何其他bean一样部署在容器中。
注意,当在配置类上使用@Bean工厂方法声明BeanPostProcessor时,工厂方法的返回类型应该是实现类本身或至少是org.springframework.beans.factory.config.BeanPostProcessor接口,清楚地表明该bean的后处理器性质。否则,ApplicationContext将不能在完全创建它之前通过类型自动检测它。因为BeanPostProcessor需要尽早的实例化,以便应用于上下文中其他bean的初始化,所以这种尽早的类型检测是至关重要的。
虽然BeanPostProcessor注册的推荐方法是通过ApplicationContext自动检测(如上所述),但是也可以使用addBeanPostProcessor方法在ConfigurableBeanFactory上注册它们。 当需要在注册之前评估条件逻辑,或者甚至在层次结构中跨上下文复制bean post processors时,这可能是有用的。 注意,BeanPostProcessors以编程方式添加不遵守Ordered接口。注册的顺序就是执行的次序。 还要注意,“BeanPostProcessor”以编程方式注册,总是在通过自动检测注册之前处理,而不管任何显式排序。
实现BeanPostProcessor接口的类是special的,并且容器的处理方式不同。所有BeanPostProcessor 和他们直接引用的bean在启动时被实例化,作为ApplicationContext的特殊启动阶段的一部分。接下来,所有的BeanPostProcessors都会按照次序注册到容器中,在其他bean使用BeanPostProcessors处理时也会使用此顺序。因为AOP的auto-proxying自动代理是BeanPostProcessor的默认实现,它既不引用BeanPostProcessors也不引用其他bean,不会发生auto-proxying自动代理,因此不会有切面织入。对于任何这样的bean,你应该看到一个信息日志消息:“Bean foo不能由所有的BeanPostProcessor接口处理(例如:不符合自动代理)”注意,如果你有bean连接到你的BeanPostProcessor使用autowiring或@Resource(可能回退到自动装配),Spring可能会在搜索类型匹配依赖关系候选时访问意外的bean,因此使它们不适合自动代理或他们是其他种类的bean post-processing。例如,如果你有一个使用@Resource注解的依赖,其中field/ setter名称不直接对应于bean的声明名称,并且没有使用name属性,那么Spring将访问其他bean以按类型匹配它们。
以下示例显示如何在ApplicationContext中写入,注册和使用BeanPostProcessor。
Example: Hello World, BeanPostProcessor-style
第一个例子说明了基本用法。 该示例显示了一个自定义的BeanPostProcessor实现,它调用每个bean的toString()方法,因为它是由容器创建的,并将结果字符串打印到系统控制台。
下面找到自定义的“BeanPostProcessor”实现类定义:
package scripting;import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.BeansException;public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {// simply return the instantiated bean as-ispublic Object postProcessBeforeInitialization(Object bean,String beanName) throws BeansException {return bean; // we could potentially return any object reference here...}public Object postProcessAfterInitialization(Object bean,String beanName) throws BeansException {System.out.println("Bean '" + beanName + "' created : " + bean.toString());return bean;}}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:lang="http://www.springframework.org/schema/lang"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/langhttp://www.springframework.org/schema/lang/spring-lang.xsd"><lang:groovy id="messenger"script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy"><lang:property name="message" value="Fiona Apple Is Just So Dreamy."/></lang:groovy><!--when the above bean (messenger) is instantiated, this customBeanPostProcessor implementation will output the fact to the system console--><bean class="scripting.InstantiationTracingBeanPostProcessor"/></beans>
注意InstantiationTracingBeanPostProcessor是如何定义的。 它甚至没有名称,并且因为它是一个bean,它可以像任何其他bean一样依赖注入。 (前面的配置也定义了一个由Groovy脚本支持的bean。Spring动态语言支持在标题为第31章,动态语言支持.)
以下简单的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
示例:RequiredAnnotationBeanPostProcessor
使用回调接口或注解结合自定义的“BeanPostProcessor”实现是扩展Spring IoC容器的常见手段。。例如Spring的RequiredAnnotationBeanPostProcessor,是个BeanPostProcessor实现类,spring内置,作用是确保Spring bean定义上的带注解的JavaBean属性确实被注入了值。
3.8.2 使用BeanFactoryPostProcessor定制配置元数据
我们将要看到的下一个扩展点是“org.springframework.beans.factory.config.BeanFactoryPostProcessor”。这个接口的语义类似于BeanPostProcessor,其中一个主要区别是:BeanFactoryPostProcessor对 bean配置元数据操作;也就是说,Spring IoC容器允许BeanFactoryPostProcessor读取配置元数据,并可能在容器实例化(除BeanFactoryPostProcessor之外的任何)bean之前改变它。
你可以配置多个BeanFactoryPostProcessors,你可以通过设置orderproperty来控制这些BeanFactoryPostProcessor的执行顺序。但是,如果BeanFactoryPostProcessor实现了 Ordered接口,则只能设置此属性。如果你写自己的“BeanFactoryPostProcessor”,你应该考虑实现 Ordered接口。有关更多详细信息,请参阅BeanFactoryPostProcessor和 Ordered接口的javadoc。
如果你想改变实际的bean 实例(即从配置元数据创建的对象),那么你需要使用一个BeanPostProcessor(如上所述第3.8.1节“ BeanPostProcessor“)。虽然在技术上可以使用BeanFactoryPostProcessor(例如,使用BeanFactory.getBean())中的bean实例,这样做会导致提前的bean实例化,违反标准容器生命周期。这可能导致负面的副作用,如绕过bean后处理。此外,BeanFactoryPostProcessors的作用域也是在各自的容器内。这仅在使用容器层次结构时才相关。如果你在一个容器中定义一个BeanFactoryPostProcessor,它只会应用于该容器中的bean定义。一个容器中的Bean定义不会被另一个容器中的BeanFactoryPostProcessor进行后置处理,即使这两个容器都是同一层次结构的一部分。
当在“ApplicationContext”中声明一个bean工厂后置处理器时,它会自动执行,以便将定义容器的配置元数据进行的更改应用。 Spring包括一些预定义的bean工厂后置处理器,例如PropertyOverrideConfigurer和PropertyPlaceholderConfigurer。 例如,定制的BeanFactoryPostProcessor也可以用来注册自定义属性编辑器。
一个ApplicationContext自动检测到它里面实现BeanFactoryPostProcessor接口的任何bean并部署。 它使用这些bean作为bean工厂后置处理器,在适当的时间。 你可以像部署其他bean一样部署这些后置处理器bean。(即ApplicationContext自动探测BeanFactoryPostProcessor接口的实现类。容器使用这些bean作为bean工厂post-processors。可以像其他bean那样将post-processor部署在容器内。)
和BeanPostProcessors一样,你通常不想为延迟初始化配置BeanFactoryPostProcessors。 如果没有其他bean引用Bean(Factory)PostProcessor,后置处理器将不会被实例化。 因此,标记它的延迟初始化将被忽略,并且Bean(Factory)PostProcessor将被及时实例化,即使你在元素的声明上将default-lazy-init属性设置为 true 。也不行
Example: the Class name substitution PropertyPlaceholderConfigurer
你使用PropertyPlaceholderConfigurer将bean的属性值使用标准的Java Properties格式定义在一个单独的文件中。这样可以将应用的自定义环境配置属性隔离出来,例如数据库URL和密码,而不需要修改容器的主XML定义文件或Java 代码文件的复杂性或风险。
考虑以下基于XML的配置元数据片段,其中定义了具有占位符值的“DataSource”。 该示例显示从外部Properties文件配置的属性。 在运行时,将一个PropertyPlaceholderConfigurer应用于将替换DataSource的某些属性的元数据。 要替换的值被指定为遵循Ant / log4j / JSP EL样式的${property-name}形式的 placeholder 。
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"><property name="locations" value="classpath:com/foo/jdbc.properties"/>
</bean><bean id="dataSource" destroy-method="close"class="org.apache.commons.dbcp.BasicDataSource"><property name="driverClassName" value="${jdbc.driverClassName}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/>
</bean>
在标准java Properties格式文件中实际的值:
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
因此,字符串${jdbc.username}在运行时被替换为值’sa’,这同样适用于与属性文件中的键匹配的其他占位符值。 PropertyPlaceholderConfigurer检查bean定义的大多数属性和属性中的占位符。 此外,可以自定义占位符前缀和后缀。
使用Spring 2.5中引入的context命名空间,可以使用专用配置元素配置属性占位符。 一个或多个位置可以在location属性中以逗号分隔的列表形式提供。
<context:property-placeholder location="classpath:com/foo/jdbc.properties"/>
PropertyPlaceholderConfigurer不仅在你指定的Properties文件中寻找属性。 默认情况下,如果在指定的属性文件中找不到属性,它还会检查Java“System”属性。 你可以通过将configurer的systemPropertiesMode属性设置为以下三个支持的整数值之一来定制此行为:
- never * (0):不检查系统属性
- fallback * (1):如果在指定的属性文件中不可解析,请检查系统属性。 这是默认值。
- override * (2) :首先检查系统属性,然后尝试指定的属性文件。 这允许系统属性覆盖任何其他属性源。
有关更多信息,请参阅PropertyPlaceholderConfigurerjavadocs。
[Tip]
你可以使用PropertyPlaceholderConfigurer来替换类名,当你必须在运行时选择一个特定的实现类时,这是有用的。 例如:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"><property name="locations"><value>classpath:com/foo/strategy.properties</value></property><property name="properties"><value>custom.strategy.class=com.foo.DefaultStrategy</value></property>
</bean><bean id="serviceStrategy" class="${custom.strategy.class}"/>
若类在运行时期间不能解析为合法类,ApplicationContext创建非延迟初始化bean的preInstantiateSingletons()期间抛错误
Example: the PropertyOverrideConfigurer
“PropertyOverrideConfigurer”是另一个bean工厂后置处理器,类似于PropertyPlaceholderConfigurer,但是与后者不同,原始定义可以设置默认值或者根本不设置值。 如果重写的“Properties”文件没有特定bean属性的条目,那么就使用默认的上下文定义。
注意,bean定义并不知道它会被重写,所以使用了重写配置在XML配置中并不直观。 在多个PropertyOverrideConfigurer实例为同一个bean属性定义不同的值的情况下,最后一个实例配置会生效。
Properties文件配置格式如下:
beanName.property=value
例如:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
此示例文件可用于包含名为 dataSource 的bean的容器定义,该bean具有 driver 和 url 属性。
还支持复合属性名称,只要路径(除了覆盖的最终属性)的每个组件都已为非空(可能由构造函数初始化)。 在此示例中…
foo.fred.bob.sammy=123
foo bean的fred属性的bob属性的sammy属性设置为标量值123。
指定的重写值都是字面值; 它们不会转换为bean引用。 当XML bean定义中的原始值指定一个bean引用时,此约定也适用。
使用Spring 2.5中引入的context命名空间,可以使用专用配置元素配置属性覆盖:
<context:property-override location="classpath:override.properties"/>
3.8.3 使用FactoryBean定制实例化逻辑
对象实现org.springframework.beans.factory.FactoryBean接口,则成为它本身的工厂。
FactoryBean接口是Spring IoC容器实例化逻辑的扩展点。假如初始化代码非常复杂,此时使用java编码比使用XML配置更容易表达。这种场景中,你可以创建自己的FactoryBean,在该类中编写复杂的初始化程序,然后将你的自定义FactoryBean插入到容器。
FactoryBean接口提供了三种方法:
- Object getObject():返回此工厂创建的对象的实例。实例可以共享,这取决于这个工厂是返回单例还是原型。
- boolean isSingleton():如果这个“FactoryBean”返回单例,则返回true,否则返回false。
- 类getObjectType():返回由getObject()方法或null返回的对象类型,如果类型不是预先知道的。
FactoryBean概念和接口在Spring框架中的许多地方使用; Spring内置的有超过50个实现。
当你需要向容器请求一个实际的FactoryBean实例本身而不是它生成的bean时,在调用ApplicationContext的getBean()方法时,用符号(&)作为前缀。所以对于给定的FactoryBean,id为myBean,在容器上调用getBean(“myBean”)会返回FactoryBean所产生的bean;而调用getBean(“&myBean”)返回FactoryBean实例本身。
Spring Framework 5.0.0.M4中文文档第3章相关推荐
- Android 3.0 r1 API中文文档(107) —— AsyncPlayer
一.结构 public class AsyncPlayer extends Object java.lang.Object android.media.AsyncPlayer 二.概述 播放一个连续( ...
- Solidity 最新 0.5.8 中文文档发布
本文首发于深入浅出区块链社区 热烈祝贺 Solidity 最新 0.5.8 中文文档发布, 这不单是一份 Solidity 速查手册,更是一份深入以太坊智能合约开发宝典. 翻译说明 Solidity ...
- ASP.NET Core 中文文档 第三章 原理(5)错误处理
原文:Error Handling 作者:Steve Smith 翻译:谢炀(Kiler) 校对:高嵩(jack2gs).何镇汐 当你的ASP.NET应用发生错误的时候, 你可以采用本文所述的各种方法 ...
- ASP.NET Core 中文文档 第三章 原理(3)静态文件处理
原文:Working with Static Files 作者:Rick Anderson 翻译:刘怡(AlexLEWIS) 校对:谢炀(kiler398).许登洋(Seay).孟帅洋(书缘) 静态文 ...
- ASP.NET Core 中文文档 第四章 MVC(2.2)模型验证
原文:Model Validation 作者:Rachel Appel 翻译:娄宇(Lyrics) 校对:孟帅洋(书缘) 在这篇文章中: 章节: 介绍模型验证 验证 Attribute 模型状态 处理 ...
- Android 3.0 r1 API中文文档(113) ——SlidingDrawer
正文 一.结构 public class SlidingDrawer extends ViewGroup java.lang.Object android.view.View android.view ...
- Android 3.0 r1 API中文文档(108) —— ExpandableListAdapter
正文 一.结构 public interface ExpandableListAdapter android.widget.ExpandableListAdapter 间接子类 BaseExpanda ...
- 【预留】Apache Doris 0.12 官方中文文档学习
https://www.bookstack.cn/read/ApacheDoris-0.12-zh/8376e91fcde1d3d6.md 预留:后续实际操作并记录
- ASP.NET Core 中文文档 第三章 原理(13)管理应用程序状态
原文:Managing Application State 作者:Steve Smith 翻译:姚阿勇(Dr.Yao) 校对:高嵩 在 ASP.NET Core 中,有多种途径可以对应用程序的状态进行 ...
最新文章
- 【转】揭开正则表达式的神秘面纱
- C语音和易语言实现内存补丁
- mysql 5.7和8.0区别_前沿观察 | MySQL性能基准测试对比:5.7 VS 8.0
- 【转】Azure 命令行工具大混战,都是什么,该选哪个?
- Fiddler工具杂记-将某些数据收集起来并且发送HTTP数据包
- Web开发中实用小工具
- sql server 2005中的except和INTERSECT运算
- 语音转文字的软件APP
- 来来来!docker-composeup很慢
- git解决 remote: Permission to wuheng1991/site-manager.git denied to XXX
- 计算机是概念化不是程序化,走进计算思维
- Alpha Fold 2
- 80老翁谈人生(314):别了,亲爱的CSDN读者朋友们!
- 目标检测之Faster RCNN分析
- switch调函数 vue_vue3中轻松实现switch功能组件的全过程
- 游戏评测HTML5网站模板是一款适合游戏视频 游戏评测 游戏介绍网站模板。
- PHP 7.0+新特性
- Element Select选择器从服务器搜索数据,输入关键字进行查找(从服务器获得的数据有分页)
- 文件操作,函数练习及答案
- 基于python和tkinter实现的随机点名程序
热门文章
- 2018年科大讯飞校招内推提前批面试总结
- 层次分析法(多准则决策方法)
- python mysql导出到excel_python:mysql导出数据到excel工具方法
- python实战-03-币安量化机器人API接入(更新中)
- 大一学生关心的问题(二)
- 【软购商城】Splashtop Business Access 屏幕远程控制软件 1年订阅 仅需 398 主控端设备不限,授权随账户,被控端数量屈居于购买的数量。
- css代码写3D盒子动画
- 基于JAVA的高校教务管理系统
- 南通大学计算机专业怎么样啊,南通大学优势专业排名,2021年南通大学最好的专业排名...
- 关于Pytorch中dataset的迭代问题(这就是为什么我们要使用dataloader的原因之一)