当Spring的Bean的成员变量为null时,想一想动态代理
写在前面
这是我第一次写文章,还是有一些想要说的话,只对内容感兴趣的小伙伴们请略过。 本人是在大连的日资企业做对日项目(懂的都懂,debuff拉满),近来感觉到技术成长陷入了瓶颈。需要一些外在因素来促进自己的进步。 因为没有实际使用场景+人懒话多,不太喜欢自己做一些项目,所以选择写一些文章(输出带动输入)。 主要内容是在平时解决疑难杂症的过程中,发现的值得思考的问题。
起因
在一个平(shi)平(he)无(mo)奇(yu)的工作日,组里的一个小老弟遇到了一个奇怪的问题叫我过去看看。
问题是Spring的Bean中成员变量,有个方法中调用会是null,而其他方法中没有问题。
这里使用的是SpringBoot 2.X
,简单写一下伪代码
public interface Service {
OutDtoA methodA(InDtoA in);
OutDtoB methodB(InDtoB in);
OutDtoC methodC(InDtoC in);
}
@Service
public class ServiceImpl implements Service {
private Mapper mapper;
ServiceImpl(Mapper mapper){
this.mapper = mapper;
}
@Override
OutDtoA methodA(InDtoA in) {
return mapper.mapA(in);
}
@Override
OutDtoB final methodB(InDtoB in){
return mapper.mapB(in); // 执行到这里会发现mapper为null。methodA、methodC方法都没有问题。
}
@Override
OutDtoC methodC(InDtoC in){
return mapper.mapC(in);
}
}
到这里聪明的小伙伴们已经发现问题了,由于小兄弟的失误,methodB
前多了个final
修饰。
但实际代码量和注释量很大,找了半天才发现这个final
。当时也没多想,抱着试一试的态度删除了final
运行了一下,发现问题解决了,到这里我不由得思考了一下,为什么会出现这种奇怪的现象呢。
分析问题
问题已经明确,是由于函数的final
导致的,顺着final
来思考一下。
如果函数加上final
修饰会产生什么影响呢,基本就是子类无法继承这个函数。
而这个业务级别的Service没有子类,排除了业务代码的问题。突然灵光一闪,拍案而起,是CGLIB
啊。
到这里需要一些java动态代理的知识,如果有不太了解的小伙伴,可以看一下站内大佬们的文章,写的都很好。
我会在下面简单介绍一下,不需要的朋友们可以略过
动态代理简介
动态代理是一种可以在不修改原代码的前提下,添加功能的设计模式。当前被广泛使用,Spring的AOP就是基于此完成的。
就Spring来讲,动态代理的实现方式有两种,一种为JDK动态代理
,一种为CGLIB
。
JDK动态代理:
通过代理类来包裹被代理类,通过实现相同接口,替代被代理类
。
CGLIB:
通过创建被代理类的子类,并且拦截被代理类的方法调用,转移至代理类的相应方法,通过子类调用父类的方法,替代被代理类
。
梳理
顺着CGLIB
的思路,简单梳理一下产生成员变量为null的现象。
OK流程: ①调用目标方法 ②CGLIB代理类拦截,并调用代理类中对应方法 ③正常处理
NG流程: ①调用目标方法 ②由于目标方法被final修饰,无法被拦截,导致直接调用了目标方法 ③Spring只为我们创建了代理类的实例,没有创建被代理类的实例,所以就没有调用被代理类的构造函数,成员变量为null
问题到此基本就结束了,但是在我的印象里,Spring中如果被代理类有接口的话,会默认使用JDK动态代理
,而现象却不是,为此还需要进一步调查。
调查
翻看了大佬们的文章,发现Spring确实如此,而SpringBoot却不太一样。
先说结论:
①Spring根据被代理类是否有接口来判断,有接口就使用JDK动态代理
,没有就使用CGLIB
②SpringBoot 2.0以前会默认使用JDK动态代理
,而2.0以后会默认使用CGLIB
(可以通过配置指定代理模式)
由于英语能力有限,在SpringBoot官方文档中没找到,就不贴官方说明了。
有兴趣的可以参考大佬们文章,会贴在最后。
到这里为止,所有的问题都能理的通。但是呢,正常JDK8之后的JDK动态代理
处理效率会高于CGLIB
,而SpringBoot团队又是为了什么退而求其次呢。
在翻看文章时发现了 SpringBoot的issue ,是关于动态代理的讨论。
简单来讲,官方为了更低的使用门槛 (JDK动态代理必须要使用接口) ,而使用了CGLIB
。只要类与方法前没有final
修饰,就适配了大部分的使用场景。
但是实际使用时需要注意是否引用了大量使用final的包
,如果有的话就需要考虑更换代理方式了。
引用
记录一下对自己有帮助的一些文章,上述过程中有不明白地方的小伙伴们,可以看一下大佬们的文章(写的真好!) 如果有版权问题,请留言或者联系我,必删! 动态代理 Spring的动态代理
转载自:https://juejin.cn/post/7199484168294907941