当Spring的Bean的成员变量为null时,想一想动态代理

1:起因

在一个平(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){
        // 执行到这里会发现mapper为null。methodA、methodC方法都没有问题。
        return mapper.mapB(in); 
    }
    
    @Override
    OutDtoC methodC(InDtoC in){
        return mapper.mapC(in);
    }
}

到这里聪明的小伙伴们已经发现问题了,由于小兄弟的失误,methodB前多了个final修饰。
但实际代码量和注释量很大,找了半天才发现这个final。当时也没多想,抱着试一试的态度删除了final运行了一下,发现问题解决了,到这里我不由得思考了一下,为什么会出现这种奇怪的现象呢。

2:问题分析

问题已经明确,是由于函数的final导致的,顺着final来思考一下。
如果函数加上final修饰会产生什么影响呢,基本就是子类无法继承这个函数。
而这个业务级别的Service没有子类,排除了业务代码的问题。突然灵光一闪,拍案而起,是CGLIB啊。

3:动态代理简介

动态代理是一种可以在不修改原代码的前提下,添加功能的设计模式。当前被广泛使用,Spring的AOP就是基于此完成的。
就Spring来讲,动态代理的实现方式有两种,一种为JDK动态代理,一种为CGLIB。
JDK动态代理:
通过代理类来包裹被代理类,通过实现相同接口,替代被代理类。
CGLIB:
通过创建被代理类的子类,并且拦截被代理类的方法调用,转移至代理类的相应方法,通过子类调用父类的方法,替代被代理类。

4:梳理

顺着CGLIB的思路,简单梳理一下产生成员变量为null的现象。

OK流程:
①调用目标方法
②CGLIB代理类拦截,并调用代理类中对应方法
③正常处理

NG流程:
①调用目标方法
②由于目标方法被final修饰,无法被拦截,导致直接调用了目标方法
③Spring只为我们创建了代理类的实例,没有创建被代理类的实例,所以就没有调用被代理类的构造函数,成员变量为null

问题到此基本就结束了,但是在我的印象里,Spring中如果被代理类有接口的话,会默认使用JDK动态代理,而现象却不是,为此还需要进一步调查。

5:调查

翻看了大佬们的文章,发现Spring确实如此,而SpringBoot却不太一样。
先说结论:
①Spring根据被代理类是否有接口来判断,有接口就使用JDK动态代理,没有就使用CGLIB
②SpringBoot 2.0以前会默认使用JDK动态代理,而2.0以后会默认使用CGLIB(可以通过配置指定代理模式)
【由于英语能力有限,在SpringBoot官方文档中没找到,就不贴官方说明了。
有兴趣的可以参考大佬们文章,会贴在最后。】

到这里为止,所有的问题都能理的通。但是呢,正常JDK8之后的JDK动态代理处理效率会高于CGLIB,而SpringBoot团队又是为了什么退而求其次呢。
在翻看文章时发现了 SpringBoot的issue ,是关于动态代理的讨论。

简单来讲,官方为了更低的使用门槛 (JDK动态代理必须要使用接口) ,而使用了CGLIB。只要类与方法前没有final修饰,就适配了大部分的使用场景。
但是实际使用时需要注意是否引用了大量使用final的包,如果有的话就需要考虑更换代理方式了。

6:引用

记录一下对自己有帮助的一些文章,上述过程中有不明白地方的小伙伴们,可以看一下大佬们的文章(写的真好!)
如果有版权问题,请留言或者联系我,必删!

动态代理
Spring的动态代理

文章转载

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值