The tasks and initial investigation
I try to set up two Oracle Coherence near cache instances at one java swing application. The idea a solution could be found here. My case is a bit more complicated and this is where the game starts.
Short description
In my case there is an account service. It can have two endpoints: SIT and UAT. In order to create two such services, I need to load two 'instances' of the Coherence in order to override the endpoints with system variables (tangosol.coherence.cacheconfig).
I have:
- the main code of the app is located in the mainapp.jar;
- the AccountService interface that is located in the account-interfaces.jar;
- the AccountServiceImpl class that is located in the account-impl.jar and implements the AccountService interface;
- my main application has the following structure
bin: startup.bat, startup.sh conf: app.properties lib: mainapp.jar, account-interfaces.jar, account-impl.jar, coherence.jar
Approach tried
I created a dedicated child-first classLoader - InverseClassLoader and made the AppLaunchClassLoader (the default Thread.currentThread().GetContextClassLoader() classLoader) it's parent. With the InverseClassLoader I load the AccountServiceImpl class:
Class<AccountServiceImpl> acImplClass = contextClassLoader.selfLoad(AccountServiceImpl.class).loadClass(AccountServiceImpl.class);
Constructor<AccountServiceImpl> acConstructor =
acImplClass .getConstructor(String.class);
AccountService acService = acConstructor .newInstance(serviceURL);
Issues and questions
- I get the 'AccountServiceImpl cannot be cast to AccountService' exceptions, which means that those two classes loaded by different classloaders. But those classloaders are in the parent-child relationship. So am I right that even if a class is loaded by a parent (interface - 'abstract' type) it can't be used with a class (concrete impl) loaded by a child classloader? Why then we need this parent-child relation?
- I specified the AccountService interface in a code and it got loaded by a default classloader. I tried wrap the code above is a thread and set the InverseClassLoader it's context classloader. Nothing changed. So am I right that I can't use such interface-implementation coding (as usual coding) and need to use reflection all the time to invoke concrete methods all the time? (Hope there is a solution) ;
- Say, I listed both the AccountService and AccountServiceImpl classes for being loaded by the InverseClassLoader. What if I need other classes, that are accessible by those two, to be also loaded by the InverseClassLoader? It there a way to say that all 'related' classes must be loaded by the same classloader?
Update
Here is the InverseClassLoader:
public class InvertedClassLoader extends URLClassLoader {
private final Set<String> classesToNotDelegate = new HashSet<>();
public InvertedClassLoader(URL... urls) {
super(urls, Thread.currentThread().getContextClassLoader());
}
public InvertedClassLoader selfLoad(Class<?> classToNotDelegate) {
classesToNotDelegate.add(classToNotDelegate.getName());
return this;
}
@Override
public Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
if (shouldNotDelegate(className)) {
System.out.println("CHILD LOADER: " + className);
Class<?> clazz = findClass(className);
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
else {
System.out.println("PARENT LOADER: " + className);
return super.loadClass(className, resolve);
}
}
public <T> Class<T> loadClass(Class<? extends T> classToLoad) throws ClassNotFoundException {
final Class<?> clazz = loadClass(classToLoad.getName());
@SuppressWarnings("unchecked")
final Class<T> castedClass = (Class<T>) clazz;
return castedClass;
}
private boolean shouldNotDelegate(String className) {
if (classesToNotDelegate.contains(className) || className.contains("tangosol") ) {
return true;
}
return false;
}
Issue 1, part one I cannot reproduce (see below). As for part 2: the hierarchy of class-loaders is to prevent the "X cannot be cast to X" exceptions. But if you break the parent-first rule, you can get into trouble.
About issue 2: setting a thread's context classloader does not do anything in itself, see also this article (javaworld.com) for some more background. Also, in relation to issue 1, part 2, a quote from the article that describes what can happen if there is no parent-child relation between the current classloader and the thread's context classloader:
Below is a simple demo-program to show that a cast to an interface from another classloader can work (note I'm using a simple Java project with classes in a bin-folder and the
InvertedClassLoader
from your question in the same (test) package):If you change
ITest t2 =
toCTest t2 =
you will get the "CTest cannot be cast to CTest" exception, but using the interface prevents that exception. Since this little demo works fine, I'm guessing there is more going on in your application which somehow breaks the class-loading. I suggest you work from a situation where the class-loading works and keep adding code until it breaks the class-loading.The
InvertedClassLoader
looks a lot like the "child first classloader", see this question for some good answers discussing this manner of class-loading. The child first classloader can be used to load "related classes" (from your third issue) separately. You could also update theInvertedClassLoader
to always "self-load" classes in certain packages. And remember that "once a class is loaded by a classloader it uses that classloader to load every other class it needs" (quote from this blog article).