Spring源码(二-2)-lookup-method、replaced-method标签

lookup-method

通常称为获取器注入,spring in action中对它的描述是,一种特殊的方法注入,它是把一个方法声明为返回某种类型的bean,而实际要返回的bean是在配置文件里面配置的,可用在设计可插拔的功能上,解除程序依赖。

  1. 首先创建一个父类,并编写一个方法 eat()。
/**
 * 父类:水果
 */
public class Fruit {

    public void eat(){
        System.out.println("吃什么水果呢");
    }
}

2、然后创建一个子类,继承父类,并重写父类的方法。

/**
 * 子类:苹果
 */
public class Apple extends Fruit {

    @Override
    public void eat(){
        System.out.println("吃苹果");
    }
}

3、创建调用方法

public abstract class TestLookupMethod {

    public abstract Fruit getBean();
    /**
     * 这个方法的创建不会对于 LookupMethod 的覆盖不会有任何影响
     * 这个参数Spring并不会去处理它
     * 影响的地方也就是在 createBean() 里的 prepareMethodOverrides() 方法了,
     * overloaded 属性不会被设置为 false 了。

AbstractBeanDefinition#prepareMethodOverride
protected void prepareMethodOverride(MethodOverride mo) throws BeanDefinitionValidationException {
        // 获取对应类中对应方法的个数
        int count = ClassUtils.getMethodCountForName(getBeanClass(), mo.getMethodName());
        if (count == 0) {
            throw new BeanDefinitionValidationException(
                    "Invalid method override: no method with name '" + mo.getMethodName() +
                    "' on class [" + getBeanClassName() + "]");
        }
        else if (count == 1) {
            // Mark override as not overloaded, to avoid the overhead of arg type checking.
            //标记 overloaded 暂未被覆盖 避免参数类型检查的开销
            mo.setOverloaded(false);
        }
    }
     */
    public abstract Fruit getBean(String str);
    public void start(){
        this.getBean().eat();
    }
}

4、然后编写配置文件

<bean id="fruit" class="com.gongj.lookupMethod.Fruit"></bean>
<bean id="apple" class="com.gongj.lookupMethod.Apple"></bean>

<bean class="com.gongj.lookupMethod.TestLookupMethod" id="lookupMethod">
    <lookup-method name="getBean" bean="apple"></lookup-method>
</bean>

5、进行测试

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("lookup-method.xml");
        TestLookupMethod bean =(TestLookupMethod) context.getBean("lookupMethod");
        bean.start();
    }
输出结果:吃苹果

当业务变更或者在其它情况下, apple里面的业务逻辑已不再符合我们的业务要求,需要进行替换怎么办 ?这时我们需要新增逻辑类。

/**
 * 子类:香蕉
 */
public class Banana extends Fruit{

    @Override
    public void eat(){
        System.out.println("正在吃香蕉");
    }
}

然后只需修改配置类,新增名为 banana 的Bean,并将 lookup-method 标签中配置的 apple 修改为 banana 。再次调用测试方法。

<bean class="com.gongj.lookupMethod.Banana" id="banana"></bean>
    <bean class="com.gongj.lookupMethod.TestLookupMethod" id="lookupMethod">
        <lookup-method name="getBean" bean="banana"></lookup-method>
    </bean>

Spring框架通过使用CGLIB库中的字节码来动态生成覆盖该方法的子类,从而实现此方法注入。所以被覆盖的类不能为final,并且要被覆盖的方法也不能为final。还需注意 banana 的 scope 的配置,如果 scope 配置为singleton,则每次调用 getBean 方法 ,返回的对象都是相同的;如果scope配置为prototype,则每次调用,返回都不同。

在包含要注入的方法的 TestLookupMethod 类中,要注入的方法需要以下形式的签名:

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

  • public|protected:要求方法必须是可以被子类重写和调用的。
  • abstract:可选,如果是抽象方法,CGLIB的动态代理类就会实现这个方法,如果不是抽象方法,就会覆盖这个方法。
  • return-type:返回类型,当然可以是它的父类或者接口。
  • no-arguments:不允许有参数。

到这,我们已经会使用 lookup-method 这个标签了,接下来我们就结合源码去看。直接进入到BeanDefinitionParserDelegate 类的 parseLookupOverrideSubElements方法。

    public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) {
        // 获取该bean节点下所有子节点
        NodeList nl = beanEle.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            //获取每个子节点
            Node node = nl.item(i);
            // 子元素为 lookup-method 时才有效
            if (isCandidateElement(node) && nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) {
                Element ele = (Element) node;
                // 获取 name 属性的值,也就是要修饰的方法
                String methodName = ele.getAttribute(NAME_ATTRIBUTE);
                // 获取 bean 属性的值,也就是配置返回的bean 名称
                String beanRef = ele.getAttribute(BEAN_ELEMENT);
                // 根据 methodName、beanRef 构建 LookupOverride 对象
                LookupOverride override = new LookupOverride(methodName, beanRef);
                override.setSource(extractSource(ele));
                // 将该对象添加到 MethodOverrides对象里的 overrides集合中
                overrides.addOverride(override);
            }
        }
    }

replaced-method

方法替换:可以在运行时用新的方法替换现有的方法,与之前的 look-up 不同的是,replaced-method 不但可以动态地替换返回实体 bean ,而且还能动态地更改原有方法的逻辑。我们先来看使用示例:

1、再次新创建一个类,里面提供两个重载方法。


public class Replaced {

    public void replacedName(String name){
        System.out.println("我是初版:"+name);
    }

    public void replacedName(String name,Integer age){
        System.out.println("我是初版:" + name + "年年:"+ age);
    }
}

2、编写一个实现了 MethodReplacer 的类

public class TestReplacedMethod implements MethodReplacer {
    @Override
    public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
        for (Object arg : args) {
            System.out.println("接收参数:" + arg);
        }
        return null;
    }
}

3、修改配置文件

<bean class="com.gongj.lookupMethod.Replaced" id="replaced">
        <replaced-method name="replacedName" replacer="replacedMethod">
<!--<arg-type></arg-type>可以配置任意多个 取决于想替换哪个重载方法-->
            <arg-type>String</arg-type>
            <arg-type>Integer</arg-type>
        </replaced-method>
    </bean>

    <!-- replacedMethod -->
    <bean class="com.gongj.lookupMethod.TestReplacedMethod" id="replacedMethod"></bean>

4、测试类

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("replace-method.xml");
        Replaced bean =(Replaced) context.getBean("replaced");
        bean.replacedName("gongj",88);

    }
输出结果:
接收参数:gongj
接收参数:88

运行测试类就可以看到预期的结果了,也就是做到了动态替换原有方法,知道了这个元素的用法,我 再次来看元素的提取过程。

直接进入到BeanDefinitionParserDelegate 类的 parseReplacedMethodSubElements方法。

public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) {
        // 获取该bean节点下所有子节点
        NodeList nl = beanEle.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            //获取每个子节点
            Node node = nl.item(i);
            // 子元素为 replaced-method 时才有效
            if (isCandidateElement(node) && nodeNameEquals(node, REPLACED_METHOD_ELEMENT)) {
                Element replacedMethodEle = (Element) node;
                // 获取 name 属性的值,也就是要被替换的旧方法
                String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE);
                // 获取 replacer 属性的值,也就是新的替换方法
                String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE);
                // 根据 name、replacer 构建 ReplaceOverride 对象
                ReplaceOverride replaceOverride = new ReplaceOverride(name, callback);
                // Look for arg-type match elements. 寻找arg-type元素
                List<Element> argTypeEles = DomUtils.getChildElementsByTagName(replacedMethodEle, ARG_TYPE_ELEMENT);
                for (Element argTypeEle : argTypeEles) {
                    // 记录参数
                    String match = argTypeEle.getAttribute(ARG_TYPE_MATCH_ATTRIBUTE);
                    match = (StringUtils.hasText(match) ? match : DomUtils.getTextValue(argTypeEle));
                    if (StringUtils.hasText(match)) {
                        replaceOverride.addTypeIdentifier(match);
                    }
                }
                replaceOverride.setSource(extractSource(replacedMethodEle));
                // 将该对象添加到 MethodOverrides对象里的 overrides集合中
                overrides.addOverride(replaceOverride);
            }
        }
    }

我们可以看到无论是 lookup-method 还是 replaced-method 都是构造了 MethodOverride,并最终记录在了 AbstractBeanDefinition 中的 methodOverrides 属性中。

参考文献:
spring bean中子元素lookup-method和replaced-method
Spring - lookup-method方式实现依赖注入

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容