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.
Related
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.
I am aware of getting the path of the jar that contains a specific class
<ClassName>.class.getProtectionDomain().getCodeSource().getLocation()
This is well explored in other SO questions.
My question is :
"How to get the path of the JAR file in the classpath given ONLY the JAR FILE NAME.
If you are looking to find the path to the currently running jar, try this
new File("", jarName);
If you are looking for something else in the classpath, you could use the same strategy, assuming that they are in the same directory as the jar.
However, if you have set the classpath, either by command line or in the manifest, it might be a little bit more complicated.
This is an example that I found on some website:
ClassLoader cl = ClassLoader.getSystemClassLoader();
URL[] urls = ((URLClassLoader)cl).getURLs();
This will get a list of absolute URLs that point to everything that has been loaded. All that you have to do is do url.getFile()
Say I have an exported Jar file on my Desktop, and then a folder I want to access so my jar file can load files from it. How can I access that folder without hard coding the path?
See here: https://stackoverflow.com/a/6849255/514463
Pick one of your classes that you want the directory of, e.g.: Test:
String path = Test.class.getProtectionDomain().getCodeSource().getLocation().getPath();
String decodedPath = URLDecoder.decode(path, "UTF-8");
If I clearly understand, you may use relative path when you try to access folder. For example, if you run your jar as a standalone application, relative path "." will be a folder that contains your jar. So, when you place names.txt file next to your jar, you can get access to it with:
new File("./names.txt");
Hope I understood you right way and this will help.
The following code should provide the directory containing the JAR file:
URL url = getClass().getProtectionDomain().getCodeSource().getLocation();
File directory = new File(url.toURI()).getParentFile();
Hmmm...
I heard this question so often, and it always boils down to this: How to load resources at runtime?
The main reason for this type of question is, that one is developping an application and now wants to create a distributable package. This normally ends in something like that:
... / my-application-folder
| -- lib / *.jar
| -- main.jar
| -- config.properties
There could be several configuration files. A configuration for the application itself, a configuration for a logging framework that is used, etc. It does not matter. If you want to access such resources, you should do it in two steps:
1) Make sure all folders containing your resources (such configuration files are resources) are part of the classpath. If you run your JAR file (here main.jar) with a java -jar main.jar command, this JAR file should contain a manifest file containing the directory . and all needed library JARs in the class-path entry. [Alternative: Maybe all your config files are located in a config/ subfolder. Then this folder must be part of the class-path.]
2) Inside your application you access such resources with a class loader:
ClassLoader loader = Thread.currentThread().getContextClassLoader();
URL url = loader.getResource(neededResource);
// do something with url, e.g. open stream, read from it, and close stream
If you have a user customizable path, there are several possibilities. You could for example pass a system property to the application, as Reddy suggested in his comment. You could also have a property in a configuration file, which you are loading in the beginning part of your application. However, you have the path to this customizable folder in hand (at runtime). Then you do the following:
String customizablePath = getCustomizablePath();
URL customizablePathURL = new File(customizablePath).toURI().toURL();
ClassLoader loader = new URLClassLoader(new URL[] {customizablePathURL});
Then you can continue like above.
File f = new File("Folder")
This File object points to "Folder" directory in the the working directory of the Jar.
I have a quick question about altering the build path as the code is running.
For example, I have a class which downloads a .jar file from the internet and then into the same directory as the code is running from. How, if possible, could I load the jar into the build path to access the classes within the .jar file?
Some suggested amendments / comments:
Remove the jar: prefix and the !/ suffix - these are note required and are probably confusing the matter
Can you verify the jar file exists:
System.out.println(new File(new URL("file://test.jar")).exists());
Amend your class declaration to the following (get the File object to generate the URL for you, to avoid problems):
URL[] classes = new URL[] { new File("test.jar").toURI().toURL() };
This worked for my test example, using commons-codec as the jar, and loading the Base64 class
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.