Spring IoC容器:容器扩展点(Container Extension Points)

https://docs.spring.io/spring-framework/reference/core/beans/factory-extension.html

通常,应用程序开发人员不需要对ApplicationContext实现类进行子类化。相反,可以通过插入特殊集成接口的实现来扩展Spring IoC容器。

使用BeanPostProcessor自定义bean

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

你可以配置多个BeanPostProcessor实例,并且可以通过设置order属性来控制这些BeanPostProcessor实例的运行顺序。只有当BeanPostProcessor实现了Ordered接口时,你才能设置这个属性。如果你编写自己的BeanPostProcessor,也应该考虑实现Ordered接口。

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

BeanPostProcessor实例是每个容器范围内的。这只在使用容器层次结构时才相关。如果你在一个容器中定义了一个BeanPostProcessor,它只会后处理那个容器中的bean。换句话说,即使在相同的层次结构中,一个容器中定义的bean也不会被另一个容器中定义的BeanPostProcessor后处理。

要更改实际的bean定义(即定义bean的模板),你需要使用BeanFactoryPostProcessor

org.springframework.beans.factory.config.BeanPostProcessor接口正好包含两个回调方法。当这样一个类作为后处理器在容器中注册时,对于容器创建的每一个bean实例,后处理器都会在容器初始化方法(如InitializingBean.afterPropertiesSet()或任何声明的init方法)被调用之前和之后从容器获得回调。后处理器可以对bean实例采取任何操作,包括完全忽略回调。一个bean后处理器通常会检查回调接口,或者它可能会用代理包装一个bean。一些Spring AOP基础设施类就是作为bean后处理器实现的,以便提供代理包装逻辑。

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后处理器时,这可能会很有用。然而,请注意,以编程方式添加的BeanPostProcessor实例不会遵循Ordered接口指定的顺序。在这里,执行的顺序由注册的顺序决定。还要注意的是,无论是否有任何显式排序,以编程方式注册的BeanPostProcessor实例总是会在通过自动检测注册的实例之前处理。

实现BeanPostProcessor接口的类是特殊的,并且会被容器以不同的方式处理。所有的BeanPostProcessor实例及其直接引用的bean都会在启动时实例化,作为ApplicationContext特殊启动阶段的一部分。接下来,所有的BeanPostProcessor实例会以排序的方式注册,并应用到容器中所有后续的bean。因为AOP自动代理是通过BeanPostProcessor本身实现的,所以BeanPostProcessor实例及其直接引用的bean都不适合自动代理,因此不会有切面编织到它们中。

对于任何这样的bean,你应该会看到一条信息性日志消息:Bean someBean不符合由所有BeanPostProcessor接口处理的资格(例如:不符合自动代理的资格)。

如果你使用自动装配或@Resource(可能会回退到自动装配)将bean注入到你的BeanPostProcessor中,Spring在搜索类型匹配的依赖候选时可能会访问到意外的bean,因此,使它们不符合自动代理或其它类型的bean后处理的资格。例如,如果你有一个用@Resource注解的依赖,其中字段或setter的名称与bean的声明名称不直接对应,且没有使用name属性,Spring会通过类型匹配来访问其它bean。

以下示例展示了如何在ApplicationContext中编写、注册和使用BeanPostProcessor实例。

示例:Hello World,BeanPostProcessor风格

这个例子展示了基本的用法。示例中展示了一个自定义的BeanPostProcessor实现,它在容器创建每个bean时调用其toString()方法,并将结果字符串打印到系统控制台。

下面的列表显示了自定义BeanPostProcessor实现类的定义:

package scripting;

import org.springframework.beans.factory.config.BeanPostProcessor;

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

	// simply return the instantiated bean as-is
	public Object postProcessBeforeInitialization(Object bean, String beanName) {
		return bean; // we could potentially return any object reference here...
	}

	public Object postProcessAfterInitialization(Object bean, String beanName) {
		System.out.println("Bean '" + beanName + "' created : " + bean.toString());
		return bean;
	}
}

下面的beans元素使用了InstantiationTracingBeanPostProcessor

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:lang="http://www.springframework.org/schema/lang"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/lang
		https://www.springframework.org/schema/lang/spring-lang.xsd">

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

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

</beans>

注意InstantiationTracingBeanPostProcessor仅仅是如何定义的。它甚至没有一个名字,由于它是一个bean,它可以像其它bean一样进行依赖注入。(上面的配置还定义了一个由Groovy脚本支持的bean。)

下面的Java应用程序运行前面的代码和配置:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;

public final class Boot {

	public static void main(final String[] args) throws Exception {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
		Messenger messenger = ctx.getBean("messenger", Messenger.class);
		System.out.println(messenger);
	}

}

前面应用程序的输出类似于以下内容:

Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961

示例:AutowiredAnnotationBeanPostProcessor

使用回调接口或注解与自定义BeanPostProcessor实现结合是扩展Spring IoC容器的常见手段。一个例子是Spring的AutowiredAnnotationBeanPostProcessor —— 一个随Spring分发提供的BeanPostProcessor实现,它自动装配带注解的字段、setter方法和任意的配置方法。

BeanFactoryPostProcessor自定义配置元数据

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

你可以配置多个BeanFactoryPostProcessor实例,并且可以通过设置order属性来控制这些BeanFactoryPostProcessor实例的运行顺序。但是,只有当BeanFactoryPostProcessor实现了Ordered接口时,你才能设置这个属性。如果你编写自己的BeanFactoryPostProcessor,也应该考虑实现Ordered接口。

如果你想要更改实际的bean实例(即从配置元数据创建的对象),那么你需要使用BeanPostProcessor。虽然在技术上可以在BeanFactoryPostProcessor中操作bean实例(例如,通过使用BeanFactory.getBean()),但这样做会导致过早的bean实例化,违反了标准的容器生命周期。这可能会产生负面副作用,比如绕过bean后处理。

此外,BeanFactoryPostProcessor实例是按容器范围定义的。这只有在你使用容器层次结构时才相关。如果你在一个容器中定义了BeanFactoryPostProcessor,它只应用于该容器中的bean定义。即使两个容器是同一层次结构的一部分,一个容器中的bean定义也不会被另一个容器中的BeanFactoryPostProcessor实例后处理。

当bean工厂后处理器在ApplicationContext内部声明时,它会自动运行,以便对定义容器的配置元数据进行更改。Spring包括许多预定义的bean工厂后处理器,如PropertyOverrideConfigurerPropertySourcesPlaceholderConfigurer。你也可以使用自定义的BeanFactoryPostProcessor —— 例如,用于注册自定义属性编辑器。

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

BeanPostProcessor一样,你通常不希望为BeanFactoryPostProcessor配置延迟初始化。如果没有其它bean引用Bean(Factory)PostProcessor,那么后处理器根本不会被实例化。因此,将其标记为延迟初始化将被忽略,即使你在<beans />元素的声明上将default-lazy-init属性设置为trueBean(Factory)PostProcessor也会被立即实例化。

示例:类名替换PropertySourcesPlaceholderConfigurer

你可以使用PropertySourcesPlaceholderConfigurer将bean定义中的属性值外置到单独的文件中,并使用标准的Java Properties格式。这样做使得部署应用程序的人能够自定义特定于环境的属性,如数据库URL和密码,而无需修改容器的主要XML定义文件或承担这种风险。

考虑以下基于XML的配置元数据片段,其中定义了一个带有占位符值的DataSource

<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
	<property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>

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

示例显示了从外部Properties文件配置的属性。在运行时,PropertySourcesPlaceholderConfigurer应用于元数据,用于替换DataSource的某些属性。要替换的值被指定为${property-name}形式的占位符,遵循Ant、log4j和JSP EL风格。

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

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

因此,${jdbc.username}字符串在运行时被替换为值’sa’,其它与属性文件中的键匹配的占位符值也是如此。PropertySourcesPlaceholderConfigurer会检查bean定义中的大多数属性和属性中的占位符。此外,还可以自定义占位符的前缀和后缀。

通过在Spring 2.5中引入的context命名空间,你可以使用专用的配置元素配置属性占位符。可以在location属性中提供一个或多个位置,用逗号分隔,如下例所示:

<context:property-placeholder location="classpath:com/something/jdbc.properties"/>

PropertySourcesPlaceholderConfigurer不仅在你指定的Properties文件中查找属性。默认情况下,如果在指定的属性文件中找不到属性,它将检查Spring Environment属性和常规Java System属性。

对于给定的应用程序,应该只定义一个此类元素,并提供它所需的属性。只要它们具有不同的占位符语法(${…​}),就可以配置多个属性占位符。

如果你需要模块化用于替换的属性源,那么不应该创建多个属性占位符。相反,应该创建自己的PropertySourcesPlaceholderConfigurer bean来收集要使用的属性。

可以使用PropertySourcesPlaceholderConfigurer替换类名,这在你必须在运行时选择一个特定的实现类时有时会很有用。以下示例显示了如何做到这一点:

<bean class="org.springframework.beans.factory.config.PropertySourcesPlaceholderConfigurer">
	<property name="locations">
		<value>classpath:com/something/strategy.properties</value>
	</property>
	<property name="properties">
		<value>custom.strategy.class=com.something.DefaultStrategy</value>
	</property>
</bean>

<bean id="serviceStrategy" class="${custom.strategy.class}"/>

如果在运行时无法将类解析为有效的类,则在创建bean时(对于非延迟初始化的bean,这是在ApplicationContextpreInstantiateSingletons()阶段)bean的解析会失败。

示例:PropertyOverrideConfigurer

另一个bean工厂后处理器PropertyOverrideConfigurer类似于PropertySourcesPlaceholderConfigurer,但与后者不同的是,原始定义可以为bean属性设置默认值或根本没有值。如果覆盖的Properties文件没有某个bean属性的条目,则使用默认的上下文定义。

请注意,bean定义并不知道被覆盖了,所以从XML定义文件中并不立即显而易见正在使用覆盖配置器。如果有多个PropertyOverrideConfigurer实例为同一个bean属性定义了不同的值,由于覆盖机制,最后一个将获胜。

Properties文件配置行采用以下格式:

beanName.property=value

以下列表显示了该格式的示例:

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

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

只要路径中的每个组件(除了被覆盖的最终属性)都已非空(可能是由构造函数初始化的),还支持复合属性名称。在以下示例中,tom bean的fred属性的bob属性的sammy属性被设置为标量值123

tom.fred.bob.sammy=123

指定的覆盖值始终是字面值。它们不会被转换为bean引用。即使XML bean定义中的原始值指定了bean引用,此约定也适用。

通过在Spring 2.5中引入的context命名空间,可以使用专用的配置元素配置属性覆盖,如下例所示:

<context:property-override location="classpath:override.properties"/>

使用FactoryBean自定义实例化逻辑

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

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

FactoryBean<T>接口提供了三个方法:

  • T getObject():返回此工厂创建的对象的实例。根据此工厂是否返回单例或原型,实例可能是共享的。
  • boolean isSingleton():如果此FactoryBean返回单例,则返回true,否则返回false。此方法的默认实现返回true
  • Class getObjectType():返回getObject()方法返回的对象类型,如果提前不知道类型,则返回null

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

当需要向容器请求实际的FactoryBean实例本身而不是它产生的bean时,在调用ApplicationContextgetBean()方法时,使用&符号前缀bean的id。因此,对于具有idmyBean的给定FactoryBean,调用容器上的getBean("myBean")返回FactoryBean的产品,而调用getBean("&myBean")则返回FactoryBean实例本身。

  • 18
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值