内容简介:本文内容选自 Spring Framework 5.1.6.RELEASE 官方文档中 core 部分的 1.8 小节,简单介绍了如何利用 Spring 容器扩展点进行定制化扩展,以及注意点。若有任何问题,欢迎交流。原文地址:通常,一位应用开发者不需要继承
本文内容选自 Spring Framework 5.1.6.RELEASE 官方文档中 core 部分的 1.8 小节,简单介绍了如何利用 Spring 容器扩展点进行定制化扩展,以及注意点。若有任何问题,欢迎交流。
原文地址: docs.spring.io/spring/docs…
正文 1.8. Spring 容器扩展点
通常,一位应用开发者不需要继承 ApplicationContext
实现类。相反,Spring IoC 容器能够通过插入特殊的集成接口来实现扩展。下面一些章部分内容描述了这些集成接口。
1.8.1 用 BeanPostProcessor
定制 Beans
BeanPostProcessor
接口定义了允许实现的回调方法,来用于提供自己(或者覆盖容器默认)的初始化逻辑,依赖处理逻辑等等。如果你想要在 Spring 容器完成容器初始化,配置和初始化 Bean 之后实现一些定制的逻辑,你可以通过插入一个或者多个定制的 BeanPostProcessor
实现。
你可以配置多个 BeanPostProcessor
实例,并且通过设置 order
属性来控制这些 BeanPostProcessor
实例的执行顺序。只有当你的 BeanPostProcessor
实现了 Ordered
接口才能设置这个属性。如果你要实现自己 BeanPostProcessor
,你也应该考虑实现 Ordered
接口。有关详细信息,可见 BeanPostProcessor
和 Ordered
的javadoc 。也可以参考
programmatic registration of BeanPostProcessor
instances
上的注释。
BeanPostProcessor
实例作用于 Bean(或者对象)实例上。也就是说,Spring IoC 容器初始化一个 Bean 实例,然后 BeanPostProcessor
实例完成它们的工作。
BeanPostProcessor
实例作用范围于每个容器。这仅当你使用到容器的层次结构才有关。如果你在一个容器里定义了一个 BeanPostProcessor
,它只会后置处理这个容器下的 beans。换句话说,定义在一个容器的 beans 不能被定义在另个容器里的 BeanPostProcessor
对象执行后置处理,即使这些容器在同一个层级下。
想要改变 Bean 定义(也就是说,定义 Bean 的蓝图),你需要使用 BeanFactoryPostProcessor
,如
Customizing Configuration Metadata with a BeanFactoryPostProcessor
所描述的。
org.springframework.beans.factory.config.BeanPostProcessor
接口由两个回调方法组成,当一个类在容器里作为后置处理器注册时,对于每个由这个容器创建的 bean 实例,后置处理器会在容器初始化方法(例如 InitializingBean.afterPropertiesSet()
或者任何声明 init
方法)调用前得到回调,并且在任何 bean 初始化之后得到回调。一个 Bean 后置处理器通常在回调接口用于检查,或者它可能使用一个代理对一个 bean 进行包装。一些 SpringAOP 基础结构的类就是用通过 bean 后置处理器实现的,以便提供代理包装的逻辑。
ApplicationContext
自动检测在配置元信息里那些实现了 BeanPostProcessor
接口的 beans。 ApplicationContext
会将这些 beans 注册为后置处理器,以便于后面在 bean 创建时被调用。Bean 后置处理器可以像采用其他 beans一样的方法部署在容器中。
注意的是,但在一个配置类通过 @Bean
工厂方法声明一个 BeanPostProcessor
时,这个工厂方法的返回类型应该是这个实现类本身,或者 org.springframework.beans.factory.config.BeanPostProcessor
接口,明确指明这个 bean拥有 后置处理器的性质。否则, ApplicationContext
无法在完全创建它之前,通过类型自动检测到它。由于 BeanPostProcessor
需要过早实例化以便于作用于在同个上下文的其他 bean 实例化,因此这种前期的类型检测至关重要。
编程方式注册 BeanPostProcessor
实例
虽然 BeanPostProcessor
注册的推荐方式为让 ApplicationContext
自动检测(如之前描述的一样),你可以注册他们通过编程方式,通过 ConfigurableBeanFactory
使用 addBeanPostProcessor
方法。当你需要在注册前处理条件逻辑,或者在一个层次里跨上下文拷贝bean后置处理器时所有帮助。然而要注意的是,以编程方式添加的 BeanPostProcessor
实例不遵循 Ordered
接口。这里,注册的顺序确定了执行的顺序。也要注意的是,通过编程方式注册的 BeanPostProcessor
实例总是在通过自动检测 注册的实例之前处理,任何显式的 排序 不会起作用。
BeanPostProcessor
实例和 AOP 自动代理
在容器中实现了 BeanPostProcessor
接口的类是特殊的,且被区别对待。作为特殊启动阶段的一部分,所有 BeanPostProcessor
实例以及他们所直接引用的 beans 都在启动时实例化。接下来,所有 BeanPostProcessor
实例将以有序的方式注册,并作用到当前容器中所有其他 beans。因为 AOP 自动代理是基于 BeanPostProcessor
实现的, BeanPostProcessor
实例以及他们直接引用的beans不符合自动代理的条件,因此这些 bean无法被切面织入。
对于这样的 bean,你应该会看到一个信息日志消息: Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)
.
如果你通过自动注入或者 @Resource
方式在你的 BeanPostProcessor
注入beans,当 Spring 基于类型匹配的依赖候选时,Spring 可能会访问到非所期望的 beans ,因此,他们不适合自动代理或者其他类型的 bean 后置处理器。例如,你有一个依赖标记了 @Resource
,,而这个字段或者 setter 方法名没有直接对应 bean 的声明名称,也没有使用到名称属性,Spring 会按照类型匹配他们访问其他 beans
接下来的示例展示了在 ApplicationContext
中如何写,注册以及使用 BeanPostProcessor
实例。
示例:Hello World, BeanPostProcessor
-style
第一个实例演示了基本用法,这个示例展示了一个定制的 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; } } 复制代码
下面使用 InstantiationTracingBeanPostProcessor
的 beans 元素
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:lang="http://www.springframework.org/schema/lang" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/lang https://www.springframework.org/schema/lang/spring-lang.xsd"> <lang:groovy id="messenger" script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy"> <lang:property name="message" value="Fiona Apple Is Just So Dreamy."/> </lang:groovy> <!-- when the above bean (messenger) is instantiated, this custom BeanPostProcessor implementation will output the fact to the system console --> <bean class="scripting.InstantiationTracingBeanPostProcessor"/> </beans> 复制代码
注意 InstantiationTracingBeanPostProcessor
的定义方式,它甚至没有好名称,并且因为他们一个 bean,它能够像其他任何 bean 一样被依赖注入。(前面配置还定义了一个由 Groovy 脚本创建的 bean。Spring 动态语言支持在 Dynamic Language Support
一章中详细介绍。
下面 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
一种常用扩展 Spring IoC 容器的方法就是将回调接口或注解与定制的 BeanPostProcessor
实现结合使用。Spring的 RequiredAnnotationBeanPostProcessor
就是这样的例子,一个 BeanPostProcessor
实现,在Spring 运行阶段确保 beans 上被特定注解标记的JavaBean 属性能用值进行依赖注入。
1.8.2 用 BeanFactoryPostProcessor
定制配置的元数据
下个扩展点我们来看下 org.springframework.beans.factory.config.BeanFactoryPostProcessor
.这个接口的语义与 BeanPostProcessor
类似,主要的不同在于: BeanFactoryPostProcessor
操作于 Bean 的配置元数据。也就是说,Spring IoC 容器让 BeanFactoryPostProcessor
读取配置元数据,在容器实例化除了 BeanFactoryPostProcessor
实例之外的 Beans 之前,改变其配置元数据。
你可以配置多个 BeanFactoryPostProcessor
实例,并且通过设置 order
属性,来控制这些 BeanFactoryPostProcessor
实例的运行顺序。但只有 BeanFactorPostProcessor
实现了 Ordered
接口,才能设置这个属性。如果你实现了自己的 BeanFactoryPostProcessor
,你也需要考虑实现 Ordered
接口。有关详细信息,可见
BeanFactoryPostProcessor
和
Ordered
的 javadoc 。
如果你想要改变 Bean 实例,那么你应该使用 BeanPostProcessor
(描述于之前的
Customizing Beans by Using a BeanPostProcessor
)虽然在技术上是可以用 BeanFactoryPostProcessor
(例如,使用 BeanFactory.getBean()
)实现,但这样做会造成让 bean 过早的实例化,违背了标准的容器生命周期。这样可能会产生负面作用,如绕过 Bean 常规的后置处理。
除此之外, BeanFactoryPostProcessor
实例作用范围于每个容器。仅当你使用到容器的层次结构时才相关。如果你在一个容器里定义了一个 BeanFactoryPostProcessor
,它只能作用于在这个容器里的 bean 定义。即使这些容器在同一个层次结构里,一个容器的Bean 定义不会被定义在另一个容器里的 BeanFactoryPostProcessor
实例进行后置处理。
为了将定义容器的配置元数据的改变生效,当bean 工厂后置处理器声明在 ApplicationContext
中,就会自动执行。Spring 包含了许多预定义的 bean 工厂后置处理器,例如 PropertyOverrideConfigurer
和 PropertyPlaceholderConfigurer
。你也可以使用定制的 BeanFactoryPostProcessor
- 例如,注册定制的属性编辑器。
ApplicationContext
会自动检测到声明在自己内部实现了 BeanFactoryPostProcessor
接口的的 beans。它会在合适的时机,将这些 beans 作为 bean 工厂后置处理器。你可以像其他 beans 一样声明这些后置处理器 beans。
与 BeanPostProcessor
一样,你通常不想配置 BeanFactoryPostProcessor
后被延时初始化。如果没有其他 bean 引用BeanFactoryPostProcessor,那么这个后置处理器根本不会被实例化。因此,延迟加载的标记会被忽略,即使你在元素的声明中将 default-lazy-init
属性设置为 true
, BeanFactoryPostProcessor
也会尽早地实例化。
示例:类名替换 PropertyPlaceholderConfigurer
你可以使用 PropertyPlaceholderConfigurer
从一个独立的使用标准 Java Properties
格式的文件来表达一个bean 定义的属性值。这样做让人们根据环境特定的属性来部署应用,如数据库 URLs 和密码,没有了修改主配置 XML文件或者容器文件的复杂和风险。
参考下面基于 XML的 配置元数据的片段,里面使用占位值声明了一个 dataSource
:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations" value="classpath:com/something/jdbc.properties"/> </bean> <bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> 复制代码
示例展示属性配置来自于一个外部 Properties 文件。在运行时, PropertyPlaceholderConfigurer
会将应用的元数据替换到 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,相同方式会生效于在属性文件中匹配到对应键的其他占位值。 PropertyPlaceholderConfigurer
会检查绝大多数的属性的占位符和 bean 定义的属性。此外,你可以定制占位符的前缀和后缀。
在 Spring 2.5 引入的 context
命名空间里,你可以用专门配置元素来配置属性占位符。你可以在 location
属性里提供一个或多个位置用逗号隔开的列表,如下面例子所示:
<context:property-placeholder location="classpath:com/something/jdbc.properties"/> 复制代码
PropertyPlaceholderConfigurer
不仅在你限定的 Properties
文件里查找属性。默认情况下,如果不能再特定属性文件中找到属性,它也会在 Java 的 System
属性上检查。你可以通过设置配置对象的 systemPropertiesMode
属性定制这个行为,以下是它所支持的三个整数值:
never fallback override
有关详细信息,可见
PropertyPlaceholderConfigurer
javadoc。
你可以使用 PropertyPlaceholderConfigurer
替换类名,当你需要在运行时才选定一个特定实现类时这个功能可以派上用场。下面展示如何去做的例子:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <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 的解析会失败。这样将发生于 ApplicationContext
里 非懒加载 bean的 preInstantiateSingletons
阶段。
示例: PropertyOverrideConfigurer
PropertyOverrideConfigurer
,另一个bean 工厂后置处理器,与 PropertyPlaceholderConfigurer
很相似,但是不同于后者,对于 bean 属性,原始定义可以具有默认值或者没有值。如果一个覆盖的 Properties
文件没有某个 bean 属性时,默认上下文的定义会被使用。
请注意,bean 定义是不会感知到被覆盖,因此不能立即看出是XML 定义文件覆写了在使用的配置。如果有多个 PropertyOverrideConfigurer
实例定义了一个 bean 属性但不同的值,那么由于覆写机制,最后定义的一个值会生效。
Properties
文件配置行都采用以下格式:
beanName.property=value 复制代码
下面列举了示例的格式:
dataSource.driverClassName=com.mysql.jdbc.Driver dataSource.url=jdbc:mysql:mydb 复制代码
示例配置文件可用于容器中定义了名为 dataSource
的 bean的 driver
和 url
属性。
也支持复合属性名称,只要路径的每个组件(被重写的最终实现属性除外)都是非 null(都由构造函数初始化)。
下面的示例中,名为 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"/> 复制代码
1.8.3 用 FactoryBean
定制实例化逻辑
你可以实现 org.springframework.beans.factory.FactoryBean
接口来创建本身是工厂的对象。
FactoryBean
接口对 Spring IoC 容器实例化逻辑实现是可插拔的。如果你有复杂的初始化代码,使用 Java 代码 好于冗长的 XML 配置,你可以创建自己的 FactoryBean
,在这个类里写复杂的实例化,并且将定制的 FactoryBean
插入到容器中。
FactoryBean
接口提供了三个方法:
-
Object getObject()
: 返回工厂创建的实例对象。这个实例可能是共享的,这个依赖于这个工厂师傅返回单例对象还是原型对象。 -
boolean isSignletion()
: 如果FactoryBean
返回单例对象则返回true
,否则为false
-
Class getObjectType()
: 返回 方法getObject()
的对象的类型,如果类型还没确定则返回null
FactoryBean
概念和实现用于 Spring Framework 的许多处地方,Spring 自身提供了超过 50 多种的 FactoryBean
实现。
当你需要向一个容器访问特定 FactoryBean
实例而不是它产生的 beans 时,在用 ApplicationContext
的 getBean
方法时,使用 &
符号作用 bean 的 id
前缀。例如,给定一个 id
为 myBean 的 FactoryBean
,调用 getBean("myBean")
可以获得 FactoryBean
生成的 bean,而调用 getBean("&myBean")
返回 FactoryBean
实例本身。
以上所述就是小编给大家介绍的《译 - Spring 核心技术之 Spring 容器扩展点》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Spring源码解析:高级容器的扩展内幕
- PCF 2.0平台扩展:包含容器和Serverless
- 谁说docker-compose不能水平扩展容器、服务多实例?
- html – Flex容器不会扩展以适应IE中的内容
- 红帽Ansible Tower 3.3面世,加强控制、集成、可扩展性和容器支持
- 【php 扩展开发】扩展生成器
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。