外置application.yml读取异常Bug记录
外置application.yml读取异常Bug记录
排查问题
大年二十七,公司马上要放假了,结果接到客户反馈前不久更新的服务数据库链接异常,报错信息为ORA-01017。引起这个报错通常可能是下面的原因:
-
Oracle用户过期
-
Oracle用户被锁定
-
配置文件密码错误
-
配置文件的编码异常,无法正确被解析
对这些问题一一排查后,竟然都是正常的!瞬间我整个人不正常了,到底是什么问题呢??????
补充一下项目部署目录的文件结构:
|-app.jar
|-application.yml
|-application-prod.yml
|-start.sh
application.yml和application-prod.yml都和app.jar同级,并且application.yml中的active就是prod。
springboot配置文件读取优先级
我们知道在springboot项目中外置的yml文件优先级如下:
- file:./config/ ,即优先读取jar包外的config目录中的配置文件
- file:./ ,即第二顺序读取jar包外的config目录同级的配置文件
- /config ,即项目目录下和src同级的config目录
- classpath:/config/ ,即第三顺序读取jar包内classpath下/config文件夹内的配置文件
- classpath:/ ,即第四顺序读取jar包内classpath中的配置文件
如果在优先级更高的目录找到了可用的key,就会直接获取,未找到的key会在优先级第一级中继续获取。即最终生效的文件可能是好几个文件的合集。
注意:如果最高级别的active是prd,此级别却没有 prd环境的配置的话,即使更低优先级的目录有也不会采用,会直接报错。
解决办法
最终抱着死马当活马医的态度,我准备将程序的jar包解压,把外置配置文件替换到jar包内重新启动,解压后的目录结构如下:
# 解压jar包 到xxx目录
unzip xxx.jar -d xxx
# 也可以使用jar命令解压,但是jar命令无法指定解压到的目录
jar -xvf xxx.jar
解压后的文件目录如下:
|-BOOT-INF
|--classes
|--lib
|-META-INF
|-org
其中/BOOT-INF/classes内存放的是yml文件,将外置的yml文件复制到此目录下进行覆盖操作。
使用jar命令将文件压回
# 进入存放BOOT-INF、META-INF、org三个文件夹的目录
cd /xxx
# 在xxx目录执行此命令,压缩三个目录为jar包,取名为xxx.jar
jar cfM0 xxx.jar *
# 如果是windows机器打jar 提示没有此命令,可以在开始菜单搜索jar 确认jar的目录,例如在:C:/java/jdk/bin/jar.exe,可以在xxx目录用如下命令:
C:/java/jdk/bin/jar.exe -cfM0 xxx.jar *
将jar包打包放到服务器,删除所有外置文件重新启动
奇迹发生了,程序一切正常~~~~~~~终于可以安心过年了。。。。。。。。。。。
后续我又在测试环境检查了外置文件,默认读取的确实是外置的文件,甚至使用md5sum
命令计算了两个jar包的MD5也是一样的。这个问题后续有空会好好研究一下,后续结果后更新~
更新,补充一种可能会导致不读取外部yml文件的可能
加入部署包位置在/opt/app
,那么使用java -jar /opt/app/xxx.jar
的形式运行jar包不会读取外部的yml文件,会默认读取jar包内部的。
只有先cd到jar包所在目录/opt/app
,再执行java -jar xxx.jar
才会生效。
优化建议
对于生产环境的程序,尽量增加一步操作:检验配置文件的准确性,因为外置配置文件和jar包内的key可以控制到一致,所以可以忽略key替换的问题,只需确认目前生效的是哪一个文件即可。
注意:下面案例中增加Version的办法仅适用于配置文件与内部完全一致的情况。
增加检测类
在yml文件中增加version,针对jar包内的文件和jar包外的version值进行区分。
application.yml内容
spring:
profiles:
active: dev
application-dev.yml内容
version: 1.0.0
检验代码,这里也可以替换为验证中间件链接是否OK,并打印状态:
package cn.vantee.runner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
/**
* @author :rayfoo@qq.com
* @date :Created in 2022/1/30 11:03 下午
* @description:启动类自动执行块
* @modified By:
* @version: 1.0.0
*/
@Component
//如果有多个runner需要指定一些顺序
@Order(1)
public class ApplicationRunner implements ApplicationRunner {
@Value("${version}")
private String version;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println(version);
}
}
启动类
package cn.vantee;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author :rayfoo@qq.com
* @date :Created in 2022/1/30 10:59 下午
* @description:启动类
* @modified By:
* @version: 1.0.0
*/
@SpringBootApplication
public class ApplicationStater {
public static void main(String[] args) {
SpringApplication.run(ApplicationStater.class,args);
}
}
根据打印结果,就可以确认最终生效的是哪一个文件啦~
其他建议
针对数据库、中间件的连接健康状态检验,也可以在Runner中编写,验证的顺序可以使用@Order(X)
控制
分割线
2022.3.1更新 破案啦~
关于外置文件读取异常的问题,原因是因为pom文件中的spring-boot-maven-plugin插件。https://docs.spring.io/spring-boot/docs/current/maven-plugin/reference/htmlsingle/#goals-repackage-parameters-details-executable
使用某些插件打包会导致jar包被加密,无法使用解压工具打开替换其内部的文件(提示压缩包以损坏),也无法使用vim直接修改jar包内的内容。
建议包上生产前可以先用vim验证一下是否可以编辑。