Gradle Custom Plugins: Read Compiled Java Class - java

In a custom plugin (or task) I would like to read all compiled classes (preferrably those that have changed from last compilation) with a classloader so that I'll be able to use reflection on them.
Is that possible?
1) It would be great to have a cook right after a Java class was compiled so that I could read it, but I found no way to do this.
2) I'm thinking of something like this ...
compileJava.doLast {
ClassLoader parent = getClass().getClassLoader();
GroovyClassLoader loader = new GroovyClassLoader(parent);
// retrieve all class files
// for each class file, loader.parseClass(classFile)
}

In a gradle script getClass().getClassloader() will get you the classloader of the gradle script. This will NOT contain the compiled classes or compile/runtime jars. I think you want to do something similar to:
Collection<URL> urls = sourceSets.main.runtimeClasspath.files.collect { it.toURI().toURL() }
Classloader parent = new URLClassLoader(urls.toArray());
If you want to only act on the classes that have changed you are best to do do that in an incremental task

Related

Load a class from a jar having dependency on another jar

My project structure is the following (very simplified of course):
So under lib-ext i download on a daily basis from a Jenkins server 2 jar files 'jar1 and jar2' to be checked by my program, i need one file from 'jar1' lets call it: "Class2Bloaded".
The issue is that this file implements an interface that is to be found in 'jar2', lets call this 'Dependency'
What i would like to do is, from my class under src "ClassThatLoads.java", load 'Class2Bloaded.class' and tell the class loader to look into 'jar2' to search for the implementing interface "Dependency.class"
My code so far (omitting exceptions handling):
//Create the URL pointing to Jar1
private URL getJarUrl(JarFile jarFile)
{
return new File(jarFile.getName()).toURI().toURL();
}
URL jar1Url = getJarUrl(jar1);
ClassLoader jar1classLoader = new URLClassLoader(new URL[] { jar1Url });
Class<?> Class2Bloaded = Class.forName(fullClassName, false, jar1classLoader );
So the problem happens within the Class.forName invocation, because the class i want to load implements an interface that is in jar 2.
Exception in thread "main" java.lang.NoClassDefFoundError: com/packagewithinJar2/Dependency
So eventually i have prepared another class loader that points to 'jar2', and i have even got the actual Interface i need:
URL jar2Url = getJarUrl(jar2);
ClassLoader jar2classLoader = new URLClassLoader(new URL[] { jar2Url });
Class<?> Interface2Bloaded = Class.forName(fullClassName, false, jar2classLoader );
Where 'fullClassName' in the second case is the fully qualified name of the interface from which 'Class2Bloaded' depends on.
Is just that i cant find anything in the javadocs of ClassLoader that allows me to 'inject' an additional class loader for the dependencies.
I hope my explanation is clear.
The first thing to do would be to add jar2 to the list of jars your URLClassLoader reads:
ClassLoader jarclassLoader = new URLClassLoader(new URL[] { jar1Url, jar2Url });
BUT the normal thing to do would be to add jar1 and jar2 on your classpath from the beginning.
To do so you would use the -cp parameter of the java executable.
for example, if you compile your classes into the bin directory:
java -cp libext/jar1.jar:libext/jar2.jar:bin ClassThatLoads
That way, you could use the classes seamless in your own java source and get rid of the cumbersome loading part :
public class ClassThatLoads {
public static void main(String[] args) {
Class2Bloaded stuff = new Class2Bloaded();
//use stuff from here...
}
}

How to implemented to a shared interface pulled from a WAR

I have a web service we'll call service.war. It implements an interface we'll call ServicePluginInterface. During the startup of service.war, it reads in environment variables and uses them to search for a jar (MyPlugin.jar). When it finds that jar, it then uses a second environment variable to load the plugin within the jar. The class that it loads looks like this:
public class MyPlugin implements ServicePluginInterface {...}
The servlet attempts to load the plugin using code like:
try {
if (pluginClass == null) {
plugin = null;
}
else {
ZipClassLoader zipLoader = new ZipClassLoader(Main.class.getClassLoader(), pluginJar);
plugin = (ServicePluginInterface)zipLoader.loadClass(pluginClass).newInstance();
plugin.getAccount(null,null);
}
} catch (Exception e) {
...
}
The trick is that I don't have source or a jar for ServicePluginInterface. Not wanting to give up so easily, I pulled the class files out of the service.war files. By using those class files as dependencies, I was able to build, without compiler warnings, MyPlugin. However, when actually executed by Tomcat, the section of code above generates a runtime exception:
java.lang.ClassCastException: com.whatever.MyPlugin cannot be cast to com.whomever.ServicePluginInterface
As a second point of reference, I am also able to construct a synthetic class loader (separate java executable that uses the same class loading mechanism. Again, since I do not have the original source to ServicePluginInterface, I used the class files from the WAR. This second, synthetic loader, or faux-servlet if you will, CAN load MyPlugin just fine. So I would postulate that the Tomcat JVM seems to be detecting some sort of difference between the classes found inside the WAR, and extracted class files. However, since all I did to extract the class files was to open the WAR as a zip and copy them out, it is hard to imagine what that might be.
Javier made a helpful suggestion about removing the definition of ServicePluginInterface, the problem with that solution was that the ZipClassLoader that the servlet uses to load the plugin out of the jar overrides the ClassLoader findClass function to pull the class out of the JAR like so:
protected Class<?> findClass(String name) throws ClassNotFoundException
{
ZipEntry entry = this.myFile.getEntry(name.replace('.', '/') + ".class");
if (entry == null) {
throw new ClassNotFoundException(name);
}
...
}
The class ZipClassLoader then recursively loads all parent objects and interfaces from the jar. This means that if the plugin jar does not contain the definition for ServicePluginInterface, it will fail.
Classes defined by different class loaders are different:
At run time, several reference types with the same binary name may be
loaded simultaneously by different class loaders. These types may or
may not represent the same type declaration. Even if two such types do
represent the same type declaration, they are considered distinct. JLS
In that case zipLoader returns an instance of MyPlugin that implements the other ServicePluginInterface (is it loaded from the zip too?):
(ServicePluginInterface)zipLoader.loadClass(pluginClass).newInstance();
It seems that the application server already has a definition of ServicePluginInterface, then you don't need to redeploy it. It should be enough to add the required files (ServicePluginInterface, etc.) as non-deployed dependecies of your project.
Another approach goes by living with the fact, and accessing methods in ServicePluginInterface via reflection (use the Class object returned by zipLoader, instead of ServicePluginInterface.class).

Dynamic ClassLoader

I have a large desktop Java application and I want to allow other developers to develop plugins for. Plugins will be jars placed in a specified dir. They will not be on the classpath at startup. I will load and deploy them at runtime.
The complication is that some plugins will have dependencies on each other, as well as the core application. So I cannot load each plugin/jar in its own URLClassLoader. Therefore I want to load all plugins into 1 URLClassLoader. Furthermore, some plugins may fail to initialise for various reasons. And I only want a ClassLoader at the end of day that knows about the successfully loaded plugins. The reasons are quite bizarre and relate to some legacy stuff that is using reflection to instantiate classes. This needs to fail if the plugin doesn't initialise for classes defined inside the plugin jar that failed.
Without this requirement, the solution would be:
Collect the jar URLs and build a ClassLoader based on them
Try to initialise a plugin class from each jar (defined in config in the manifest)
Now the ClassLoader here would be passed to the legacy system for it to use for its reflection stuff. However, it's my understanding that it will still be able to instantiate classes from plugin jars whose plugin failed to initialise (since the jar will still in the URL[] of the ClassLoader). Hence this breaks my requirement above.
The only solution I have come up with so far is to create a custom URLClassLoader as follows (simply to allow access to findClass()):
public class CustomURLClassLoader extends URLClassLoader {
public CustomURLClassLoader(final URL[] urls, final ClassLoader parent) {
super(urls, parent);
}
#Override
protected Class<?> findClass(final String name) throws ClassNotFoundException {
return super.findClass(name);
}
}
And then I made another custom ClassLoader that essentially knows about multiple child ClassLoaders:
public class MultiURLClassLoader extends ClassLoader {
private Set<CustomURLClassLoader> loaders = new HashSet<CustomURLClassLoader>();
public MultiURLClassLoader(final ClassLoader parent) {
super(parent);
}
#Override
protected Class<?> findClass(final String name) throws ClassNotFoundException {
Iterator<CustomURLClassLoader> loadersIter = loaders.iterator();
boolean first = true;
while (first || loadersIter.hasNext()) {
try {
if (first) {
return super.findClass(name);
} else {
return loadersIter.next().findClass(name);
}
} catch (ClassNotFoundException e) {
first = false;
}
}
throw new ClassNotFoundException(name);
}
public void addClassLoader(final CustomURLClassLoader classLoader) {
loaders.add(classLoader);
}
public void removeClassLoader(final CustomURLClassLoader classLoader) {
loaders.remove(classLoader);
}
}
Then my loading plugin alogorithm will be something like
MultiURLClassLoader multiURLClassLoader = new MultiURLClassLoader(ClassLoader.getSystemClassLoader());
for (File pluginJar : new File("plugindir").listFiles()) {
CustomURLClassLoader classLoader = null;
try {
URL pluginURL = pluginJar.toURI().toURL();
final URL[] pluginJarUrl = new URL[] { pluginURL };
classLoader = new CustomURLClassLoader(pluginJarUrl, multiURLClassLoader);
multiURLClassLoader.addClassLoader(classLoader);
Class<?> clazz = Class.forName("some.PluginClass", false, multiURLClassLoader);
Constructor<?> ctor = clazz.getConstructor();
SomePluginInterface plugin = (SomePluginInterface)ctor1.newInstance();
plugin.initialise();
} catch (SomePluginInitialiseException e) {
multiURLClassLoader.removeClassLoader(classLoader);
}
}
Then I can pass the multiURLClassLoader instance onto the legacy system and it will only be able to find classes (via reflection) whose plugin successfully loaded.
I've done some basic testing and it seems to work as I'd like so far. But I would very much like someones opinion on whether this seems like a good idea or not? I have never played this much with ClassLoaders before and I am wanting to avoid getting myself in too deep before its too late.
Thanks!
The problem I see is that if you don't know in advance which plugin depends on which, it's very hard to do anything reasonable, to debug problems, to isolate non-functional or bad-behaving plugins, etc.
Therefore I'd suggest another option: Add another field into each plugin's manifest, which will say on what other plugins it depends. Perhaps just a list of other plugin JARs it needs to function. (The core application classes would be always available.) I believe this would make the design much more robust and simplify many things.
Then, you could choose from different designs, for example:
For each plugin you could create a separate ClassLoader that would load just the JARs it needs. Probably the most robust solution. But I see a drawback: plugins that act as dependencies for many other ones will be loaded repeatedly in different class-loaders. It depends on circumstances (plugin count, JARs size, ...) if this could be a problem or not, it could even be an advantage.
You could have one big ClassLoader for all plugins, as you suggest, but you could ask it for plugin classes in the order of their dependencies. The ones that don't depend on anything first, then the ones that depend on those first ones etc. If some plugin class fails to load/initialize, you could immediately discard all plugins that depend on it.
Are you looking for something like the OSGi approach?
You could do something like Petr Pudlák has said, however you should take in account the fact that one of the solutions you have can create cyclic dependencies...

Load classes from folder without specifying the package

I have an application that allows, using an abstract class, people to write their own implementations. I load these implementations as .class-files from a directory. Currently, I have this solution:
File classDir = new File("/users/myproject/classes/");
URL[] url = { classDir.toURI().toURL() };
URLClassLoader urlLoader = new URLClassLoader(url);
String filename;
for (File file : classDir.listFiles()) {
filename = string.getFilenameWithoutExtension(file);
if (filename.equals(".") || filename.equals("..") || filename.startsWith("."))
continue;
AbstractClass instance = (AbstractClass)urlLoader
.loadClass("org.mypackage." + filename)
.getConstructor(ConfigUtil.class, DatabaseUtil.class, StringUtil.class)
.newInstance(config, database, string));
instance.doSomething();
}
As you see - I need to specify the package the classes are located in in order to correctly load them. Omitting the package, I get an
java.lang.NoClassDefFoundError:
MyClass (wrong name: org/mypackage/MyClass)
error.
Now, from a architectural POV, I think it is very ill-designed that classes other people designed have to be compiled to MY package when loading them.
So I ask you: Is there a way I can load classes form the file system without having to specify the package they reside in?
Yes; implement an interface (or use an annotation).
Then use any class-scanning library (there are lots of SO questions about this, like this one) to load the particular class in question. Searching for "Java class scanning" or "Java plugin mechanism" will help.
You might also just want to use the Java Plugin Framework and avoid some effort. Although it's not clear to me that it's maintained any more, I know people are still using it.
You can use the ServiceProvider to load implementations which you don't know.

How do you change the CLASSPATH within Java?

How do you change the CLASSPATH of a Java process from within the Java process?
Before you ask me "Why would you want to do that?" I'll explain it shortly.
When you have a Clojure REPL running it is common to need more jars in your CLASSPATH to load a Clojure source file, and I'd like to do it without having to restart Clojure itself (which is not really an option when using it on Slime on Emacs).
That's the reason but I don't want this question tagged as some-weird-language some-weird-editor and be disregarded by the majority of Java developers that may have the answer.
Update Q4 2017: as commented below by vda8888, in Java 9, the System java.lang.ClassLoader is no longer a java.net.URLClassLoader.
See "Java 9 Migration Guide: The Seven Most Common Challenges"
The class loading strategy that I just described is implemented in a new type and in Java 9 the application class loader is of that type.
That means it is not a URLClassLoader anymore, so the occasional (URLClassLoader) getClass().getClassLoader() or (URLClassLoader) ClassLoader.getSystemClassLoader() sequences will no longer execute.
java.lang.ModuleLayer would be an alternative approach used in order to influence the modulepath (instead of the classpath). See for instance "Java 9 modules - JPMS basics".
For Java 8 or below:
Some general comments:
you cannot (in a portable way that's guaranteed to work, see below) change the system classpath. Instead, you need to define a new ClassLoader.
ClassLoaders work in a hierarchical manner... so any class that makes a static reference to class X needs to be loaded in the same ClassLoader as X, or in a child ClassLoader. You can NOT use any custom ClassLoader to make code loaded by the system ClassLoader link properly, if it wouldn't have done so before. So you need to arrange for your main application code to be run in the custom ClassLoader in addition to the extra code that you locate.
(That being said, cracked-all mentions in the comments this example of extending the URLClassLoader)
And you might consider not writing your own ClassLoader, but just use URLClassLoader instead. Create a URLClassLoader with a url that are not in the parent classloaders url's.
URL[] url={new URL("file://foo")};
URLClassLoader loader = new URLClassLoader(url);
A more complete solution would be:
ClassLoader currentThreadClassLoader
= Thread.currentThread().getContextClassLoader();
// Add the conf dir to the classpath
// Chain the current thread classloader
URLClassLoader urlClassLoader
= new URLClassLoader(new URL[]{new File("mtFile").toURL()},
currentThreadClassLoader);
// Replace the thread classloader - assumes
// you have permissions to do so
Thread.currentThread().setContextClassLoader(urlClassLoader);
If you assume the JVMs system classloader is a URLClassLoader (which may not be true for all JVMs), you can use reflection as well to actually modify the system classpath... (but that's a hack;)):
public void addURL(URL url) throws Exception {
URLClassLoader classLoader
= (URLClassLoader) ClassLoader.getSystemClassLoader();
Class clazz= URLClassLoader.class;
// Use reflection
Method method= clazz.getDeclaredMethod("addURL", new Class[] { URL.class });
method.setAccessible(true);
method.invoke(classLoader, new Object[] { url });
}
addURL(new File("conf").toURL());
// This should work now!
Thread.currentThread().getContextClassLoader().getResourceAsStream("context.xml");
I don't believe you can - the right thing to do (I believe) is create a new classloader with the new path. Alternatively, you could write your own classloader which allows you to change the classpath (for that loader) dynamically.
There's no need to write your own class loader! There's clojure.lang.DynamicClassLoader.
http://blog.japila.pl/2011/01/dynamically-redefining-classpath-in-clojure-repl/
You may want to look into using java.net.URLClassLoader. It allows you to programmatically load classes that weren't originally in your classpath, though I'm not sure if that's exactly what you need.
It is possible as seen from the two links below, the method VonC gives seems to be the best but check out some of these posts and google for "Java Dynamic Classpath" or "Java Dynamic Class Loading" and find out some info from there.
I'd post in more depth but VonC has pretty much done the job.
From Dynamic loading of class and Jar files.
Also check this sun forum post.
String s="java -classpath abcd/ "+pgmname+" "+filename;
Process pro2 = Runtime.getRuntime().exec(s);
BufferedReader in = new BufferedReader(new InputStreamReader(pro2.getInputStream()));
is an example of changin the classpath in java program

Categories

Resources