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
当中对于这句话的体现的淋漓尽致!
个人博客:http://wanna1314y.top:8090/