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

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.

Related

How to retrieve some resources from external JAR file using reflections

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.

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.

Java Classloader unable to load modules?

I seem to be having a problem with loading classes in a module loader for an application I'm developing. Basically, all classes I'm going to be loading with it extend another class, which is located in a package in the actual application. For our purposes, we'll call it Module. Modules are located in a separate folder outside the actual application.
The loader iterates through a folder and executes the loadFile() method on any file with the extension .class. All classes have the package declaration as the Module class, as well as the extends Module declaration in the class header.
This is the loadFile() method, header and exception clauses excluded:
String fileName = file.getName();
String className = fileName.replace(".class", ""); //Strips extension
Class<?> aClass = Class.forName(className, true, new URLClassLoader(new URL[] { file.toURI().toURL() }));
Class<? extends Module> modClass = aClass.asSubclass(Module.class);
return modClass.getConstructor().newInstance();
I keep getting a ClassNotFoundException on the third line. And past that, if it ClassNotFoundException weren't thown, would all dependencies be resolved?
From the documentation for URLCLassLoader:
This class loader is used to load classes and resources from a search path of URLs referring to
both JAR files and directories. Any URL that ends with a '/' is assumed to refer to a directory.
Otherwise, the URL is assumed to refer to a JAR file which will be opened as needed.
So, you must use URLs for either directories or .jars
Two solutions:
Force your users to give you .jar files, including a manifest of some sort inside with the classname that they wish to be loaded. This approach is used by the Bukkit developers. Having used this method in the past, the dependencies should all be packaged in the .jar file and thus in the URLClassLoader's search path and able to be loaded.
Use the URL of the file's directory, and search that directory for .class files. I'm not sure if dependencies will be loaded using this method.
In the URLClassLoader, do not pass the file, but the parent folder. However, this works correctly if classes are all in the "default package", so the .class files you are loading must not have a package declaration on top.
By default, a class loader will also trigger loading of all classes required to properly build the class: it will try to load the super class, the super super class etc... all the interfaces and super interfaces, the classes needed for static fields and methods, the classes needed for method signatures (return types and parameters). It will not usually try to load classes used internally by methods, not until you execute those methods.
However, usually a class loader does not "contain" all those classes, for example your class will end up inheriting from java.lang.Object, and your URLClassLoader will not contain the Object.class file. So, class loaders delegate to their parent class loaders.
You are currently creating a URLClassLoader without specifying a parent, in Java 7 at least the parent will default to the "system class loader", which is fine as long as you are in a plain java application, and not executing your code itself inside a specific hierarchy of class loaders. If however you are running that code in a web application, or in an OSGI container etc.. the you should give the URLClassLoader a proper parent to delegate to, for example Thread.currentThread().getContextClassLoader() or this.getClass().getClassLoader().
I suppose you need all of this because you need to load those class dynamically at runtime.

Does a .class file on disk have to follow the same directory structure as its qualified name in Java for us to run it?

After reading about dynamic class loading (that is, loading a .class file from disk) I am getting a bit worried.
Let's say I have a file called MyClass.class that contains the class a.b.c.MyClass.
Assuming I now decide to move the file to C:\(my root folder in Windows), I'd like to dynamically load this class. Is that possible, at all? From what I understood it seems MyClass' path would always have to be of the form *a/b/c.MyClass.
Thus, the following bit of code doesn't seem to work:
URL[] urls = new URL[] { new File("C:\\").toURL() };
URLClassLoader classLoader = new URLClassLoader(urls);
Class<?> targetClass = classLoader.loadClass("a.b.c.MyClass");
Forcing us to put a .class file in a directory structure that reflects its full internal name is insane, IMO. Am I missing something?
A possible implication of this fact would be that if I decide to copy a couple of .class files into a temporary directory so I can perform some awesome wizardry over them, I'll have to replicate all their dirty paths in that same temporary directory, which is awkward, at best.
Does a .class file on disk have to follow the same directory structure as its qualified name in Java for us to run it?
Yes, if you are using the standard classloaders.
In theory, you could implement a custom classloader that used a different scheme for locating the class files. But there's a good chance that you would run into problems when (for example) debugging your code. So I wouldn't recommend it.

Load a class for reflection when it's already loaded from elsewhere in java

I'm trying to write a decompiler for Java using reflection (all I need is the method signature information from a jar file passed in). I'm using a URLClassLoader to load classes from the jar file, and then pass that class on to my decompiler.
However, I've run into a problem. The jar I'm trying to read contains different versions of classes already loaded into my environment. So when I call
ClassLoader myClassLoader = URLClassLoader.newInstance(jars, null);
Class<?> classToDecompile = Class.forName(className, false, myClassLoader);
It returns not the class from my jar file, but the one that's already been loaded. Is there a way to load the class information for reflection only and get it from the jar passed in rather than from the JRE?
Edit:
The jar I'm trying to decompile contains classes in the java.lang package, which causes a security exception to be thrown:
java.lang.SecurityException: Prohibited package name: java.lang
ClassLoader.loadClass(String, boolean) - which URLClassLoader inherits - first checks for a previously-loaded class, and then, not finding any, checks with the parent class loader. You could override that method to bypass the check for a previously-loaded class. Not sure if you'd have to bypass the parent.
Mind you, this would be an evil class loader; you probably shouldn't use it for anything else.
You might also try reading the jar yourself, getting the contents of the class file (the byte codes), and calling ClassLoader.defineClass(name, byte[], int, int) yourself, supplying a unique name for the class. I think that's a better idea, actually.
Why wasting time when there is JAD? If you still want to achieve that you have to write your own class loader with child-first strategy.

Categories

Resources