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

简介: lookup-method 通常称为获取器注入,spring in action 中对它的描述是,一种特殊的方法注入,它是把一个方法声明为返回某种类型的 bean,而实际要返回的 bean 是在配置文件里面配置的。
日积月累,水滴石穿 😄

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">
    // name 值为 getBean
    <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");
        // 调用 start 方法
        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 。还需注意 scope 的配置,如果 scope 配置为 singleton,则每次调用 getBean 方法 ,返回的对象都是相同的;如果 scope 配置为 prototype,则每次调用返回都不同。

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

<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 属性中。

  • 如你对本文有疑问或本文有错误之处,欢迎评论留言指出。如觉得本文对你有所帮助,欢迎点赞和关注。
相关文章
|
7天前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
7天前
|
XML 缓存 Java
手写Spring源码(简化版)
Spring包下的类、手写@ComponentScan注解、@Component注解、@Autowired注解、@Scope注解、手写BeanDefinition、BeanNameAware、InitializingBean、BeanPostProcessor 、手写AnnotationConfigApplicationContext
手写Spring源码(简化版)
|
7天前
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
|
23天前
|
人工智能 前端开发 Java
【实操】Spring Cloud Alibaba AI,阿里AI这不得玩一下(含前后端源码)
本文介绍了如何使用 **Spring Cloud Alibaba AI** 构建基于 Spring Boot 和 uni-app 的聊天机器人应用。主要内容包括:Spring Cloud Alibaba AI 的概念与功能,使用前的准备工作(如 JDK 17+、Spring Boot 3.0+ 及通义 API-KEY),详细实操步骤(涵盖前后端开发工具、组件选择、功能分析及关键代码示例)。最终展示了如何成功实现具备基本聊天功能的 AI 应用,帮助读者快速搭建智能聊天系统并探索更多高级功能。
174 2
【实操】Spring Cloud Alibaba AI,阿里AI这不得玩一下(含前后端源码)
|
2月前
|
Java Spring 容器
Spring Boot 启动源码解析结合Spring Bean生命周期分析
Spring Boot 启动源码解析结合Spring Bean生命周期分析
80 11
|
30天前
|
设计模式 Java 程序员
学习 Spring 源码的意义是什么呢?
研究Spring源码能深化框架理解,提升代码分析与设计能力,助您掌握设计模式及最佳实践,增强解决问题的效率,促进职业生涯发展,并激发技术热情。选择稳定版本,从核心模块开始,结合实际项目并参与社区,让学习之旅既充实又具乐趣。
|
2月前
|
缓存 Java 程序员
spring IoC 源码
spring IoC 源码
47 3
|
2月前
|
JavaScript Java 数据安全/隐私保护
基于SpringBoot+Vue毕业生信息招聘平台系统【源码+论文+演示视频+包运行成功】_基于spring vue的校园招聘系统源码(2)
基于SpringBoot+Vue毕业生信息招聘平台系统【源码+论文+演示视频+包运行成功】_基于spring vue的校园招聘系统源码
73 0
基于SpringBoot+Vue毕业生信息招聘平台系统【源码+论文+演示视频+包运行成功】_基于spring vue的校园招聘系统源码(2)
|
2月前
|
JavaScript Java 关系型数据库
基于SpringBoot+Vue毕业生信息招聘平台系统【源码+论文+演示视频+包运行成功】_基于spring vue的校园招聘系统源码(1)
基于SpringBoot+Vue毕业生信息招聘平台系统【源码+论文+演示视频+包运行成功】_基于spring vue的校园招聘系统源码
51 0
|
3月前
|
Java Spring 容器
spring重点标签有哪些
spring重点标签有哪些