Spring扩展点-MethodOverride(运行时方法重写)

1. 了解什么是Spring当中的MethodOverride

Spring框架当中,为我们提供了一个机制,称为MethodOverride,我们称之为运行时方法重写。

1.1 XML版本的IOC容器中的相关实现

其实运行时方法重写,在SpringXML版本当中就已经提供了实现,可以通过配置lookup-methodreplace-method这两个标签去进行的相关配置,就会使用到运行时方法重写这个玩意。

image.png

XML版IOC容器中,我们来看看它的XmlBeanDefinitionReader组件对于bean标签的解析过程中,其中就包含了对这两个标签的解析

image.png

这个类所在的类和方法是BeanDefinitionParserDelegate.parseBeanDefinitionElement

我们来看关键的对ReplaceOverrideLookupOverride的处理工作

image.png
image.png

我们可以看到,针对于replace-method标签,被解析成为一个ReplaceMethodOverride对象,加入到BeanDefinition当中,而对于lookup-method标签,则是被解析成为一个LookupMethodOvrride对象。

我们来看MethodOverride和这些组件有什么关系?

image.png

我们可以看到,MethodOverride的两个实现就是LookupOverrideReplaceOverride

MethodOverride对象被维护在哪里?实际上在BeanDefinition当中就有维护这样的一个列表,用来存放需要进行运行时方法重写的方法信息。

image.png

1.2 注解版IOC容器中的实现

在注解版中,并未提供ReplaceMethodOverride的实现,但是对于LookupOverride,是有提供相关的实现的。

在注解版的IOC容器中,提供了@Lookup这样一个注解,专门用来提供实现LookupMethodOverride,对应的就是XML版本当中对于lookup-method的实现。

1.3 ReplaceMethodOverrideLookupOverride如何使用?

LookupMethodOverride的功能,是提供让方法能够返回自定义的对象,而不是调用目标方法去return对象,也就是实现的是方法返回值的替换功能。

比如,可以通过如下这样的注解版的配置方式去使用到LookupOverride

    @Lookup
    public User getUser() {
        return null;
    }

在xml当中,对应的等价代码是

image.png

它实现的功能是:

  • 1.如果@Lookup注解配置了value属性,那么,将会从容器中按照beanName去获取Bean作为方法的返回值去进行返回;如果没配置value属性,那么将会按照方法的返回值作为beanType,从容器中去获取Bean
  • 2.lookup-method指定的方法本身可以是抽象方法(比如接口当中的方法),因为并不会执行到真实的方法的真正逻辑,而是走的代理逻辑去从容器中获取要返回的对象。
  • 3.因为lookup-method实际上就是调用getBean从容器中拿对象,因此它可以实现原型Bean的获取功能。

ReplaceMethodOverride实现的功能是什么?它其实更有方法重写这句话的韵味,在运行时去替换目标方法的逻辑。

我们首先编写一个自定义的Replacer,实现MethodReplacer接口,并实现它的reimplement方法,在这个方法当中执行的逻辑,实际上就你要替换的方法逻辑。

    public static class Replacer implements MethodReplacer {
        @Override
        public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
            return null;
        }
    }

下面要做的,就是在XML当中去进行配置(注解版似乎并未提供ReplaceMethodOverride的相关实现)。

image.png

这样,在运行时,目标方法,就会被替换成为我们自定义的Replacer组件当中实现的方法。

1.4 @Lookup注解的扫描

既然注解版容器也有提供@Lookup的支持,那么必然会存在扫描这个注解的逻辑,那么这个注解究竟是在何处被扫描的呢?

这个注解的扫描其实隐藏的比较深,在AutowiredAnnotationBeanPostProcessor这个组件的推断构造器的同时对@Lookup注解去进行处理。

而这个组件本身的作用是什么?其实就是处理@Autowired/@Resource/@Inject这些注解的自动注入逻辑,另外一方面是使用一大段的逻辑去进行推断即将要进行创建Bean的构造器,最终根据候选构造器选出一个合适的构造器去创建目标对象。

我们可以看到它是遍历beanClass以及它的所有父类当中的所有标注了@Lookup注解的方法,将其封装成为LookupOverride对象,加入到BeanDefinition当中。

image.png

2. Spring当中如何实现MethodOverride

2.1 对MethodOverride的实现

其实实现运行时方法重写很简单,就是创建代理嘛,代理的目的不就是运行时方法重写嘛,因此很显然是创建代理去实现的逻辑。我们从Spring源码当中找到如何创建代理的逻辑?

doGetBean当中,有如下的代码

image.png

很显然,我们可以知道,创建Bean的真正逻辑,一定是在createBeanInstance这个方法当中的。

image.png

在这个方法当中,首先有两个逻辑的判断,就是使用Supplier去创建Bean(这个一般使用的少,但是其实有在使用,如果有了解过SpringCloud-OpenFeign组件的源码的朋友,相信会见过这个Supplier的使用),以及使用FactoryMethod(@Bean标注的方法,这个用的就比较多了)去创建逻辑。这部分我们都暂时不管,pass掉。

image.png

接下来,就是根据自动注入的类型(XML版本当中配置的自动注入的情况),以及推断出来的构造器的类型,选用instantiateBean或者是autowireConstructor这两个方法去创建对象。

其实最终都会走到SimpleInstantiationStrategy中的下面这样的一个方法,这里才是最终的逻辑。

image.png

如果没有MethodOverride的情况,直接使用构造去创建对象就行了,如果有MethodOverride的情况,那么需要走到下面的逻辑,我们从注释当中已经看清楚了,使用CGLIB去生成目标子类。

image.png

我们来看真正进行实例化的逻辑,我们发现已经差不多到底了,已经到了设置Callbacks的层面了,设置了一个LookupOverrideMethodInterceptor和一个ReplaceOverrideMethodInterceptor这两个关键的回调。

image.png

为什么设置了多个回调呢?怎么确定运行时要使用哪个回调?其实这就涉及到CGLIB代理当中的CallbackFilter,当accept方法返回了Callbacks数组中的哪个位置的索引index,运行时将会采用的目标回调就是Callbacks[index]所在的Callback

image.png

2.2 ReplaceMethodOverrideLookupMethodOverride对应的回调

对于LookupMethodOverride的实现,我们可以看到,就是如果配置了name,将会采用按名去进行注入,如果没配置name,那么将会按照方法的返回值作为type去进行注入,蛮简单的。

image.png

对于ReplaceMethodOverride的实现,其实相当简单,从容器中按照beabName去获取配置的Replacer对象,然后调用Replacer对象的reimplement方法就行。

image.png

我们可以总结一个点:运行时方法重写,其实就是使用JDK/CGLIB去创建代理,去拦截掉目标方法的逻辑,去指定要替换的逻辑,搞不定,用代理总没错,在Spring当中对于这句话的体现的淋漓尽致!

个人博客:http://wanna1314y.top:8090/

你可能感兴趣的:(Spring扩展点-MethodOverride(运行时方法重写))