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.
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.
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 have a program in which I am generating classes at runtime (included only variable and associated getters and setters methods). Later I want to fill the classes.
To get the class - I know its Name, but its not in the classpath - I tried .forName() but I always get a ClassNotFoundException.
Here is my example:
Exception in thread "main" java.lang.ClassNotFoundException: com.test.wam.business.wsobjects.Testclass
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:264)
at gui.Application.main(Application.java:94)
And the code:
URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
DynamicURLClassLoader dynamicURLClassLoader = new DynamicURLClassLoader(urlClassLoader);
dynamicURLClassLoader.addURL(new URL("file://C:\\dev\\Eclipse_was\\guitest\\generated"));
Class c = Class.forName("com.test.wam.business.wsobjects.Testclass");
Object classInstance = c.newInstance();
The ClassLoader:
public class DynamicURLClassLoader extends URLClassLoader {
public DynamicURLClassLoader(URLClassLoader classLoader) {
super(classLoader.getURLs());
}
#Override
public void addURL(URL url) {
super.addURL(url);
}
}
The full qualified Name to the file (created with eclipse -> copy full qualified Name)
/guitest/generated/com/test/wam/business/wsobjects/Testclass.java
What is wrong here?
I can see two problems:
1) You are creating a DynamicURLClassLoader and adding the URL to it, but you are not actually using it. This statement:
Class c = Class.forName("com.test.wam.business.wsobjects.Testclass");
will use the classloader that loaded the current class. That is probably the application's default classloader, but it is certainly NOT the classloader you just created. The javadoc says:
[Class.forName(className)] returns the Class object associated with the class or interface with the given string name. Invoking this method is equivalent to:
Class.forName(className, true, currentLoader)
where currentLoader denotes the defining class loader of the current class.
So .... the solution is:
Class c = Class.forName("com.test.wam.business.wsobjects.Testclass",
true, dynamicURLClassLoader);
2) This string:
"file://C:\\dev\\Eclipse_was\\guitest\\generated"
is not a valid "file:" URL. The correct URL for the path you are trying to reference would be:
"file:///C:/dev/Eclipse_was/guitest/generated"
The way that you wrote the URL might work, but it is not the correct way to do it.
Reference:
File URIs in Windows
The method Class.forName(String) uses the ClassLoader of the caller class, if you want to use a specific ClassLoader to load your class you need to use Class.forName(String name, boolean initialize, ClassLoader loader) instead as next:
Class c = Class.forName(
"com.test.wam.business.wsobjects.Testclass", true, dynamicURLClassLoader
);
NB: This will work if and only if the URL that you provide to your DynamicURLClassLoader is valid and is the path to the parent folder in which you have your class
I am attempting to load a class object from some compiled class file sitting in my Desktop dir.
I am feeding in two arguments to main in my program which uses URLClassLoader to get an instance of a class from a compiled file TheClassToLoad.class.
I have, in Main of the classLoading program: (args[0] is for something unrelated)
String classFile_FilePath = args[1];
String className = args[2];
URL classUrl = new URL(classFile_FilePath);
URLClassLoader ucl = new URLClassLoader(new URL[]{classUrl});
When running this program from the shell while in the project directory:
Me:ClassLoadingProgramRootDir Me$ java com.company.Main argZero file:///Users/Me/Desktop/ TheClassToLoad.class
I find a raised exception:
Exception in thread "main" java.lang.ClassNotFoundException: TheClassToLoad.class
So, there is a file TheClassToLoad.class in Desktop/ yet URLClassLoader raises an exception without providing the detail I need to debug the situation.
I am new to Java and am aware that class paths like com.company.Class is often needed to refer to a class's true class name based on package directory structure. However, in this case, I am simply requested that URLClassLoader give me an instance of the Class Object for an arbitrary compiled class file sitting somewhere on a machine.
For URLClassLoader, the URL should be of the directory containing the class+package structure, not the class file itself. In your case, it should be file:///Users/Me/Desktop/.
The argument to loadClass should be the name of the class, not the name of the class file. In your case, it should be TheClassToLoad.
If the class is in a package (e.g., my.pkg.TheClassToLoad), then you should use that class name as the argument to loadClass, and the URL for URLClassLoader should still be the root of the package structure (e.g., file:///Users/Me/Desktop if the file is file:///Users/Me/Desktop/my/pkg/TheClassToLoad.class).
So I have a classloader loading a class like so:
ClassLoader cl = new URLClassLoader(urls);
Class cls = cl.loadClass("modules.Test");
Method method = cls.getDeclaredMethod("getModule", noparams);
Class<?> type = method.getReturnType();
if(type.newInstance() instanceof Module){
System.out.println("Accessed field with type: Module");
}
The class Module is in another jar at runtime. And the Test.class was generated within that main jar then i unarchived it, so the dependency would be there.
How can I access other dependencies from the external .class file I have loaded?
The exception:
java.lang.NoClassDefFoundError: com/xxxxxxx/xxxx/objects/Module
Caused by: java.lang.ClassNotFoundException: com.xxxxxxx.xxxx.objects.Module
I think that the problem is happening because your modules.Test class depends the Modules class, but your custom class loader can't find that class.
I think that is because you have instantiated the custom classloader incorrectly. You wrote:
ClassLoader cl = new URLClassLoader(urls);
That creates a classloader whose parent classloader is the default system classloader. But the error implies that the default classloader is not the one that knows about Modules. Try this instead:
Classloader cl = new URLClassLoader(
urls, this.getClass().getClassLoader());
This should at least give you a classloader that knows about Modules.
Note: adding the URL for the JAR containing Modules to the urls array is a non-solution. You are liable to end up loading the Modules class twice, and that is liable to lead to other problems. (The instanceof won't work, for example.)