简体   繁体   中英

ClassLoader: Resources from Callee

I have a project which contains two sub-projects.

parent
 |
 + - - my-api
 |     |
 |      ` - config.properties
 |
 ` - - my-project
       |
        ` - config.properties

my-api contains methods which loads resources using

ClassLoader loader = Thread.currentThread().getContextClassLoader();
InputStream input = loader.getResourceAsStream(name);

my-project calls my-api's method to load config.properties stored in my-project/src/test/resources

When I delete my-api/src/test/resources/config.properties , the config.properties from my- project is loaded correctly. However, if I don't delete it, my-api/src/test/resources/config.properties is loaded.

The behaviour you're seeing is far from unexpected.

In a normal Java setup, almost all code (apart from core low-level libraries) shares the same ClassLoader, which finds its resources using the CLASSPATH. If more than one classpath entry contains a resource with the same path, then the resource belonging to entry that appears first on the classpath will be returned. From the URLClassLoader documentation :

The URLs will be searched in the order specified for classes and resources after first searching in the specified parent class loader.

In your case, my-api must be appearing first on the classpath, so whenever it contains a config.properties resource, that will be loaded in preference to the one contained in my-project .

You appear to want to load the config.properties that resides in the same classpath entry as a particular calling class. This is possible, but it requires you to manually determine the root of the classpath entry the calling class is in. It involves quite a lot of code, and it's very difficult to cover all possible class-loading cases (ie class folders vs jars vs shaded jars built by downstream projects vs classes loaded over the network vs custom classloaders used downstream vs security managers, etc). So, I'm not going to post details of how to do this here, as I think it's a bad idea. If you really want to go down this route, start by looking at:

Foo.class.getProtectionDomain().getCodeSource().getLocation()

So, what are the alternatives?

You have several options, but my preference would simply be to put the various config.properties files in different packages. It's gnerally good practice to have a different base package for each project, so you could do something like this:

parent
 |
 + - - my-api
 |     |
 |      ` - - com.my.api
 |            |
 |             ` - config.properties
 |
 ` - - my-project
       |
        ` - - com.my.project
              |
               ` - config.properties

Then, you've got 2 choices for how to load these files. If you have a class that you know is in the same package as each properties file, you can load it using that class:

void loadConfig(Class<?> sibling) {
    InputStream config = sibling.getResourceAsStream("config.properties");
    //...
}

Alternatively, you can simply pass in the package name:

void loadConfig(String packageName) {
    String path = "/" + packageName.replace(".", "/") + "/config.properties";
    InputStream config = this.getClass().getClassLoader().getResourceAsStream(path);
    //...
}

If you really want to determine the package from the callee, you can use the first method along with an ugly hack:

void loadConfig() {
    try {
        StackTraceElement[] stackTrace = new Throwable().getStackTrace();
        Class<?> sibling = Class.forName(stackTrace[1].getClassName());
        InputStream config = sibling.getResourceAsStream("config.properties");
        //...
    } catch (ClassNotFoundException e) {
        // Calling class must have been loaded using a different ClassLoader.
        // There's might be some complex code that could be used to recover here,
        // but this should happen rarely, so maybe just propogate Exception
    }
}

I don't recommend this last approach.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM