We are working on a dynamic class loader project and trying to invoke a method in a dynamically loaded class via URLClassLoader. It works pretty well when it's run in Eclipse, so the invoking and dynamically loaded classes are bundled in to two different jars then deployed into Server because it’s part of requirement to split into two jars.
NoSuchMethodException exception thrown when calling mapClass.getDeclaredMethod("run", oneParam) using reflection api.
We have verified Jar and it confirms methods being invoked is present in class.
Here is sample code..
import com.altova.io.Input;
import com.altova.io.FileInput;
Input sourceInput = new FileInput(inputFileFullPath);
Class oneParam[] = { Input.class };
Object mapObj = mapClass.newInstance();
Method method = mapClass.getDeclaredMethod(RUN, oneParam);
li = (List) method.invoke(mapObj, sourceInput);
Exception is
java.lang.NoSuchMethodException:
com.sample.test.TrackingService.run(com.altova.io.Input)
Only difference I doubt is Input(in method signature) and FileInput (in the parameter).
It works well in Eclipse, but not in server. It's tested with JDK 1.6 & 1.7, but server runs on jdk 1.7
Second option.. tried as shown below but this time it throws
java.lang.IllegalArgumentException: argument type mismatch
Code
for (Method method : mapClass.getDeclaredMethods()) {
method.setAccessible(true);
if (method.getName().equals("run")) {
Class<?>[] params = method.getParameterTypes();
if (params.length == 2) {
if (params[0].isInstance(sourceInput) && params[1].isInstance(outputStream)) {
li = (List<ByteArrayOutputStream>) method.invoke(mapObj, sourceInput, outputStream);
System.out.println(" Parsing is complete:");
}
}
}}
It seems that you are trying to invoke non-public method. All you need is to make it accessible:
Method method = mapClass.getDeclaredMethod(RUN, oneParam);
method.setAccessible(true);
It worked when the class loader changed as shown below.
URLClassLoader loader = new URLClassLoader(new URL[] { new URL("file:/opt/jars/Tracking.jar") }, this.getClass().getClassLoader());
Rest everything same... as I said, with-out adding the current class loader also worked when run in eclipse.
Related
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.
Until java9 for adding external jar to classpath in runtime by programmatically everybody used:
URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Method method = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class});
method.invoke(sysloader, new Object[]{file.toURI().toURL()});
Now with java9 we have problem:
Exception in thread "main" java.lang.ClassCastException:
java.base/jdk.internal.loader.ClassLoaders$AppClassLoader
cannot be cast to java.base/java.net.URLClassLoader
URLClassLoader doesn't work anymore in Java 9. What to do now under jdk9 for adding an external jar to the classpath in runtime programmatically?
The JavaSE9 release notes read about the same :
The application class loader is no longer an instance of
java.net.URLClassLoader (an implementation detail that was never
specified in previous releases).
Code that assumes that
ClassLoader::getSytemClassLoader returns a URLClassLoader object will
need to be updated.
Note that Java SE and the JDK do not provide an
API for applications or libraries to dynamically augment the class
path at run-time.
Additionally when an extended classpath is required, one can make use of
Class<?> clazz = Class.forName("nameofclass", true, new URLClassLoader(urlarrayofextrajarsordirs));
as suggested in this thread from Oracle. This comes with caveats:
java.util.ServiceLoader uses the thread's ClassLoader context Thread.currentThread().setContextClassLoader(specialloader);
java.sql.DriverManager does honors the calling class' ClassLoader, -not- the Thread's ClassLoader. Create Driver directly
using Class.forName("drivername", true, new
URLClassLoader(urlarrayofextrajarsordirs).newInstance();
javax.activation uses the thread's ClassLoader context (important for javax.mail).
Naman's answer is not a correct replacement for what you are looking for.
The correct way to add a jar to the classpath in Java 9 and above is to use Java Instrumentation's appendToSystemClassLoaderSearch(JarFile jarfile) method.
First you will need to add your Agent class to your MANIFEST.MF
Launcher-Agent-Class: com.yourpackage.Agent
Then add your agent.
The example below will allow you to call Agent.addClassPath(File f) to add a Jar to the classpath in both Java 8 & 9+
public class Agent {
private static Instrumentation inst = null;
// The JRE will call method before launching your main()
public static void agentmain(final String a, final Instrumentation inst) {
Agent.inst = inst;
}
public static boolean addClassPath(File f) {
ClassLoader cl = ClassLoader.getSystemClassLoader();
try {
// If Java 9 or higher use Instrumentation
if (!(cl instanceof URLClassLoader)) {
inst.appendToSystemClassLoaderSearch(new JarFile(f));
return;
}
// If Java 8 or below fallback to old method
Method m = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
m.setAccessible(true);
m.invoke(cl, (Object)f.toURI().toURL());
} catch (Throwable e) { e.printStackTrace(); }
}
}
I have a few groovy classes in the same package ( com.company.config ) in a multi-modular java-8 project.
All used to inherit a java interface ( MyInterface ), but some refactoring was needed so I created a groovy abstract class which resides in the same package as the other scripts; it implements MyInterface and is inherited by the other scripts.
Ever since this change, I can't seem to execute the scripts from java code anymore.
In particular, GroovyClassLoader::parseClass(File) throws:
org.codehaus.groovy.control.MultipleCompilationErrorsException:
startup failed:<|C:\my_workspace\a_project_name\modules\module-setup\src\com\company\config\MyScript1Impl.groovy:
7: unable to resolve class com.company.config.AbstractGroovyClass
# line 7, column 1.
import com.company.config.AbstractGroovyClass
^
At line 7, you can indeed find the import declaration
import com.company.config.AbstractGroovyClass
which I added (despite the class being in the same package) after the first time the same error was thrown and after I read this.
The Exception is triggered in the following line in the java code:
public Object getInstance(File sourceFile) {
try {
GroovyClassLoader gcl = new GroovyClassLoader();
Class clazz = gcl.parseClass(sourceFile); // << Here
Object inst = clazz.newInstance();
// ....
}
// ...
}
whereas I call this function with the following parameters
getInstance(
new File("./modules/module-setup/src/com/company/config/"
className + ".groovy" // className = "MyScript1Impl" in this case
)
);
As already stated, before the introduction of the abstract class, everything was working fine.
Why can't the groovy class find its superclass in the same package, even with the import declaration?
This is the stack-trace of the internal calls:
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed ...
at org.codehaus.groovy.control.ErrorCollector.failIfErrors(ErrorCollector.java:310)
at org.codehaus.groovy.control.CompilationUnit.applyToSourceUnits(CompilationUnit.java:946)
at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:593)
at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:542)
at groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:298
at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:268)
at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:254)
at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:195)
The rest stack trace is relative to the application calls so it's unhelpful.
Edit I
I noticed indeed that the GroovyClassLoader does not know anything about other groovy classes and where they are located so I just added "./modules/module-setup/src/com/company/config/" in
GroovyClassLoader::addClassPath(String)
but I'm getting the same result as before.
The path is surely correct as the File instance is created with it and can be opened by the class loader.
I resolved it temporarily by loading the superclass via GroovyClassLoader::parseClass right before loading the actual inherited class.
final GroovyClassLoader gcl = new GroovyClassLoader();
// ...
// load superclass first
Class<?> abstractClass = gcl.parseClass(new File(classPath, "AbstractGroovyClass.groovy"));
// load the actual script
Class<?> clazz = gcl.parseClass(sourceFile);
It's definitely a bad answer as, if I had more groovy classes on which I depend on, I would have to manually parse them one by one. But it works...
Hoping someone can give a better answer.
I am trying to load classes from a jar file. Basically, I want to call a method in a particular class in a package of that jar. The problem I am facing here is that after the class is successfully loaded from the jar and when I try to instantiate I get exception : ClassNotFound for classes imported in my class.
Here is the class which loads the class:
inputs: D:\Myjar.jar , com.vendor.epbroker.VNFLCMCommunicator
public Class<?> loadClass(String libPath, String pkgName) {
LogManager.getLogger().info("Adding Class");
File jarFile = null;
try {
jarFile = new File(libPath);
URL fileURL = jarFile.toURI().toURL();
String jarURL = "jar:" + fileURL + "!/";
URL urls[] = { new URL(jarURL) };
URLClassLoader ucl = new URLClassLoader(urls);
Class<?> beanClass = ucl.loadClass(pkgName);
ucl.close();
return beanClass;
} catch (Exception ex) {
LogManager.getLogger().error("Given Library: " + libPath + " or Class name: " + pkgName + " is not Valid");
LogManager.getLogger().error("Exception occurred : ", ex);
}
LogManager.getLogger().error("Class loading Error: Returning NULL");
return null;
}
The code snippet which receives this Class:
Object instance = classToLoad.newInstance();
// To get the list of methods exist in the Class
Method[] listOfMethods = classToLoad.getMethods();
The following error is encountered:
SEVERE: Servlet.service() for servlet [spring] in context with path [/vnflcm] threw exception [Handler processing failed; nested exception is java.lang.NoClassDefFoundError: com/vendor/epbroker/exception/EPBrokerException] with root cause
java.lang.ClassNotFoundException: com.vendor.epbroker.exception.EPBrokerException
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.getDeclaredConstructors0(Native Method)
at java.lang.Class.privateGetDeclaredConstructors(Class.java:2671)
at java.lang.Class.getConstructor0(Class.java:3075)
Any help would be appreciated?
Have a look at the following lines:
URLClassLoader ucl = new URLClassLoader(urls);
Class<?> beanClass = ucl.loadClass(pkgName);
ucl.close();
and consider the documentation of URLClassLoader.close():
Closes this URLClassLoader, so that it can no longer be used to load new classes or resources that are defined by this loader.
In other words, you should only close a class loader if you are really done using the classes of that loader. Even if all required classes had already been loaded at this point, there was still the possibility that a required resource needs to be accessed. Note that some frameworks have there own reflection library requiring access to the byte code of the classes, which will be accesses like a resource.
In your specific case, it’s even simpler. You have just loaded one class, which only triggered resolving of the required minimum set of classes (e.g. the direct super class), but no other dependencies. Then you close the class loader, preventing subsequent loading of any other class from your jar file, which hits you when resolving the constructors needs resolving more referenced classes.
There are a few considerations you have to take when trying to use Reflection.
Your URLClassLoader must contain the URL of the jar that you want to reflect into.
If the desired jar depends on any other jars, you must load the URLs for those jars as well.
As #VGR pointed out, you cannot simply use the file path for the jar to use as a URL. One thing you can do is :
File myJar = new File("path/to/myJar.jar");
URL myJarUrl = myJar.toURI().toURL();
A simple example to demonstrate the issue:
Let's call your jar myToolProject. And let's say while developing this tool you created a class, call it JsonMaker, that converts a POJO to a JSON and you accomplish this via the gson.jar. When you build your jar, let's call it myjar.jar, you mention that in the manifest that it depends on gson.
When trying to reflect on myjar, you reflect on each class in your jar, until you reach JsonMaker.class. When trying to reflect here we notice that there is com.google.Gson type object here. The URLClassLoader looks through the URLS in its array and tries to find com.google.Gson in some class. If it cannot find any com.google.Gson class, it cannot reflect on that class, and throws a ClassNotFoundException.
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