I am trying to load a class from a URL using java reflection and classLoader. The application is deployed on weblogic 12c server
File f = new File("/a/abc");
URL url = f.toURI().toURL();
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
method.invoke(classLoader, url);
Class jobClass = classLoader.loadClass("com.test.abc");
However, I get the below error:
exception thrown:
java.lang.IllegalArgumentException: object is not an instance of declaring class
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)
Please let me know what might be the issue here or if this is not the correct way to do it.
You are invoking the method 'addURL' on a class of type ClassLoader. The class ClassLoader does not contain the member method 'addURL'. That's why you get the error.
Instead you should call the Method's 'invoke' method with an object of type URLClassLoader.
Related
I'm writting a web application that will run on Tomcat8, that should be able to update while it's still running.
In order to do that, it will create a new ClassLoader and load the whole API again on top of that, every time a given "reload" button is pressed.
// get the urls from the current loader
URLClassLoader loader = (URLClassLoader) Thread.currentThread().getContextClassLoader();
urls.addAll(Arrays.asList(loader.getURLs()));
// get the urls from the tomcat loader
loader = (URLClassLoader) loader.getParent();
urls.addAll(Arrays.asList(loader.getURLs()));
URL urlArray[] = new URL[urls.size()];
urlArray = urls.toArray(urlArray);
// my loader
loader = new URLClassLoader(urlArray, loader.getParent());
// this will throw ClassCastException
// because the newInstance will not return the System object
// that this loader knows
System newSystem = (System) loader.loadClass(System.class.getCanonicalName()).newInstance();
But! The problem begins when I need to call a shutdown method of the system that's about do die.
If I try to store the "system" in a variable, to be able to call shutdown later, I'll get a ClassCastException because, for Java, the System class I've loaded from that other ClassLoader is not the same thing as the System class Tomcat knows about.
How could I call the System.shutdown() I need from the servlet context?
Or is there a very different approach to handle this kind of situation?
The issue seems to be that you have that class in multiple class loaders then - you should not have this class to load from your main class loader as then you would not be able to actually reload that code.
Load class by raw name like that:
System newSystem = (System) Class.forName("my.system.System", true, myClassLoader).newInstance();
newSystem.shutdown();
Or you can use reflections to call method too:
Class<?> systemClass = Class.forName("my.system.System", true, myClassLoader);
Method shutdown = systemClass.getMethod("shutdown");
Object newSystem = systemClass.newInstance();
shutdown.invoke(newSystem);
Or you could use java services, and have interface in your main class loader, and implementation only in that dynamic one you want to be able to reload: https://docs.oracle.com/javase/tutorial/sound/SPI-intro.html
I'm using Javassist(Java 1.7) to add an annotation to the class ClassA, but i get the exception. What am i doing wrong? The code I tried looks like this:
ClassA.java
public class ClassA
{
}
add method
public static <T> Class<T> addXmlRootAnnotationDynamicly(Class<T> declaredTyp) throws NotFoundException, CannotCompileException, InstantiationException, IllegalAccessException
{
//pool creation
ClassPool pool = ClassPool.getDefault();
//extracting the class
CtClass cc = pool.getCtClass(declaredTyp.getCanonicalName());
// create the annotation
ClassFile ccFile = cc.getClassFile();
ConstPool constpool = ccFile.getConstPool();
AnnotationsAttribute attr = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);
Annotation annot = new Annotation("javax.xml.bind.annotation.XmlRootElement", constpool);
attr.addAnnotation(annot);
// add the annotation to the class
cc.getClassFile().addAttribute(attr);
// transform the ctClass to java class
Class<T> dynamiqueBeanClass = cc.toClass();
//instanciating the updated class
// T sayHelloBean = dynamiqueBeanClass.newInstance();
return dynamiqueBeanClass;
}
call
Class<ClassA> addXmlRootAnnotationDynamicly = addXmlRootAnnotationDynamicly(ClassA.class);
Exception
javassist.CannotCompileException: by java.lang.LinkageError: loader (instance of sun/misc/Launcher$AppClassLoader): attempted duplicate class definition for name: "de/it_p/pvlight/share/util/ClassA"
at javassist.ClassPool.toClass(ClassPool.java:1099)
at javassist.ClassPool.toClass(ClassPool.java:1042)
at javassist.ClassPool.toClass(ClassPool.java:1000)
at javassist.CtClass.toClass(CtClass.java:1224)
at de.it_p.pvlight.share.util.JAXBUtil.addXmlRootAnnotationDynamicly(JAXBUtil.java:107)
at de.it_p.pvlight.share.util.JAXBUtilTest.addXmlRootAnnotationDynamicly(JAXBUtilTest.java:60)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.lang.reflect.Method.invoke(Method.java:606)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: java.lang.LinkageError: loader (instance of sun/misc/Launcher$AppClassLoader): attempted duplicate class definition for name: "de/it_p/pvlight/share/util/ClassA"
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:800)
at java.lang.ClassLoader.defineClass(ClassLoader.java:643)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.lang.reflect.Method.invoke(Method.java:606)
at javassist.ClassPool.toClass2(ClassPool.java:1112)
at javassist.ClassPool.toClass(ClassPool.java:1093)
... 15 more
The root of your problem can be found in your stack trace:
attempted duplicate class definition for name: "de/it_p/pvlight/share/util/ClassA"
Your addXmlRootAnnotationDynamicly method takes a loaded class and redefines this very same class without changing its name. After this redefinition, you attempt to load the altered class one more time. This is however not possible in Java where any ClassLoader can only load a class of a given name one single time.
For this reason, the pool.getCtClass method takes a String instead of a loaded Class and works with CtClasses which are used to describe unloaded Classes. To overcome your problem, you have different choices:
Change the signature of your method to addXmlRootAnnotationDynamicly(String) and deliver de.it_p.pvlight.share.util.ClassA as the argument. Make sure that this class is not loaded before you transform it, anywhere in your code. You should therefore run the transformation at your application's startup to make sure that the class is not accidentally loaded before the transformation. Your altered Class is then loaded on cc.toClass().
Create a subclass of the argument class (or use interfaces) which uses a random name. The subclass is then type compatible to your argument class but is never loaded.
Use the Instrumentation API to redefine your loaded class at runtime.
Make sure that the input class and the output class are loaded with different ClassLoaders. (not recommended)
I have small problem. I learn java SE and find class ClassLoader. I try to use it in below code:
I am trying to use URLClassLoader to dynamically load a class at runtime.
URLClassLoader urlcl = new URLClassLoader(new URL[] {new URL("file:///I:/Studia/PW/Sem6/_repozytorium/workspace/Test/testJavaLoader.jar")});
Class<?> classS = urlcl.loadClass("michal.collection.Stack");
for(Method field: classS.getMethods()) {
System.out.println(field.getName());
}
Object object = classS.newInstance();
michal.collection.Stack new_name = (michal.collection.Stack) object;
The java virtual machine does not see me class, and I get the following exception:
Exception in thread "main" java.lang.Error: Unresolved compilation problems: michal cannot be resolved to a type michal cannot be resolved to a type at Main.main(Main.java:62)
Do you know how I can solve this problem?
The above answers are both wrong, they don't understand the root problem. Your main refers to the Stack class which was loaded by one class loader. Your urlclassloader is attempting to load a class with the same name. You cannot cast the loaded to the referred because they are not the same, they belong to different classloaders. You can print the has code of each to see they are different. An equality test will also show the cclass references to be different. Your problem is probably because dependent classes referenced by sstack can be found, which will result in NoClassDefErrors etc. Your main will probably fail with a classcastexception.
Class<?> classS = urlcl.loadClass("michal.collection.Stack");
[...]
Object object = classS.newInstance();
michal.collection.Stack new_name = (michal.collection.Stack) object;
So you're attempting to dynamically load a class and then you statically refer to it. If you can already statically link to it, then its loaded and you can't load it again. You'll need to access the methods by reflection.
What you would usually do is have the loaded class implement an interface from the parent class loader. After an instance is created (usually just a single instance), then you can refer to it through a reference with a type of the interface.
public interface Stack {
[...]
}
[...]
URLClassLoader urlcl = URLClassLoader.newInstance(new URL[] {
new URL(
"file:///I:/Studia/PW/Sem6/_repozytorium/workspace/Test/testJavaLoader.jar"
)
});
Class<?> clazz = urlcl.loadClass("michal.collection.StackImpl");
Class<? extends Stack> stackClass = clazz.asSubclass(Stack.class);
Constructor<? extends Stack> ctor = stackClass.getConstructor();
Stack stack = ctor.newInstance();
(Usual Stack Overflow disclaimer about not so much as compiling.)
You'll need to add error handling to taste. URLClassLoader.newInstance adds a bit of refinement to URLClassLoader. Class.newInstance has completely broken exception handling and should be avoided.
You can't refer to the dynamically-loaded type by name in the code, since that has to be resolved at compile-time. You'll need to use the newInstance() function of the Class object you get back from loadClass().
I want to load class files from module dependencies (external jar files in the classpath). When I tried getResourceAsStream I got null pointer exception:
ClassParser parser = new ClassParser(this.getClass().getResourceAsStream("org/apache/tools/ant/taskdefs/optional/net/FTP.class"), "FTP.class");
Exception in thread "main" java.lang.NullPointerException
at com.sun.org.apache.bcel.internal.classfile.ClassParser.<init>(ClassParser.java:101)
at ParaNamesTest.printUtilsParNames(ParaNamesTest.java:52)
at ParaNamesTest.main(ParaNamesTest.java:45)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
ant.jar on myclasspath but still java can't find it what should I do?
You ask the class loader of your class (this.getClass()) for a resource from a different JAR, this won't work.
Try replacing ... (this.getClass().getResourceAsStream(... by ... (FTP.class.getResourceAsStream(... whith FTP being imported as org.apache.tools.ant.taskdefs.optional.net.FTP.
If you have the class name only at runtime, you can dynamically get a Class object by using the fully qualified class name and Class.forName:
String className = "org.apache.tools.ant.taskdefs.optional.net.FTP";
... (Class.forName(className).getResourceAsStream( ...
If you getting null for this resource it means this class could not be found in your class path. I would try #Philipp's suggestion (not because its more likely to work) but because it will make it obvious if this class is on your class path.
use Class.forName("java.lang.String") for example to get String.class
I am using the following code to dynamically load a class in java:
URL url = new File(ACTIONS_PATH).toURI().toURL();
URLClassLoader clazzLoader = new URLClassLoader(new URL[]{url});
Class<RatingAction> clazz = (Class<RatingAction>) clazzLoader.loadClass(name);
return clazz.newInstance();
This code works with simple classes (no inheritance or interfaces), but the class I want to load is implementing an interface (that the class loader can find using findClass)
and when i call class.newInstance I get the mentioned exception.
What am i doing wrong?
Thank you.
You have problems with your classpath. My guess it happens since you don't define the parent classloader - does "url" contains all the needed classes including the system classes?
You are getting the exception, when the class is actually resolved, so the classes that appear in the loaded class are also loaded. If you change clazzLoader.loadClass(name) to clazzLoader.loadClass(name, true), you will get the exception in loadClass line.
Try the following:
URL url = new File(ACTIONS_PATH).toURI().toURL();
URLClassLoader clazzLoader = new URLClassLoader(new URL[]{url}, getClass().getClassLoader());
Class<RatingAction> clazz = (Class<RatingAction>) clazzLoader.loadClass(name);
return clazz.newInstance();