Spring IllegalAccessException after isolating a module in a separate ClassLoader

早过忘川 提交于 2021-02-10 17:15:02

问题


I have had a problem involving jar clash between incompatible versions of BouncyCastle.

We have solved it by creating a bean that, using a Spring-defined ClassLoader bean injected as property, invokes services from classes not stored in official WEB-INF/lib folder.

Following are the beans definitions

<bean id="metainfJarClassloader" class="com.jdotsoft.jarloader.JarClassLoaderFactory" factory-method="create"/>
<bean id="jadesFactory" class="it.csttech.proxy.jades.JadesFactory">
      <constructor-arg index="0" ref="metainfJarClassloader"/>       
</bean>
<bean id="bouncyCastleBeanFactory" class="it.csttech.proxy.bouncyCastle.BouncyCastleBeanFactory">
      <constructor-arg index="0" ref="metainfJarClassloader"/>       
</bean>


    <bean id="timestampService" class="it.csttech.pcp.services.spring.TimestampServiceImpl" lazy-init="true">
        <property name="timestampServerConfig">
            <bean factory-bean="jadesFactory" factory-method="createTSServerCfg">
            -------------------
            </bean>
        </property>
        <property name="jadesFactory" ref="jadesFactory" />
        <property name="bouncyCastleBeanFactory" ref="bouncyCastleBeanFactory" />
        <property name="jarClassLoader" ref="metainfJarClassloader" />
    </bean>

How does that work? Certified Timestamp service is a wrapper around services that are defined in a separate JAR and are instantiated via reflection using the metaInfClassLoader. metaInfClassLoader service loads classes that are contained in JARs under META-INF/lib

E.g.

WEB-INF
  -- lib
    -- timestamp.jar (expanded below)
      -- META-INF
        -- lib
          -- it.infocert-jades-dts.jar
          -- org.bouncycastle-bcprov.jar
       -- src
          -- it/csttech/pcp/services/spring
             -- TimestampServiceImpl.java

TimestampServiceImpl will have its dependent classes loaded from that META-INF directory.

What I can't understand is why after this component is enabled, and invoked only by the certified timestamping service which is lazily-initialized, I get plenties of IllegalAccessErrors in Spring.

Specifically, I can't access anymore any private static class defined in an MVC controller.

Evidence:

org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.IllegalAccessError: it/package/NotificationsController$Dto
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:978) [spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897) [spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) [spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
        at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872) [spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:648) [servlet-api.jar:?]
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) [spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) [servlet-api.jar:?]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:292) [catalina.jar:8.0.39]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207) [catalina.jar:8.0.39]
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) [tomcat-websocket.jar:8.0.39]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240) [catalina.jar:8.0.39]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207) [catalina.jar:8.0.39]
        at it.phoenix.web.context.PhoenixFilter.doFilter(PhoenixFilter.java:89) [phoenix-web-3.5.0.15.jar:17]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240) [catalina.jar:8.0.39]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207) [catalina.jar:8.0.39]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:317) [spring-security-web-4.2.1.RELEASE.jar:4.2.1.RELEASE]
        at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:127) [spring-security-web-4.2.1.RELEASE.jar:4.2.1.RELEASE]
        at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91) [spring-security-web-4.2.1.RELEASE.jar:4.2.1.RELEASE]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) [spring-security-web-4.2.1.RELEASE.jar:4.2.1.RELEASE]
        at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:114) [spring-security-web-4.2.1.RELEASE.jar:4.2.1.RELEASE]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) [spring-security-web-4.2.1.RELEASE.jar:4.2.1.RELEASE]
        at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137) [spring-security-web-4.2.1.RELEASE.jar:4.2.1.RELEASE]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) [spring-security-web-4.2.1.RELEASE.jar:4.2.1.RELEASE]
        at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111) [spring-security-web-4.2.1.RELEASE.jar:4.2.1.RELEASE]
------------
Caused by: java.lang.IllegalAccessError: it/package/NotificationController$Dto
        at it.phoenix.web.controllers.secure.common.NotificationsController$$FastClassBySpringCGLIB$$7a88e7c5.invoke(<generated>) ~[?:?]
        at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-4.3.5.RELEASE.jar:4.3.5.RELEASE]
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:721) ~[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) ~[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE]
        at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:133) ~[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE]
        at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:121) ~[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE]
        at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:656) ~[spring-aop-4.3.5.RELEASE.jar:4.3.5.RELEASE]
        at it.phoenix.web.controllers.secure.common.NotificationsController$$EnhancerBySpringCGLIB$$5c5467a.scrollBottom(<generated>) ~[phoenix-web-3.5.0.15.jar:17]

Part 1 of the question

What does that IllegalAccessError mean? I have always defined DTOs within my MVC controller classes by putting them private static, and it always worked

Part 2 of the question

I can see no evidence that the JarClassLoader was actually involved in loading controller classes. Does Spring replace main class loader (or enhance itself with that class loader) once it finds any bean of type ClassLoader?


回答1:


It was not a problem with Spring itself or my code, but the JarClassLoader has an issue itself. While well documented and understandable, the following line is culprit

    Thread.currentThread().setContextClassLoader(this); //loadClass method

Analysis

The author's analysis is correct as the JarClassLoader must be the primary classloader of the current thread. After you load a class from a jar resource, that class may load other classes because of reflection or simply because it provides services which reference other classes. So who loads the new classes recurisvely? The JarClassLoader of course.

But then there is a problem with Spring, I still deem it unbelievable. Spring does not care about the custom class loader bean, but the ContextLoader class cares about the current thread to create a mapping between threads and contexts. Probably because Spring wants to isolate different contexts. Kudos!

Eventually debugging Spring I found the odd. The Context map had the JarClassLoader instead of Tomcat's main URLClassLoader

Solution

Amend the JarClassLoader provided by jdotsoft so as to restore the original class loader after instantiating the class. This may not prevent further errors if classes who depend on classes who depend on classes want to use the ClassLoader from the thread rather than from getClass()

    Thread.currentThread().setContextClassLoader(this);

Becomes

    ClassLoader old = Thread.currentThread().getContextClassLoader();
    Thread.currentThread().setContextClassLoader(this);

    try {
    ---------
    } finally {
        Thread.currentThread().setContextClassLoader(old);
    }


来源:https://stackoverflow.com/questions/44827534/spring-illegalaccessexception-after-isolating-a-module-in-a-separate-classloader

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!