Accessing resource files from external modules

cen picture cen · Oct 21, 2017 · Viewed 11.1k times · Source

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?

Answer

Jano picture Jano · Oct 22, 2017
// 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