Spring ioC容器扩展点
通常来说,开发这不需要继承ApplicationContext
来扩展功能,相反,Spring容器可以通过许多指定的接口进行扩展。
使用BeanPostProcessor
进行bean自定义
BeanPostProcessor
定义的回掉方法可以用来实现自己bean初始化逻辑,解决依赖逻辑等,如果需要在Spring容器对bean进行实例化、配置、初始化后进行一些自定义操作,也可以实现一系列BeanPostProcessor
添加到容器中
BeanPostProcessor
会作用于bean(或者说时对象)上,每当Springioc容器初始化一个bean,BeanPostProcessor
就会被回掉。
BeanPostProcessor
作用于一个容器中,如果使用父子关系的容器,bean只会被同一个容器中的BeanPostProcessor
所处理。如果需要修改bean的定义元数据,即beanDefinition,需要使用
BeanFactoryPostProcessor
org.springframework.beans.factory.config.BeanPostProcessor
由2个回掉方法组成,当一个对象被注册为BeanPostProcessor
,只要当Spring容器创建bean,BeanPostProcessor
就会在bean的初始化前和初始化后被回掉,这次期间,BeanPostProcessor
可以对bean做任何事情,比如:检查bean被回掉的次数、Spring Aop就是通过BeanPostProcessor
给bean实现代理逻辑
ApplicationContext
会自动检测到容器中实现BeanPostProcessor
接口的bean,ApplicationContext
会注册这些bean以便后期被调用,BeanPostProcessor
可以就像注册普通bean一样被注册到容器。
手动注册
BeanPostProcessor
实例:虽然推荐使用
ApplicationContext
自动检测来注册,但是也可以通过ConfigurableBeanFactory
的addBeanPostProcessor
方法来手动注册,这种做法的好处就是可以在注册之前实现一些逻辑。注意:手动注册的
BeanPostProcessor
不会遵循Ordered
接口来配置执行顺序,手动注册的BeanPostProcessor
总是优先于自动检测的BeanPostProcessor
执行。
BeanPostProcessor
实例和AOP 自动代理:实现
BeanPostProcessor
接口的类将会被容器区分对待,所有的BeanPostProcessor
的实例以及他们直接依赖的bean都将在应用启动的时候实例化,作为ApplicationContext
的一个特殊启动阶段。因为AOP自动代理是通过
BeanPostProcessor
实现的,所以BeanPostProcessor
实例或它们直接引用的bean都不适合进行自动代理,因此不会被注入切面。对于这样的Bean,通常会看到以下日志信息:
Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)
如果我们通过
@autowire
或@Resource
(可能会退回到自动装配)注入bean到BeanPostProcessor
实例中,则Spring在搜索类型匹配的依赖项候选对象时可能会访问意想不到的Bean,因此使这些bean不符合自动代理或被后置处理。
案例:BeanPostProcessor
BeanPostProcessor
的基本使用,以下案例将会在bean创建完成之前调用bean的toString()
方法获取bean信息:
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;
}
}
<?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>
以上xml配置使用Groovy定义bean:messager
使用BeanPostProcessor
:
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
使用BeanFactoryPostProcessor
自定义配置元数据
org.springframework.beans.factory.config.BeanFactoryPostProcessor
也是Ioc容器的扩展点,和BeanPostProcessor
类似,但是有一个主要的区别:BeanFactoryPostProcessor
操作bean的配置元数据(beanDefinition),Spring Ioc容器能够让BeanFactoryPostProcessor
读取到配置元数据并有可能在容器实例化除BeanFactoryPostProcessor
实例以外的任何bean之前更改它。
我们可以配置多个BeanFactoryPostProcessor
实例,并通过order
属性来控制它们的调用顺序,但是只能通过实现Ordered
接口来配置顺序,如果需要使用BeanFactoryPostProcessor
则需要考虑到实现Ordered
接口。
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
如果需要改变实际返回的bean实例需要使用上述的
BeanPostProcessor
,从技术上来说,在postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
中可以使用BeanFactory.getBean()
来获取bean然后修改它,但是这回导致提前bean实例化并且打乱容器正常的生命周期,这种做法会带来许多负面影响,比如:bean不会被所有的BeanPostProcessor
做后置处理
ApplicationContext
会自动调用这些BeanFactoryPostProcessor
来修改bean的配置元数据,Spring内置了一些实现:PropertyOverrideConfigurer
和PropertySourcesPlaceholderConfigurer
,也可以自定义BeanFactoryPostProcessor
,比如:实现自定义的bean属性修改器
Bean(Factory)PostProcessor
会忽略延迟加载配置,比如:标签的 default-lazy-init
属性
BeanFactoryPostProcessor
使用案例:PropertySourcesPlaceholderConfigurer
PropertySourcesPlaceholderConfigurer
是Spring内置的占位符处理器,就是使用的BeanFactoryPostProcessor
来实现,通过PropertySourcesPlaceholderConfigurer
,我们可以在bean的属性配置中使用占位符:
<beans>
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<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>
</beans>
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
在启动过程中。PropertySourcesPlaceholderConfigurer
会读取外部的Properties
文件,来替换相应的占位符。
PropertySourcesPlaceholderConfigurer
不仅会在Properties
文件中查找值,默认情况下,如果在Properties
文件不能找到相应的属性,会自动的在Spring的Environment
对象中寻找,甚至是System
属性
小技巧:使用
PropertySourcesPlaceholderConfigurer
在运行时决定接口实现类:<beans> <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}"/> </beans>
如果在运行时找不到指定的类,则在
ApplicationContext
的preInstantiateSingletons()
阶段就会报错
BeanFactoryPostProcessor
使用案例:PropertyOverrideConfigurer
PropertyOverrideConfigurer
也是Spring的常用BeanFactoryPostProcessor
实现类,和PropertySourcesPlaceholderConfigurer
类似,但不同的是原始定义对于bean属性可以具有默认值,也可以完全没有值。 如果覆盖的属性文件没有某个bean属性的条目,则使用默认的上下文定义。
<beans>
<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
<property name="location" value="classpath:myproperties.properties" />
</bean>
<bean id="person" class="com.sample.Employee" >
<property name="name" value="Dugan"/>
<property name="age" value="50"/>
</bean>
</beans>
myproperties.properties:
person.age=40
person.name=Stanis
如上配置,当获取person
bean时:
Employee e = (Employee)context.getBean(Employee.class);
e.getAge() => 40 //被properties文件的值覆盖掉
e.getName() => "Stanis" //被properties文件的值覆盖掉
使用FactoryBean
自定义实例化逻辑
如果一个对象本身作为工程类的话,可以实现org.springframework.beans.factory.FactoryBean
接口,FactoryBean
接口是Spring Ioc容器实例户逻辑的一个插件类,如果有一些复杂的实例化逻辑使用FactoryBean
来实现比用XML配置精简的多:
FactoryBean
接口提供3个方法:
Object getObject()
: 获取FactoryBean
创建的对象,对象是否为单实例取决于第二个方法返回值。boolean isSingleton()
: 如果FactoryBean
返回单实例则返回true
,否则返回false
Class getObjectType()
: 返回getObject()
方法放回对象的类型,如果为止则返回null
FactoryBean
在Spring中被大量使用,超过50个类实现了FactoryBean
接口。
当我们需要获取
FactoryBean
本身而不是FactoryBean
产生的对象时,需要在bean 的id
前加上&
。比如:有一个FactoryBean
的id为myBean
,当调用getBean("myBean")
时,将会得到FactoryBean
的getObject()
方法产生的对象,而调用getBean("&myBean")
时会得到FactoryBean
本身