Spring核心技术(七)——Spring容器的扩展
本文将讨论如何关于在Spring生命周期中扩展Spring中的Bean功能。
容器的扩展
通常来说,开发者不需要通过继承ApplicationContext
来实现自己的子类扩展功能。但是Spring IoC容器确实可以通过实现接口来增加一些功能。下面将描述一下这些接口。
通过BeanPostProcessor
定义Bean
BeanPostProcessor
接口定义了一些回调方法,开发者可以通过实现来自己的实例化逻辑,依赖解析逻辑等等。如果开发者只是想在Spring容器完成了实例化,配置以及初始化Bean之后来做一些操作的话,可以通过使用BeanPostProcessor
来做到。
开发者可以配置多个BeanPostProcessor
实例,开发者可以通过配置order
属性来指定配置的BeanPostProcessor
的执行顺序。当然,想要配置顺序必须同时要实现Ordered
接口。如果开发者写了自己的BeanPostProcessor
,开发者最好同时考虑实现Ordered
接口。如果想了解更多的信息,可以参考BeanPostProcessor
以及Ordered
接口的javadoc。
BeanPostProcessors
是操作Bean实例的,换言之,Spring IoC容器必须先初始化好Bean,然后BeanPostProcessors
才开始工作。
BeanPostProcessors
作用范围是基于容器的。当然,只有当开发者使用容器的层级的时候才是需要考虑的。如果开发者在容器中定义了一个BeanPostProcessor
,这个实例只会在它所在的容器来处理Bean初始化以后的操作。换言之,一个容器中的Bean不会被另一个容器中定义BeanPostProcessor
的在初始化以后进行后续处理,甚至就算两个容器同属同一个容器的部分。
org.springframework.beans.factory.config.BeanPostProcessor
接口包含了2个回调方法。当这个接口的实例注册到容器当中时,对于每一个由容器创建的实例,这个后置处理器都会在容器开始进行初始化之前获得回调的调用。后置处理器可以针对Bean实例采取任何的操作,包括完全无视回调函数。Bean的后置处理器通常检查回调接口或者将Bean用代理包装一下。一些诸如Spring AOP代理的基础类都是通过Bean的后续处理器来实现的。
ApplicationContext
会自动检查配置的Bean是否有实现BeanPostProcessor接口,ApplicationContext
会将这些Bean注册为后续处理器,这样这些后续处理器就会在Bean创建之后调用。Bean的后续处理器就像其他的Bean一样,由容器管理的。
需要注意的是,在配置类的工厂方法上使用@Bean
注解的时候,工厂方法的返回值应该是实现类本身,或者至少为org.springframework.beans.factory.config.BeanPostProcessor
接口,来明确表明这个Bean是一个后置处理器。否则,ApplicationContext
是无法在自动创建这个Bean的时候来知道它的属于后置处理器的。因为一个Bean后续处理器是需要按顺序来处理容器中的其他的Bean的,所以需要尽早严格检查类型。
尽管Spring团队推荐的注册Bean的后续处理的方式是通过
ApplicationContext
的自动检查,但是Spring也支持通过编程的方式,通过addBeanPostProcessor
方法。这种方式有的时候也很有用,当需要在注册前执行一些条件判断的时候,或者在结构化的上下文中复制Bean后续处理器的时候尤其有效。需要注意的是,通过编程实现的BeanPostProcessors
是会忽略掉Ordered
接口的:由编程注册的BeanPostProcessors
总是在自动检查到的BeanPostProcessors
之前来执行的,而回忽略掉明确的顺序定义。
容器会特殊对待那些实现了
BeanPostProcessor
接口的类。所有的BeanPostProcessors
和他们所引用的Bean都会在启动时直接初始化,作为ApplicationContext
启动的一个特殊阶段。然后,所有的BeanPostProcessors
会按顺序注册并应用到容器中的Bean。因为AOP自动的代理就是通过BeanPostProcessor
来实现的,无论是BeanPostProcessor
或者是它本身引用的Bean
都不是自动代理的,也不是这样注入到Bean中的。对于这样的Bean,开发者应该注意到类似于info的日志信息Bean不适合由所有的BeanPostProcessor接口处理
。
需要注意的是,一旦开发者在BeanPostProcessor
使用自动装载或者@Resource
装载了Bean,Spring也许通过类型匹配进入了不被期望的Bean,因此令这些Bean无法自动代理或者后续处理。比如,如果开发者的Bean有注解@Resource
的依赖注入而不是直接通过名字属性来进行依赖注入,那么Spring就会通过类型匹配来找到匹配的依赖。
下面的例子描述了如何写,注册和使用BeanPostProcessors
。
例子:Hello World BeanPostProcessor
方式
第一个例子阐述了基本的用法。例子中展示了BeanPostProcessor
的自定义实现,在Bean创建之后调用了toString()
方法并打印到控制台。
参考如下代码:
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>
从XML的配置中可以发现,InstantiationTracingBeanPostProcessor
只是简单的定义成了一个Bean。它甚至连一个名字都没有,但是它是一个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 = (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
就是用来确保JavaBean的标记有注解的属性确实注入了依赖。
通过BeanFactoryPostProcessor
定义配置元数据
下一个扩展是org.springframework.beans.factory.config.BeanFactoryPostProcessor
。语义上来说,这个接口有些类似BeanPostProcessor
,只是有一个很大的区别:BeanFactoryPostProcessor
操作Bean配置元数据,也就是说,Spring允许BeanFactoryPostProcessor
来读取配置源数据,并且可能会在容器实例初始话Bean之前就改变配置元数据。
如前文所述,开发者可以配置多个BeanFactoryPostProcessors
,而且开发者可以控制其具体执行的顺序。当然,配置顺序是必须实现Ordered
接口的。如果实现了自己的BeanFactoryPostProcessor
,开发者也应该考虑实现Ordered
接口。可以参考BeanFactoryPostProcessor
和Ordered
接口的javadoc来了解更多细节。
如果开发者希望改变实际的Bean实例(比如由配置元数据创建的对象),那么你需要使用
BeanPostProcessor
。但是BeanPostProcessor
是可能通过BeanFactoryPostProcessor
来和Bean实例在技术上进行协同工作的(比如,通过使用BeanFactory.getBean()
函数),这样做会引起一些不成熟的Bean初始化问题,破坏容器标准的标准生命周期。这种行为可能带来负面的行为,比如绕过Bean的后续处理流程等等。同时,BeanFactoryPostProcessors
作用范围是基于容器的。当然仅仅在使用容器层级的时候才相关。如果开发者在容器中定义了一个BeanFactoryPostProcessor
,那么其配置只会应用到那个配置的容器之中。另一个容器中的Bean定义将不会通过BeanFactoryPostProcessors
进行后续处理,就算两个容器都是同一层级中的一部分也是一样。
当在ApplicationContext
中声明了后续处理器,Bean的后续处理器就会自动的执行,来实现在配置中定义的行为。Spring包括一些预定义好的后续处理器都可以使用,比如PropertyOverrideConfigurer
和PropertyPlaceholderCOnfigurer
,也能够使用自定义的BeanFactoryPostProcessor
,比如,来注册自定义属性编辑器。
ApplicationContext
会自动的检查容器中实现了BeanFactoryPostProcessor
接口的Bean。然后合适的时候将其用作工厂的后续处理器。开发者可以像其他的Bean一样来部署后续处理器。
如果使用BeanPostProcessor,开发者通常不会配置BeanFactoryPostProcessors来延迟初始化。因为如果没有其他的Bean引用
Bean(Factory)PostProcessor
,那么后续处理器根本不会实例初始化。所以,令其延迟初始化配置将被Spring无视掉,而且Bean(Factory)PostProcessor
将无论是否配置延迟初始化都会被实例化,就算在<beans />
配置了default-lazy-init
属性为true
。
例子:类名替换BeanFactoryPostProcessor
开发者使用PropertyPlaceholderConfigurer
来从使用标准JavaProperties
格式的不同文件中来具体化属性的值。通过这个类,让开发者可以将应用部署到不同的环境使用不同的属性,比如数据库的URLs
或者是用户名密码等,而不需要修改XML的定义。
参考下面的XML配置,使用DataSource
占位符定义了数据库的一些配置。这个例子也展示了配置的属性通过额外的Properties
文件来配置。在运行时,PropertyPlaceholderConfigurer
应用到元数据上,将会替换掉DataSource
的一些属性。替换的值,就是占位符的名字,这些属性配置是基于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>
而实际占位符的值是配置在另一个文件中的,使用的是JavaProperties
的格式,如下:
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
命名空间,可以专用的配置元素来配置属性占位符。多个地址可以使用逗号分隔符。
<context:property-placeholder location="classpath:com/foo/jdbc.properties"/>
PropertyPlaceholderConfigurer
不仅仅查看开发者指定的Properties
文件。默认的话,它如果不能再指定的属性文件中找到属性的话,仍然会检查Java的System
配置。开发者可以通过配置systemPropertiesMode
属性来修改这个行为,这个属性有如下三种值:
- nerver(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实例的时候失败。
例子:PropertyOverrideConfigurer
PropertyOverrideConfigurer
,是另一个bean的后置处理器,有些类似于PropertyPlaceholderConfigurer
,但是区别于后者,原有的定义可能有默认值,或者没有任何值。如果覆盖的Properties
文件没有一个确切的Bean属性,就使用默认的定义。
需要注意的是,Bean定义本身是不会知道被覆盖的,所以,从XML的定义上很难直观看到配置被覆盖了。如果配置了多个PropertyOverrideConfigurer
实例定义了不同的值,那么基于覆盖的机制,最后一个配置会生效。
属性文件的配置类似如下:
beanName.property=value
比如:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
上面的实例文件,可以用于一个名为dataSource的Bean,包含着driver和url的属性。
复合的配饰名称也是支持的,但是要求每一个路径上的组件,需要是非空的,比如:
foo.fred.bob.sammy=123
指定的覆盖的值总是字面值;而不能是其他的Bean依赖,这一约定也会应用到原来的值特指Bean引用的情况下。
在Spring 2.5引入的context
命名空间中,也可以指定配置覆盖的文件属性如下:
<context:property-override location="classpath:override.properties"/>
自定义FactoryBean
的初始化逻辑
一些对象本身类似于工厂的可以考虑实现org.springframework.beans.factory.FactoryBean
接口。
FactoryBean
接口是一种类似于Spring IoC容器的插件化的逻辑。如果当开发者的代码有复杂的初始化代码,在配置上使用Java代码比XML更有效时,开发者可以考虑创建自己的FacotoryBean
对象,将复杂的初始化操作放到类中,将自定义的FactoryBean
扩展到容器中。
FacotryBean
接口提供如下三个方法:
Object getObject()
:返回一个工厂创建的对象。实例可被共享,取决于返回Bean的作用域为原型还是单例。boolean isSingleton()
:如果FactoryBean
返回单例,为True
,否则为False
Class getObjectType()
:返回由getObject()
方法返回的对象的类型,如果对象类型未知,返回null。
FactoryBean
概念和接口广泛用预Spring框架,Spring本身就有多于50个FactoryBean
的实现。
当开发者需要一个FactoryBean
实例而不是其产生的Bean的时候,在调用ApplicationContext
的getBean()
方法时,在其id之前加上&
符号。也就是说,对于一个给定的FactoryBean
,其id为myBean
,调用getBean("myBean")
返回其产生的Bean对象,而调用getBean("&myBean")
返回FactoryBean
实例本身。
Spring核心技术(七)——Spring容器的扩展的更多相关文章
- Spring官网阅读(七)容器的扩展点(二)FactoryBean
在上篇文章中我们已经对容器的第一个扩展点(BeanFactoryPostProcessor)做了一系列的介绍.其中主要介绍了Spring容器中BeanFactoryPostProcessor的执行流程 ...
- Spring系列(七) Spring MVC 异常处理
Servlet传统异常处理 Servlet规范规定了当web应用发生异常时必须能够指明, 并确定了该如何处理, 规定了错误信息应该包含的内容和展示页面的方式.(详细可以参考servlet规范文档) 处 ...
- Spring学习(七)--Spring MVC的高级技术
一.Spring MVC配置的替代方案 我们已经了解如何通过AbstractAnnotationConfigDispatcherServlet- Initializer快速搭建了Spring MVC环 ...
- spring学习七 spring和dynamic project进行整合
spring和web项目进行整合,其实就是在项目启动时,就创建spring容器,然后在servlet中使用spring容器进行开. 注意:为了页面可以访问到servlet,因此servlet必须放进t ...
- Spring学习(七)-----Spring Bean的5种作用域
在Spring中,bean作用域用于确定哪种类型的 bean 实例应该从Spring容器中返回给调用者.bean支持的5种范围域: 单例(singleton) - 每个Spring IoC 容器返回一 ...
- 从零开始学spring cloud(七) -------- Spring Cloud OpenFegin
一.OpenFegin 介绍 Feign是一个声明性的Web服务客户端. 它使编写Web服务客户端变得更容易. 要使用Feign,请创建一个界面并对其进行注释. 它具有可插入的注释支持,包括Feign ...
- spring入门(七) spring mvc+mybatis+generator
1.Mybatis-Generator下载 地址:https://github.com/mybatis/generator/releases 我使用的是 mybatis-generator-core- ...
- Spring官网阅读(六)容器的扩展点(一)BeanFactoryPostProcessor
之前的文章我们已经学习完了BeanDefinition的基本概念跟合并,其中多次提到了容器的扩展点,这篇文章我们就开始学习这方面的知识.这部分内容主要涉及官网中的1.8小结.按照官网介绍来说,容器的扩 ...
- 0001 - Spring 框架和 Tomcat 容器扩展接口揭秘
前言 在 Spring 框架中,每个应用程序上下文(ApplicationContext)管理着一个 BeanFactory,BeanFactory 主要负责 Bean 定义的保存.Bean 的创建. ...
随机推荐
- Vue父子组件传值之——访问根组件$root、$parent、$children和$refs
Vue组件传值除了prop和$emit,我们还可以直接获取组件对象: 根组件: $root // 单一对象 表示当前组件树的根 Vue 实例,即new Vue({...根组件内容}).如果当前实例没有 ...
- [poj2096] Collecting Bugs【概率dp 数学期望】
传送门:http://poj.org/problem?id=2096 题面很长,大意就是说,有n种bug,s种系统,每一个bug只能属于n中bug中的一种,也只能属于s种系统中的一种.一天能找一个bu ...
- iOS开发之邮件发送代码
邮件发送功能是由MessageUI Framework提供的,这个框架是iPhone sdk中最简单的框.由一个类.一个视图控制器,一个protocol组成. 一.创建视图控制器: MFMailCom ...
- 转-AFNetwork 作用和用法详解
来自:http://www.maxiaoguo.com/clothes/269.html AFNetworking是一个轻量级的iOS网络通信类库.它建立在NSURLConnection和NSOper ...
- Multitenant best Practice clone pdb seed and Clone a Pluggable Database – 12c Edition
1. 1.Tnsnames when connecting to either Container or Pluggable instance The tnsnames.ora should be c ...
- APP多渠道打包
多渠道打包的概念: 打包是指使用证书文件对app签名生成一个apk文件. 多渠道打包指的就是我们的app在开发完成之后需要投放到不同的市场,比如说Google市场.百度市场等,为了统计应用在各个市场的 ...
- Java核心技术梳理-异常处理
一.引言 异常总是不可避免的,就算我们自身的代码足够优秀,但却不能保证用户都按照我们想法进行输入,就算用户按照我们的想法进行输入,我们也不能保证操作系统稳定,另外还有网络环境等,不可控因素太多,异常也 ...
- SpringBoot 2.x (2):请求和传参
其实请求和传参这些知识属于SpringMVC 不过这也属于必须掌握的知识,巩固基础吧 GET请求: 以第一篇文章自动的方式创建SpringBoot项目: 然后新建Controller: package ...
- 学习笔记 第十三章 使用CSS3新布局
第13章 使用CSS3新布局 [学习重点] 设计多列布局 设计弹性盒布局样式 使用CSS3布局技术设计适用移动需求的网页 13.1 多列布局 CSS3使用columns属性定义多列布局,用法如下 ...
- 学习笔记 第十章 使用CSS美化表单
第10章 使用CSS美化表单 [学习重点] 正确使用各种表单控件 熟悉HTML5新增的表单控件 掌握表单属性的设置 设计易用性表单页面 10.1 表单的基本结构 表单包含多个标签,由很多控件组成 ...