CGLIB with spring throws IllegalAccessError

6ton picture 6ton · Jul 20, 2012 · Viewed 7.4k times · Source

I have a spring app with aop enabled using cglib proxies for logging:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy proxy-target-class="true"/>

<bean id="loggingPointcuts" class="com.coverall.integration.commons.logging.LoggingPointcuts"/>

<bean id="loggingAspect" class="com.coverall.integration.commons.logging.LoggingAspect"/>
</beans>

I am using cglib-nodep-2.2.2.jar with spring 3.1.1 This works well in tomcat or jetty. However when I deployed this on OC4J (with jdk1.6) I get the following error: The class its trying to proxy - ComponentRegistryImpl is package private

Caused by: org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class com.company.int.components.core.registration.ComponentRegistryImpl]: Common causes of this problem include using a final class or a non-visible class; nested exception is net.sf.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null
    at org.springframework.aop.framework.Cglib2AopProxy.getProxy(Cglib2AopProxy.java:207) ~[org.springframework.aop-3.1.1.RELEASE.jar:3.1.1.RELEASE]
    at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:112) ~[org.springframework.aop-3.1.1.RELEASE.jar:3.1.1.RELEASE]
    at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.createProxy(AbstractAutoProxyCreator.java:476) ~[org.springframework.aop-3.1.1.RELEASE.jar:3.1.1.RELEASE]
    at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(AbstractAutoProxyCreator.java:362) ~[org.springframework.aop-3.1.1.RELEASE.jar:3.1.1.RELEASE]
    at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessAfterInitialization(AbstractAutoProxyCreator.java:322) ~[org.springframework.aop-3.1.1.RELEASE.jar:3.1.1.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:407) [org.springframework.beans-3.1.1.RELEASE.jar:3.1.1.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1461) [org.springframework.beans-3.1.1.RELEASE.jar:3.1.1.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:519) [org.springframework.beans-3.1.1.RELEASE.jar:3.1.1.RELEASE]
    ... 40 common frames omitted
Caused by: net.sf.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null
    at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:237) ~[cglib-nodep-2.2.2.jar:na]
    at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377) ~[cglib-nodep-2.2.2.jar:na]
    at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:285) ~[cglib-nodep-2.2.2.jar:na]
    at org.springframework.aop.framework.Cglib2AopProxy.getProxy(Cglib2AopProxy.java:201) ~[org.springframework.aop-3.1.1.RELEASE.jar:3.1.1.RELEASE]
    ... 47 common frames omitted
Caused by: java.lang.reflect.InvocationTargetException: null
    at sun.reflect.GeneratedMethodAccessor12.invoke(Unknown Source) ~[na:na]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) ~[na:1.6.0_20]
    at java.lang.reflect.Method.invoke(Method.java:597) ~[na:1.6.0_20]
    at net.sf.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:384) ~[cglib-nodep-2.2.2.jar:na]
    at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:219) ~[cglib-nodep-2.2.2.jar:na]
    ... 50 common frames omitted
Caused by: java.lang.IllegalAccessError: class com.company.int.components.core.registration.ComponentRegistryImpl$$EnhancerByCGLIB$$730712da cannot access its superclass com.company.int.components.core.registration.ComponentRegistryImpl
    at java.lang.ClassLoader.defineClass1(Native Method) ~[na:1.6.0_20]
    at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632) ~[na:1.6.0_20]
    at java.lang.ClassLoader.defineClass(ClassLoader.java:616) ~[na:1.6.0_20]
    ... 55 common frames omitted

Answer

Eric picture Eric · Sep 24, 2012

I'm not sure about OC4J, but we were hitting the exact same error on JBoss 6.

Normally, CGLIB can enhance (proxy) classes that are package-private. This works fine on Jetty & Tomcat as you have observed. However, it does not work in the default JBoss 6 classloader setup.

The issue is basically that Spring is instructing CGLIB to create the proxy class (subclass, com.company.int.components.core.registration.ComponentRegistryImpl$$EnhancerByCGLIB$$730712da) using a different classloader than the target class (superclass, com.company.int.components.core.registration.ComponentRegistryImpl). So while those classes are in the same textual package, they are actually created in different packages at runtime (since they are in different classloaders).

CGLIB defaults to defining the proxy class in the same classloader as the proxy target. However, Spring overrides the classloader used by the CGLIB enhancer. From org.springframework.aop.framework.Cglib2AopProxy.getProxy(ClassLoader)

            // Configure CGLIB Enhancer...
            Enhancer enhancer = createEnhancer();
            if (classLoader != null) {
                enhancer.setClassLoader(classLoader);
                if (classLoader instanceof SmartClassLoader &&
                        ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
                    enhancer.setUseCache(false);
                }
            }

Tracing the dataflow of that classloader shows that it comes from the classloader defined on the BeanFactory itself, which is defaulted to Thread.currentThread().getContextClassLoader() (in org.springframework.util.ClassUtils.getDefaultClassLoader()).

In sane servlet containers (like Jetty and Tomcat) the context classloader is the same as the classloader that loads all your application classes (in WEB-INF/lib or WEB-INF/classes). However, in JBoss 6 (and I'm guessing OC4J), these two are not the same. The context classloader is actually a child of the webapp classloader. It actually has no classes defined in it and just delegates up to the parent for everything. In JBoss, the context classloader is an instance of WebCtxLoader$ENCLoader).

The workaround that we used was to override the classloader for your WebApplicationContext (BeanFactory) so that the "real" webapp classloader is used. We did this by creating our own ContextLoaderListener (the classloader that loads your MyContextLoaderListener implementation itself is the one we want):

public class MyContextLoaderListener extends ContextLoaderListener {
    @Override
    protected void customizeContext(ServletContext servletContext, ConfigurableWebApplicationContext applicationContext) {
        ClassLoader classLoader = getClass().getClassLoader();
        logger.debug("Overriding WebApplicationContext classloader from %s to %s", applicationContext.getClassLoader(), classLoader);
        ((DefaultResourceLoader) applicationContext).setClassLoader(classLoader);
    }
}

You need to add class as a context listener in web.xml:

<listener>
    <listener-class>com.company.MyContextLoaderListener</listener-class>
</listener>