How do runtime dependencies in Java work exactly. For example, is code like this possible if Impl1 or Impl2 are not in the classpath at runtime:
Thinger t;
if (classIsAvailable(Impl1.class)) t = new Impl1();
else t = new Impl2();
t.doThing();
Or if there is no common interface:
if (classIsAvailable(Impl1.class)) Impl1.doThingThisWay();
else Impl2.doThingTheOtherWay();
You can't do it exactly like this, because in order to evaluate Impl1.class, said class must be available (i.e. loaded). You can however try to load a specific class by its name
Class aClass = classLoader.forName("Impl1");
If this does not fail (throw an Exception) you can create an instance of this class using newInstance().
Of course, in order to be able to use your class, you have to make sure it implements an Interface, which is known at compile time. In this case you can cast your created object to that interface type and continue using it.
This article has some sample code.
ClassNotFoundException is thrown when
an application tries to load in a class through its string name using:
* The forName method in class Class.
* The findSystemClass method in class ClassLoader .
* The loadClass method in class ClassLoader.
but no definition for the class with the specified name could be found.
You may also find http://www.xyzws.com/javafaq/what-does-classforname-method-do/17 useful.
HTH
Your example would fail with a NoClassDefFoundError when your class is loaded with either Impl1 or Impl2 not in the classpath, so none of the code would execute in that case.
Related
I was playing with classloaders in java and found the following behavior. I could logically reason out about this, but I'm not sure what I'm assuming is completely true. I'd like to know more formal explanation of this behavior.
What I was trying?
So I had the following code:
URL[] classURLs = {new URL("file://C:/Users/HP/IdeaProjects/test/out/production/test/")};
URLClassLoader urlClassLoader = new URLClassLoader(classURLs, null);
Class<?> personClass = urlClassLoader.loadClass("com.test.Person");
// the following line will give a ClassCastException
Person p = (Person) personClass.getDeclaredConstructor().newInstance();
Now the last line gives me a ClassCastException.
My reasoning (guess) about why I'm getting a ClassCastException: The classloader of personClass is urlClassLoader whereas the classloader of Person class is actually application class loader or system class loader (please correct me if I'm wrong). These class loaders don't match and I'm getting a ClassCastException. (I'm here assuming that when typecasting a check is performed on the classloaders)
So now I continue exploring and alter the construction of URLClassLoader in the following way:
URLClassLoader urlClassLoader = new URLClassLoader(classURLs, Main.class.getClassLoader());
Here Main is the enclosing class. The above line saves me from a ClassCastException.
My reasoning (guess) about this: As now the urlClassLoader has application class loader as its parent (this application class loader is same that is used to load Person class), while trying to cast, Java check if the classloaders match and this check continues with the parent of the urlClassLoader, after going one step up the classloaders match and there is no ClassCastException.
I assume that the classloader of the class of the object to be typecasted is checked against the classloader of the class into which you need to typecast and if this don't match the parent of the classloader of the class of object is tried for the match and this continues.
Please correct me if I'm wrong at any point and also provide pointers to the formal documentation of this behavior.
I have seen this link, but this don't provide the details I've asked.
The formal documentation for the behaviour that you observe is in the ClassLoader#loadClass() documentation:
Loads the class with the specified binary name. The default implementation of this method searches for classes in the following order:
Invoke findLoadedClass(String) to check if the class has already been loaded.
Invoke the loadClass method on the parent class loader. If the parent is null the class loader built-in to the virtual machine is used, instead.
Invoke the findClass(String) method to find the class.
If you specify a parent class loader your URLClassLoader checks the parent class loader for the class before trying to load the class itself, which means that it will find the class from your application class path.
So if you set the parent class loader, this line:
Class<?> personClass = urlClassLoader.loadClass("com.test.Person");
behaves the same as
Class<?> personClass = Main.class.getClassLoader().loadClass("com.test.Person");
if the class com.test.Person is available on the application class loader (which it must be, otherwise your Main class cannot be loaded).
You are loading the classes dynamically, thus, since you're able to compile class "Person", it means you're loading the same class twice, resulting in class cast exception.
Remove the library from your classpath and you won't get this error however, you also will loose access to the Person object.
Its still there when you load it, but the way to access it would be via Reflection, and you'll have to store the "Person" object as an "Object".
I'm currently developing a custom ORM framework and utilising ASM to dynamically generate sub classes at runtime. The generation process seems to complete OK, however when I try to instantiate the resulting class I'm getting a "NoClassDefFoundError".
The error seems to pertain to the Super class rather then the actual subclass. Here is an excerpt from the subclass generation method:
private Class generateProxyClass(Class anEntitySuperClass,
List<Field> fieldsToIntercept) throws ClassNotFoundException{
String entitySuperClassName = this.convertToInternalName(anEntitySuperClass.getName());
//String entityProxySubClassName = "com/flux/dynamic/".concat(anEntitySuperClass.getSimpleName()).concat("Proxy");
String entityProxySubClassName = anEntitySuperClass.getSimpleName().concat("Proxy");
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cw.visit(V1_6,ACC_PUBLIC+ACC_SUPER,entityProxySubClassName,null,entitySuperClassName,null);
cw.visitSource(entityProxySubClassName.concat(".java"),null);
//create constructor
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC,"<init>","()V",null,null);
mv.visitCode();
//have our consturctor initailise its super class.
mv.visitVarInsn(ALOAD,0);
mv.visitMethodInsn(INVOKESPECIAL,entitySuperClassName,"<init>","()V");
mv.visitInsn(RETURN);
mv.visitMaxs(0,0);
mv.visitEnd();
this.generateAndAppendProxyAccessorMethods(anEntitySuperClass,fieldsToIntercept, cw);
cw.visitEnd();
//at this point our class should be fully generated an include all required fields. next we
//convert the class to a byte array and pass it in to our helper method to load an
//actual class from the byte array.
return this.loadProxyClass(cw.toByteArray(),entityProxySubClassName);
}
The "loadProxyClass" method called above is a helper method that basically instantiates
and calls a custom ClassLoader in order to load the dynamically created class:
/**loads the generated proxy class from the provided bytes. */
private Class loadProxyClass(byte[] aGeneratedProxyClass,String proxyClassName) throws ClassNotFoundException{
return new ProxyClassLoader(Thread.currentThread().getContextClassLoader(),aGeneratedProxyClass)
.loadClass(this.convertToExternalName(proxyClassName));
}
The ProxyClassLoader simply extends ClassLoader and overrides the "findClass" method in order to load the Dynamically Generated class bytes:
public class ProxyClassLoader extends ClassLoader {
private byte[] rawClassBytes;
public ProxyClassLoader(ClassLoader parentClassLoader,byte[] classBytes){
super(parentClassLoader);
this.rawClassBytes = classBytes;
}
#Override
public Class findClass(String name) {
return defineClass(name,this.rawClassBytes, 0,this.rawClassBytes.length);
}
}
The error I get is: Exception in thread "main" java.lang.NoClassDefFoundError: DummyEntity (wrong name: DummyEntityProxy)
Where the DummyEntity is the super class I pass into the generateProxyClass method and the DummyEntityProxy is the class I'm attempting to generate. I'm stumped, any help would be greatly appreciated.
Generally, it isn’t a good idea to implement a ClassLoader that tries to return the same class regardless of what it has been asked for. This is perfectly illustrated by the error you get: NoClassDefFoundError: DummyEntity (wrong name: DummyEntityProxy). The system asked your ClassLoader for a class named DummyEntity and you returned a class named DummyEntityProxy.
The remaining question is why your loader has been asked for that class as usually the parent loader is asked first. It seems that the parent loader has not found the super class which indicates that the parent class loader you have used (Thread.currentThread().getContextClassLoader()) has no access to your super class. It would have been easier if you used anEntitySuperClass.getClassLoader() as parent loader.
Of course, you have to ensure that all other classes used by your generated proxy are accessible by anEntitySuperClass’s class loader. If not, you might need a very complex loader delegation structure to make both group of classes available. It might even be impossible (that depends on what your proxy actually ought to do).
The problem is revealed by your exception's message:
Exception in thread "main" java.lang.NoClassDefFoundError: DummyEntity (wrong name: DummyEntityProxy)
Your class loader expected to load a class DummyEntity but the linked resource contained a class named DummyEntityProxy. How could that happen? It is your class loader's findClass method's implementation:
#Override
public Class findClass(String name) {
return defineClass(name, this.rawClassBytes, 0, this.rawClassBytes.length);
}
You do not distinguish what class is attempted to be loaded but you define any class of name with the only class it knows, the DummyEntityProxy's byte representation. Rather implement:
#Override
public Class findClass(String name) {
if (!name.equals(entityProxySubClassName)) {
throw new ClassNotFoundException(name);
}
return defineClass(name, this.rawClassBytes, 0, this.rawClassBytes.length);
}
This way, you are making sure that you are not defining a class of another name. It seems however as if the ProxyClassLoader should not be queried for the class in the first place but that one of its parents should have successfully resolved it.
It seems like ASM is quite a low-level API for your needs. Have you considered a more high-level API like for example my library Byte Buddy? Other ORMs like Hibernate or Eclipse link also use an API on that level, simply because the things you are struggling with are difficult to get right.
Thank you all very much for your suggestions. After many hours of tinkering I managed to resolve the error. It appears that the error was attributed to the method:
this.generateAndAppendProxyAccessorMethods(anEntitySuperClass,fieldsToIntercept, cw);
More specifically, some of the code generated by this method incorrectly referenced the super class by its simple name rather than its internal fully qualified class name. I omitted the implementation of this method from my question for brevity and also because I genuinely didn't expect that the problem was associated with this method. Generally, when errors occur in dynamically generated byte code logic it can be immensely difficult to pinpoint the cause, simply because JVM error messages are so ambiguous.
I have a Java based RMI Server in which one of the interface method is like this :
public Properties process(String operation, Properties params) {
Class nodecls = Class.forName("com.example.commands." + operation);
}
This method runs fine all the time but sometimes (say once in a million RMI calls) throws ClassNotFoundException. What could be the reason for this? I am sure that the name passed is correct.
From XYZWS - What does Class forname method do?:
A call to Class.forName("X") causes the class named X to be dynamically loaded (at runtime). A call to forName("X") causes the class named X to be initialized (i.e., JVM executes all its static block after class loading). Class.forName("X") returns the Class object associated with the "X" class. The returned Class object is not an instance of the "x" class itself.
Class.forName("X") loads the class if it not already loaded. The JVM keeps track of all the classes that have been previously loaded. This method uses the classloader of the class that invokes it. The "X" is the fully qualified name of the desired class.
The tag wiki for classnotfoundexception has a simpler description:
The Java exception thrown when an application tries to load a class by name. Usually raised by one of:
the forName method in class Class [...]
when no definition for the class with the specified name could be found in the classpath.
Therefore, there can only be two causes for this:
The class named "com.example.commands." + operation is not present on the Class Path.
The ClassLoader of the Class which your method is in cannot find the "com.example.commands." + operation class. Maybe some reflection trick broke it.
However, you should never have to bother with this, as your method wouldn't even compile - it's of a non-void return type, while you never included a proper return statement.
I'm not sure what's the difference of loading static variables/blocks between MyClass.class and Class.forName("MyClass"), for example, I have below class:
package test;
public class SampleClass{
public static SampleClass instance = new SampleClass();
private SampleClass(){
System.out.println("SampleClass Instance Created");
}
}
Then, in another class, I accessed the class object of above SampleClass by using:
System.out.println(SampleClass.class);
Then, the output will be:
class test.SampleClass
If I changed to use class.forName(), as below:
System.out.println(Class.forName("test.SampleClass"));
Then, the output will be:
SampleClass Instance Created
class test.SampleClass
Does anybody can give me an explanation?
Thanks a lot.
The call to the Class.forName("MyClass") causes the class to be loaded at runtime. JVM also initializes that class after the class has been loaded by the classloader, so static blocks get executed.
In your case you have a static field which is the instance of your class, as this static block get executed your object is being initialized. That's why you are seeing the System.out get printed.
The .class syntax is used to get the Class of the called class. It doesn't not load the class actually.
Reference:
What does Class.forname method do?
Java Doc
Retrieving Class Object
Class.forName() uses the ClassLoader and tries to resolve class name at runtime, while .class is resolved at compile time.
class.forName() loads the class using the "caller's" class loader if the class is not already loaded.
.class doesn't load the class.
Its is just because of Class.forName() is dynamically loaded at run time your class into memory(RAM). and it will execute all static block within this class without creating reference of that class,
From offical doc:
A call to Class.forName("X") causes the class named X to be dynamically loaded (at runtime). A call to forName("X") causes the class named X to be initialized (i.e., JVM executes all its static block after class loading). Class.forName("X") returns the Class object associated with the "X" class. The returned Class object is not an instance of the "x" class itself.
Class.forName("X") loads the class if it not already loaded. The JVM
keeps track of all the classes that have been previously loaded. This
method uses the classloader of the class that invokes it. The "X" is
the fully qualified name of the desired class.
here is more information about it: http://www.xyzws.com/Javafaq/what-does-classforname-method-do/17
Thank you all above for your answers and discussions.
I thought the class will be loaded no matter using Class.forName() or MyClass.class, obviously I'm wrong. But I don't understand why it doesn't load the class when using MyClass.class.
I've been trying to use the ASM bytecode toolkit to replace the body of a public and static method in a class. The actual body replacement seems to work fine and I do get the expected behavior if execute the following once the transformation has completed:
Class cls = loadClass("ext.hm.cmd.MyProg");
cls.getMethod("hello").invoke(instance);
However if I try to cast the new instance to MyProg like so
MyProg p = (MyProg) instance;
p.hello();
I get the error message:
java.lang.ClassCastException: ext.hm.cmd.MyProg cannot be cast to ext.hm.cmd.MyProg
As I'm not adding or deleting any methods in the class I can't really understand why I get this error. Has anyone seen this before and if so, what is the cause of it and how can I solve it?
Thanks
Daniel Martinsson
Kind of a guess, but I'd say you have the same named class loaded by two different ClassLoaders. Those are actually considered two separate classes and one cannot be cast to the other.
One is loaded before the line
MyProg p = (MyProg) instance;
is executed. The other is loaded through your call to loadClass.
To fix this you would probably need the class that performs the line of code above to be loaded by the same ClassLoader that loads the altered instance of MyProg. Then it should work.
If you use spring boot dev tool, you can try exclude it from the project