properties文件是常用的配置文件类型,本文以properties为例,总结了线上环境常遇到的加载properties文件的问题
先给一个例子
项目A:
其中ReadProperties内容如下:
package com.young.properties;
public class ReadProperties {
static {
read();
}
public ReadProperties(){
System.out.println("+++++++++++++++");
readInner();
}
private void readInner() {
//方式 1
InputStream inputStream = this.getClass().getResourceAsStream("/test.properties");
Properties properties = new Properties();
try {
properties.load(inputStream);
} catch (IOException var13) {
}
System.out.println(properties);
}
private static void read() {
//方式 2
InputStream inputStream = ReadProperties.class.getClassLoader().getResourceAsStream("test.properties");
Properties properties = new Properties();
try {
properties.load(inputStream);
} catch (IOException var13) {
}
System.out.println(properties);
}
}
test.properties 内容如下:
key=A
value=2
加载配置文件的方式
上述方式1和方式2是常用的加载方式,使用方式1加载test.properties时,要加上代表根目录的前缀;若以不带"/"的方式加载,如下图显示,实际上是在当前class的路径下寻找,自然是找不到的。
方式2不能添加"/",默认是在classpath的根路径下寻找文件。
项目B:
另有Maven工程B依赖A
工程B下面也包含了一个同名的test.properties文件。
test.properties
key=B
value=1
public class App {
public static void main( String[] args ) {
new ReadProperties();
}
}
如果在idea中运行上述main方法,结果是什么呢?可以发现读的是项目B的配置。
{key=B, value=1}
+++++++++++++++
{key=B, value=1}
为什么呢?
idea里默认的classpath的顺序如下图所示(这些路径下的class都由SystemClassLoader加载),第一个为jdk下的jar,第二个Module source代表target/classes,第三个就是项目A的class;因为项目B的配置在第二个Module source下,所以上面读的是项目B的配置。如果把下图中的相对顺序改变一下,读的配置就会不同(你可以试一下)。
线上环境运行
大家都知道,线上运行代码的方式与编辑器里稍有不同。要么将代码打成fat-jar,要么将所有的依赖单独放在一起(通常是放在单独的文件夹lib中),并将这些依赖放入classpath中。
fat-jar
fat jar会将依赖以及配置文件都打包进一个jar文件中,使用maven的朋友可能会使用maven-assembly-plugin来进行打包。把上述工程B打成fat jar目录如下:
此时会发现该配置文件test.properties是工程A的配置,工程B的配置被工程A中同名的配置覆盖掉了。因此在开发时,依赖中配置和主项目的配置名称最好不要相同,否则打成fat jar时会出现上述问题。maven-assembly-plugin可能有相关配置可以解决这个覆盖问题,本人并没有找到。
依赖单独打包
除了fat jar,通常线上也会采用将依赖放在独立的文件夹中的方式进行部署。
将上述工程B这样打包后,目录如下:
运行下面的命令
//读到了B.jar里的配置
java -classpath B.jar:lib/A.jar com.young.App
//读到了A.jar里的配置
java -classpath lib/A.jar:B.jar com.young.App
//读到了lib下即A.jar里的配置
java -Djava.ext.dirs=/.../lib -classpath B.jar com.young.App
java -cp B.jar -Djava.ext.dirs=/.../lib com.young.App
前两种都是按照classpath下jar包字典顺序寻找,找到第一个就停止遍历;第三个依赖是由ExtClassLoader加载的,所以jar包的顺序不重要。当com.young.App去加载配置时,先委托给父ClassLoader(ExtClassLoader)加载,所以先加载了A.jar的配置。
//resource的加载也是双亲委派模型
public URL getResource(String name) {
URL url;
if (parent != null) {
url = parent.getResource(name);
} else {
url = getBootstrapResource(name);
}
if (url == null) {
url = findResource(name);
}
return url;
}
总结
当项目中多个地方出现相同的配置文件时,读取的文件会与打包的方式以及项目的名称有关。尽量在项目中保持配置文件命名的唯一性。