Accessing resource files from external modules

2020-01-25 09:45发布

So far until non-modularized java, you would simply put a file in src/main/java/resources make sure it is in classpath and then load it with

file = getClass().getClassLoader().getResourceAsStream("myfilename"); 

from pretty much anywhere in the classpath.

Now with modules, the plot thickens.

My project setup is the following:

module playground.api {
    requires java.base;
    requires java.logging;
    requires framework.core;
}

Config file is placed inside src/main/resources/config.yml.

Project is run with

java -p target/classes:target/dependency -m framework.core/com.framework.Main

Since the main class does not reside in my own project, but in an external framework module, it can't see config.yml. Now the question is, is there a way to somehow put my config file into the module or open it up? Do I have to change the way file is loaded by framework upstream?

I tried using "exports" or "opens" in module-info but it wants to have a package name, not a folder name.

How to achieve this in best practical way so it would work as in Java 8 and with as little changes as possible?

2条回答
家丑人穷心不美
2楼-- · 2020-01-25 10:08
// to scan the module path
ClassLoader.getSystemResources(resourceName)

// if you know a class where the resource is
Class.forName(className).getResourceAsStream(resourceName)

// if you know the module containing the resource
ModuleLayer.boot().findModule(moduleName).getResourceAsStream(resourceName)

See a working example below.


Given:

.
├── FrameworkCore
│   └── src
│       └── FrameworkCore
│           ├── com
│           │   └── framework
│           │       └── Main.java
│           └── module-info.java
└── PlaygroundApi
    └── src
        └── PlaygroundApi
            ├── com
            │  └── playground
            │      └── api
            │          └── App.java
            ├── config.yml
            └── module-info.java

Main.java could be

package com.framework;

import java.io.*;
import java.net.URL;
import java.util.Optional;
import java.util.stream.Collectors;

public class Main {
    public static void main( String[] args )
    {
        // load from anywhere in the modulepath
        try {
            URL url = ClassLoader.getSystemResources("config.yml").nextElement();
            InputStream is = url.openStream();
            Main.read(is);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        // load from the the module where a given class is
        try {
            InputStream is = Class.forName("com.playground.api.App").getResourceAsStream("/config.yml");
            Main.read(is);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

        // load from a specific module
        Optional<Module> specificModule = ModuleLayer.boot().findModule("PlaygroundApi");
        specificModule.ifPresent(module -> {
            try {
                InputStream is = module.getResourceAsStream("config.yml");
                Main.read(is);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
    }

    private static void read(InputStream is) {
        String s = new BufferedReader(new InputStreamReader(is)).lines().collect(Collectors.joining("\n"));
        System.out.println("config.yml: " + s);
    }
}

And you would launch with

java --module-path ./FrameworkCore/target/classes:./PlaygroundApi/target/classes \
     --add-modules FrameworkCore,PlaygroundApi \
       com.framework.Main

To clone this example: git clone https://github.com/j4n0/SO-46861589.git

查看更多
Juvenile、少年°
3楼-- · 2020-01-25 10:27

While you are using the java command to launch an application as follows:-

java -p target/classes:target/dependency -m framework.core/com.framework.Main 
  • you are specifying the modulepath using the option -p aternate for --module-path which would look up into target/classes and target/dependency for your modules.

  • Alongside, using -m alternate for --module specifies the initial module to resolve with the name framework.core and constructs the module graph with the main class to execute explicitly listed as com.framework.Main.

Now, the problem here seems to be that the module framework.core doesn't requires or read playground.api module because of which the module graph doesn't include the desired module consisting of the actual resource config.yml.

As suggested by @Alan, a good way to list out the module resolution output during startup is to make use of the --show-module-resolution option.


I just naively tried to opens src/main/resources, doesn't compile ofc

Since the resource in your module is at the root level, it is, therefore, not encapsulated and does not need to be opened or exported to any other module.

In your case, you just need to make sure that the module playground.api ends up in the module graph and then the resource would be accessible to the application. To specify root modules to resolve in addition to the initial module, you can make use of the --add-modules option.


Hence the overall solution to work for you along with some debugging shall be :

java --module-path target/classes:target/dependency 
     --module framework.core/com.framework.Main
     --add-modules playground.api
     --show-module-resolution
查看更多
登录 后发表回答