cancel
Showing results for 
Search instead for 
Did you mean: 

ReflectUtil.loadClass() and custom classloader

pgadecki
Champ in-the-making
Champ in-the-making
I'm implementing custom classloader which loads classes from BAR archives and I'm wondering why it is not directly called in ReflectUtil.loadClass()

public static Class<?> loadClass(String className) {
   Class<?> clazz = null;
   ClassLoader classLoader = getCustomClassLoader();
  
   // First exception in chain of classloaders will be used as cause when no class is found in any of them
   Throwable throwable = null;
  
   if(classLoader != null) {
     try {
       LOG.finest("Trying to load class with custom classloader: " + className);
            clazz = Class.forName(className, true, classLoader);
     } catch(Throwable t) {
       throwable = t;
     }
   }


Is there any particular reason to load class using custom classloader in this way:
clazz = Class.forName(className, true, classLoader);
instead of directly calling classloader method:
clazz = classLoader.loadClass(className);
?

My goal is to have possibility to load two versions of the same class during runtime depending on deployment's id during runtime.
It seems that call with Class.forName() my classloader is not called but some cached class is loaded which is not what I (and probably other custom classloader developers) want to achieve.
17 REPLIES 17

meyerd
Champ on-the-rise
Champ on-the-rise
http://www.javaworld.com/javaworld/javaqa/2003-03/01-qa-0314-forname.html

Class.forName() does what you want but activiti caches instances of JavaDelegate in the DeploymentCache.
This hurts you if you deploy a process and then redeploy your classes without creating a new process engine deployment. Then the DeploymentCache will continue to hold on to your "old" class definition.

pgadecki
Champ in-the-making
Champ in-the-making
Could you be more specific on how DeploymentCache is meant to hold classes?

I've written my own BarDeployer on top of com.camunda.fox.activiti.enterprise.deployer.ActivitiDeployerJboss5. For each BAR deployment it creates a BarClassloader(also written by myself) which caches and loads classes from BAR resource file. I can see two possibilities here: I can manage handling class caching with DeploymentCache or give up DeploymentCache and do it all by myself. But to do it I would have to know how is it done in engine.
I've debbuged DeploymentCache uses during deployment of Bar file and all that is done there is that the process definition is added to the cache.

pgadecki
Champ in-the-making
Champ in-the-making
I did more research and debugging and with
clazz = classLoader.loadClass(className);the method loadClass of my classLoader is called every time loadClass in ReflectUtil was called - which is the way I want to achieve
but with original
clazz = Class.forName(className, true, classLoader);it is done only for the first time the class is loaded

So maybe I have to change my question to: What I have to do to be sure that my classloader is called every time some activiti engine class calls ReflectUtil.loadClass?

From my point of view, possibility of specifying custom classloader in the enginge is a great idea, but it gives a user almost no flexibility with dynamic loading different versions of the same class.

meyerd
Champ on-the-rise
Champ on-the-rise
Hi Pawel,

1) On the deployment cache:

Instances of JavaDelegate/ActivityBehavior referenced by a process are loaded by activiti at process execution time. (See for instance org.activiti.engine.impl.bpmn.helper.ClassDelegate.activityBehaviorInstance).

However, as activiti maintains a cache of process definitions (the org.activiti.engine.impl.persistence.deploy.DeploymentCache), process definitions will continue to reference an ActivityBehavior instance for the lifetime of the process engine (the cache does not support eviction).

This means that:
  • after an instance of the delegate is loaded for the first time the process engine continues referencing that instance through the DeploymentCache

  • instances of JavaDelegate are shared between all process instances in the same process engine

  • you will see inconsistent behavior if you use custom classloaders and the the deployment lifecycle of your classes is not in sync with your processes (i.e. if you redeploy your classes without creating a new process definition, activiti will continue referencing the "old" classes. If you "restart" the engine, it will load the "new" classes.)

  • also in combination with a custom classloader: activiti will continue to hold on to any classes loaded by the process definition: even if you "undeploy" these classes through some other mechanism (ie. if you load classes fro man osgi bundle and undeploy that bundle, activiti will still reference them)
Here are a few tips on how to deal with this:
  • deactivate the deployment cache (implement a subclass which does not cache anything)

  • use proxy delegates (a delegate which loads the "actual" delegate)

  • do not use java delegates (but lookup objects the lifecycle of which is managed by some other container like EJB/CDI, OSGi, Spring, … )
The related Jira is ACT-834.

2) On Class.forName(name, initialize, classloader) vs. Classloader.loadClass(name)

You have to garbage collect the classloader that loaded a class / create a new instance of your classloader. If you don't, the JVM will not attempt to reload the class. Th JVM will ask the same classloader only once for the same class. Maybe that is the behavior you see.

pgadecki
Champ in-the-making
Champ in-the-making
Hi Daniel,
thanks for the comprehensive reply.

At the end of the last week I had an idea to override ProcessEngineConfigurationImpl.getClassLoader() in the manner that it returns deployment-based classloader (deploymentId is obtained from the ExecutionContext; if there's no classloader for specified deployment or there's no execution context, some default classloader is returned) and it seems that in my simple test cases for all of the processes proper version of classes are loaded.

Do you see any possible drawbacks in doing it this way?
(Actually I have to think it over as wellSmiley Happy

oefimov
Champ in-the-making
Champ in-the-making
On Class.forName(name, initialize, classloader) vs. Classloader.loadClass(name)

Method forName caches classes (see http://blog.bjhargrave.com/2007/09/classforname-caches-defined-class-in.html) internally in native implementation for no understandable reason.

This blocks implementing per-deployment class loading with a single dispatcher class loader set to activiti configuration, which detects deployment context and delegates to a per-deployment class loader.

Could anyone explain why ReflectUtil shouldn't use ClassLoader.loadClass instead of Class.forName?

To anyone who is trying to implement per-deployment class loading: the workaround is to override <java>org.activiti.engine.ProcessEngineConfiguration.getClassLoader()</java> method and return your per-deployment class loader (you can detect deployment context via <java>org.activiti.engine.impl.context.Context.getExecutionContext().getProcessDefinition().getDeploymentId()</java>).

jbarrez
Star Contributor
Star Contributor
I didn't know that. I don't think there is a reason for it - the main bit is that the correct classloader is used.

oefimov
Champ in-the-making
Champ in-the-making
Joram, the problem is that the correct classloader doesn't have a chance to be used – Java caches the result and bypasses classloader invocation!

jbarrez
Star Contributor
Star Contributor
Yes - indeed (according to the article). I remember we added the Class.forName method back in the day when we added OSGI support.

Do you reckon it is simply a matter of changing the implementation? I'm kind of nervous cause the OSGO bits are always hard (and hard to test!) and I definitely don't want to go break them.