Blog-Archiv

Sonntag, 3. Mai 2020

Java Resource Loading

Loading resources in Java always was a little risky. This Blog is about the different ways how resources can be loaded. It contains a test class proving that the mechanism didn't change in Java 9, although resources can not be loaded across module boundaries any more (my next Blog will be about this).

Resources

A resource denotes a file that may be packed into the JAR archive representing a Java application, be it a web or a desktop app. Normally you load a Java resource through a file path or name and an anchor class. Consider following example files and directories:

src
root.properties
resources
package.properties
ResourceAnchor.java

The files root.properties and resources/package.properties are example resources. For this demo they can be empty. We will use the class resources/ResourceAnchor.java to load them.

Root Resources

Resources that do not belong to a specific class often are stored in the root directory of the project. Example is root.properties. Here is the standard way how to load it:


URL url = ResourceAnchor.class.getResource("/root.properties");

Alternatively you can fetch also an InputStream, the mechanism is the same:


InputStream inputStream = ResourceAnchor.class.getResourceAsStream("/root.properties");

You can use any class for loading such resources.

Package Resources

Because we want to be safe against package moves and renames, we always place a resource in the same package as its using Java class, and we load the resource through that class. Let's assume that package.properties belongs to ResourceAnchor.java, so they are in the same package named "resources". (Mind that in a Maven structure they would be in different directories though). Here is the default way to load package.properties:


URL url = ResourceAnchor.class.getResource("package.properties");

Or:


InputStream inputStream = ResourceAnchor.class.getResourceAsStream("package.properties");

Class.getResource(resourcePath) will work on both the filesystem and inside a packed application JAR.

Alternatives

Some people prefer to load resources through the class-loader, not the class, which makes it more difficult to understand:


URL url = ResourceAnchor.class.getClassLoader().getResource("resources/package.properties");

All variants shown until now would work, but you see that this variant is quite strange, as it uses "resources/package.properties" as path, which seems to point to a sub-directory "resources" below the "resources" directory where the anchor class is. Nevertheless this variant works, although I would strongly discourage it. There is no need to use the class-loader for loading resources.

Test App

We see that a lot of combinations is possible (and unfortunately also used) for loading resources:

  • just file name without path and leading slash
  • full path without leading slash (relative path)
  • full path with leading slash (absolute path)
  • loading through class
  • loading through class-loader

The following test class may help those that are confused and want to know what each of these combinations does. It can also be used to find out behavior changes between different Java versions (there ain't any until now).

 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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package resources;

import java.io.IOException;
import java.net.URL;

public class ResourceAnchor
{
    public void run() throws Exception   {
        System.out.println("Trying to load a root resource:");
        loadRootResource();
        
        System.out.println("Trying to load a package resource:");
        loadPackageResource();
    }
    
    private void loadRootResource() throws IOException   {
        checkThat( classResource("/root.properties") != null );
        checkThat( classResource("root.properties") == null );
        
        checkThat( classLoaderResource("/root.properties") == null );
        checkThat( classLoaderResource("root.properties") != null );
    }

    private void loadPackageResource() throws Exception   {
        checkThat( classResource("package.properties") != null );
        checkThat( classResource("/resources/package.properties") != null );
        checkThat( classResource("resources/package.properties") == null );
        
        checkThat( classLoaderResource("package.properties") == null );
        checkThat( classLoaderResource("/resources/package.properties") == null );
        checkThat( classLoaderResource("resources/package.properties") != null );
    }

    private URL classResource(String resourcePath) throws IOException {
        URL url = getClass().getResource(resourcePath);
        System.out.println("getClass().getResource(\""+resourcePath+"\"): "+(url != null ? "found" : "missing"));
        return url;
    }
    
    private URL classLoaderResource(String resourcePath) throws IOException {
        URL url = getClass().getClassLoader().getResource(resourcePath);
        System.out.println("getClass().getClassLoader().getResource(\""+resourcePath+"\"): "+(url != null ? "found" : "missing"));
        return url;
    }
    
    private void checkThat(boolean result)    {
        if (result != true)
            throw new IllegalStateException("Expected to be true!");
    }
    
    
    public static void main(String[] args) throws Exception {
        System.out.println("Standing in = "+System.getProperty("user.dir"));
        System.out.println("Java Version = "+System.getProperty("java.version"));
        new ResourceAnchor().run();
    }
}

Wherever you see checkThat(classResource(".....") != null) it means success, while checkThat(classResource(".....") == null) denotes failed resource loading.

If you build such a project and run the resources.ResourceAnchor class, you will see following output:

Trying to load a root resource:
getClass().getResource("/root.properties"): found
getClass().getResource("root.properties"): missing
getClass().getClassLoader().getResource("/root.properties"): missing
getClass().getClassLoader().getResource("root.properties"): found
Trying to load a package resource:
getClass().getResource("package.properties"): found
getClass().getResource("/resources/package.properties"): found
getClass().getResource("resources/package.properties"): missing
getClass().getClassLoader().getResource("package.properties"): missing
getClass().getClassLoader().getResource("/resources/package.properties"): missing
getClass().getClassLoader().getResource("resources/package.properties"): found

Conclusion

Try this out with Java 1.8 and Java 9 (10, 11), you will see that all succeed and output the same result.

Don't load resources through the class-loader, this gives a wrong impression of the actual location of the file.




Keine Kommentare: