Url to Load Resources from the Classpath in Java

URL to load resources from the classpath in Java

Intro and basic Implementation

First up, you're going to need at least a URLStreamHandler. This will actually open the connection to a given URL. Notice that this is simply called Handler; this allows you to specify java -Djava.protocol.handler.pkgs=org.my.protocols and it will automatically be picked up, using the "simple" package name as the supported protocol (in this case "classpath").

Usage

new URL("classpath:org/my/package/resource.extension").openConnection();

Code

package org.my.protocols.classpath;

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

/** A {@link URLStreamHandler} that handles resources on the classpath. */
public class Handler extends URLStreamHandler {
/** The classloader to find resources from. */
private final ClassLoader classLoader;

public Handler() {
this.classLoader = getClass().getClassLoader();
}

public Handler(ClassLoader classLoader) {
this.classLoader = classLoader;
}

@Override
protected URLConnection openConnection(URL u) throws IOException {
final URL resourceUrl = classLoader.getResource(u.getPath());
return resourceUrl.openConnection();
}
}

Launch issues

If you're anything like me, you don't want to rely on a property being set in the launch to get you somewhere (in my case, I like to keep my options open like Java WebStart - which is why I need all this).

Workarounds/Enhancements

Manual code Handler specification

If you control the code, you can do

new URL(null, "classpath:some/package/resource.extension", new org.my.protocols.classpath.Handler(ClassLoader.getSystemClassLoader()))

and this will use your handler to open the connection.

But again, this is less than satisfactory, as you don't need a URL to do this - you want to do this because some lib you can't (or don't want to) control wants urls...

JVM Handler registration

The ultimate option is to register a URLStreamHandlerFactory that will handle all urls across the jvm:

package my.org.url;

import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.util.HashMap;
import java.util.Map;

class ConfigurableStreamHandlerFactory implements URLStreamHandlerFactory {
private final Map<String, URLStreamHandler> protocolHandlers;

public ConfigurableStreamHandlerFactory(String protocol, URLStreamHandler urlHandler) {
protocolHandlers = new HashMap<String, URLStreamHandler>();
addHandler(protocol, urlHandler);
}

public void addHandler(String protocol, URLStreamHandler urlHandler) {
protocolHandlers.put(protocol, urlHandler);
}

public URLStreamHandler createURLStreamHandler(String protocol) {
return protocolHandlers.get(protocol);
}
}

To register the handler, call URL.setURLStreamHandlerFactory() with your configured factory. Then do new URL("classpath:org/my/package/resource.extension") like the first example and away you go.

JVM Handler Registration Issue

Note that this method may only be called once per JVM, and note well that Tomcat will use this method to register a JNDI handler (AFAIK). Try Jetty (I will be); at worst, you can use the method first and then it has to work around you!

License

I release this to the public domain, and ask that if you wish to modify that you start a OSS project somewhere and comment here with the details. A better implementation would be to have a URLStreamHandlerFactory that uses ThreadLocals to store URLStreamHandlers for each Thread.currentThread().getContextClassLoader(). I'll even give you my modifications and test classes.

Java URL Protocols: classpath:/?

Well you can always register URL handlers. Java also has a file:/// and jar: handler. Also class.getResource will by default read from the classpath.

http://code.google.com/p/madura-classpath-protocol-handler/

apparently it is a spring feature.

"You can see these standard handlers, and associated implementation classes,in the JDK's RT.JAR file. Look for classes whose fully-qualified name starts with sun.net.www.protocol.For example,the class sun.net.www.protocol.http.Handler defines the HTTP protocol handler. Class sun.net.www.protocol.ftp.Handler defines the FTP protocol handler class."

http://java.sun.com/developer/onlineTraining/protocolhandlers/

Trying to use classpath: in Java 1.6 results in:

Exception in thread "main" java.net.MalformedURLException: unknown protocol: classpath

Unable to find classpath/URL for Resource

The first thing I noticed is you're trying to acquire resources at the field level. In other words, this:

public class SomeClass{
ApplicationContext ctx = new ClassPathXmlApplicationContext();
private Resource resource = this.ctx.getResource("classpath:validation.xml");
}

Would make me think that application context isn't fully initialized.

After pulling down your project and checking, my assumption was correct. ApplicationContext isn't fully initialized until after the classes are initialized. To get around this fact, you can use the @PostConstruct annotation. Your class will now look like this:

@Configuration
public class Validator {
@Autowired
private ApplicationContext applicationContext;
private Resource validationResource;

@PostConstruct
public void init(){
validationResource = applicationContext.getResource("classpath*:validation.xml");
}

On a side note, I'd recommend you take some time to properly configure your pom. Ideally, someone should be able to pull down your code, build, and run. It took me a while to get your project running locally due to manual configuration.

How to really read text file from classpath in Java

With the directory on the classpath, from a class loaded by the same classloader, you should be able to use either of:

// From ClassLoader, all paths are "absolute" already - there's no context
// from which they could be relative. Therefore you don't need a leading slash.
InputStream in = this.getClass().getClassLoader()
.getResourceAsStream("SomeTextFile.txt");
// From Class, the path is relative to the package of the class unless
// you include a leading slash, so if you don't want to use the current
// package, include a slash like this:
InputStream in = this.getClass().getResourceAsStream("/SomeTextFile.txt");

If those aren't working, that suggests something else is wrong.

So for example, take this code:

package dummy;

import java.io.*;

public class Test
{
public static void main(String[] args)
{
InputStream stream = Test.class.getResourceAsStream("/SomeTextFile.txt");
System.out.println(stream != null);
stream = Test.class.getClassLoader().getResourceAsStream("SomeTextFile.txt");
System.out.println(stream != null);
}
}

And this directory structure:

code
dummy
Test.class
txt
SomeTextFile.txt

And then (using the Unix path separator as I'm on a Linux box):

java -classpath code:txt dummy.Test

Results:

true
true

Load resource from anywhere in classpath

If all else fails you could use two different file names, say props-default.properties inside myJar.jar and props.properties to override on the command-line. In your code, you'd try loading the props.properties file first and fallback to props-default.properties if it wasn't found.

Loading nested resource from the classpath in Spring Boot

Since you are already using Spring, try using one of their resource loaders

URL url = new PathMatchingResourcePatternResolver( null ).getResource( "classpath:/images/my_logo.png" ).getURL();

Notice: I added a leading slash to the path.

EDIT: I did check on @duffymo's comment and it was correct. The leading slash is not necessary.



Related Topics



Leave a reply



Submit