记一次Tomcat7和Spring4下配置无法读取的调试过程

本文介绍

本文是一篇小记,多图,切勿当作教程看待!

描述

一个SpringMVC项目,出现了在Tomcat7下能正常运行,但是使用Tomcat9/8部署时,会有某个配置文件的属性值读取错误的问题(该属性值是${redis.password})。项目的配置结构如下:

配置
Spring版本4.0.4.RELEASE
项目架构多模块项目,domain、service、dao、web分成四个模块,使用mvn install和pom.xml引用依赖的方式进行关联
Tomcat版本7.0.94 (能正常运行的Tomcat版本)

分析

起因

起初项目一直使用Tomcat7部署,有一次本地调试时用的是Tomcat9,结果运行时发现配置文件的属性${redis.password}的值读取错误了,不是项目的配置文件的值。该项目的redis的配置属性共有3个,包括ipportpassword,但结果只有password属性的值读取错误。

可能的原因

在检查了项目配置后,发现最重要的一个配置,是Springxml配置文件中指明了classpath*,这个表示将扫描classpath其他jar包内置的properites配置文件。

<context:property-placeholder location="classpath*:*.properties" ignore-unresolvable="true"/>

在了解了项目启用加载jar包内其他配置文件的配置后,接下来可能就是如下几个方向了:


  1. 项目引用的第三方jar包里有配置文件,并且可能是跟我项目的${redis.password}重名了
  2. 为什么Tomcat9会出现配置值读取错误,而Tomcat7不会,跟Tomcat版本有关系吗?

其中第一点马上就得到验证了,是正确的方向,项目引用了一个smart-sso的单点登录jar包,里面的确有个service.properties的配置文件,并且也有个${redis.password}的属性值,且该属性值就是Tomcat9部署下读取到的那个值。那现在就只能是分析第2点了。

针对配置文件加载顺序,可以先补充一点,配置文件的加载顺序,在SpringMVCXML配置里面,是可以通过order属性来指定加载顺序的,order值越小,加载优先级别越高

如何用idea调试Tomcat源码

在调试项目代码过程中,发现到一旦运行到Tomcat部分的代码时,idea总是突然间就不动了,光标一直在某行中停着,直到step out几步之后才开始跳到Spring源码的其他地方。还好idea给出了提示,的确是运行到Tomcat的源码部分。(下图红线部分表示idea运行到了org.apache.catalina.loader的代码)

在这里插入图片描述

idea怎么调试Tomcat源码,其实也挺简单,把相关Tomcat版本的依赖添加到pom.xml就好了,如下所示


        <!--仅调试使用-->
        <!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-catalina -->
        <!--        <dependency>-->
        <!--            <groupId>org.apache.tomcat</groupId>-->
        <!--            <artifactId>tomcat-catalina</artifactId>-->
        <!--            <version>9.0.22</version>-->
        <!--            <scope>provided</scope>-->
        <!--        </dependency>-->
        <!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-catalina -->
        <!--        <dependency>-->
        <!--            <groupId>org.apache.tomcat</groupId>-->
        <!--            <artifactId>tomcat-catalina</artifactId>-->
        <!--            <version>8.5.43</version>-->
        <!--            <scope>provided</scope>-->
        <!--        </dependency>-->
        <!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-catalina -->
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-catalina</artifactId>
            <version>7.0.94</version>
            <scope>provided</scope>
        </dependency>

添加后,之后idea调试到tomcat源码部分,就可以正常进入了

跟Tomcat版本有什么关系?

首先我建了一个测试项目testProgram,该项目testProgram依赖了我的一个jar包,包内有个配置文件app.properties,里面仅有属性redis.password=newcih,然后测试项目testProgram有三个配置文件,分别是jdbc.propertiesjdbd.propertiesz.properties

之所以这样命名,是为了测试配置文件加载顺序是否跟文件名的字典顺序有关,实验证明配置文件的加载顺序跟文件名的字典顺序无关。

为了真实还原现场,调试的代码可能有真实项目中的,也有本测试项目testProgram中的,同时由于调试的代码包括了Spring框架和Tomcat,所以一旦出现源码部分,我会明说是Spring的或者是Tomcat的源码。为了缩短篇幅,对于正常的部分,不进行调试说明,如存在多配置文件时,Spring会逐一加载配置文件,存在重复键值时,则后加载覆盖先加载。

  • 测试操作

移除真实项目本身的配置文件后,发现Tomcat7启动会报错,显示${redis.password}无法解析出值,而Tomcat9则可以正常启动并读取到值。

  • 提出假设

这边假设是Tomcat79在加载配置文件的实现不同。

  • 查看启动日志

如图所示,通过启动日志可以发现,负责加载配置文件的类是SpringPathMatchingResourcePatternResolver,具体位置是org.springframework.core.io.support,位于spring-core包。

在这里插入图片描述

下图即类PathMatchingResoucePatternResolver的关键方法的源码

在这里插入图片描述

通过调试Tomcat79下的运行情况,可以发现,在该方法内,Tomcat7加载到的资源明显比Tomcat9要少非常多,可以看的出来,少的那些就是classpath*模式下应该要加载出来的那些jar包。如下图所示。

在这里插入图片描述

上图是Tomcat7的运行情况,下图是Tomcat9的运行情况。

在这里插入图片描述

该方法是Spring框架的源码,所以不应该是Spring的原因造成Tomcat79加载资源不同。可以注意到,方法里指明资源是getResources方法加载出来的,调试进入该方法,还是Spring的实现,方法实现如下。当资源加载模式是classpath*时,进入下划线的方法中。下划线的方法实现如第二张图。

在这里插入图片描述

下图即为上图下划线方法的具体实现

在这里插入图片描述

直到这个Spring的方法实现出来,我才开始看到Tomcat相关的东西了。即方法中的getClassLoader()cl.getResouces(path),这里获取到的类加载器是Tomcat自实现的WebappClassLoader (双亲委托机制什么的,就不balabala了。。),这里的类加载器实际上是个抽象类WebappClassLoaderBase,有两个实现类WebappClassLoaderParallelWebappClassLoader,(调试过程中发现Tomcat7采用WebappClassLoader类实现,而Tomcat8/9使用ParallelWebappClassLoader实现,不过这个不是影响的因素,因为加载资源的方法是写在WebappClassLoaderBase里面的)

  • 上代码

下面分别列出Tomcat79在关键部分的源码,其上下部分的调试过程略过,基本可以定位到如下位置的实现导致Tomcat79在资源加载时的不同,其实Tomcat8的这个方法实现和9是一样的。该类的具体位置是org.apache.catalina.loader.WebappClassLoaderBase,方法是findResources(String name)

在这里插入图片描述


我是分割区间,上Tomcat7,下Tomcat9


在这里插入图片描述

上第一图是Tomcat7实现,由于代码过长,截取一段,记住那个红色框起来的代码部分。上第二图是Tomcat89的实现。Tomcat对于资源的加载,虽然实现的方法以及不同了,但是Tomcat7采用的jarFiles数组的方式时,jarFiles数组的大小其实跟Tomcat9webResources数组大小的值只差1 (Tomcat7加载到133个资源,Tomcat9加载到134个资源),即少了WEB-INF/classes目录,因为jarFiles不包含目录。如下图。

在这里插入图片描述

上图是Tomcat7加载的资源,下图是Tomcat9加载的资源

在这里插入图片描述

到这一步的时候,Tomcat79加载的资源其实是一样的,也包括了第三方jar包,但是jarproperties文件为什么最后是Tomcat7少了很多呢?问题就是刚才红线框起来的代码部分,它把所有jarfiles都跳过了,没有把路径添加到result值里面。这时候方法的返回值,也就是result,就变成只有一个值,就是WEB-INF/classes。而Tomcat9的返回值,也就是result的集合封装,大小是134,是全部返回的。 (这里其实省略了一部分代码,如Tomcat7的返回值result是1个对象,但是外部资源却显示大小为2,实际上外部有一层父类资源加载,那一层多了个资源对象,就是/Users/newcih/Downloads/apache-tomcat-7.0.94/lib/,所以最后返回值大小为2)。 所以实际上Tomcat7findResources方法并不会去加载第三方jar包内部的properties文件。

我个人其实对技术研究不是那么热爱,技术更新迭代那么频繁的时代下,有些东西我都只作非常简单的了解,不想深究。比如为什么Tomcat7要加一层jarEntry不为null时的判断才允许添加该资源,明明你赋值JarFile对象时就不给manEntry属性赋值,让它成null的。等等之类的东西,这篇文章其实也不是教程,只是一个小记,怕以后还遇到这种问题不知所措。对于Tomcat789的源码分析可能也有误,仅供参考。

不仅仅是Tomcat

就在我以为,这个事情可以告一段落了,是Tomcat7不支持的问题时。转念一想,不对劲啊。这可是Tomcat7啊!!世界上大把项目在用着,难道就没人提出来吗?

我这时候才想起来,之前一直是真实项目中发生的情况,所以一直是在调试真实项目的代码,这时候才想起新建一个项目来测试才能验证是否正确。于是新建了如上一开始所说的testProgram测试项目,结果测试发现。Tomcat7Tomcat9部署下,均可以正常获取得依赖包里面的properties文件的属性值。(我是直接注释testProgram项目的配置文件的相关属性了,只留下依赖包的配置文件的属性存在,但是Tomcat7部署下是可以正常获取的)。

遇到这种看起来奇葩的事情时,不要着急,物理老师告诉过我们要用控制变量法。想想这个测试项目跟真实项目不同的地方在哪里?

我仔细看了看,是Spring版本的不同,真实项目是 4.0.4.RELEASE,测试项目是 4.2.6.RELEASE。都是4.x版本啊?能差到哪里去?

不管,不慌,按照一开始Spring部分的源码开始调试进去看看。果然在Spring这一层面的时候,源码就不一样了,跟Tomcat加载无关了,上图有截图到Spring一个findAllClassPathResources方法的实现,还记得吗?

你能记得才有鬼?我调试过那么多遍都要回去看看呢!

下图直接给出Spring这个方法在4.0.x版本和4.1.x版本的区别。

在这里插入图片描述

可以看出来,在Spring4.1.x版本时,加载资源已经多了一个方法了,就是下面这个


		if ("".equals(path)) {
			// The above result is likely to be incomplete, i.e. only containing file system references.
			// We need to have pointers to each of the jar files on the classpath as well...
			addAllClassLoaderJarRoots(cl, result);
		}

实际调试证明,Tomcat7进入这个方法前,result还是大小为2,但是经过这个方法之后,result的大小增到171。。。如下图
在这里插入图片描述

所以很明显的一点是,Tomcat7的一个不足之处 (或者说是一段我不了解为什么这么写的代码),好像被Spring给顺带修复了?而且人家Spring的注释好像也说了,上面那部分加载出来的不是全部的jar资源,需要下面那个方法去加载classpath的每一个jar文件呢!但是再仔细想想,Spring是不是只是刚好在这个版本加了这个功能而已,以后可能就没有了!

于是赶紧把Spring仓库的分支切换到5.1.x,可能不是最新,但是也挺新的了,看看这个方法的具体实现更新成什么样了,如下图所示:

在这里插入图片描述
在这里插入图片描述

额,,一模一样,看来这个方法的实现是不会大修大改了。


写得有点乱,总结一下,其实就是一个SpringMVC项目,在Spring加载配置文件的配置中,使用了classpath*,导致在Tomcat7下部署是正常运行的,但是用Tomcat8/9部署时,发生配置属性被覆盖的事情。原因是,Tomcat7本身不去加载jar包内配置文件,所以不会有配置属性覆盖的情况,而Tomcat8/9会加载,所以会覆盖。那Tomcat7本身不去加载jar包内配置文件的行为不是很好,所以Spring 4.1的版本已经有顺带处理了,附带下图在SpringGithub上看到的issue,不知道是否与此有关

在这里插入图片描述
因个人能力问题,本文仅作为小记,用于提醒以后遇到同样的情况,但不作为一篇讲解和说明分析。

解析方案

  1. 改属性键名,这样做最不会影响项目,只要不是依赖的jar包内配置文件有的属性键值就行了
  2. 如果不必要,可以不要在Spring配置里的加载配置使用classpath*,用classpath就好了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

newcih

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值