understanding urlclassloader, how to access a loaded jar's classes - java

I am trying to understand how to access/make available a jar file using URLClassLoader.
Firstly I am loading the jar file with
package myA;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import org.jgroups.JChannel;
public class loader {
JChannel channel;
String user_name=System.getProperty("user.name", "n/a");
private void start() throws Exception {
channel=new JChannel(); // use the default config, udp.xml
channel.connect("ChatCluster");
}
public void loadMe()throws ClassNotFoundException, MalformedURLException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
URL classUrl;
classUrl = new URL("file:///home/myJars/jgroups-3.4.2.Final.jar");
URL[] classUrls = { classUrl };
URLClassLoader ucl = new URLClassLoader(classUrls);
Class<?> c = ucl.loadClass("org.jgroups.JChannel");
for(Field f: c.getDeclaredFields()) {
System.out.println("Field name=" + f.getName());
}
Object instance = c.newInstance();
//Method theMethod = c.getMethod("main");
//theMethod.invoke(instance);
}
public static void main(String[] args) throws Exception {
new loader().loadMe();
new loader().start();
}
}
the printout shows the declared fields that are in jgroups-3.4.2.Final.jar, however it then throws a classnotfound error.
java -cp myA.jar myA.loader
Field name=DEFAULT_PROTOCOL_STACK
Field name=local_addr
Field name=address_generator
Field name=name
Field name=cluster_name
Field name=my_view
Field name=prot_stack
Field name=state_promise
Field name=state_transfer_supported
Field name=flush_supported
Field name=config
Field name=stats
Field name=sent_msgs
Field name=received_msgs
Field name=sent_bytes
Field name=received_bytes
Field name=probe_handler
Exception in thread "main" java.lang.NoClassDefFoundError: org/jgroups/JChannel
at myA.loader.start(loader.java:23)
at myA.loader.main(loader.java:45)
Caused by: java.lang.ClassNotFoundException: org.jgroups.JChannel
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)
... 2 more
I don't understand why the printout shows that the class is loaded but then it is not found?
thx
Art

Your code has maybe a couple of problems. First, you instantiate the loader 2 times within main, so the second instance is independent from the first one and might not be aware that the first one loaded the class file definition of JChannel.
Moreover you've defined JChannel as a member of loader before, therefore the JRE should require the class definition for it at startup - else it should not know what this field should be. I've replaced it with the class you've loaded via the URLClassLoader which you should instantiate in start().
package myA;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import org.jgroups.JChannel;
public class Loader
{
Class<?> clazz;
String user_name=System.getProperty("user.name", "n/a");
private void start() throws Exception
{
if (this.clazz == null)
throw new Exception("Channel class was not loaded properly");
Object channel = this.clazz.newInstance(); // use the default config, udp.xml
Method chatCluster = this.clazz.getDeclaredMethod("connect", new Class[] { String.class });
chatCluster.invoke(channel, "ChatCluster");
}
public void loadMe() throws Exception
{
URL classUrl;
classUrl = new URL("file:///home/myJars/jgroups-3.4.2.Final.jar");
URL[] classUrls = { classUrl };
URLClassLoader ucl = new URLClassLoader(classUrls);
Class<?> c = ucl.loadClass("org.jgroups.JChannel");
for(Field f: c.getDeclaredFields())
{
System.out.println("Field name=" + f.getName());
}
this.clazz = c;
Object instance = c.newInstance();
//Method theMethod = c.getMethod("main");
//theMethod.invoke(instance);
}
public static void main(String[] args) throws Exception
{
Loader loader = new Loader();
loader.loadMe();
loader.start();
}
}
You should further add some error handling to the code.

Exception in thread "main" java.lang.NoClassDefFoundError: org/jgroups/JChannel
at myA.loader.start(loader.java:23)
The code fails on this line:
channel=new JChannel(); // use the default config, udp.xml
The type JChannel is not visible to loader's ClassLoader. This will be obvious if you try:
loader.class
.getClassLoader()
.loadClass("org.jgroups.JChannel");
You should not have any compile-time references to a dependency that will not be on the type's classpath at runtime.
Loading with a new child ClassLoader does not add that type to some global class pool. The loaders are hierarchical with child-parent relationships.

Related

ClassCastException thrown when calling .asSubclass from separate jar

I have a program through which I have the following three classes. These first two are in jar1.jar:
Main (uses the jar loading trick found here):
package prob1;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class Main {
static List<Class<?>> classes = new ArrayList<Class<?>>();
static String pathToJar = "/Users/vtcakavsmoace/Desktop/jar2.jar";
public static void main(String[] args) throws ClassNotFoundException, IOException, InstantiationException, IllegalAccessException {
loadJar();
classes.get(0).asSubclass(A.class).newInstance().someMethod();
}
private static void loadJar() throws IOException, ClassNotFoundException {
JarFile jarFile = new JarFile(pathToJar);
Enumeration<JarEntry> e = jarFile.entries();
URL[] urls = { new URL("jar:file:" + pathToJar + "!/") };
URLClassLoader cl = URLClassLoader.newInstance(urls);
while (e.hasMoreElements()) {
JarEntry je = e.nextElement();
if (je.isDirectory() || !je.getName().endsWith(".class")) {
continue;
}
String className = je.getName().substring(0, je.getName().length() - 6);
className = className.replace('/', '.');
classes.add(cl.loadClass(className));
}
jarFile.close();
}
}
A:
package prob1;
public interface A {
void someMethod();
}
And a second class found in jar2.jar, B:
package prob2;
import prob1.A;
public class B implements A {
#Override
public void someMethod() {
System.out.println("Hello, world!");
}
}
As you can see, class B obviously implements interface A. However, when loading class B, and then attempting to call asSubclass on it, fails with the following exception:
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.eclipse.jdt.internal.jarinjarloader.JarRsrcLoader.main(JarRsrcLoader.java:58)
Caused by: java.lang.ClassCastException: class prob2.B
at java.lang.Class.asSubclass(Class.java:3404)
at prob1.Main.main(Main.java:20)
... 5 more
Note that this does not fail when run through Eclipse.
What am I doing wrong here? How can I fix this?
Okay, found the answer using the knowledge that Erwin Bolwidt gave me; in class Main, replacing:
URLClassLoader cl = URLClassLoader.newInstance(urls);
with:
URLClassLoader cl = URLClassLoader.newInstance(urls, Main.class.getClassLoader());
Fixes the problem. :P

Though my class was loaded, Class.forName throws ClassNotFoundException

The code is as follows
what it does is it loads all the classes inside a jar file which I placed inside my home directory .
import java.io.File;
import java.util.jar.JarFile;
import java.util.jar.JarEntry;
import java.net.URLClassLoader;
import java.net.URL;
import java.util.Enumeration;
import java.lang.ClassLoader;
public class Plugin extends ClassLoader {
public static void main(String[] args) throws Exception {
File file = new File(System.getProperty("user.home") + "/HelloWorld.jar");
URLClassLoader clazzLoader = URLClassLoader.newInstance(new URL[]{file.toURI().toURL()});
JarFile jarFile = new JarFile(file);
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry element = entries.nextElement();
if (element.getName().endsWith(".class")) {
try {
Class c = clazzLoader.loadClass(element.getName().replaceAll(".class", "").replaceAll("/", "."));
c.newInstance(); // this proves that class is loaded
} catch (Exception e) {
e.printStackTrace();
}
}
}
Class cls = Class.forName("HelloWorld");
cls.newInstance();
Plugin p = new Plugin();
p.checkIfLoaded();
}
public void checkIfLoaded() {
System.out.println("coming in");
if (findLoadedClass("HelloWorld") != null){
System.out.println("Yepee, HelloWorld class is loaded !");
}
}
}
My HelloWorld is as in https://github.com/HarishAtGitHub/doc/blob/master/makeExecutableJar/HelloWorld.java
and the jar is got using the instructions in my github account mentioned above .
c.newInstance() works .
How did I confirm ?
the static block got executed ...
but Class.forName("HelloWorld") throws ClassNotFoundException
also findLoadedClass("HelloWorld") is null ..
I cannot understand why this strange behaviour ?
Please guide ...
This is a classloader issue.
As per the Javadocs to Class.forName, you are looking up the class using the classloader of the current class. As your main class, this will be the JVM's bootstrap classloader (and will more or less just include the standard library plus anything you provided as a -cp command line argument). It is not going to delegate to the classloader that you instantiated as a local variable, and so will not return classes that that classloader could find.
If you were to specify the classloader explicitly, and call
Class.forName("HelloWorld", true, clazzloader)
then the classloader you just created will be searched instead and your class should be found.
Because Class.forName(String) uses currentClassLoader and you have load the class in different ClassLoader.
According with javadoc, invoking Class.forName(String) is equivalent to:
Class.forName(className, true, currentLoader)

Dynamic class-loading from specific folder

I have a requirement where i have to dynamically load java program based on the input. All java class files are placed in folder : C://Users/me/workspace/File/bin/Encrypt/.
there are 3 class files: Class1.class, Class2.class, Class3.class in this folder
To pick them up in runtime i am using below code:
package First;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
public class Main
{
public static void main(String[] args) {
// Say Class.class is the input file to be picked up.
String abc = "Class1.class";
try {
File file = new File("C:\\Users\\me\\workspace\\File\\bin\\Encrypt");
//convert the file to URL format
URL url = file.toURI().toURL();
URL[] urls = new URL[]{url};
//load this folder into Class loader
ClassLoader cl = new URLClassLoader(urls);
Class cls = cl.loadClass(abc);
System.out.println("cls.getName() = " + cls.getName());
cls.encrypt();
}
catch ( ClassNotFoundException | MalformedURLException e) {
e.printStackTrace();
}
}
}
I am facing below issues here:
The above code shows the error: .NoClassDefFoundError: Class1??
have used all types of - / ,\ ,//,\ in the path.
How can i call a method sum() in Class1 file ??
since you have ...\bin\Encrypt is the folder where you classes are i would bet the package for your classes is Encrypt. in this case abc should be
String abc = "Enrcypt.Class1";
if the classes are in the default package which means they have no package declaration than use
String abc = "Class1";
note also that there is no need for the extension .class in the name of the class.
To call the a method of class loaded this way you should refer to java refelction api.
first get a Method object from Class cl using getMethod
than use that method object's method invoke to call the function. you'll need to provide an instance of the Class dynamically loaded if the the method to call is not static, if it is static just pass null.

how do I call a constructor from a class loaded at runtime? Java

I have a class loader that loads the "main" class from all jar files in the /plugins folder
this assumes that all jars have the package plugin.(plugin name) containing the class called main. each main class has a constructor called main.
the classes load successfully, but I need to know how to call the main constructor from the loaded class.
(this class/classes are loaded at runtime)
I have tried using this:
Constructor c = cls.getConstructor(Integer.class); //line 41
Plugin plug = (Plugin) c.newInstance(0);
but I get this error:
java.lang.NoSuchMethodException: plugin.myplugin.main.<init>(java.lang.Integer)
at java.lang.Class.getConstructor0(Unknown Source)
at java.lang.Class.getConstructor(Unknown Source)
at hkr.classloader.PluginLoader.loadPlugins(PluginLoader.java:41)
at hkr.core.startup.InitializeGame.inigame(InitializeGame.java:32)
at hkr.launcher.main.LauncherMain.main(LauncherMain.java:16)
package hackers.classloader;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import org.java.plugin.Plugin;
public class PluginLoader
{
#SuppressWarnings({ "unused", "rawtypes", "resource" })
public static void loadPlugins() throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
{
Class cls = null;
int x = hackers.core.startup.InitializeGame.map.size();
for (int i = 1; i<=x;i++)
{
String className = hackers.core.startup.InitializeGame.map.get(i + "");
File file = new File(System.getProperty("user.dir") + File.separator + "plugins" + File.separator + className + ".jar");
URL url = null;
try {
url = file.toURI().toURL();
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
URL[] urls = new URL[]{url};
ClassLoader cl = new URLClassLoader(urls);
try {
cls = cl.loadClass("plugin." + className + ".main");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Constructor c = cls.getConstructor(Integer.TYPE);
Plugin plug = (Plugin) c.newInstance(0);
}
}
}
If your constructor takes an java.lang.Integer, from what I see, your code should work.
But if your constructor's sole parameter is an int, getConstructor will fail. You have to use Integer.TYPE instead of Integer.class in that case.
I I am right, what you need to do is:
Constructor c = cls.getConstructor(Integer.TYPE);
Edit: Based on your edits and your comments, there are several problems.
The class you want to load does not seem to have any explicit constructor, which means that you simply need to do cls.getConstructor()
What you want to execute (public static void main), is a static method for which you normally don't need an instance of a class. Also, I'm not sure "main" would be a good name for the reasons explained by user #Eric B.
Since you want to call a method, You have to instantiate the constructor AND also call the method.
Based on my understanding, the code you would want to execute should be something like that:
Constructor c = cls.getConstructor(); // we get the implicit constructor without parameters
Plugin plugin = (Plugin) c.newInstance(); // we instantiate it, no parameters
Method m = cls.getDeclaredMethod("main", Integer.TYPE);
m.invoke(plugin, 0); // we invoke the method "main" on our dynamically loaded class, with the 0 parameter.

How to marshall java.lang.Exception using JAXB Marshaller?

I am trying to marshall java.lang.Exception, but without success. Here's my code -
import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
public class JAXBTester
{
public static void main(String[] args) throws Exception
{
TestReport report = new TestReport();
report.setReportLog("Tests successful.");
File file = new File("TestReport.xml");
JAXBContext jaxbContext = JAXBContext.newInstance(TestReport.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxbMarshaller.marshal(report, file);
}
}
This is the class I want to marshall -
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class TestReport
{
private String reportLog;
private Exception exception;
#XmlElement
public void setReportLog(String reportLog) { this.reportLog = reportLog; }
public String getReportLog() { return reportLog; }
#XmlElement
public void setException(Exception exception) { this.exception = exception; }
public Exception getException() { return exception; }
}
I get the following exception -
Exception in thread "main" com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
java.lang.StackTraceElement does not have a no-arg default constructor.
this problem is related to the following location:
at java.lang.StackTraceElement
at public java.lang.StackTraceElement[] java.lang.Throwable.getStackTrace()
at java.lang.Throwable
at java.lang.Exception
at public java.lang.Exception TestReport.getException()
at TestReport
at com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException$Builder.check(IllegalAnnotationsException.java:91)
at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.getTypeInfoSet(JAXBContextImpl.java:451)
at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.<init>(JAXBContextImpl.java:283)
at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.<init>(JAXBContextImpl.java:126)
at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl$JAXBContextBuilder.build(JAXBContextImpl.java:1142)
at com.sun.xml.internal.bind.v2.ContextFactory.createContext(ContextFactory.java:130)
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:601)
at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:248)
at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:235)
at javax.xml.bind.ContextFinder.find(ContextFinder.java:445)
at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:637)
at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:584)
at JAXBTester.main(JAXBTester.java:14)
This is because I am trying to marshall java.lang.Exception. How to solve this problem?
There are other ways of doing it, such as the one referenced by the commenter. But here's another way...
The Throwable class in Java (superclass of Exception) is serializable in the sense of java.io.Serializable. This means that you can write it to a byte stream and then recompose it later from those bytes. (It's possible that your application might have poorly-written non-serializable subclasses of Throwable. If that's the case, the following won't work.)
So one way to deal with this is to write a custom adapter that serializes the Throwable (or Exception) to bytes. And in the XML, you see the hex for those bytes. Then on the receiving end you can un-serialize and then work with (an exact replica of) the Throwable you started with.
The bad part about this way is that the Exception is not human-readable inside the XML. The good part is that it's really simple. On your TestReport class, put this annotation on your Exception getter:
#XmlJavaTypeAdapter(ThrowableAdapter.class)
public Exception getException() { return exception; }
public void setException(Exception exception) { this.exception = exception; }
And then add this adapter class to your project:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class ThrowableAdapter extends XmlAdapter<String, Throwable> {
private HexBinaryAdapter hexAdapter = new HexBinaryAdapter();
#Override
public String marshal(Throwable v) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(v);
oos.close();
byte[] serializedBytes = baos.toByteArray();
return hexAdapter.marshal(serializedBytes);
}
#Override
public Throwable unmarshal(String v) throws Exception {
byte[] serializedBytes = hexAdapter.unmarshal(v);
ByteArrayInputStream bais = new ByteArrayInputStream(serializedBytes);
ObjectInputStream ois = new ObjectInputStream(bais);
Throwable result = (Throwable) ois.readObject();
return result;
}
}
Then your XML will contain an element like this:
<exception>AED...</exception>
except in instead of ... you'll see a huge hex string. When it's un-marshalled on the other side, it'll be just like the original.
Do you use JRE 1.7? I do not get this exception if I use JAXB version that is distributed with the JRE. However, if I include explicitly JAXB v. 2.1.7, I run into this problem. Therefore, I'd recommend getting rid of all the jaxb instances from your classpath and use the one that is included in the Java runtime.
JRE 6 seems to use 2.1.x, while JRE 7 2.2.x, which probably handles the change in Throwable implementation correctly.
The JAXB version included in your JDK can be found by running
<JDK_HOME>/bin/xjc -version

Categories

Resources