I'm using java to do some thing as same as C++ Dynamic Library usage.
I didn't find the way to directly use the Same Class Object without reflect invoke style code.
this is my dynamic library code, I make it a jar.
package com.demo;
public class Logic {
public String doWork() {
System.out.println("Hello from Dll");
return "Dll";
}
}
In my main application, I can create the instance by URLClassLoader, invoke by reflect is fine:
public class Main {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
File file = new File("C:\\plugin.jar");
URL url = file.toURI().toURL();
URL[] urls = {url};
ClassLoader parentLoader = Thread.currentThread().getContextClassLoader();
ClassLoader loader = new URLClassLoader(urls, parentLoader);
Thread.currentThread().setContextClassLoader(loader);
Class<?> clazz = loader.loadClass("com.demo.Logic");
System.out.println("New Instance!!");
Object logic = clazz.newInstance();
Method method = logic.getClass().getMethod("doWork");
method.invoke(logic);
}
Output:
New Instance!!
Hello from Dll
But when I change the code without using Reflect invoke:
public class Main {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
File file = new File("C:\\plugin.jar");
URL url = file.toURI().toURL();
URL[] urls = {url};
ClassLoader parentLoader = Thread.currentThread().getContextClassLoader();
ClassLoader loader = new URLClassLoader(urls, parentLoader);
Thread.currentThread().setContextClassLoader(loader);
Class<?> clazz = loader.loadClass("com.demo.Logic");
Logic logic = (Logic)clazz.newInstance();
logic.doWork();
}
}
Compile success(compile with external modules), but when I run the program, it failed at the line Logic logic = (Logic)clazz.newInstance();
Exception:
Exception in thread "main" java.lang.NoClassDefFoundError: com/demo/Logic
at Main.main(Main.java:31)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
Caused by: java.lang.ClassNotFoundException: com.demo.Logic
at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
... 6 more
Is there any way to make it work? Without reflect/interface.(In C++ I can easily achieve this, share same struct/class declare, make sure using same compiler compile two parts. IMHO Java would do it also)
additional explanation 1
I want change the current classloader behavior to make it recognize the dynamic loaded class, this try is to simple and naive, can't find other direction:
ClassLoader parentLoader = Thread.currentThread().getContextClassLoader();
ClassLoader loader = new URLClassLoader(urls, parentLoader);
Thread.currentThread().setContextClassLoader(loader);
In order to make this work, you have to logically split the classes up into three sets:
Your main class
Your plugin classes
Your plugin-dependent classes
When you creating a new class loader, you have to ensure that classes #2 and #3 are both loaded by the same class loader because URLClassLoader delegation only goes towards the parent. That means classes in the JVM application class loader, which loaded your main class, cannot see the class in your new class loader. In order to work like C, you have to update the classpath of your main class, and this is not supported (it is possible, but it is not supported; I've read that Java 9 will remove this capability).
In practice, you should split your main class into two pieces (#1 and #3), and then use reflection to load/invoke the plugin-dependent class (one reflection call, and if your plugin-dependent class implements Runnable, you could use ((Runnable)loadClass("PluginDependent").newInstance()).run() to reduce even that). However, you need to ensure your URLClassLoader does not delegate the load of the plugin-dependent class, for example:
Split your application into the three discrete sets listed above (main.jar, plugin.jar, and main-plugin-dependent.jar), and list all of them on the URLClassLoader.
Change the creation of the URLClassLoader to specify an explicit null parent so that it will not delegate to the JVM application class loader, and then specify both plugin.jar and your main JAR.
Write a custom URLClassLoader that overrides loadClass to ensure that your plugin-dependent classes are loaded by that class loader rather than being delegated to the JVM application class loader.
I tested your code with a small change (used default class loader and placed the Logic in my classpath).
This works:
public class Main {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
ClassLoader parentLoader = Thread.currentThread().getContextClassLoader();
Class<?> clazz = parentLoader.loadClass("com.demo.Logic");
Logic logic = (Logic)clazz.newInstance();
logic.doWork();
}
}
Your problem lies in retrieving the class from the plugin.jar.
Updated:
I also tried retrieving the class from the jar with the code from the second example.
I put the compiled Logic.class in com\demo and built the jar with jar cvf plugin.jar .\com\demo\Logic.class and put in the same path as you did.
It worked without an issue as well.
Also, you don't need to set the current's thread class loader to loader.
At least not for the purposes of this example.
Your plugin.jar may actually not contain the class.
Update to:
additional explanation 1
I want change the current classloader behavior to make it recognize
the dynamic loaded class, this try is to simple and naive, can't find
other direction:
ClassLoader parentLoader = Thread.currentThread().getContextClassLoader();
ClassLoader loader = new URLClassLoader(urls, parentLoader);
Thread.currentThread().setContextClassLoader(loader);
You are doing it right with child class loader.
But if you want to "change the current classloader behavior" you should read this answer
Classloaders are meant to be immutable; you shouldn't be able to
willy-nilly add classes to it at runtime.
thus the solution you came up with a child class loader.
That is why i said "you don't need to set the current's thread class loader to loader (child classloader)."
Related
I have a main method that creates custom classloader and instantiates a class, called Test, with it.
public class App {
public static void main(String[] args) throws Exception {
try {
Class.forName("com.mycompany.app2.Test2"); // We ensure that Test2 is not part of current classpath
System.err.println("Should have thrown ClassNotFound");
System.exit(1);
} catch (ClassNotFoundException e) {
// ignore
}
String jar = "C:\\experiments\\classloader-test2\\target\\classloader-test2-1.0-SNAPSHOT.jar"; // Contains Test2
URL[] classPaths = new URL[] { new File(jar).toURI().toURL() };
ClassLoader classLoader = new URLClassLoader(classPaths, App.class.getClassLoader());
Thread.currentThread().setContextClassLoader(classLoader);
Class.forName("com.mycompany.app2.Test2", true, classLoader); // Check that custom class loader can find the wanted class
Test test = (Test) Class.forName("com.mycompany.app.Test", true, classLoader).getDeclaredConstructor().newInstance();
test.ex(); // This throws ClassNotFound for Test2
}
}
This class then itself instantiates another class that is not part of the original classpath, but is part of the custom one.
public class Test {
public void ex() {
new Test2().test();
}
}
In my understanding of classloader, since Test was created with the custom classloader any class loadings within should be done with the same loader. But this does not seem to be the case.
Exception in thread "main" java.lang.NoClassDefFoundError: com/mycompany/app2/Test2
at com.mycompany.app.Test.ex(Test.java:7)
at com.mycompany.app.App.main(App.java:28)
What do I need to do in the main method to make Test#ex work, without changing Test?
I'm using Java 17.
You create the URLClassLoader using App.class.getClassLoader() as the parent class loader. Hence, the request to load Test through the custom class loader is resolved through the parent loader, ending up at exactly the same class you’d get with Test.class in your main method.
You could pass a different parent loader, e.g. null to denote the bootstrap loader, to forbid resolving the Test class though the parent loader but this would result in either of two unhelpful scenarios
If the custom class loader has no com.mycompany.app.Test class on its own, the loading attempt would simply fail.
If the custom class loader has a com.mycompany.app.Test class, i.e. inside classloader-test2-1.0-SNAPSHOT.jar, it would be a different class than the Test class referenced in your main method, loaded by the application class loader. In this case, the type cast (Test) would fail.
In other words, the Test class referenced by you main method can not be affected by another, unrelated class loader at all.
There is an entirely different approach which may work in some scenarios. Do not create a new class loader, when all you want to do, is to inject a new class.
byte[] code;
try(var is = new URL("jar:file:C:\\experiments\\classloader-test2\\target\\" +
"classloader-test2-1.0-SNAPSHOT.jar!/com/mycompany/app2/Test2.class").openStream())
{
code = is.readAllBytes();
}
MethodHandles.lookup().defineClass(code);
Test test = new Test();
test.ex();
This adds the class to the current class loading context, so subsequent linking will succeed. With the following catches:
No previous attempt to link this class must have been made so far
It only works for a classes without dependencies to other absent classes (as there’s no class loader resolving those to the jar file outside the class path).
In some cases, when the dependencies are non-circular or resolved lazily, you could add all the classes with multiple define calls, if you know which you need and in which order.
The class must be in the same package, otherwise, you’d have to move the lookup context to the right package, with the documented restrictions
An entirely different approach to add the classes to the existing environment, would be via Java Agents, as they can add jar files to the class path.
We have a reporting aplication that by default generates pdf outputs, but you can write your own classes to generate any other output format. This way I have generated xls files using apache poi 10.0. But, now came a request to generate xlsx file. When I try to create a workbook with this code:
XSSFWorkbook wbTemplate=new XSSFWorkbook()
I got the error:
java.lang.NoSuchMethodError: org.apache.xmlbeans.XmlOptions.setSaveAggressiveNamespaces()Lorg/apache/xmlbeans/XmlOptions;
I have discovered that the application already uses a very old version of the xmlbeans file that of course doesn't contain the above method. First I tryed to replace the xml bean file with a newer version just in case I have luck, but the application freezes.
My next idea is to use classLoader and when the app runs my class to generate the xlsx file I load the above method. To do so I have implemented this solution found on the internet:
URL[] classLoaderUrls = new URL[]{new URL("file:/C:/HOME/Installs/Apache POI/poi-3.10/ooxml-lib/xmlbeans-2.6.0.jar")};
URLClassLoader urlClassLoader = new URLClassLoader(classLoaderUrls);
Class<?> beanClass = urlClassLoader.loadClass("org.apache.xmlbeans.XmlOptions");
Constructor<?> constructor = beanClass.getConstructor();
Object beanObj = constructor.newInstance();
Method[] m=beanClass.getMethods();
Method method = beanClass.getMethod("setSaveAggressiveNamespaces");
method.invoke(beanObj);
But what a surprise when it wants to get the "setSaveAggressiveNamespaces" method name I got again the error that this function doesn't exist.
Then I have written into a file all the function names of this class and it is true, that name doesn't exist. But exist another one called "setSaveAggresiveNamespaces" with one S! If I invoke this it works, but of course when I wan't to create the XSSF workbook I still get the message that the setSaveAggressiveNamespaces (with double SS) doesn't exist.
But the setSaveAggressiveNamespaces should be in the class since this is coming with the apache poi package.
What can I do in this case to make it work?
The application runs under java 1.6
Thanks in advance for the answers.
UPDATE
Axel, this is how I load now the class:
public void customClassLoader() throws Exception
{
URL[] classLoaderUrls = new URL[]{new URL("file:/C:/HOME/Installs/Apache POI/poi-3.10/ooxml-lib/xmlbeans-2.3.0.jar")};
URLClassLoader urlClassLoader = new URLClassLoader(classLoaderUrls,null);
Class<?> beanClass = urlClassLoader.loadClass("org.apache.xmlbeans.XmlOptions");
log("RESOURCES:" +beanClass.getResource("/org/apache/xmlbeans/XmlOptions.class"));
Constructor<?> constructor = beanClass.getConstructor();
Object beanObj = constructor.newInstance();
Method[] m=beanClass.getMethods();
for (int i=0;i<m.length;++i)
log("QQQ:" +String.valueOf(i)+".: "+ m[i].getName());
Method method = beanClass.getMethod("setSaveAggressiveNamespaces");
method.invoke(beanObj);
}
And then I call the above function at the first row of the class that generates the report. There is nothing before it.
The RESOURCE is written in the log as:
"RESOURCES:jar:file:/C:/HOME/Installs/Apache POI/poi-3.10/ooxml-lib/xmlbeans-2.3.0.jar!/org/apache/xmlbeans/XmlOptions.class"
URLClassLoader(java.net.URL[]) states:
Constructs a new URLClassLoader for the specified URLs using the
default delegation parent ClassLoader.
So the default delegation parent ClassLoader will also be used and so the org.apache.xmlbeans.XmlOptions will be loaded from there if found and not from the additional given URL.
So we need not using the default delegation parent ClassLoader. URLClassLoader(java.net.URL[], null) is doing this.
Example:
import java.net.URL;
import java.net.URLClassLoader;
import java.lang.reflect.Constructor;
public class UseURLClassLoader {
public static void main(String[] args) throws Exception {
URL[] classLoaderUrls;
URLClassLoader urlClassLoader;
Class<?> beanClass;
classLoaderUrls = new URL[]{new URL("file:/home/axel/Dokumente/JAVA/poi/poi-3.10.1/ooxml-lib/xmlbeans-2.6.0.jar")};
urlClassLoader = new URLClassLoader(classLoaderUrls); //default delegation parent ClassLoader is used
beanClass = urlClassLoader.loadClass("org.apache.xmlbeans.XmlOptions");
System.out.println(beanClass.getResource("/org/apache/xmlbeans/XmlOptions.class")); //class is loaded using default parent class loader
URL context = new URL("file:/home/axel/Dokumente/JAVA/poi/poi-3.10.1/");
classLoaderUrls = new URL[] {
new URL(context, "poi-3.10.1-20140818.jar"),
new URL(context, "poi-ooxml-3.10.1-20140818.jar"),
new URL(context, "poi-ooxml-schemas-3.10.1-20140818.jar"),
// maybe others also necessary
new URL(context, "lib/commons-codec-1.5.jar"),
// maybe others also necessary
new URL(context, "ooxml-lib/xmlbeans-2.6.0.jar")
// maybe others also necessary
};
for (int i = 0; i < classLoaderUrls.length; i++) {
System.out.println(classLoaderUrls[i]);
}
urlClassLoader = new URLClassLoader(classLoaderUrls, null); //set default parent class loader null
beanClass = urlClassLoader.loadClass("org.apache.xmlbeans.XmlOptions");
System.out.println(beanClass.getResource("/org/apache/xmlbeans/XmlOptions.class")); //class is loaded using this class loader
}
}
For me called as follows:
axel#arichter:~/Dokumente/JAVA/poi/poi-4.0.0$ java -cp .:./*:./lib/*:./ooxml-lib/* UseURLClassLoader
it produces:
jar:file:/home/axel/Dokumente/JAVA/poi/poi-4.0.0/ooxml-lib/xmlbeans-3.0.1.jar!/org/apache/xmlbeans/XmlOptions.class
file:/home/axel/Dokumente/JAVA/poi/poi-3.10.1/poi-3.10.1-20140818.jar
file:/home/axel/Dokumente/JAVA/poi/poi-3.10.1/poi-ooxml-3.10.1-20140818.jar
file:/home/axel/Dokumente/JAVA/poi/poi-3.10.1/poi-ooxml-schemas-3.10.1-20140818.jar
file:/home/axel/Dokumente/JAVA/poi/poi-3.10.1/lib/commons-codec-1.5.jar
file:/home/axel/Dokumente/JAVA/poi/poi-3.10.1/ooxml-lib/xmlbeans-2.6.0.jar
jar:file:/home/axel/Dokumente/JAVA/poi/poi-3.10.1/ooxml-lib/xmlbeans-2.6.0.jar!/org/apache/xmlbeans/XmlOptions.class
So at first the class is loaded using default parent class loader. For me it loads org.apache.xmlbeans.XmlOptions farther from the newer xmlbeans-3.0.1.jar. For you it loads farther from the older xmlbeans-1.*.jar. That is because those jars are in class path of the default parent class loader.
The second code part then sets the default parent class loader null and so class is loaded only using this class loader.
But messing around with the class loaders is a mess. As implied in my code, having the default parent class loader set null, we need giving the current class loader all the needed class sources. This often becomes very expensive. So not having the old jars in the class path will always be the better solution than messing around with the class loaders.
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...
}
}
I'd like to create a classloader that extends classpath by several third-party jars. Currently my code looks like this:
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader originalClassLoader = Main.class.getClassLoader();
URL[] jarUrls = ...;
ClassLoader customClassLoader = URLClassLoader.newInstance(jarUrls, originalClassLoader);
Class classFromJar = customClassLoader.loadClass("class.from.Jar");
originalClassLoader.equals(classFromJar.getClassLoader()); // true
customClassLoader.equals(classFromJar.getClassLoader()); // false
// I want classFromJar.getClassLoader() to return customClassLoader
}
}
The problem is that even though the class loading using ClassLoader#loadClass works fine, method #getClassLoader of loaded classes returns original classloader instead of the custom one. Such state causes calls like classFromJar.newInstance() to throw ClassNotFoundExeption because originalClassLoader doesn't know about additional jars and so dependencies of classFromJar can't be loaded.
How to make custom classloader to propagate itself to results of Class#getClassLoader for loaded classes?
I'd like to achieve that purely in runtime, without customizing "java.class.path" system property or JVM -Djava.system.class.loader switch.
My application uses the Standard Widget Toolkit (SWT) for it's GUI. My problem is that the 32-bit SWT library does not work on a 64-bit JVM. But I don't want to make people select the correct architecture when getting the software. So, I want to bundle both the 32-bit and 64-bit libraries, and auto-detect the architecture during runtime. I found out I can get the correct architecture of the JVM like so:
if (System.getProperty("os.arch").contains("64")) {
// ...
}
Now all that's left is to load the jar. But the problem is, all the examples I found require that you manually load the class before using it.
Class.forName("MyClass", false, myClassLoader);
So my question is, is it possible to "register" my class loader, so that I don't have to load classes beforehand?
Update: I created my own child class of URLClassLoader and set it as the default class loader with the command line argument -Djava.system.class.loader; but I get this error:
Error occurred during initialization of VM
java.lang.Error: java.lang.NoSuchMethodException: com.program.LibraryLoader.<init>(java.lang.ClassLoader)
at java.lang.ClassLoader.initSystemClassLoader(Unknown Source)
at java.lang.ClassLoader.getSystemClassLoader(Unknown Source)
I think LibraryLoader.<init> refers to the constructor... but it's there (public LibraryLoader(URI[] urls)).
Update 2: Almost there, the JVM runs now. I added this constructor to make it work:
public LibraryLoader(ClassLoader classLoader) {
super(new URL[0], classLoader);
}
But after adding the jars with addPath() (file:lib/jars/swt.jar), it only produces a NoClassDefFoundError. Yes, I double-checked that the file exists.
You could try to inject your custom class loader by means of the "java.system.class.loader" property (see ClassLoader#getSystemClassLoader). However, I'd recommend to use OSGi and let the framework do the complicated stuff.
As part of the constructor for your custom ClassLoader, call definePackage with the appropriate information, with the URL pointing to the desired jar file.
This example shows that the custom class loader is called when I try to instantiate a class from swing, because I defined my class loader as the loader of that package.
import java.net.URL;
public class junk extends ClassLoader {
byte[] dummy = new byte[0];
public static void main(String[] args) throws Exception {
new junk();
new javax.swing.JPanel();
}
public junk() throws Exception {
definePackage("javax.swing","","","","","","",new URL("file://junk.class"));
}
public Class<?> findClass(String s) throws java.lang.ClassNotFoundException{
Class<?> retVal = super.findClass(s);
System.out.println("delegated responsibility for "+s+" to superclass");
return retVal;
}
public Package getPackage(String s) {
Package retVal = super.getPackage(s);
System.out.println("delegated responsibility for "+s+" to superclass");
return retVal;
}
}
Result:
delegated responsibility for javax.swing to superclass