【工作技术栈】【源码解读】一次springboot注入bean失败问题的排查过程

目录

前言

对这次的过程排查如果要形容的话,我觉得更像是悬疑剧,bean not found 这种错误,已经看腻了,甚至有时候都看不起这种错误,但是似乎这个想法被springboot听见了,所以这几天他就给我来了一记大耳刮子。。。

现象

版本(抛开版本就是耍流氓~)

jdk8

sprintboot 2.3.12

现象

首先我们的项目存在一个名叫common的项目,另一个叫fusionXXX的微服务依赖了这个common的项目,并且common中存在一个beandefination注册的过程(具体可以参考mybatis的mapper),因为要实现动态代理。

ok,当前idea同时打开了common项目和fusionXXX的项目,并且在一个window下,无需install到maven本地就可以直接运行,运行成功没有任何问题。

但是,现在我们将common项目install进了自己的maven仓库之后,用idea直接打开fusionXXX项目,这个时候开始运行fusionXXX项目,使用@Autowire来获取我们自己的bean,结果发现bean注入不进来,报错 bean not found

这是什么原因???

分析原因

因为二合一的项目确实可以运行,所以我认为代码没有问题,一定是springboot出了问题!具体来说应该是我使用springboot出了问题,这个分析方向对最终问题的排查起到了关键性的作用。

没啥好说的,直接开debug模式找到bean not found的堆栈处,自己去logback.xml或者log4j.xml 文件中设置日志级别,设置之后springboot会将初始化的过程打印出来,报错之后会直接打印堆栈日志,这样直接就可以点进来看了,我这边直接运行二合一和单fusionXXX来比较哪里出现了不一致导致了bean没有注入,最终定位到这里两个项目出现了不一致的问题!

因为是Autowire注解,该注解先通过Type来获取bean,获取不到之后再使用name的方式获取bean,二合一在这里返回的String[] 存在一个bean的名称就是我给自己的bean起的名字,但是单fusionXXX就没有,所以我们继续看503行,因为cache刚开始是没有值的,与此同时我们也了解到springboot的bean查找存在一层缓存(不虚此行!)

这里我们看到如果type匹配到了才会添加这个beanName(这里的beanName是循环的临时变量,这里使用了循环的方式进行type的匹配,循环的就是spring实例化bean前的beanDefination列表,只有spring第一次实例化bean的情况才会这样循环,目前我们代码中有1222个定义,所以启动起来最耗费时间的应该就是这个过程了。),二合一的536行已经匹配到了,但是fusionXXX没有匹配到那么我么继续追踪下去。

通过层层的比对,最终发现不一致的地方在348行,单fusionXXX在这里直接进入了349行,我们看下最终返回false的条件为什么是false

终于到了重要的地方,这里我们发现单fusionXXX的两个Class不是一个对象,但是二合一的都是一个对象


上面两个图如果不注意正常来讲ClassName都是一样的,但是确实无法相互赋值,为什么呢?ClassLoader不一样。。。这种真的很难排查。。。那么现在分析一下为什么不一样

首先,如果两个类的ClassLoader不一样,那么jvm会认为两个类不是同一个类,即使两个类的reference一模一样,就如上面所看

ChatGPT说RestartClassLoader是springboot中的devtools的一个类加载器,其功能是为了热加载类文件设计的,当二合一运行时,在idea中,除了jar包以外的所有class都是可编辑的,所以他们都统一被RestartClassLoader加载,因此在二合一中的两个类的加载器都是RestartClassLoader

如果不是二合一,也就是单fusionXXX的时候,common模块以jar包形式进来,在beandefination加载的时候,jar包中的类提前手动(我自己写的)被java中的应用加载器加载(AppClassLoader ,java经典三层类加载器 :bootstrap -> ext->app),而在运行过程中,比对前spring确实找到了对应的类,但是却使用了devtools中的classloader进行了加载,从而导致了在匹配Type的时候无法匹配而最终找不到bean。

被restartclassloader加载的类可以在class文件修改的时候重新加载从而起到热修改的作用。

解决方法

将devtools注释掉即可,所有类都通过java的appclassloader加载就好了,但是不支持热修改了

思考感悟

这次的排查使用了不少的时间才排查到,一个很常见的错误本以为很快就能解决,没想到下面藏了一个冰山。。。

整个项目仅仅在本地运行不起来,打包成功后都能够运行,总的来说不会影响到业务,但是后面引入jar包的时候也应该对jar有一定的了解。

相关推荐
CoderYanger4 小时前
C.滑动窗口-求子数组个数-越长越合法——2799. 统计完全子数组的数目
java·c语言·开发语言·数据结构·算法·leetcode·职场和发展
C++业余爱好者4 小时前
Java 提供了8种基本数据类型及封装类型介绍
java·开发语言·python
想用offer打牌4 小时前
RocketMQ如何防止消息丢失?
java·后端·架构·开源·rocketmq
皮卡龙4 小时前
Java常用的JSON
java·开发语言·spring boot·json
利刃大大5 小时前
【JavaSE】十三、枚举类Enum && Lambda表达式 && 列表排序常见写法
java·开发语言·枚举·lambda·排序
float_六七5 小时前
Java反射:万能遥控器拆解编程
java·开发语言
han_hanker5 小时前
java 异常类——详解
java·开发语言
源码获取_wx:Fegn08955 小时前
基于springboot + vue健身房管理系统
java·开发语言·前端·vue.js·spring boot·后端·spring
峥嵘life5 小时前
Android16 EDLA 认证测试CTS问题分析解决
android·java·服务器
Mr1ght5 小时前
为什么 InheritableThreadLocal 在 Spring 线程池中“偶尔”能传递变量?——一次线程池上下文传播的误解
java·spring