ScopedClassPoolRepositoryImpl and default ClassPool
flavia.rainone Feb 5, 2010 2:28 PMWhile trying to fix the JBoss AOP + AS tests with the new jboss-classpool project, I bumped into a problem involving ScopedClassPoolRepositoryImpl and DefaultClassPool.
Our factories were using DefaultClassPool to represent null class loaders so that bootstrap classes could be loaded without duplicates. This turned out to be a problem because we were seeing duplicates of JBoss normal classes. Take a look at this example:
-> BaseClassLoader vfsfile:xxx/server/all/conf/jboss-service.xml belongs to DefaultDomain.
When this ClassLoader is requested to load class org.jboss.jms.client.container.StateCreationAspect, it will delegate to DefaultDomain, which will in turn find the original BaseClassLoader as a candidate to load that class. That BaseClassLoader loads the class and the result is returned.
When this example is ported to ClassPool-level, we get a CNFException. The reason for this is that the BaseClassPool vfsfile:xxx/server/all/conf/jboss-service.xml has a parent that can find the resource that contains the requested class:
public class TranslatableClassLoaderIsLocalResourcePlugin extends AbstractIsLocalResourcePlugin { public boolean isMyResource(String resourceName) { ClassLoader loader = getPool().getClassLoader(); if (loader instanceof Translatable == false) { throw new IllegalStateException("ClassLoader of pool " + getPool() + " is not instance of Translatable " + loader); } URL url = ((Translatable)getPool().getClassLoader()).getResourceLocally(resourceName); if (url == null) { return false; } RETURNS TRUE -----> if (isSameInParent(resourceName, url)) { return false; } return true; } }
This is isSameInParent implementation:
protected boolean isSameInParent(String classResourceName, URL foundURL) { ClassPool parent = pool.getParent(); if (parent != null) { ClassLoader parentLoader = parent.getClassLoader(); URL parentURL = parentLoader.getResource(classResourceName); if (parentURL == null) { return false; } URI parentURI = URI.create(parentURL.toString()); URI foundURI = URI.create(foundURL.toString()); if (parentURI.equals(foundURI)) { return true; } } return false; }
And ClassPool.getClassLoader implementation:
public ClassLoader getClassLoader() { return getContextClassLoader(); }
In the given example, getClassLoader will return the BaseClassLoader for AOP module, which can of course find the requested resource, even though it shouldn't.
Another problem I had is that ScopedClassPoolRepositoryImpl edits the classpath of the DefaultClassPool:
private ScopedClassPoolRepositoryImpl { classpool = ClassPool.getDefault(); // FIXME This doesn't look correct ClassLoader cl = Thread.currentThread().getContextClassLoader(); classpool.insertClassPath(new LoaderClassPath(cl)); }
This is similar to the problem explained above, and makes this class pool capable of loading classes that other classpools should be responsible to load. As a result, we get duplicates of the same CtClass. Its FIXME tells me that this was already spotted before as a possible bug.
I worked around this issue by replacing the usage of the Default ClassPool by this Singleton class:
public class SystemClassPool extends ClassPool { private SystemClassPool() { super(null); appendSystemPath(); } @Override public ClassLoader getClassLoader() { return ClassLoader.getSystemClassLoader(); } }
Equally to DefaultClassPool, it has the SystemPath appended, but its getClassLoader method returns the SystemClassLoader, which is the closest I can get to Bootstrap class loader (take a look at the Javadoc for Class<?>. getResource for more on this).
That didn't solve my problem entirely, because ScopedClassPoolRepositoryImpl has its own “default” classpool field:
public class ScopedClassPoolRepositoryImpl implements ScopedClassPoolRepository { /** The default class pool */ protected ClassPool classpool; /** The factory for creating class pools */ protected ScopedClassPoolFactory factory = new ScopedClassPoolFactoryImpl(); /** * Get the instance. * * @return the instance. */ public static ScopedClassPoolRepository getInstance() { return instance; } /** * Singleton. */ private ScopedClassPoolRepositoryImpl() { classpool = ClassPool.getDefault(); // FIXME This doesn't look correct ClassLoader cl = Thread.currentThread().getContextClassLoader(); classpool.insertClassPath(new LoaderClassPath(cl)); } ... }
Differently from what one may have thought, the ClassPoolRepository class (from jboss-classpool project) is not a subclass of ScopedClassPoolRepositoryImpl, given this class cannot be subclassed. This has been an issue for me before, because I always thought my code would be much cleaner if I could extend ScopedClassPoolRepositoryImpl instead of delegating to it.
So, what should be done in this regard? I'm opening this thread se we can find an elegant solution to my problem. Should ScopedClassPoolRepository be singleton? Should we use ScopedClassPoolFactory at all (the problem with not doing that is that we will have duplicate code in both classes)? I know that ScopedClassPool and auxiliary classes have been created to be used as the ClassPool solution to AS, but so many things have changed ever since that this solution is far from being complete, and that's why we need jboss-classpool now. So now, I'm not even sure what to do of that.
Regarding ClassPool.getClassLoader()'s implementation, I don't think it is a bug, even though we are not using this implementation at all (ScopedClassPool overwrites it, and so does the new SystemClassPool class). I just think that it is a way of doing things that works with simple standalone scenarios, but that doesnot fulfill our needs with AS.