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 ThreadLocal
s to store URLStreamHandler
s 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
Unresponsive Keylistener for Jframe
Java8 Lambdas VS Anonymous Classes
How to Get the Current Time in Yyyy-Mm-Dd Hh:Mi:Sec.Millisecond Format in Java
How to Get Parameters from the Url with Jsp
Simpledateformat Parsing Date with 'Z' Literal
How to Check If a Single Character Appears in a String
Javafx 11: Illegalaccesserror When Creating Label
How to Use a Wildcard in the Classpath to Add Multiple Jars
Java Unsupported Major Minor Version 52.0
Problems Converting Byte Array to String and Back to Byte Array
How to Get the Size of a Java.Sql.Resultset
Differencebetween "Class.Forname()" and "Class.Forname().Newinstance()"
What Is the Java's Internal Represention for String? Modified Utf-8? Utf-16
Useful Example of a Shutdown Hook in Java
Error: Selection Does Not Contain a Main Type