Visibility issue for classes loaded by dex loader - java

I have two Android applications A and B. I want to generate a DEX of app B and load it dynamically during runtime into app A via a DexClassLoader. In application B I have mainly two classes that are important to be used by app A.
However, I have problems with the visibility of the needed classes in the first application.
This can be illustrated by the following:
final DexClassLoader dexClassLoader = new DexClassLoader(file.getAbsolutePath(), optDexDir.getAbsolutePath(), libsPath, ClassLoader.getSystemClassLoader());
Class<?> classToLoad = dexClassLoader.loadClass(completeClassNameOfA);
final Object instance = classToLoad.newInstance();
Method methodToLoad = classToLoad.getMethod(methodNameOfClassA, ClassLoader.class);
methodToLoad.invoke(instance, context.getClassLoader());
In the method I am invoking in app B, I try to find the class in the given class loader of A that I pass via a parameter like:
appAClassLoader.loadClass(AppBSecondClass.class.getName());
which obviously results in a NoClassDefFound exception.
Is there any way to make the class visible in the class loader of app A?
Thanks in advance

Related

why sun.misc.Launcher has a public constructor in Java?

I encountered the following problems when learning the loading mechanism of classes in Java
Launcher launcher = new Launcher();
ClassLoader classLoader = launcher.getClassLoader();
ClassLoader classLoader1 = Launcher.getLauncher().getClassLoader();
System.out.println(classLoader);
System.out.println(classLoader1);
The results are as follows:
sun.misc.Launcher$AppClassLoader#5e2de80c
sun.misc.Launcher$AppClassLoader#18b4aac2
Then I wonder if this is what it means to provide a public constructor For some reason, we want different instances of sun.misc.Launcher$AppClassLoader to load classes. Am I right,and why? And are there any use cases?

Java: calling method from class in different ClassLoaders

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

Unable to cast to interface class with URLClassLoader and reflection

I have a jar:
/home/cole/lib/a.jar
And in this jar I have the following interface/classes (horrible names for illustration purposes only!):
CreatorInterface.java
Base.java (implements CreatorInterface.java)
AbstractBase.java (extends Base.java)
Implementation.java (extends AbstractBase.java)
In a separate project I have the following code:
final URL[] jars = new URL[] {
new File("/home/cole/lib/a.jar").toURL();
}
final URLClassLoader classLoader = new URLClassLoader(jars, null);
final Class<?> implementation = classLoader.loadClass("Implementation");
final CreatorInterface object = (CreatorInterface)implementation.newInstance();
However when I run the above, I get the following:
java.lang.ClassCastException: Implementation cannot be cast to CreatorInterface
Given Implementation is ultimately an instance of a class that implements CreatorInterface, why do I get the ClassCastException?
Update 1
This isn't a question about using URLClassLoader, the class is found ok, the problem appears to be in the instantiation. For example, the following code works fine:
final Object object = implementation.newInstance();
Update 2
As #davidxxx answered, I have the interface class twice (once in the jar and once in the project using it). Although the interface was the same, this was the cause of the issue.
However to make it work, I needed to fix my URLClassLoader like this, to avoid a ClassNotFoundException:
final ClassLoader parent = this.getClass().getClassLoader();
final URLClassLoader classLoader = new URLClassLoader(jars, parent);
This exception :
java.lang.ClassCastException: Implementation cannot be cast to
CreatorInterface
makes me think that you have very probably two distinct CreatorInterface classes : one included in the jar and another other coming from the client program that tries to load it.
Even if the two classes have the same name (qualified names), these are different classes for each classloader as here you use two unassociated classloaders.
You have the current classloader of the program that you run and this other classloader as you specified null as parent classloader :
final URLClassLoader classLoader = new URLClassLoader(jars, null);
So as you try to assign the object created by reflection to the CreatorInterface variable, the cast fails because two distinct CreatorInterface were loaded by each classloader and are used : one coming from the classloader of your client code and another coming from the the instantiated classloader.
Using a single classloader would solve the issue but a best practice would be including the jar in the classpath of the project and to ensure to have a single version of the classes provided in the jar.
To decouple things you should probably split the jar into 2 jars : an API jar that contains only the interface and an implementation jar that depends on the API jar and that contains other classes.
In the client code, add in the classpath only the API jar to be able to assign to a variable with the interface declared type.
About your second point :
This isn't a question about using URLClassLoader, the class is found ok, the problem appears to be in the instantiation. For example, the
following code works fine:
final Object object = implementation.newInstance();
In this case you don't refer the interface type.
You indeed assign the Implementation object to an Object and not to a CreatorInterface variable.
The correct/consistent interfaces and subclasses are loaded by the classloader but here you never give a chance to provoke a ClassCastException as you never assign it to a type of a duplicate class but Object that is defined a single time.
So the problem previously encountered cannot occur.
About the third point :
However to make it work, I needed to fix my URLClassLoader like this,
to avoid a ClassNotFoundException:
final ClassLoader parent = this.getClass().getClassLoader();
final URLClassLoader classLoader = new URLClassLoader(jars, parent);
It works because here you create a classloader associated to the parent classloader.
In fact if you did :
final URLClassLoader classLoader = new URLClassLoader(jars);
It would produce the same result as the URLClassLoader object created would use by default the delegation to the parent classloader (here the classloader that started your application).

Difference in functionality between instance created by classloader and new keyword

I am little bit confused in class loading and initializing concept
1: Class.forName("test.Employee").newInstance();
2: ClassLoader.getSystemClassLoader().loadClass("test.Employee").newInstance();
3: new test.Employee();
Every line of above written code is creating an instance of Employee class but I don't understand what is the difference in all three methods.
The core differences between the three approaches come down to how the classes are located at runtime and what you can do with them.
For example...
Class.forName("test.Employee").newInstance();
Will use the current class's ClassLoader to search the class named Employee in the test package. This would allow you to discover classes that might not be available at compile time and which are loaded dynamically into the same class loader context. This will also search it's parent class loaders if the class is not found within the current context...
ClassLoader.getSystemClassLoader().loadClass("test.Employee").newInstance();
This will use the "system" ClassLoader, this typically the one that launched the main application.
Using either of these two methods is a great way to generate dynamic applications where the actual implementation of a Class is not known at compile type. The problem here is it can affect visibility and restrict what you can do with the loaded classes.
For example, while you may have loaded the test.Employee class and created an instance of it, unless you have a reference to test.Employee at compile time, you want be able to cast it. This is typically where interfaces come in very handy.
Equally, you could create your own ClassLoader instance and load classes or jars at runtime to provide, for example, plugins, factories or managers where the implementation is unknown at compile time. The functionality for these would, typically, be described through the use of interfaces.
Examples would include java.awt.Toolkit and JDBC java.sql.Driver
At the end of the day, the ClassLoader mechanism is providing a means by which a class file can be loaded and instantiated into the current JVM. The new keyword does a similar job, but the results are pre-determined at compile time
ClassLoaders are a very powerful feature and provide a lot of functionality, but can also be down right confusion, especially the way that they chain together
You might find...
The basics of Java class loaders
How ClassLoader Works in Java
of some help
You cannot create a instance of an Object unless class is loaded into the memory. In all three cases class is loaded and then instance is created.
class is loaded by Class.forName("test.Employee")
class is loaded by ClassLoader.getSystemClassLoader().loadClass("test.Employee")
class is loaded automatically as Employee class is referenced for 1st time.
Just to illustrate it with an example and complete the other answers:
public class ClassLoaderTest {
public ClassLoaderTest() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
System.out.println("Current CL: "+getClass().getClassLoader());
System.out.println("Parent CL: "+getClass().getClassLoader().getParent());
// ClassTest class is defined in the current CL so I can dynamically create an instance
// from the current CL and assign it (forName uses the current CL)
ClassTest c1 = (ClassTest)Class.forName("ClassTest").newInstance();
System.out.println("CL using forName: "+c1.getClass().getClassLoader());
// the new keyword creates an instance using the current CL but doesn't have the
// advantages of creating instances dynamically
ClassTest c2 = (ClassTest) new ClassTest();
System.out.println("CL using new: "+c2.getClass().getClassLoader());
// Here we are indicating to use the system CL that in this case is the parent of the current CL
Object c3 = ClassLoader.getSystemClassLoader().loadClass("ClassTest").newInstance();
System.out.println("CL using system CL: "+c3.getClass().getClassLoader());
// This won't work because the ClassTest is defined in the current CL but I'm trying to assign it to a
// dynamically created instance of ClassTest associated to the system CL so:
// java.lang.ClassCastException: ClassTest cannot be cast to ClassTest
// ClassTest c4 = (ClassTest)ClassLoader.getSystemClassLoader().loadClass("ClassTest").newInstance();
}
public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
CustomClassLoader cl = new CustomClassLoader(Test.class.getClassLoader());
cl.loadClass("ClassLoaderTest").newInstance();
}
}
The output in my case is:
Current CL: CustomClassLoader#1cfb549
Parent CL: sun.misc.Launcher$AppClassLoader#8ed465
CL using forName: CustomClassLoader#1cfb549
CL using new: CustomClassLoader#1cfb549
CL using system CL: sun.misc.Launcher$AppClassLoader#8ed465
I'm using this custom ClassLoader (CL): www.javablogging.com/java-classloader-2-write-your-own-classloader/

Dynamically loaded Class cannot access Applet loaded Class

My StartApplet is small to keep startup quick.
It then downloads various classes in various jars using (URLClassLoader)getSystemClassLoader().
The problem I am experiencing is that there are several interfaces defined in the StartApplet which are passed to the dynamically downloaded classes using method invoke. I always get class not defined.
It seems the system class loader does not contain any StartApplet loaded classes including the interfaces.
So, I try loading in the interfaces into the systemClassLoader using a downloaded jar but I still get class not defined and I guess this is because the same class has been loaded in twice using difference class loaders and therefore is seen as two difference classes.
I tried loading the downloaded jars using the classloader of one of the interfaces(StartApplet) but there were errors.
I tried forgetting about the system class loader and instead creating a new URLClassLoader using the classloader of the interfaces(StartApplet) as the parant class loader but again errors occurred.
I have tried loading the dynamic jars into Thread.currentThread().getContextClassLoader() but again errors occurred.
My question...
Is there a way to dynamically load classes in jars using (URLClassLoader)getSystemClassLoader() and allow them to see/access and use the classes that have already been loaded by the instantiating applet??
some code example would be really nice.
Many Thanks.
The crux is the system class loader doesnt reference the applet class loader.
The applet cannot start with any external jars so whatever classes it passes have to be loaded in with the applet.
I just need the dynamically loaded classes in the systemclassloader to be able to use the classes loaded with the applet.
please help.
ps. here are some snipets...
private void addPath(String _path)
{
try{
File f=new File(_path);
if(!f.exists())return;
if(!f.isDirectory())return;
Method method=SYSTEM_CLASS_LOADER_CLASS.getDeclaredMethod("addURL",parameters);
method.setAccessible(true);
method.invoke(SYSTEM_CLASS_LOADER,new Object[]{f.toURI().toURL()});
}catch(Throwable _t){
handle(_t);
disconnect();}
}
private void addLibrary(String _name)
{
try{
Method method=SYSTEM_CLASS_LOADER_CLASS.getDeclaredMethod("addURL",parameters);
method.setAccessible(true);
method.invoke(SYSTEM_CLASS_LOADER,new Object[]{ClassLoader.getSystemResource(_name)});
}catch(Throwable _t){handle(_t);}
}
SYSTEM_CLASS_LOADER=(URLClassLoader)ClassLoader.getSystemClassLoader(); // DOESNT WORK
SYSTEM_CLASS_LOADER=(URLClassLoader)MyInterface.class.getClassLoader(); // DOESNT WORK
SYSTEM_CLASS_LOADER=(URLClassLoader)Thread.currentThread().getContextClassLoader(); // DOESNT WORK
private void callWithInterface(MyInterface _myI)
{
Class<?> myClass=Class.forName("dynamic.MyClass",true,SYSTEM_CLASS_LOADER);
Constructor<?> myConstructor=myClass.getConstructor();
Object myInstance=myConstructor.newInstance();
Method m=myClass.getMethod("MyTest",new Class<?>[]{MyInterface.class});
String s=(String)m.invoke(myInstance,new Object[]{_myI});
}
last line causes...
Thread=Thread[Thread-17,4,http://MyDomain/-threadGroup]
java.lang.ClassNotFoundException: MyInterface
java.net.URLClassLoader$1.run(-1)
java.net.URLClassLoader$1.run(-1)
java.security.AccessController.doPrivileged(-2)
java.net.URLClassLoader.findClass(-1)
java.lang.ClassLoader.loadClass(-1)
sun.misc.Launcher$AppClassLoader.loadClass(-1)
java.lang.ClassLoader.loadClass(-1)
java.lang.Class.forName0(-2)
java.lang.Class.forName(-1)
StartApplet.run(23759)
java.lang.Thread.run(-1)
I have figured it out..
The problem I had was caused by a jar name conflict causing the required classes to fail at loading. Once I realised this and corrected the problem I successfully enabled the dynamically loaded classes to access the applet loaded classes by loading the dynamically loaded classes using the applet class loader instead of the system class loader.
I modified my code using the following lines and other adjustments to suit...
MyDynamicClassLoader=new URLClassLoader(new URL[0],MyAppletLoadedInterface.class.getClassLoader());
method.invoke(MyDynamicClassLoader,new Object[]{MyDynamicClassLoader.getResource(DynamicJarName)});
MyDynamicClassLoader now holds references to all applet loaded classes and dynamically loaded classes with the ability to reference each other. For some reason the system class loader does not hold the applet loaded classes.
Regards
Penny

Categories