Spring方法注入&方法替换(偷梁换柱)
引子
含FXNewsBean
类型成员变量的MockNewsPersister
的定义:
public class MockNewsPersister implements IFXNewsPersister {
private FXNewsBean newsBean;
public void persistNews(FXNewsBean bean) {
persistNewes();
}
public void persistNews()
{
System.out.println("persist bean:"+getNewsBean());
}
public FXNewsBean getNewsBean() {
return newsBean;
}
public void setNewsBean(FXNewsBean newsBean) {
this.newsBean = newsBean;
}
}
复制代码
FXNewsBean
和MockNewsPersister
的bean注册:
<bean id="newsBean" class="..domain.FXNewsBean" singleton="false">
bean>
<bean id="mockPersister" class="..impl.MockNewsPersister">
<property name="newsBean">
<ref bean="newsBean"/>
property>
bean>
复制代码
两次调用MockNewsPersister
的persistNews()
方法:
BeanFactory container = new XmlBeanFactory(new ClassPathResource(".."));
MockNewsPersister persister = (MockNewsPersister) container.getBean("mockPersister");
persister.persistNews();
persister.persistNews();
复制代码
输出结果:
输出:
persist bean:..domain.FXNewsBean@1662dc8
persist bean:..domain.FXNewsBean@1662dc8
复制代码
以上代码中,FXNewsBean
的scope是prototype,但是由于MockNewsPersister
的实例在实例化后被注入FXNewsBean
的实例后一直持有该实例,导致两次调用的FXNewsBean
是同一个。
我们的期望是每次调用persistNews()
方法得到的FXNewsBean
不一样,且看以下方案。
方法注入(Method Injection)
为解决上述问题,我们使用Spring容器提出的一种叫做方法注入(Method Injection)的方式。
方法注入是将硬编码的方法替换成设定规则的方法来实现成员变量注入的目的。
- 需注入的方法须符合以定义:
[abstract] theMethodName(no-arguments); - 使用
节点注册方法:
<bean id="newsBean" class="..domain.FXNewsBean" singleton="false">
bean>
<bean id="mockPersister" class="..impl.MockNewsPersister">
<lookup-method name="getNewsBean" bean="newsBean"/>
bean>
复制代码
引子中的getNewsBean()
方法符合定义规则,该方法必须能够被子类实现或者覆写,因为容器会为我们要进行方法注入的对象使用Cglib动态生成一个子类实现,从而替代当前对象;
动态生成的方法实现类似于下文的
setBeanFactory()
方法
通过
的 name
属性指定需要注入的方法名, bean
属性指定需要注入的对象,当 getNewsBean()
方法被调用的时候,容器可以每次返回一个新的 FXNewsBean
类型的实例。
此时输出结果为:
persist bean:..domain.FXNewsBean@18aaa1e
persist bean:..domain.FXNewsBean@a6aeed
复制代码
持有BeanFactory
引用来硬编码获取新实例的成员变量
通过实现BeanFactoryAware
接口来获取当前Bean所在BeanFactory
的引用,然后为所欲为。
public interface BeanFactoryAware {
void setBeanFactory(BeanFactory beanFactory) throws BeansException;
}
复制代码
实现了BeanFactoryAware
的MockNewsPersister
:
public class MockNewsPersister implements IFXNewsPersister, BeanFactoryAware {
private BeanFactory beanFactory;
public void setBeanFactory(BeanFactory bf) throws BeansException {
this.beanFactory = bf;
}
public void persistNews(FXNewsBean bean) {
persistNews();
}
public void persistNews() {
System.out.println("persist bean:" + getNewsBean());
}
public FXNewsBean getNewsBean() {
return beanFactory.getBean("newsBean");
}
}
复制代码
当此MockNewsPersister
实例化时,当前BeanFactory
会自动执行setBeanFactory()
方法,并且入参是自己的引用。
此时的bean注册相当简单:
<bean id="newsBean" class="..domain.FXNewsBean" singleton="false">
bean>
<bean id="mockPersister" class="..impl.MockNewsPersister">
bean>
复制代码
通过ObjectFactoryCreatingFactoryBean
持有ObjectFactory
引用来硬编码获取新实例的成员变量
持有ObjectFactory
引用的MockNewsPersister
:
public class MockNewsPersister implements IFXNewsPersister {
private ObjectFactory newsBeanFactory;
public void persistNews(FXNewsBean bean) {
persistNews();
}
public void persistNews()
{
System.out.println("persist bean:"+getNewsBean());
}
public FXNewsBean getNewsBean() {
return newsBeanFactory.getObject();
}
public void setNewsBeanFactory(ObjectFactory newsBeanFactory) {
this.newsBeanFactory = newsBeanFactory;
}
}
复制代码
为MockNewsPersister
注入相应的ObjectFactory
的bean配置:
<--!此处缘何调用了ObjectFactoryCreatingFactoryBean的createInstance方法暂不清楚
复制代码
以上配置将targetBeanName
为FXNewsBean
的ObjectFactoryCreatingFactoryBean
的createInstance()
方法返回值setter注入给MockNewsPersister
。ObjectFactoryCreatingFactoryBean
是FactoryBean
的实现类。
MockNewsPersister
的
getNewsBean()
方法时,返回的是
newsBeanFactory.getObject()
的结果。
FactoryBean
的getObject()
返回其所对应的产品类实例。详细自行联系了解FactoryBean
API;
也可以使用ServiceLocatorFactoryBean
。
ObjectFactoryCreatingFactoryBean
源码解读(建议自行完整阅读相关类源码):
//AbstractFactoryBean实现了BeanFactoryAware接口,使其可以持有当前容器BeanFactory的引用;AbstractFactoryBean实现了FactoryBean类
public class ObjectFactoryCreatingFactoryBean extends AbstractFactoryBean<ObjectFactory<Object>> {
//产品类beanName
private String targetBeanName;
public ObjectFactoryCreatingFactoryBean() {
}
public void setTargetBeanName(String targetBeanName) {
this.targetBeanName = targetBeanName;
}
public void afterPropertiesSet() throws Exception {
Assert.hasText(this.targetBeanName, "Property 'targetBeanName' is required");
super.afterPropertiesSet();
}
public Class> getObjectType() {
return ObjectFactory.class;
}
//实现自父类AbstractFactoryBean声明的抽象方法
//返回新new的内部类实例,this.getBeanFactory()是调用的父类AbstractFactoryBean的方法,且返回值是其持有的BeanFactory。
protected ObjectFactory{
return new ObjectFactoryCreatingFactoryBean.TargetBeanObjectFactory(this.getBeanFactory(), this.targetBeanName);
}
//实现ObjectFactory接口,故可由ObjectFactory类型变量持有
private static class TargetBeanObjectFactory implements ObjectFactory<Object>, Serializable {
private final BeanFactory beanFactory;
private final String targetBeanName;
public TargetBeanObjectFactory(BeanFactory beanFactory, String targetBeanName) {
this.beanFactory = beanFactory;
this.targetBeanName = targetBeanName;
}
//包装了BeanFactory的getBean方法,使得BeanFactory只有此方法对客户端类暴露。
//此方法实现自ObjectFactory接口的唯一声明方法
//返回产品类的实际是此方法,而不是ObjectFactoryCreatingFactoryBean实现自FactoryBean的getObject方法。
public Object getObject() throws BeansException {
return this.beanFactory.getBean(this.targetBeanName);
}
}
}
复制代码
方法替换(Method Replacement)
可以灵活替换或者说以新的方法实现覆盖掉原来某个方法的实现逻辑。基本上可以认为,方法替换可以帮助我们实现简单的方法拦截功能。类似于AOP。
MethodReplacer
接口定义:
package org.springframework.beans.factory.support;
import java.lang.reflect.Method;
public interface MethodReplacer {
Object reimplement(Object var1, Method var2, Object[] var3) throws Throwable;
}
复制代码
实现MethodReplacer
的FXNewsProviderMethodReplacer
:
public class FXNewsProviderMethodReplacer implements MethodReplacer {
private static final transient Log logger = LogFactory.getLog(FXNewsProviderMethodReplacer.class);
public Object reimplement(Object target, Method method, Object[] args) throws Throwable {
logger.info("before executing method[" + method.getName() + "] on Object[" + target.getClass().getName() + "].");
System.out.println("sorry,We will do nothing this time.");
logger.info("end of executing method[" + method.getName() + "] on Object[" + target.getClass().getName() + "].");
return null;
}
}
复制代码
注册bean,并替换方法:
<bean id="djNewsProvider" class="..FXNewsProvider">
<constructor-arg index="0">
<ref bean="djNewsListener"/>
constructor-arg>
<constructor-arg index="1">
<ref bean="djNewsPersister"/>
constructor-arg>
<replaced-method name="getAndPersistNews" replacer="providerReplacer">
replaced-method>
bean>
<bean id="providerReplacer" class="..FXNewsProviderMethodReplacer">
bean>
...
复制代码
执行djNewsProvider
的getAndPersistNews
方法结果:
771 [main] INFO ..FXNewsProviderMethodReplacer
- before executing method[getAndPersistNews]
on Object[..FXNewsProvider$$EnhancerByCGLIB$$3fa709d3].
sorry,We will do nothing this time.
771 [main] INFO ..FXNewsProviderMethodReplacer
- end of executing method[getAndPersistNews]
on Object[..FXNewsProvider$$EnhancerByCGLIB$$3fa709d3].
复制代码
执行结果表明方法被成功替换。
此处示例了方法替换用法,实际功能类似AOP,而且AOP功能更强、效率更高,建议各场景优先使用AOP。