天天看点

Spring| 3.8 容器扩展的点

3.8 容器扩展的点

通常,应用程序开发人员不需要子类化ApplicationContext实现类。相反,Spring IoC容器可以通过插入特殊集成接口的实现来扩展。接下来的几节将描述这些集成接口。

3.8.1 使用BeanPostProcessor定制bean

BeanPostProcessor接口定义了回调方法,你可以实现这些方法来提供你自己的(或覆盖容器的默认)实例化逻辑、依赖解析逻辑等等。如果希望在Spring容器实例化、配置和初始化bean之后实现一些定制逻辑,可以插入一个或多个BeanPostProcessor实现。

你可以配置多个BeanPostProcessor实例,并且可以通过设置order属性来控制这些BeanPostProcessor的执行顺序。只有当BeanPostProcessor实现Ordered接口时,才可以设置此属性;如果你编写自己的BeanPostProcessor,也应该考虑实现Ordered接口。要了解更多细节,请参考BeanPostProcessor和Ordered接口的javadocs。请参阅下面关于beanpostprocessor编程注册的说明。

注意:BeanPostProcessor操作bean(或对象)实例;也就是说,Spring IoC容器实例化一个bean实例,然后BeanPostProcessor执行它们的工作。

BeanPostProcessor的作用域是每个容器。这只在使用容器层次结构时才相关。如果在一个容器中定义一个BeanPostProcessor,那么它将只在后处理该容器中的bean。换句话说,在一个容器中定义的bean不会由在另一个容器中定义的BeanPostProcessor进行后处理,即使两个容器都是相同层次结构的一部分。

更改实际的bean定义(例如:定义bean的蓝图),你需要使用BeanFactoryPostProcessor类,描述在第3.8.2节使用BeanFactoryPostProcessor定制配置元数据”。

org.springframework.beans.factory.config.BeanPostProcessor接口恰好由两个回调方法组成。当这样一个类注册为后处理器的容器,每个容器创建bean实例,后处理器从容器之前得到一个回调容器初始化方法(如InitializingBean afterPropertiesSet()和任何声明的init方法)也被称为任何bean初始化后回调。后处理器可以对bean实例执行任何操作,包括完全忽略回调。bean后处理器通常检查回调接口,或者使用代理包装bean。为了提供代理包装逻辑,一些Spring AOP基础设施类被实现为bean后处理器。

ApplicationContext自动检测在实现BeanPostProcessor接口的配置元数据中定义的任何bean。ApplicationContext将这些bean注册为后处理程序,以便稍后在创建bean时调用它们。Bean后处理器可以像任何其他Bean一样部署在容器中。

注意,当在配置类上使用@Bean工厂方法声明BeanPostProcessor时,工厂方法的返回类型应该是实现类本身,或者至少是org.springframework.beans.factory.config.BeanPostProcessor接口,清楚地指示该bean的后处理器特性。否则,ApplicationContext将无法在完全创建它之前按类型自动检测它。由于为了应用于上下文中其他bean的初始化,需要尽早实例化BeanPostProcessor,所以这种早期类型检测非常重要。

注意:虽然推荐的BeanPostProcessor注册方法是通过ApplicationContext自动检测(如上所述),但是也可以使用addBeanPostProcessor方法以编程方式针对ConfigurableBeanFactory注册它们。当需要在注册前计算条件逻辑时,甚至在层次结构的上下文中复制bean post处理器时,这都是非常有用的。但是请注意,以编程方式添加的beanpostprocessor并不遵循Ordered接口。这里,注册的顺序决定了执行的顺序。还要注意,以编程方式注册的beanpostprocessor总是在通过自动检测注册的处理器之前处理,而不考虑任何显式的顺序。

注意:实现BeanPostProcessor接口的类是特殊的,容器以不同的方式对待它们。它们直接引用的所有beanpostprocessor和bean都在启动时实例化,作为ApplicationContext的特殊启动阶段的一部分。接下来,以排序的方式注册所有beanpostprocessor,并应用于容器中的所有后续bean。因为AOP自动代理是作为一个BeanPostProcessor本身实现的,所以无论是BeanPostProcessor还是它们直接引用的bean都没有资格进行自动代理,因此没有将方面编织到它们之中。

注意:对于任何这样的bean,你应该看到一条信息日志消息:“bean foo不适合被所有BeanPostProcessor接口处理(例如:不适合自动代理)”。

请注意,如果你使用自动注入或@Resource将bean注入到BeanPostProcessor中(这可能会回到autowiring), Spring可能在搜索类型匹配依赖项候选项时访问意外的bean,因此使它们没有资格进行自动代理或其他类型的bean后处理。例如,如果你有一个带有@Resource注解的依赖项,其中字段/setter名称不直接对应于bean的声明名称,并且没有使用name属性,那么Spring将访问其他bean,以便按类型匹配它们。

下面的示例展示了如何在ApplicationContext中编写、注册和使用Beanpostprocessor。

实例: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-is
	public 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/beans
		http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/lang
		http://www.springframework.org/schema/lang/spring-lang.xsd">

	<lang:groovy id="messenger"
			script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
		<lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
	</lang:groovy>

	<!--
	when the above bean (messenger) is instantiated, this custom
	BeanPostProcessor implementation will output the fact to the system console
	-->
	<bean class="scripting.InstantiationTracingBeanPostProcessor"/>

</beans>
           

注意,InstantiationTracingBeanPostProcessor是如何简单定义的。它甚至没有名称,因为它是一个bean,所以可以像其他bean一样依赖注入。(前面的配置还定义了一个由Groovy脚本支持的bean。Spring动态语言支持在第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——一个随Spring发行版一起发布的BeanPostProcessor实现,它确保用(任意)注解标记的bean上的JavaBean属性实际上(配置为)依赖于注入一个值。

3.8.2 使用BeanFactoryPostProcessor自定义配置元数据

我们将查看的下一个扩展点是org.springframework.beans.factory.config.BeanFactoryPostProcessor。这个接口的语义与BeanPostProcessor的语义相似,但有一个主要区别:BeanFactoryPostProcessor操作bean配置元数据;也就是说,Spring IoC容器允许BeanFactoryPostProcessor读取配置元数据,并可能在容器实例化除BeanFactoryPostProcessor之外的任何bean之前更改它。

你可以配置多个beanfactorypostprocessor,并且可以通过设置order属性来控制这些beanfactorypostprocessor的执行顺序。但是,只有当BeanFactoryPostProcessor实现Ordered接口时,才能设置此属性。如果你编写自己的BeanFactoryPostProcessor,也应该考虑实现Ordered接口。有关更多细节,请参考BeanFactoryPostProcessor和Ordered接口的javadocs。

注意:如果你想要更改实际的bean实例(例如,根据配置元数据创建的对象),然后你需要使用BeanPostProcessor(上面在3.8.1节“使用BeanPostProcessor定制bean”中描述过)。虽然在技术上可以在BeanFactoryPostProcessor中处理bean实例(例如,使用BeanFactory.getBean()),但是这样做会导致bean实例化过早,违反标准容器生命周期。这可能会导致负面的副作用,比如绕过bean的后处理。

此外,beanfactorypostprocessor的作用域是针对每个容器的。这只在使用容器层次结构时才相关。如果你在一个容器中定义一个BeanFactoryPostProcessor,那么它将只应用于该容器中的bean定义。一个容器中的Bean定义不会由另一个容器中的beanfactorypostprocessor进行后处理,即使两个容器都是同一层次结构的一部分。

当在ApplicationContext中声明bean工厂后处理器时,它将自动执行,以便将更改应用于定义容器的配置元数据。Spring包含许多预定义的bean工厂后处理程序,比如PropertyOverrideConfigurer和PropertyPlaceholderConfigurer。例如,还可以使用自定义BeanFactoryPostProcessor来注册自定义属性编辑器。

ApplicationContext自动检测部署到其中实现BeanFactoryPostProcessor接口的任何bean。它在适当的时候使用这些bean作为bean工厂的后处理程序。你可以像部署任何其他bean一样部署这些后处理器bean。

注意:与使用Beanpostprocessors一样,你通常不希望配置Beanfactorypostprocessors来进行延迟初始化。如果没有其他bean引用bean(工厂)后处理器,则根本不会实例化该后处理器。因此,将其标记为延迟初始化将被忽略,即使在 < beans />元素的声明中将default-lazy-init属性设置为true, Bean(工厂)后处理器也将被急切地实例化。

实例:类名替换PropertyPlaceholderConfigurer

使用PropertyPlaceholderConfigurer将属性值从bean定义外部化到使用标准Java属性格式的单独文件中。这样做使部署应用程序的人员能够定制特定于环境的属性,如数据库url和密码,而不需要承担修改主XML定义文件或容器文件的复杂性或风险。

考虑以下基于xml的配置元数据片段,其中定义了具有占位符值的数据源。该示例显示了从外部属性文件配置的属性。在运行时,PropertyPlaceholderConfigurer应用于元数据,元数据将替换数据源的一些属性。要替换的值指定为表单${property-name}的占位符,该表单遵循Ant / log4j / JSP EL样式。

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
	<property name="locations" value="classpath:com/foo/jdbc.properties"/>
</bean>

<bean id="dataSource" destroy-method="close"
		class="org.apache.commons.dbcp.BasicDataSource">
	<property name="driverClassName" value="${jdbc.driverClassName}"/>
	<property name="url" value="${jdbc.url}"/>
	<property name="username" value="${jdbc.username}"/>
	<property name="password" value="${jdbc.password}"/>
</bean>
           

实际值来自另一个标准Java属性格式的文件:

jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
           

因此,字符串${jdbc.username}在运行时被替换为值“sa”,与属性文件中的键匹配的其他占位符值也一样。PropertyPlaceholderConfigurer检查bean定义的大多数属性和属性中的占位符。此外,还可以定制占位符前缀和后缀。

使用Spring 2.5中引入的上下文命名空间,可以使用专用的配置元素配置属性占位符。可以在location属性中以逗号分隔的列表的形式提供一个或多个位置。

PropertyPlaceholderConfigurer不仅在你指定的属性文件中查找属性。默认情况下,如果不能在指定的属性文件中找到属性,它还会检查Java系统属性。你可以使用以下三个受支持的整数值之一来设置配置程序的systemPropertiesMode属性,从而自定义此行为:

  • never(0):从不检查系统属性
  • fallback (1):如果在指定的属性文件中无法解析,则检查系统属性。这是默认值。
  • override (2):在尝试指定的属性文件之前,先检查系统属性。这允许系统属性覆盖任何其他属性源。

有关更多信息,请咨询PropertyPlaceholderConfigurer javadocs。

注意:你可以使用PropertyPlaceholderConfigurer来替换类名,当你必须在运行时选择特定的实现类时,这有时很有用。例如:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
	<property name="locations">
		<value>classpath:com/foo/strategy.properties</value>
	</property>
	<property name="properties">
		<value>custom.strategy.class=com.foo.DefaultStrategy</value>
	</property>
</bean>

<bean id="serviceStrategy" class="${custom.strategy.class}"/>
           
如果在运行时不能将类解析为有效的类,则在即将创建bean时,即在非惰性初始化bean的ApplicationContext的preInstantiateSingletons()阶段,bean的解析将失败。

例子:PropertyOverrideConfigurer

另一个bean工厂后处理器PropertyOverrideConfigurer类似于PropertyPlaceholderConfigurer,但与后者不同,原始定义可以具有bean属性的默认值,也可以完全没有值。如果覆盖属性文件没有特定bean属性的条目,则使用缺省上下文定义。

注意,bean定义不知道被覆盖,所以从XML定义文件中不能立即看出使用了覆盖配置程序。如果多个PropertyOverrideConfigurer实例为同一个bean属性定义了不同的值,由于覆盖机制,最后一个实例胜出。

属性文件配置行采用这种格式:

beanName.property=value
           

例如:

dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
           

此示例文件可以与容器定义一起使用,容器定义包含一个名为dataSource的bean,该bean具有drive和url属性。

还支持复合属性名,只要路径的每个组件(被覆盖的最终属性除外)都是非空的(假设由构造函数初始化)。在这个例子中

foo.fred.bob.sammy=123
           

foo bean的bob属性和fred属性的sammy属性被设置为标量值123。

注意:指定的覆盖值总是文字值;它们不会被转换成bean引用。当XML bean定义中的原始值指定bean引用时,此约定也适用。

使用Spring 2.5中引入的上下文命名空间,可以使用专用的配置元素配置覆盖属性:

3.8.3 使用FactoryBean定制实例化逻辑

实现org.springframework.beans.factory.FactoryBean接口用于本身就是工厂的对象。

FactoryBean接口是一个可插入Spring IoC容器实例化逻辑的点。如果你有复杂的初始化代码,相对于(潜在的)冗长的XML,用Java更好地表达,那么你可以创建自己的FactoryBean,在该类中编写复杂的初始化,然后将定制的FactoryBean插入容器中。

FactoryBean接口提供了三种方法:

  • Object getObject():返回工厂创建的对象的实例。实例可以共享,这取决于该工厂返回的是单例还是原型。
  • boolean isSingleton():如果这个FactoryBean返回单例,则返回true,否则返回false。
  • Class getObjectType():返回getObject()方法返回的对象类型,如果类型事先不知道,则返回null。

FactoryBean概念和接口在Spring框架中的许多地方都使用;FactoryBean接口的50多个实现附带Spring本身。

当你需要向容器请求实际的FactoryBean实例本身而不是它生成的bean时,在调用ApplicationContext的getBean()方法时,在bean的id前面加上&符号。因此,对于id为myBean的给定FactoryBean,在容器上调用getBean(“myBean”)返回FactoryBean内的对象;然而,调用getBean(“&myBean”)返回FactoryBean实例本身。

继续阅读