spring依赖注入成功但在调用接口的时候拿到的依赖却是null问题
作者:不喜欢吃猫的鱼
前言
使用过spring的同学们都知道,如果出现了依赖注入失败的问题首先会想到以下几点:
1、对应的类有没有写@Service或@Component注解以供能被spring扫描注册;
2、在配置中有没有配置要扫描的包路径,或是对应的类是否在已配置的包路径下;
3、配置的包路径错误导致扫描失败;
4、同一个项目中jar冲突导致在注册bean过程失败进而导致注入不成功;
问题描述
使用springboot的时候发现项目正常编译运行,所有的bean都被扫描加载了,但是在前端调用接口的时候发现controller里的service竟然是null。
具体如下图所示:
由上图可以看出Device对应的那个bean在spring容器中已经是被注册过了的,就意味着并不是对应的类没有被加载扫描到。
但我通过在测试类中运行发现使用同样的注入方式是可以注入成功并成功运行,如下图所示:
注入测试
为了进一步测试是否能正常的注入依赖,将原先的注入方式改成了构造注入的方式,发现也是能够正常的注入进去,如下图所示:
通过以上测试发现,这次的问题并不是因为扫描路径或是注解的问题导致的,当然这里的jar依赖这些也是正常的。
分析
仔细回想下这次的问题,项目服务能正常启动,在spring的bean容器中也能找到对应的bean那就说明bean注册过程是没有问题的。
接下来通过注入测试发现这几个service也是能够正常的注入到controller里,那说明项目中DI这个过程也是OK的,否则的话不可能是换一种注入方式就能注入。
由此可见,此问题并不是因为spring的问题导致。接下来本菜鸟又查询了一些资料说可能是因为动态代理的问题导致,项目中没有独特申明代理方式所以默认使用的是JDK的动态代理。
转眼一想感觉是发现什么了(此处省略一万个滑稽…),感觉离成功又更近一步了。
这里我们先来看下常用的动态代理:
1、jdk动态代理:
- 使用jdk动态代理只能对实现了接口的类生成代理,而不能针对类;
- 使用反射生成代理类;
2、cglib动态代理:
- 使用cglib代理是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承);
- 底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。
了解到这里后本菜感觉想到点什么了,本菜的项目中没有单独设置代理模式,所以spring对bean处理的时候默认是使用了jdk动态代理,而项目里的service并非是接口而是一个class,而且还未实现某个接口,只是单纯的class
本菜就想,是不是因为接口是单纯的类没有实现接口,而jdk的动态代理没有将这个类代理到,所以导致注入失败了,于是本菜就设置了下代理为cglib动态代理:
但经本菜鸟测试发现,问题依旧存在…(内心开始MMP了)。这时本菜鸟又想起了另一个点,spring在对bean进行代理的时候会自行选择代理方式:
(1)当Bean实现接口时,Spring就会用JDK的动态代理;
(2)当Bean没有实现接口时,Spring就使用CGlib动态代理;
所以说再次申明代理方式也是没有用的。
除了以上的一些疑点,本菜又发现出现问题的代码里使用了spring的AOP,
于是开始怀疑是不是AOP搞鬼了,于是找了相关代码
从这里可见这里AOP切面切点用在了调用controller方法前,进而再联想到项目目前的代理方式是cglib动态代理而非jdk动态代理。于是猜想是不是因为AOP切入的时候代理未生效没有将对应的bean给到对应的方法里,后面继续深入查询看看@Aspect使用的代理方式
锁定这个
继续深入,看到这里发现看到有ProxyCreator (代理创建)
判断生成的bean是否能够被代理
创建代理类bean实例
代码较多省略了部分,这里继续走到获取代理
这里我们使用的是cglib动态代理
关键代码判断对应bean是否需要被代理并返回实例
设置过滤
中间的代码比较多,省略了部分,关键代码
这里可以看到对映射的方法进行了过滤,依次看过去发现这里存在疑虑
看到这里就有疑问了,因为使用AOP的时候对访问修饰符是没有啥影响的,public、private、protect都是可以的,所以最终问题应该是这里在使用cglib的时候把私有方法过滤掉了不被代理,所以导致了在controller中依赖注入是成功的但调用这个接口方法时拿到的依赖为null,回看项目源代码:
解决问题
把这里的private改成public就可以了…
反思
论写代码仔细的重要性!!!虽然此次的事故代码不是出自我手,但是这个小小的问题却耗费了我好久时间,这里让我不得不反思下以后写代码要仔细仔细再仔细,同时遇到bug的时候在排查问题的时候也是要仔细仔细仔细!不然很简单的问题也是会被忽略掉。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。