Blog-Archiv

Sonntag, 24. Mai 2020

Java Resource Loading from Unnamed Module

In a recent article I discovered that resource loading between named modules is possible only when (1) the resource is in same module as the resource-anchor class, and (2) the caller of resourceAnchorClass.getResources() is in same module as the resource. But what about the "unnamed module", i.e. legacy classes that are not inside a module? Can a module-encapsulated class read a resource inside the unnamed module, and can a legacy class read a resource inside a named module?

Definitions

  1. Named Module:
    Follows the JPMS specification ("Java Platform Module System"). It has a root directory that is named like the module, with module-info.java descriptor and package directories below. It explicitly exports packages and/or service classes, all other contents can not be seen from outside. Any package or service it needs from outside must be listed in a "requires" or "uses" statement in module-info.java. It can not see the unnamed module, but can see packages in automatic modules when it "requires" them. Module dependency graphs do not allow cycles.

  2. Unnamed Module:
    All classes found on CLASSPATH not being in a JAR file belong to the one-and-only "unnamed module" (not really being a module). They can not be seen by classes in named modules, but by those in automatic modules. Vice versa they can see automatic modules, and all exported packages of named modules. If the exported classes of a named module are also found on the CLASSPATH, they would nevertheless be considered to be in the named module, not the unnamed.

  3. Automatic Module:
    Any JAR file on CLASSPATH that was not modularized gets converted to an automatic module at runtime by the Java virtual machine. Its name is derived from the JAR file name. Basically an automatic module is a named module that exports all its packages, but its classes (other than classes in named modules) can see also classes in the unnamed module and other automatic modules, and additionally all exported classes of named modules (without explicitly requiring them). Named modules can see classes of the automatic modules, but they need to have an according requires statement in module-info.java that points to the package of the classes to be used.

Example Sources

This example builds upon the sources of my recent Blog about resource loading with modules. I added another resource and resource-anchor class to fri.module2, and I added the "unnamed module". Mind that using underscore ('_') in a package name is possible but unwanted, Java plans to make it a reserved character in future.

The fri.module_unnamed project has CLASSPATH references to both fri.module1 and fri.module2. The module fri.module2 has a MODULEPATH dependency on fri.module1:

Here are the directories and files of the three involved Eclipse projects, including all source code (click to expand):

fri.module1
src
fri
module1
ResourceUtil.java
package fri.module1;

import java.io.InputStream;
import java.net.URL;

public final class ResourceUtil
{
    public static String asText(Class<?> resourceAnchor, String fileName) throws Exception {
        assert fileName != null;
        return asText(resourceAnchor.getResourceAsStream(fileName));
    }
    
    public static String asText(URL url) throws Exception {
        assert url != null;
        return asText(url.openStream());
    }
    
    public static String asText(InputStream inputStream) throws Exception {
        assert inputStream != null;
        final StringBuilder sb = new StringBuilder();
        int c;
        try (inputStream) { // will close stream at end
            while ((c = inputStream.read()) != -1)
                sb.append((char) c);
        }
        return sb.toString();
    }
    
    private ResourceUtil() {}   // do not instantiate
}
ResourceAnchor.java
package fri.module1;

public class ResourceAnchor
{
}
resource.txt
I am resource of module-one.
module-info.java
module fri.module1
{
    exports fri.module1;
}
fri.module2
src
fri
module2
ResourceAnchorInModule2.java
package fri.module2;

public class ResourceAnchorInModule2
{
}
resource_in_module2.txt
I am resource of module 2.
module-info.java
module fri.module2
{
    requires fri.module1;
}
fri.module_unnamed
src
fri
module_unnamed
Main.java
package fri.module_unnamed;

import java.net.URL;
import fri.module1.ResourceAnchor;
import fri.module1.ResourceUtil;
import fri.module2.ResourceAnchorInModule2;

public class Main
{
    public static void main(String[] args) throws Exception {
        // direct access of a resource in a named module: works!
        final URL url = ResourceAnchor.class.getResource("resource.txt");
        System.out.println(url);
     
        // access of a resource in unnamed module through a named module: works!
        final String text = ResourceUtil.asText(ResourceAnchorInUnnamedModule.class, "resource_in_unnamed_module.txt");
        System.out.println(text);
     
        // access of a resource in a named module through a named module: works!
        final String text2 = ResourceUtil.asText(ResourceAnchorInModule2.class, "resource_in_module2.txt");
        System.out.println(text2);
    }
    
    private Main() {}   // do not instantiate
}
ResourceAnchorInUnnamedModule.java
package fri.module_unnamed;

public class ResourceAnchorInUnnamedModule
{
}
resource_in_unnamed_module.txt
I am resource of the unnamed module.

Mind that module_unnamed has no module_info.java file because the JPMS "unnamed module" is not a module.

Test

We are going to execute the test Main in the unnamed module fri.module_unnamed:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package fri.module_unnamed;

import java.net.URL;
import fri.module1.ResourceAnchor;
import fri.module1.ResourceUtil;
import fri.module2.ResourceAnchorInModule2;

public class Main
{
    public static void main(String[] args) throws Exception {
        // direct access of a resource in a named module: works!
        final URL url = ResourceAnchor.class.getResource("resource.txt");
        System.out.println(url);
     
        // access of a resource in unnamed module through a named module: works!
        final String text = ResourceUtil.asText(ResourceAnchorInUnnamedModule.class, "resource_in_unnamed_module.txt");
        System.out.println(text);
     
        // access of a resource in a named module through a named module: works!
        final String text2 = ResourceUtil.asText(ResourceAnchorInModule2.class, "resource_in_module2.txt");
        System.out.println(text2);
    }
    
    private Main() {}   // do not instantiate
}

Output is:

file:/media/disk2/java/workspace-ee2/fri.module1/target/fri/module1/resource.txt
I am resource of the unnamed module.
I am resource of module 2.

All three resource loading statements succeeded.
But what do I test here?

In line 12, I directly load a resource from fri.module1. That would not be possible for a named module. When getResource() delivers non-null, you always should be able to read the returned URL's content, so this was successful.

In line 16, I ask the ResourceUtil class in fri.module1 to load a resource using a resource-anchor that is in the unnamed module. Although classes in named modules normally can not use classes of the unnamed module, this works.

In line 20, I use ResourceUtil in fri.module1 to load a resource in fri.module2, with a resource-anchor also being in fri.module2. That means, the getResource() call happens in fri.module1, but both the resource-anchor and the resource are in fri.module2. Also not possible between named modules, but works when executed by the unnamed module.

Conclusion

Resource loading is free like it always was for non-modularized classes, most likely due to backward compatibilty reasons. We can let read resources in the unnamed module through module classes, and we even can read resources inside named modules through classes of other named modules




Keine Kommentare: