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
Related
I have 2 ClassLoaders, one which I don't have access to source code and second which I'm loading classes now. ( I'm using java agents to inject code. )
My problem is when I try to load class which has been loaded by the different ClassLoader, it's not working.
// some different class loader
// also this is the ClassLoader which loaded GL11
ClassLoader cl = getDifferentClassLoader();
this.getClass().getClassLoader() == cl // false
GL11.glPushMatrix(); // NoClassDefFoundError
But when I call it using reflection, it works
Class<?> clazz = cl.loadClass("org.lwjgl.opengl.GL11");
clazz.getDeclaredMethod("glPushMatrix").invoke(null); // this works
I tried something like this but it's not working :(
Thread.currentThread().setContextClassLoader(cl);
GL11.glPushMatrix();
Is something like this possible?
I want to create dynamically a classloader for executing JSR223 script in a controlled environment but failing,
I'm trying remove/add jars using current(parent) ClassLoader, I tried solution Dynamically removing jars from classpath
public class DistributionClassLoader extends ClassLoader {
public DistributionClassLoader(ClassLoader parent) {
super(parent);
}
private Map<String, ClassLoader> classLoadersByDistribution =
Collections.synchronizedMap(new WeakHashMap<>());
private final AtomicReference<String> distribution = new AtomicReference<>();
#Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
final ClassLoader delegate = classLoadersByDistribution.get(distribution.get());
if (delegate != null) return Class.forName(name, true, delegate);
throw new ClassNotFoundException(name);
}
public void addDistribution(String key, ClassLoader distributionClassLoader){
classLoadersByDistribution.put(key,distributionClassLoader);
}
public void makeDistributionActive(String key){distribution.set(key);}
public void removeDistribution(String key){
final ClassLoader toRemove = classLoadersByDistribution.remove(key);
}
}
But it didn't include all my jars, in test this work
ClassLoader cl = this.getClass().getClassLoader();
Class cls = cl.loadClass("org.springframework.http.HttpStatus");
But using the solution doesn't find class
ClassLoader cl = new DistributionClassLoader(this.getClass().getClassLoader());
Class cls = cl.loadClass("org.springframework.http.HttpStatus");
Exception:
java.lang.ClassNotFoundException: org.springframework.http.HttpStatus
at com.DistributionClassLoader.loadClass(DistributionClassLoader.java:24)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
How can I select specific jars to add or remove from ClassLoader?
EDIT
I'm able to load jars using #czdepski answer but I still want to remove all/most classes except JDK's
Method sysMethod = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class});
sysMethod.setAccessible(true);
sysMethod.invoke(sysLoader, new Object[]{url});
You got the delegation wrong. You never check the parent class loader if it has this class.
If we look at the Javadoc for ClassLoader.loadClass(String,boolean) we find:
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 into the virtual machine is used, instead.
Invoke the findClass(String) method to find the class.
If the class was found using the above steps, and the resolve flag is true, this method will then invoke the resolveClass(Class) method on the resulting Class object.
Subclasses of ClassLoader are encouraged to override findClass(String), rather than this method.
You did override loadClass, but don't do any delegation to it's parent ClassLoader.
Instead you call classLoadersByDistribution.get(distribution.get());, which is most likely null (hard to tell, but always expect WeakHashMap.get() to return null).
If delegate is not null, then you try to load the class from there. This means the loaded class won't use your ClassLoader to load new classes, but instead the ClassLoader you delegated to.
After all, this sounds like a XY Problem. You want to execute some code using the scripting API and somehow control the environment.
Did you try to use a SecurityManager?
About your comment that you need your own ClassLoader to create a ScriptEngineManager: This ClassLoader is used to search for ScriptEngineFactory implementations. This is done using a service provider interface.
If you don't use your own script engine, this should not matter to you.
If your goal is to add a few jars so the engine can use it, create a new URLClassLoader with the platform class loader as parent. (Or extension class loader, depends on the java version.)
Set this ClassLoader as Thread.setContextClassLoader() and create the ScriptEngine.
If you did choose the parent of the URLClassLoader correctly, it will not see classes loadable by the application class loader.
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).
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
My application uses JDBC database drivers. I load these from a jar file, db2jcc.jar in the case of DB2 which I'm currently working with. With this jar in the classpath, everything is fine, but I have a requirement to find the jar from a property in the application's config file instead - for example,
database.driver=/opt/IBM/db2/V9.5/java/db2jcc.jar
I can load the class via a URLClassLoader ok, but the problem is that I need to treat the object thus created as an explicit DB2XADataSource. For example:
URLClassLoader dbClassLoader = new URLClassLoader(new URL[]{driverJar});
xaClass = dbClassLoader.loadClass("com.ibm.db2.jcc.DB2XADataSource");
DB2XADataSource dataSource = (DB2XADataSource) xaClass.newInstance();
dataSource.setCurrentSchema(DATABASE_SCHEMA); // <- dataSource has to be a
dataSource.setDatabaseName(DATABASE_NAME); // DB2XADataSource to do this
(rearranged and renamed somewhat; I actually do the loadClass in the constructor of the class that contains this code, while the newInstance is in one of its methods.)
I guess I'm getting into a classloader tangle because the classloader that loaded my class is trying to find DB2XADataSource in order to do the cast, but the URL classloader is not above it in the tree. The trouble is, it being long after I should have stopped working for the day (here in the UK) I can't think how best to solve it in a vaguely neat and sane manner.
Ideas?
Thanks.
The simplest approach is to just use the java.beans API (or direct reflection if you must) to invoke the setter methods.
Alternatively: Your database code requires to link to the dynamically loaded code. Therefore, dynamically load your database code. How much is up to you. You might load almost everything except the "bootstrap".
Yep - the class can't load its own dependencies. You could do some ClassLoader magic, but I imagine it would get messy very quickly.
One way to reduce the amount of reflection would be to put any code that depends on DB2XADataSource into an implementation that is invoked via an interface available to the calling ClassLoader.
//in mydb2driver.jar
public class MyDb2Driver implements IDriver {
private DB2XADataSource dataSource = new DB2XADataSource();
public void init() {
dataSource.setCurrentSchema(DATABASE_SCHEMA);
}
//etc.
}
This is loaded with your driver:
database.driver=/opt/IBM/db2/V9.5/java/db2jcc.jar:/foo/mydb2driver.jar
Invoking code is in the regular classpath:
public interface IDriver {
public void init();
//etc.
}
...
URLClassLoader dbClassLoader = new URLClassLoader(new URL[]{driverJar});
xaClass = dbClassLoader.loadClass("foo.MyDb2Driver");
IDriver dataSource = (IDriver) xaClass.newInstance();
dataSource.init();