背景
上一篇讲了我们从另外一个部门迁移了一个线上系统回来,迁回来是为啥呢,因为这个好几年没新需求的系统,突然有新需求要开发,然后我就开发呗,其实就是在某个服务里加点表,然后提供个查询接口给app。这个服务用的架构是厂商的,不是servlet容器那一套,它技术还是很厉害,其实是c语言写了个reactor这种类似netty的通信框架,通信协议是私有的tcp协议,然后启动时还通过jni拉起了一个java虚拟机,通信框架就负责底层通信,并且像servlet那样来调用上层的java类中的service方法这样。
至于为啥厂商这么玩,我觉得无非是把底层这套通信框架打成二进制提供给我们,提高技术壁垒,防破解啥的,并且通过私有协议提升维护难度,这样的话,就可以一直收我们维护费了。这些就不过多吐槽了,反正好多服务就是这样被厂商绑死了。
在windows上,这个进程没法像tomcat这类容器一样运行起来,每次只能对java代码部分进行junit单元测试,只能在linux上才能运行起来。
像java代码部分,我们自己是用了spring那一套,就像下面这样,启动时直接new一个spring的ClassPathXmlApplicationContext,而下面的location就是application.xml这种spring配置文件的url。

由于厂商也提供了些自己的访问数据库的框架,之前这个服务就是用的厂商这套(类似于jdbcTemplate操作sql语句);我接手这个服务之后,感觉不太喜欢厂商那套,就还是引入mybatis的mapper这套,如下:

结果,我本地junit调试时好好的,丢到服务器上,直接运行不起来了。给我报了个空指针。

解决过程
各种怀疑
上面的图里,看着是mybatis在创建一个什么XPathFactory的时候报错了,然后上图又说解析什么mapper.xml失败了,我还在想,是文件路径没对吗,结果文件路径挺对的:

接下来,开始怀疑起了mybatis,由于接手的这个项目,素质不怎么样,各种jar包冲突啥的,大家看我下图就知道了,就上面报错的堆栈里的org.apache.ibatis.parsing.XPathParser
这个类,我在项目里一搜,发现同名类有两个,为啥呢,因为下面第一个jar包是厂商的,它把mybatis的源码封进了自己的jar包里,且没改包名:

当然了,厂商那个jar包里不止这一个,拷贝了很多类进来,也不知道拷贝进来后改了些啥。
我这时候的想法是,看看到底加载的哪个class吧。
查看类加载情况
然后在jvm的启动参数加了打印类加载的jvm参数: -verbose:class

注意,这个是打印到console,或者对console进行重定向到文件。
我这边看了下,发现是对的,仔细检查了,没发现加载了厂商的jar包的类,看起来是对的:

看堆栈相关代码
然后开始在本地junit调试,结果没走到报错的地方。
看之前那个错误堆栈,报错就是在下面的jdk1.7中的XPathFactoryFinder
类的220这一行:

然后,这个resource应该就是触发空指针的,如果说resource为null,那么217行这个迭代器就有问题,通过迭代器的next获取出来的值为null;而迭代器是在215行的createServiceFileIterator
赋值的。

点进来后就傻了,有分支,不知道走了哪一条,好在天无绝人之路,此时,之前的-verbose:class参数起了作用:

通过这里分析,在加载完XPathFactoryFinder类后,不久就加载了javax.xml.xpath.XPathFactoryFinder.SingleIterator,那意思就是走了if那个分支。
但目前对怎么解决这个问题还是不清楚,无奈,先本地debug。
debug
虽然本地junit不能完全模拟linux服务器上的状况,但是先试试吧,结果,本地调试发现:
在之前那个分支处,本地windows时,classloader不是null:

那我是否怀疑了classloader的问题呢,怀疑的不多,因为以前本地调试tomcat的时候已经遇到过,本地debug时用的classloader和最终把war包丢到tomcat里运行时用的classloader那些,确实不一样。
在debug的过程中,对这个类的理解加深了一些,看起来,这个XPathFactoryFinder主要就是要获取到一个XPathFactory。

先是从property中查找,property为javax.xml.xpath.XPathFactory:http://java.sun.com/jaxp/xpath/dom
,如果能找到对应的实现类,就用这个实现类去创建对应的XPathFactory,这块就和java中的SPI机制类似:

接下来呢,会尝试从配置文件获取C:\Program Files\Java\jdk1.7.0_80\jre\lib\jaxp.properties
:

如果还是没有,才会走到我们报错的地方,即用spi机制,问题是我们因为classloader为null,走不到下图这里:

开启debug日志
在debug过程中,发现很多debug日志:

这个日志如何开启呢?

我们设置了下,如下:

这下,通过日志,我们更加明确了程序运行的轨迹。
设置property解决bug
接下来,就是怎么解决了,在debug过程中,我们发现,本地windows的话,默认最终的实现类就是com.sun.org.apache.xpath.internal.jaxp.XPathFactoryImpl
,其实就是jdk自带的:


那我们这里也就好说了,设置下property吧,解决问题就行了,至于为啥classloader为null,就没有继续深究了:

这次就运行正常了:

总结
bug记录下还是挺好,因为很快就忘了,后来要写上线变更文档,才想起来这么个事。