Java加载依赖中properties文件

properties文件是常用的配置文件类型,本文以properties为例,总结了线上环境常遇到的加载properties文件的问题

先给一个例子

项目A:

maven工程 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的根路径下寻找文件。

方式1 不带"/"加载


项目B:

另有Maven工程B依赖A
maven工程 B

工程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的配置。如果把下图中的相对顺序改变一下,读的配置就会不同(你可以试一下)。


idea默认的classpath顺序

线上环境运行

大家都知道,线上运行代码的方式与编辑器里稍有不同。要么将代码打成fat-jar,要么将所有的依赖单独放在一起(通常是放在单独的文件夹lib中),并将这些依赖放入classpath中。

fat-jar

fat jar会将依赖以及配置文件都打包进一个jar文件中,使用maven的朋友可能会使用maven-assembly-plugin来进行打包。把上述工程B打成fat jar目录如下:

fat-jar

此时会发现该配置文件test.properties是工程A的配置,工程B的配置被工程A中同名的配置覆盖掉了。因此在开发时,依赖中配置和主项目的配置名称最好不要相同,否则打成fat jar时会出现上述问题。maven-assembly-plugin可能有相关配置可以解决这个覆盖问题,本人并没有找到。

依赖单独打包

除了fat jar,通常线上也会采用将依赖放在独立的文件夹中的方式进行部署。
将上述工程B这样打包后,目录如下:


image.png

运行下面的命令

//读到了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;
    }

总结

当项目中多个地方出现相同的配置文件时,读取的文件会与打包的方式以及项目的名称有关。尽量在项目中保持配置文件命名的唯一性。

你可能感兴趣的:(Java加载依赖中properties文件)