问题
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 IllegalAccessError
s 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