Spring 在单独的类加载器中隔离模块后出现 IllegalAccessException

Spring IllegalAccessException after isolating a module in a separate ClassLoader

我遇到了涉及 jar clash between incompatible versions of BouncyCastle 的问题。

我们已经通过创建一个 bean 解决了这个问题,该 bean 使用 Spring 定义的 ClassLoader bean 作为 属性 注入,从 class 中调用未存储在官方 [=14] 中的服务=]文件夹。

以下是 beans 定义

<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>

这是如何运作的? Certified Timestamp 服务是在单独的 JAR 中定义并使用 metaInfClassLoader 通过反射实例化的服务的包装器。 metaInfClassLoader 服务加载包含在 META-INF/lib

下的 JAR 中的 classes

例如

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 将从该 META-INF 目录加载其依赖的 classes。

我不明白的是,为什么启用此组件并仅由延迟初始化的经过认证的时间戳服务调用后,我在 Spring 中得到大量 IllegalAccessError

具体来说,我无法再访问 MVC 控制器中定义的任何 private static class

证据:

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$a88e7c5.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$c5467a.scrollBottom(<generated>) ~[phoenix-web-3.5.0.15.jar:17]

问题的第 1 部分

IllegalAccessError 是什么意思?我一直在我的 MVC 控制器 classes 中定义 DTO,方法是将它们 private static,并且它始终有效

问题的第 2 部分

我看不到 JarClassLoader 实际上参与加载控制器 classes 的证据。一旦找到 ClassLoader 类型的任何 bean,Spring 是否会替换主 class 加载程序(或使用 class 加载程序增强自身)?

这不是 Spring 本身或我的代码的问题,而是 JarClassLoader 本身有问题。虽然有据可查且易于理解,但以下行是罪魁祸首

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

分析

作者的分析是正确的,JarClassLoader必须是当前线程的主要class加载器。从 jar 资源加载 class 后,class 可能 加载其他 classes 因为反射或仅仅因为它提供引用的服务其他 classes。那么谁递归加载新的 classes 呢?当然是 JarClassLoader。

但是Spring又出问题了,我还是觉得不可思议。 Spring 不关心自定义 class 加载器 bean,但 ContextLoader class 关心当前线程以创建线程和上下文之间的映射。可能是因为 Spring 想要隔离不同的上下文。荣誉!

最终调试Spring我发现了奇怪的地方。 Context map 有 JarClassLoader 而不是 Tomcat 的主要 URLClassLoader

解决方案

修改jdotsoft提供的JarClassLoader,在实例化class后恢复原来的class加载器。如果依赖 classes 的 classes 依赖 classes 的 classes 想要从线程而不是从 getClass()[=18 使用 ClassLoader,这可能无法防止进一步的错误=]

    Thread.currentThread().setContextClassLoader(this);

变成

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

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