Spring方法注入(Method Injection)&方法替换(Method Replacement)

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;
    }
}
复制代码

FXNewsBeanMockNewsPersister的bean注册:

    <bean id="newsBean" class="..domain.FXNewsBean" singleton="false">
    </bean>
    <bean id="mockPersister" class="..impl.MockNewsPersister">
        <property name="newsBean">
            <ref bean="newsBean"/>
        </property>
    </bean>
复制代码

两次调用MockNewsPersisterpersistNews()方法:

    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)的方式。

方法注入是将硬编码的方法替换成设定规则的方法来实现成员变量注入的目的。

  • 需注入的方法须符合以定义: <public|protected> [abstract] <return-type> theMethodName(no-arguments);
  • 使用<lookup-method/>节点注册方法:
    <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()方法

通过 <lookup-method>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;
}
复制代码

实现了BeanFactoryAwareMockNewsPersister

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配置:

    <bean id="newsBean" class="..domain.FXNewsBean" singleton="false">
    </bean>
    <bean id="newsBeanFactory" class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean">
        <property name="targetBeanName">
            <idref bean="newsBean"/>
        </property>
    </bean>
    <bean id="mockPersister" class="..impl.MockNewsPersister">
        <property name="newsBeanFactory">
            <--!此处缘何调用了ObjectFactoryCreatingFactoryBean的createInstance方法暂不清楚
            <ref bean="newsBeanFactory"/>
        </property>
    </bean>
复制代码

以上配置将targetBeanNameFXNewsBeanObjectFactoryCreatingFactoryBeancreateInstance()方法返回值setter注入给MockNewsPersisterObjectFactoryCreatingFactoryBeanFactoryBean的实现类。

当调用 MockNewsPersistergetNewsBean()方法时,返回的是 newsBeanFactory.getObject()的结果。

FactoryBeangetObject()返回其所对应的产品类实例。详细自行联系了解FactoryBeanAPI;
也可以使用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<Object> createInstance() {
        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;
}
复制代码

实现MethodReplacerFXNewsProviderMethodReplacer

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>
        <!--方法替换节点,将djNewsProvider的getAndPersistNews方法替换成providerReplacer的-->
        <replaced-method name="getAndPersistNews" replacer="providerReplacer">
        <!--可通过 <arg-type>节点消除重载方法歧义和指定方法参数-->
        </replaced-method>
    </bean>
    <bean id="providerReplacer" class="..FXNewsProviderMethodReplacer">
    </bean>
    <!--其他bean配置-->
    ...
复制代码

执行djNewsProvidergetAndPersistNews方法结果:

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。

转载于:https://juejin.im/post/5c63df59518825629a77a8fa

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值