How to retrieve some resources from external JAR file using reflections - java

Ok, I know that similar questions have been already posted here, and in fact, my solution is feeding from them.
The idea I have is to have some modules (a module is a set of XML files in a folder inside a JAR file) that can be added to an application by the user. That means that the user can put some jars in a predefined folder, and all resources from this JARs must be available for the application. For this purpose, I need to load all JARs from an external folder, and retrieve the XMLs as resources. The JAR files, usually have not any class file, but maybe at the future can have it.
Also the application has a "default" module that is already inside its resources folder. This module is working fine and I can list all XMLs inside it using Reflections.
My code from retrieving all XML files using Reflection is very simple:
final Set<String> resources = new Reflections(CustomPath.MODULES_FOLDER, new ResourcesScanner()).getResources(Pattern
.compile(".*\\.xml"));
that returns a Set of strings similar to modules_folder/module_name/file1.xml.
And the code to load a Jar file is using a URLClassLoader:
Method method = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] { URL.class });
method.setAccessible(true); // promote the method to public access.
method.invoke(loader, new Object[] { url });
As far as I have understood, the resources are available from the URLClassLoader. In fact, I can do something like:
URLClassLoader.getSystemResource("modules_folder/module_name/file1.xml").
And the file is loaded and If I put an invalid path, an error is thrown. Also I need to know the 'module_name' that is not predefined. Then is not a solution.
If I run again the method to retrieve the resources using Reflection, it is only retrieving the files inside the resources folder of the project. No resource from the JAR is retrieved.
Then, I cannot understand why reflections is unable to get the files from the JAR files if are already loaded. I am not sure if Reflections is unable to access to the URLCLassLoader for any reason, or that must be used in a different way.
The question is... Why Reflections is not able to access to the resources from a JAR loaded at runtime? And if it is a limitation, what is the best approach to do it without any Java classes inside the external JAR file?

Ok, then puts the final result as a future reference. My problem is that the default constructor of new Reflections(CustomPath.MODULES_FOLDER, new ResourcesScanner()) is not accessing the URLClassLoader. Then is impossible that it can obtains the resources I am looking for. A solution is to use the ConfigurationBuilder and include all ClassLoaders I need.
final ConfigurationBuilder builder = new ConfigurationBuilder();
builder.addUrls(ClasspathHelper.forPackage(PathManager.MODULES_FOLDER, ClassLoader.getSystemClassLoader(),
ClasspathHelper.contextClassLoader(), ClasspathHelper.staticClassLoader()));
builder.addScanners(new ResourcesScanner());
final Reflections reflections = new Reflections(builder);
final Set<String> resources = reflections.getResources(Pattern.compile(".*\\.xml"));
As can been seen in the previous code, I add the ClassLoader.getSystemClassLoader() to the builder, and this loader is the one I am using for loading the Jar files.

Related

Why use a ClassLoader to read a resource

I encountered a case where I need to use ClassLoader:
I have a XML file which specifies the configuration detail for sql, and I want to load it into a configuration class. The first step is to load what is in the XML into an Inputstream.
public class Resources{
public static InputStream getResourceAsStream(String path){
InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
return resourceAsStream;
}
}
I only know vaguely what is a classloader: It loads classes into JVM. It is not clear to me at all why one would use classLoader here. Can't we just read what is in path directly? My guess is that this might have something to do with the timing of when one wants to load the resource.
A project is composed of two things:
Code compiled into .class file
Ressources (any file such as properties, xml...)
Then it is packaged. The packaging can be mainly:
a JAR
a directory
A ClassLoader is what is capable to access to packaged projects (jars, directories...). The main ClassLoader is accessing jars and directories specified in the classpath, but additional ClassLoader may be added at runtime. For example, on an application server, where you can deploy new packaged applications at runtime, for every application a ClassLoader will be created.
That's why, to access ressources from a packaged project, you need to use a ClassLoader (even the name is not clear about ressources).
If you want to access a ressource packaged together with your class in the same project, you get the ClassLoader of your class so you are sure it can access the ressources of the same project.
The most typical ClassLoader is java.net.URLClassLoader, which takes a list of URLs (local or remote JARs, directories...) such as the classpath, and look into every URL to search for .class files or ressources files.
To sum up, you can see a ClassLoader as a list of locations where to search files, either .class to load classes, or any other type of file as ressources.

URLClassLoader in java

I am working on a project which requires loading classes at Runtime, so I did some research and found out that I need to use Custom Class Loader. I implemented my own custom UrlClassloader and provided it with the url of my jar files, it worked correctly and the class files got loaded. I have read the java doc for URLClassLoader and they have mentioned clearly that any URL that ends with "/" is assumed to refer to a directory so does it mean that if I have multiple jar files in the directory my classloader will all load all of them, I tried it but it didn't work. so what's the logic behind that.
please explain I am very much confused. what if I want multiple jars to be loaded at runtime from a directory?
You have to iterate over the files in the directory and add them one by one
List<URL> urls = new ArrayList<>();
try(DirectoryStream<Path> directoryStream = Files.newDirectoryStream(Paths.get(BASE_DIRECTORY), "*.jar")) {
for (Path path : directoryStream) {
urls.add(path.toUri().toURL());
}
}
URLClassLoader urlClassLoader = new URLClassLoader(urls.toArray(new URL[urls.size()]));
When it ends with "/", would be referring find loading content from that directory. Suppose you have an extracted package in that folder. If you have a class com.abc.Test, to load it form a folder, you would need the file com/abc/Test.class in the folder you are referring to.

How to load all classes of a jar file at runtime?

According to this question, it is possible to load a class from a jar file with:
ClassLoader loader = URLClassLoader.newInstance(
new URL[] { jarFileURL },
getClass().getClassLoader()
);
Class<?> clazz = Class.forName("mypackage.MyClass", true, loader);
How to load all classes contained in a jar file?
The class loader will load a .class file as soon as it's needed. If it's not needed, it won't be loaded.
Why do you think that your approach will be an improvement over what the class loader already does?
You will have to open it as zip file, go through the names, assume all entries ending in .class are class files and load them, ignoring nested classes and such.
But....why?
If you really want to read classes dynamically, leave that to the pros, who already implemented that. Implementing your own classloader is hard. Believe me. I already tried a few times. Use something like OSGi instead, which provides you dynamic class loading and much more.

Loading a resource in a JAR file

I have a Java project in Netbeans. It runs fine with Maven. So I assembled it. It contains the following code to load a file that is in the JAR:
ClassLoader loader = MyClass.class.getClassLoader();
SERVICE_URL = loader.getResource("my.wsdl");
This returns a URL like:
jar:file:/path/to/my/file/MyClass-1.0-SNAPSHOT-jar-with-dependencies.jar!/my.wsdl
but the library that needs this parameter doesn't appear to be able to use it.
Is there any way this file can be in the JAR and be referred to from the code like this?
You may have to use ClassLoader.getResourceAsStream(), copy it to a temporary file, and then create a URL with File.toURI().toURL()

How to work with file path when addressing project folders content?

Let's say I have a structure
-bin/com/abc/A.class
-src/com/abc/A.java
-config/info.txt
How to address the file info.txt from A class?
Should we use "user.dir" property or "config/info.txt" so that it would work ?
I'll compile this into the jar and after that
the jar will be used from the servlet,
but I don't think that's important
cause this file is written and read from
internal jar's methods only.
Just put it in the runtime classpath and use ClassLoader#getResourceAsStream() to get an InputStream of it. Putting it in the JAR file among the classes, or adding its (JAR-relative) path to the Class-Path entry of the JAR's manifest.mf file is more than sufficient.
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
InputStream input = classLoader.getResourceAsStream("config/info.txt");
// Do your thing to read it.
Or if you actually want to get it in flavor of a java.io.File, then make use of ClassLoader#getResource(), URL#toURI() and the File constructor taking an URI:
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
URL url = classLoader.getResource("config/info.txt");
File file = new File(url.toURI());
// Do your thing with it.
Do not use relative paths in java.io stuff. It would be dependent on the current working directory which you have no control over at any way. It's simply receipt for portability trouble. Just make use of the classpath.
That said, are you aware of the java.util.Properties API? It namely look like you're trying to achieve the same thing which is more easy to be done with propertiesfiles.

Categories

Resources