I am attempting to load a groovy class by name using a classloader, and the class fails to load in the case that the class has a reference to a static inner class in another class.
Inside my groovy class I have the following:
def classLoader = getClass().classLoader
try {
classLoader.loadClass( "com.test.TestClass" )
} catch(Throwable e) {
Sigil.logger.error("Error loading class: $it >> ${e.message}", e)
}
In the above, my groovy file TestClass has a static inner class inside it, that extends a static inner class of another file. When I try to run the above code I get the message:
ERROR [05 Aug 2013 06:53:28,851] (invoke0:?) - Error loading class: com.test.TestClass >> startup failed:
unable to resolve class UserValidity.Validator
# line 85, column 5.
public static class Validator extends UserValidity.Validator{
^
1 error
Has anyone come across any problems dealing with static inner classes and class loading in groovy before? The classes all compile correctly and unit tests run etc. I would have thought that when I try to load the class TestClass explicitly in my classloader, it would also load the other necessary classes from the source tree as needed?
UPDATE:
Here is a snippet of the class that is failing to load:
class TestClass{
//... Other normal class stuff here
public static class Validator extends UserValidity.Validator
#Override
def validate(u) {
def result = super.validate(u)
if(!u.valid ){
result += [isValid:false]
}
result
}
}
}
And this fails as it says it cannot resolve the reference to the UserValidity.Validator, which is also pretty simple:
class UserValidity {
//normal class stuff here
public static class Validator {
def validate(u){
//do validation stuff
result
}
}
}
Both are just regular groovy classes.
UPDATE 2:
If I extract the static inner class UserValidity.Validator out in to a standalone class, and just extend that with the static inner class in TestClass then it appears to work, so definitely seems to be some issue with the parent of the inner class being another inner class
Related
I hava A Class in another jar and i use it in B bean.
Now i want to add log for method in A Class. How can i do this in my project without fix the jar.
My mind:
use ApplicationListener to redefine class before bean init.
do something in onApplicationEvent() to redefine the A class. // this is my question.
I know that can use asm or other tool to fix bytecode. I hava see instrument and do not find solution . https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/Instrumentation.html
How to obtain instance of Instrumentation in Java
A class.
public class A {
public void find(){
System.out.println("aaa");
//i want to add log here.
}
B bean
#Service public class B {
public A get(){
return new A();
}
ApplicationListener
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if(event.getApplicationContext().getParent() != null){
return;
}
// redefine class
}
then when i use b.get().find() it will print the log i add.
find my solution.
using javassist modify the class. and load it (using CtClass.toClass())in advance.
https://www.javassist.org/tutorial/tutorial.html
I am trying to access annotations of a class before applying some transformation in a java agent implemented with ByteBuddy.
To access the annotations, I am trying to load the Class object, but it seems that this creates a duplicate class definition.
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.matcher.ElementMatchers;
import java.lang.instrument.Instrumentation;
public class SimpleTestAgent {
public static void premain(String arg, Instrumentation inst) {
new AgentBuilder.Default()
.type(ElementMatchers.isAnnotatedWith(SomeAnnotationType.class))
.transform((builder, type, classLoader, javaModule) -> {
try {
Class loadedClass = Class.forName(type.getName(), true, classLoader);
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
return builder;
})
.installOn(inst);
}
}
A simple class that just creates an instance of the class TestClass that is annotated with the expected annotation, throws the following exception:
Exception in thread "main" java.lang.LinkageError: loader (instance of
sun/misc/Launcher$AppClassLoader): attempted duplicate class
definition for name: "TestClass"
public class AgentTest {
public static void main (String[] args) {
TestClass pet = new TestClass();
}
}
I am trying to implement the agent like in the example described in: Easily-Create-Java-Agents-with-ByteBuddy
Is there a way to load the Class object without causing this issue or a way to access the annotations using the arguments passed to the transformation() method?
The transformation should be able to implement new interfaces and not only override methods this is why I think I cannot use "ForAdvice".
UPDATE
The following loop finds the TestClass only after the Class.forName() is executed. This means that the class has not been loaded and so probably there is no hope to use Class.forName to get the annotations.
for (Class<?> t : inst.getAllLoadedClasses()) {
System.out.println("Class name: " + t.getName());
}
It is possible to get the annotations and the full information about a class using the net.bytebuddy.description.type.TypeDescription instance passed to the transform() method.
The problem is that, for example, I need the Method objects that can be called using reflection. It would be easier if I could somehow access the Class object for the class that is being transformed.
Byte Buddy exposes all information on annotations of a class via the TypeDescription API already, you should not load classes as a class that is loaded during a transformation loads this class before the transformation is applied and aborts class loading with the error you observe. Instead, implement your own matcher:
.type(type -> check(type.getDeclaredAnnotations().ofType(SomeAnnotation.class).load())
Byte Buddy will represent the annotation for you without loading the carrier class itself but rather represent it using its own class file processor.
You should make sure the annotation class is loaded before installing your agent builder to avoid a circularity for this exact annotation.
Recently I'm creating something that have to load/unload external jar packages dynamically. I'm now trying to do this with URLClassLoader, but I keep getting NoClassDefFoundError while trying to make new instances.
It seems that the external class is loaded successfully since the codes in the constructor are executed, but ClassNotFoundException and NoClassDefFoundError still keep being thrown.
I made an small package that recreates the error and here are the codes:
The codes below are in ExternalObject.class ,which is put in a .jar file, that I'm trying to load dynamically:
package test.outside;
import test.inside.InternalObject;
public class ExternalObject
{
private final String str;
public ExternalObject()
{
this.str = "Creating an ExternalObject with nothing.";
this.print();
}
public ExternalObject(InternalObject inObj)
{
this.str = inObj.getString();
this.print();
}
public void print()
{
System.out.println(this.str);
}
}
And the codes below are in InternalObject.class:
package test.inside;
public class InternalObject
{
private final String str;
public InternalObject(String s)
{
this.str = s;
}
public String getString()
{
return this.str;
}
}
I tested the file with Main.class below:
package test.inside;
import java.io.File;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLClassLoader;
import test.outside.ExternalObject;
public class Main
{
public static void main(String[] args)
{
try
{
File externalJar = new File("F:\\Dev\\ext.jar");
URLClassLoader uclTest = new URLClassLoader(new URL[]{externalJar.toURI().toURL()});
Class<?> clazz = uclTest.loadClass("test.outside.ExternalObject");
InternalObject inObj = new InternalObject("Creating an ExternalObject with an InternalObject.");
try
{
System.out.println("Test 1: Attempt to create an instance of the ExternalObject.class with an InternalObject in the constructor.");
Constructor<?> conTest = clazz.getConstructor(InternalObject.class);
ExternalObject extObj = (ExternalObject)conTest.newInstance(inObj);
}
catch(Throwable t)
{
System.out.println("Test 1 has failed. :(");
t.printStackTrace();
}
System.out.println();
try
{
System.out.println("Test 2: Attempt to create an instance of the ExternalObject.class with a void constructor.");
Constructor<?> conTest = clazz.getConstructor();
ExternalObject extObj = (ExternalObject)conTest.newInstance();
}
catch(Throwable t)
{
System.out.println("Test 2 has failed. :(");
t.printStackTrace();
}
uclTest.close();
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
Both InternalObject.class and Main.class are in a jar pack which is included in the classpath while launched.
And I got this in the console:
Console output screenshot
As the codes this.print() in both constructors of ExternalObject.class are executed, I have really no idea what's wrong. Help! :(
UPDATE: Thank you wero!!! But I actually want to make an instance of ExternalObject for further usage such as accessing methods in it from other classes. Is there any way that I can return the created instance as an ExternalObject? Or I have to use getMethod() and invoke() to access the methods?
Sincerely,
Zevin
Your Main class references ExternalObject and therefore the compiled Main.class has a dependency on ExternalObject.
Now when you run Main and ExternalObject is only available in ext.jar but not in the classpath used to run Main the following happens:
The uclTest classloader successfully loads ExternalObject from ext.jar. Also creation succeeds (seen by the print statement in the constructor).
But what fails are the assignments to local variables ExternalObject extObj.
Main cannot use the loaded class ExternalObject since it is loaded by a different classloader. There is also no ExternalObject in the classpath of Main and you get a NoClassDefFoundError.
Your test should run without problems when you remove the two assignments ExternalObject extObj = (ExternalObject).
I think because there are two classLoaders involved, and you try to cast an object from a classLoader to an object from another class loader. But is just a guess.
How you are running the Main class is causing the problem.
As you said, I have created jar called ext1.jar with ExternalObject and InternalObjct class files inside it.
And created ext.jar with Main and InternalObject class files.
If I run the following command, it throws Exception as you mentioned
java -classpath .;C:
\path\to\ext.jar test.inside.Main
But, If I run the following command, it runs fine without any Exception
java -classpath .;C:
\path\to\ext1.jar;C:
\path\to\ext.jar test.inside.Main
Hooray!! I just found a better way for my codes! What I did is creating an abstract class ExternalBase.class with all abstract methods I need, then inherit ExternalObject.class from ExternalBase.class. Therefore dynamically loaded class have to be neither loaded into the custom loader nor imported by the classes that use the object, and the codes work totally perfect for me. :)
Is there any simple way, to load a class given a class instance?
I know that I can load a class by calling
Class.forName(nameOfClass);
where the nameOfClass is the name of the class which I want to be loaded.
I also know that by instantiating a class this way
clazz.newInstance();
where clazz is an instance of Class, the class loads if it was not loaded before, but I don't want to instantiate if it's not necessary.
My question is that, is there any simple way to load a class given a Class instance?
I could solve the problem this way:
try {
Class.forName(clazz.getName());
} catch (ClassNotFoundException ex) {
assert false : "No way!";
}
but I think this is overcomplicated.
From your comment:
I don't want to instantiate... just load
the class – Bartis Áron 15 mins ago
Then you can just do fallow:
Add method yo your class:
public static final void init() {
}
And then just call it:
YourClass.init();
I have a ClassLoader which loads a class compiled by JavaCompiler from a source file.
But when I change the source file, save it and recompile it, the ClassLoader still loads the first version of the class.
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Class<?> compiledClass = cl.loadClass(stringClass);
What am I missing? like a newInstance or something?
A classloader can't replace a class that already has been loaded. loadClass will return the reference of the existing Class instance.
You'll have to instantiate a new classloader and use it to load the new class. And then, if you want to "replace" the class, you'll have to throw this classloader away and create another new one.
In response to your comment(s): do something like
ClassLoader cl = new UrlClassLoader(new URL[]{pathToClassAsUrl});
Class<?> compiledClass = cl.loadClass(stringClass);
This classloader will use the "default delegation parent ClassLoader" and you have to take care, the class (identified by it fully qualified classname) has not been loaded and can't be loaded by that parent classloader. So the "pathToClassAsUrl" shouldn't be on the classpath!
You have to load a new ClassLoader each time, or you have to give the class a different name each time and access it via an interface.
e.g.
interface MyWorker {
public void work();
}
class Worker1 implement MyWorker {
public void work() { /* code */ }
}
class Worker2 implement MyWorker {
public void work() { /* different code */ }
}
As it was stated before,
Each class loader remembers (caches) the classes that is has loaded before and won't reload it again - essentially each class loader defines a namespace.
Child class loader delegates class loading to the parent class loader, i.e.
Java 8 and before
Custom Class Loader(s) -> App Class Loader -> Extension Class Loader -> Bootstrap Class Loader
Java 9+
Custom Class Loader(s) -> App Class Loader -> Platform Class Loader -> Bootstrap Class Loader.
From the above we can conclude that each Class object is identified by its fully qualified class name and the loader than defined it (also known as defined loader)
From Javadocs :
Every Class object contains a reference to the ClassLoader that
defined it.
The method defineClass converts an array of bytes into an instance of
class Class. Instances of this newly defined class can be created
using Class.newInstance.
The simple solution to reload class is to either define new (for example UrlClassLoader) or your own custom class loader.
For more complex scenario where you need to substitute class dynamic proxy mechanism can be utilized.
Please see below simple solution I used for a similar problem to reload same class by defining custom class loader.
The essence - override findClass method of the parent class loader and then load the class from bytes read from the filesystem.
MyClassLoader - overrides findClass and executed defineClass
package com.example.classloader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
public class MyClassLoader extends ClassLoader {
private String classFileLocation;
public MyClassLoader(String classFileLocation) {
this.classFileLocation = classFileLocation;
}
#Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classBytes = loadClassBytesFromDisk(classFileLocation);
return defineClass(name, classBytes, 0, classBytes.length);
}
private byte [] loadClassBytesFromDisk(String classFileLocation) {
try {
return Files.readAllBytes(Paths.get(classFileLocation));
}
catch (IOException e) {
throw new RuntimeException("Unable to read file from disk");
}
}
}
SimpleClass - experiment subject -
** IMPORTANT : Compile with javac and then remove SimpleClass.java from class path (or just rename it)
Otherwise it will be loaded by System Class Loader due to class loading delegation mechanism.**
from src/main/java
javac com/example/classloader/SimpleClass.java
package com.example.classloader;
public class SimpleClassRenamed implements SimpleInterface {
private static long count;
public SimpleClassRenamed() {
count++;
}
#Override
public long getCount() {
return count;
}
}
SimpleInterface - subject interface : separating interface from implementation to compile and execute output from the subject.
package com.example.classloader;
public interface SimpleInterface {
long getCount();
}
Driver - execute to test
package com.example.classloader;
import java.lang.reflect.InvocationTargetException;
public class MyClassLoaderTest {
private static final String path = "src/main/java/com/example/classloader/SimpleClass.class";
private static final String className = "com.example.classloader.SimpleClass";
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException,
InvocationTargetException, InstantiationException { // Exception hell due to reflection, sorry :)
MyClassLoader classLoaderOne = new MyClassLoader(path);
Class<?> classOne = classLoaderOne.loadClass(className);
// we need to instantiate object using reflection,
// otherwise if we use `new` the Class will be loaded by the System Class Loader
SimpleInterface objectOne =
(SimpleInterface) classOne.getDeclaredConstructor().newInstance();
// trying to re-load the same class using same class loader
classOne = classLoaderOne.loadClass(className);
SimpleInterface objectOneReloaded = (SimpleInterface) classOne.getDeclaredConstructor().newInstance();
// new class loader
MyClassLoader classLoaderTwo = new MyClassLoader(path);
Class<?> classTwo = classLoaderTwo.loadClass(className);
SimpleInterface ObjectTwo = (SimpleInterface) classTwo.getDeclaredConstructor().newInstance();
System.out.println(objectOne.getCount()); // Outputs 2 - as it is the same instance
System.out.println(objectOneReloaded.getCount()); // Outputs 2 - as it is the same instance
System.out.println(ObjectTwo.getCount()); // Outputs 1 - as it is a distinct new instance
}
}
I think the problem might be more basic than what the other answers suggest. It is very possible that the class loader is loading a different file than what you think it is. To test out this theory, delete the .class file (DO NOT recompile your .java source) and run your code. You should get an exception.
If you do not get the exception, then obviously the class loader is loading a different .class file than the one you think it is. So search for the location of another .class file with the same name. Delete that .class file and try again. Keep trying until you find the .class file that is actually being loaded. Once you do that, you can recompile your code and manually put the class file in the correct directory.