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

  • 如你对本文有疑问或本文有错误之处,欢迎评论留言指出。如觉得本文对你有所帮助,欢迎点赞和关注。
相关文章
|
5天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
18 2
|
21天前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
11天前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
37 9
|
1月前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
111 5
|
1月前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
1月前
|
设计模式 JavaScript Java
Spring 事件监听机制源码
Spring 提供了事件发布订阅机制,广泛应用于项目中。本文介绍了如何通过自定义事件类、订阅类和发布类实现这一机制,并展示了如何监听 SpringBoot 启动过程中的多个事件(如 `ApplicationStartingEvent`、`ApplicationEnvironmentPreparedEvent` 等)。通过掌握这些事件,可以更好地理解 SpringBoot 的启动流程。示例代码展示了从事件发布到接收的完整过程。
|
1月前
|
缓存 Java Spring
源码解读:Spring如何解决构造器注入的循环依赖?
本文详细探讨了Spring框架中的循环依赖问题,包括构造器注入和字段注入两种情况,并重点分析了构造器注入循环依赖的解决方案。文章通过具体示例展示了循环依赖的错误信息及常见场景,提出了三种解决方法:重构代码、使用字段依赖注入以及使用`@Lazy`注解。其中,`@Lazy`注解通过延迟初始化和动态代理机制有效解决了循环依赖问题。作者建议优先使用`@Lazy`注解,并提供了详细的源码解析和调试截图,帮助读者深入理解其实现机制。
31 1
|
1月前
|
存储 开发框架 Java
什么是Spring?什么是IOC?什么是DI?IOC和DI的关系? —— 零基础可无压力学习,带源码
文章详细介绍了Spring、IOC、DI的概念和关系,解释了控制反转(IOC)和依赖注入(DI)的原理,并提供了IOC的代码示例,阐述了Spring框架作为IOC容器的应用。
29 0
什么是Spring?什么是IOC?什么是DI?IOC和DI的关系? —— 零基础可无压力学习,带源码
|
1月前
|
XML Java 数据格式
手动开发-简单的Spring基于注解配置的程序--源码解析
手动开发-简单的Spring基于注解配置的程序--源码解析
46 0
|
1月前
|
XML Java 数据格式
手动开发-简单的Spring基于XML配置的程序--源码解析
手动开发-简单的Spring基于XML配置的程序--源码解析
79 0