上周,好友反馈了一个问题,他们项目在本地是可以跑的,但是在线上环境,就报错.报错日志如下:
说实话,每天这么忙,看到这种直接丢个异常出来的根本不想理.但是他一句话彻底改变了我的想法.
首先出现了这个几个关键词.
无法解决的bug
之前我反复强调,我们看源码,是为了解决问题,而不是简单为了面试装装逼,如果搜索引擎随便搜索第一页都能解决,那还看源码真的是风骚走位完美避开了最高效的解决问题方式
特定环境出现
从聊天记录中可以看出,该问题还受到环境的条件限制,不方便模拟,最关键是我还不能直接连上他们公司的环境去帮他看问题.
望闻问切
其实很多人写了几年代码之后都常常感叹,写代码真的好容易,就是用各种框架,堆积木式编程.其实他们之所以有这样的感叹,主要是工作中遇到的挑战还不够多.以至于他们认为 原理、 源码这些东西纯粹只是面试装逼.
当然会看源码解决搜索引擎无法解决问题,还是远远不够的.高并发下.会出现很多难以重现的问题,这个时候,必须要学会一个新的技能,就是通过日志,通过眼神编译, 静态看源码.
因此,我询问得到了报错日志如下:
从报错日志中可以看出,这个报错还和 Mybatisplus有关,但是我没用过什么 Mybatisplus啊,这可如何是好?没关系,前面都说了,静态看源码, 眼神编译!,于是我开始新建一个demo,引入相关的依赖.
九浅一深
将上图的异常栈再标记一下重点
从我标记的三个重点加上小学简单的英文就可以看出,在解析 UserPersonalMapper.xml时,没有找到 BaseResultMap.另外一点,从我标记中的重点中也可以看出,这个 BaseResultMap是在另外一个XML,也就是 UserMapper.xml中声明的.
这个时候可能就有朋友想到,那是不是加载 UserPersonalMapper.xml的时候, UserMapper.xml还没加载导致的呢.导致无法找到 UserMapper.xml定义的 BaseResultMap
坦白说,这个猜测,一点毛病都没有,非常合情合理
但是最关键的是,本地跑是没问题的.那为什么我本地跑的时候,又没有报错,这个你又怎么解释?
很多朋友都问到我怎么看源码,那么我现在就手把手,根据仅有的线索,九浅一深直入源码.
日志告诉我们是583行的时候报错的(图中已圈),然后 mapperLocations也很明显,就是我们配置我mapper集合,他就是从这里集合中遍历出每一个mapper来进行解析的.
那么关键问题来了,我们现在是静态看代码,眼神编译,我们打不了断点,那么这个mappe是什么时候set进去的,set了哪些值呢?为了做到毫不保留向公众号粉丝传输心路历程,我就详细截图一下.
以下几个技巧完全是 IDEA的使用问题
1.查看变量在哪里被引用
2.查看方法在哪里被调用
终于,让我们找到了核心处理逻辑
从 resolveMapperLocations和 PathMatchingResourcePatternResolver这两个类名和返回值 Resource[],哪怕是把单词拆开一个一个翻译都大概能猜出,这个是根据配置的 /*.xml这种配置,找到所有的xml资源. Resource[]是数组,数组是有序的,所以这个数组中的元素(mapper)顺序的顺序,就能决定我们前面的猜想是不是正确的.
验证猜想
于是我就叫该好友添加上这段日志,验证一下猜想
然后他把能正常启动的日志和异常的日志发出来,如下
我们发现,果然如我们所料,这个加载的顺序果然有问题,异常启动的, UserMapper.xml在最后才加载,自然导致遍历的时候最后才解析到这个xml,所以这个xml上定义的 BaseResultMap不能被之前加载的使用
但是关键问题是,还是没说清楚,为什么本地就没问题.为什么本地跑加载的顺序就OK了呢?
深入浅出
现在范围已经很小了,我们从前面的猜想,到验证猜想,已经把目标逐步缩小,现在问题就只剩下一个,只要弄清楚 PathMatchingResourcePatternResolver的逻辑,一切就豁然开朗了
因为线上环境,都是打成jar依赖启动的.而本地走的是classes,所以他们走的代码分支是不一样的
深入思考
其实还有一个猥琐的办法,把 PathMatchingResourcePatternResolver这个类拷贝出来,改个名字.比如 FeichaoPathMatchingResourcePatternResolver.然后打上一些简陋的日志信息,如下图
然后你本地启动,和jar启动,看日志输出.
那么,这两个分支究竟有什么区别呢?这个本地代码走的代码分支,有一段很重要的逻辑
他这里会根据资源文件进行排序,那么到底根据什么规则排序
网友评论