xsd文件命名缺失导致的故障

801 阅读4分钟

背景

公司一个小系统主要用来做批量文件解析入库之类的事情,最近由于主机国产化迁移,所以要将程序迁移到新主机上,但是迁移到新主机后后,程序一直启动失败,异常信息如下图。而这次主机迁移其实并不涉及程序上的改造,只是把程序包丢到新机器上去启动运行。程序部署也很简单,将编译机打包好的tar包解压,解压后是一个lib目录,lib目录下是所有相关的依赖包,启动命令则类似java -cp a.jar;b.jar;c.jar com.cz.WorkerStarter。

image.png

问题查证过程:

刚开始发现启动失败的时候怀疑是编译机打包丢失了依赖包,于是将生产包拿到测试环境解压后发现可以正常启动,于是开始逐个比对依赖包的大小,无意中发现将有问题的lib目录备份一下,然后将原lib目录删除,再新建一个lib目录,最后将之前备份的lib复制回去,这一顿操作听起来有点扯,但是操作完之后确实启动成功了。但是这个解决方式并不能让自己满意,也无法解释为什么会启动失败。于是开始从报错日志入手,研究了一下xml为什么会解析异常?

1、xml文件现状

image.png 从报错信息大概可以看出是因为解析xml文件时不识别22行的caching标识。

1.1 程序如何识别xml中的特定标识?

答案就是xsd文件。对于程序而言不管xml中是什么标签都无所谓,但是为了规范,会指定命名约束。 而xsd文件简单来讲就是用来规范xml中标签的命名,目的就是为了程序能够快速解析xml。一般spring的xml配置文件都会带有这种xsd文件来约束开发者。

1.2 xsd文件在哪里?

image.png 一般而言xml文件前几行会显示的展示xsd的网络路径,在互联网环境下解析xml的程序一般会直接通过http请求来获取xsd文件,但是如果在内网环境下由于网络策略原因访问互联网肯定是不行的,那么如何获取xsd文件呢?答案就是官方提供的jar包,比如spring-context-3.2.17.RELEASE.jar。jar包中有一个spring.schemas文件,它用来映射xsd网络地址对应的jar包中的xsd本地文件位置。

image.png

2、查证结果

从现象上来看,程序启动时加载业务侧test-config.xml文件时,由于不识别cache:caching标签导致启动失败。而cache:caching是否可被识别取决于命名空间的配置,“caching”元素的规范定义在www.springframework.org/schema/cach… 中,当程序解析xml时会优先访问www.springframework.org/schema/cach… 获取元素定义信息,当网络不通时则会访问jar包中的本地xsd文件。根据jar包中spring.schemas配置来看www.springframework.org/schema/cach… 网络地址对应的本地xsd文件存储路径为org/springframework/cache/config/spring-cache-3.1.xsd。因此有异常的环境程序启动时读取的是org/springframework/cache/config/spring-cache-3.1.xsd。笔者通过获取lib目录下的所有jar包解压后的文件名,筛选得到两个spring-cache-3.1.xsd。其中一个在spring-cache-2.0.0-RELEASE.jar中,另一个在spring-context-3.2.17.RELEASE.jar中。通过比对两个包中的xsd文件,发现spring-cache-2.0.0-RELEASE.jar中xsd文件缺少了caching的定义。

3、在其他环境为什么没有问题?

进程启动不成功的根本原因是spring-cache.jar缺少caching的命名空间,但是为什么迁移国产化系统之前一直不报错呢?原因是进程在启动时加载jar包的顺序决定了a*.jar在解析xml配置文件时用选用哪个xsd,而进程加载jar包的顺序是由启动命令决定的,启动命令中拼接的jar包顺序程序即程序加载的顺序,而拼接jar包的顺序则是由启动脚本中的find ./java_app/lib '*.jar'命令决定,由于不同系统不同主机的find结果返回顺序并不一致,因此当a*.jar在最后被加载时,程序大概率就会出问题了。而上文提到的复制lib包,最后再复制回去这种操作后,会改变find命令的返回结果顺序,因此会启动成功,但是这种操作具有一定的不确定性。所以最终的解决方式一定是解决spring-cahce.jar缺少caching命名的问题,而临时的解决方案则应该修改启动脚本中的find命令,在find执行之后进行自然排序(jars=find $pubpath/ -name *.jar | sort -V).

完整启动脚本

pubpath="$HOME/java_app/lib"
if [ -d "$pubpath" ];

then

#jars=`ls $pubpath/*.jar`

jars=`find $pubpath/ -name *.jar | sort -V`

for jar in $jars; do

  cp=$cp:$jar

done

fi

java -cp $cp com.cz.WorkerStarter