1. 了解什么是Spring当中的MethodOverride
在Spring框架当中,为我们提供了一个机制,称为MethodOverride,我们称之为运行时方法重写。
1.1 XML版本的IOC容器中的相关实现
其实运行时方法重写,在Spring的XML版本当中就已经提供了实现,可以通过配置lookup-method和replace-method这两个标签去进行的相关配置,就会使用到运行时方法重写这个玩意。

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

这个类所在的类和方法是BeanDefinitionParserDelegate.parseBeanDefinitionElement。
我们来看关键的对ReplaceOverride和LookupOverride的处理工作


我们可以看到,针对于replace-method标签,被解析成为一个ReplaceMethodOverride对象,加入到BeanDefinition当中,而对于lookup-method标签,则是被解析成为一个LookupMethodOvrride对象。
我们来看MethodOverride和这些组件有什么关系?

我们可以看到,MethodOverride的两个实现就是LookupOverride和ReplaceOverride。
MethodOverride对象被维护在哪里?实际上在BeanDefinition当中就有维护这样的一个列表,用来存放需要进行运行时方法重写的方法信息。

1.2 注解版IOC容器中的实现
在注解版中,并未提供ReplaceMethodOverride的实现,但是对于LookupOverride,是有提供相关的实现的。
在注解版的IOC容器中,提供了@Lookup这样一个注解,专门用来提供实现LookupMethodOverride,对应的就是XML版本当中对于lookup-method的实现。
1.3 ReplaceMethodOverride和LookupOverride如何使用?
LookupMethodOverride的功能,是提供让方法能够返回自定义的对象,而不是调用目标方法去return对象,也就是实现的是方法返回值的替换功能。
比如,可以通过如下这样的注解版的配置方式去使用到LookupOverride。
@Lookup
public User getUser() {
return null;
}
在xml当中,对应的等价代码是

它实现的功能是:
- 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的相关实现)。

这样,在运行时,目标方法,就会被替换成为我们自定义的Replacer组件当中实现的方法。
1.4 @Lookup注解的扫描
既然注解版容器也有提供@Lookup的支持,那么必然会存在扫描这个注解的逻辑,那么这个注解究竟是在何处被扫描的呢?
这个注解的扫描其实隐藏的比较深,在AutowiredAnnotationBeanPostProcessor这个组件的推断构造器的同时对@Lookup注解去进行处理。
而这个组件本身的作用是什么?其实就是处理@Autowired/@Resource/@Inject这些注解的自动注入逻辑,另外一方面是使用一大段的逻辑去进行推断即将要进行创建Bean的构造器,最终根据候选构造器选出一个合适的构造器去创建目标对象。
我们可以看到它是遍历beanClass以及它的所有父类当中的所有标注了@Lookup注解的方法,将其封装成为LookupOverride对象,加入到BeanDefinition当中。

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

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

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

接下来,就是根据自动注入的类型(XML版本当中配置的自动注入的情况),以及推断出来的构造器的类型,选用instantiateBean或者是autowireConstructor这两个方法去创建对象。
其实最终都会走到SimpleInstantiationStrategy中的下面这样的一个方法,这里才是最终的逻辑。

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

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

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

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

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

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