springboot jar包瘦身后启动提示 IllegalAccessError ?
问题描述
springboot jar包瘦身后,java -jar启动 报错
Caused by: java.lang.IllegalAccessError: class org.springframework.cloud.openfeign.HystrixTargeter$$EnhancerBySpringCGLIB$$7e887a8a cannot access its superclass org.springframework.cloud.openfeign.HystrixTargeter at java.lang.ClassLoader.defineClass1(Native Method) ~[na:1.8.0_333]
at java.lang.ClassLoader.defineClass(ClassLoader.java:756) ~[na:1.8.0_333]
at sun.reflect.GeneratedMethodAccessor28.invoke(Unknown Source) ~[na:na]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_333]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_333]
at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:535) ~[spring-core-5.2.8.RELEASE.jar:5.2.8.RELEASE]
... 130 common frames omitted
依赖版本
<springboot.version>2.3.2.RELEASE</springboot.version> <spring-cloud.version>Hoxton.SR9</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.6.RELEASE</spring-cloud-alibaba.version>
问题出现的环境背景及自己尝试过哪些方法
原先打包是通过下面spring-boot插件打包,打包后能正常java -jar运行
<plugin> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<outputDirectory>${boot-jar-output}</outputDirectory>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
后面需求变动,需要将打包的jar中的依赖库全部提出来,方便每次服务发布,减少传输的jar包大小。于是通过以下mvn插件来进行了配置
<!-- Spring Boot模块jar构建 --> <plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includes>
<include>
<groupId>null</groupId>
<artifactId>null</artifactId>
</include>
</includes>
<outputDirectory>${boot-jar-output}</outputDirectory>
<mainClass>com.bdip.cost.CostApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<useUniqueVersions>false</useUniqueVersions>
</manifest>
</archive>
</configuration>
</plugin>
<!-- 拷贝项目所有依赖jar文件到构建lib目录下 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${boot-jar-output}/lib</outputDirectory>
<excludeTransitive>false</excludeTransitive>
<stripVersion>false</stripVersion>
<silent>false</silent>
</configuration>
</execution>
</executions>
</plugin>
插件的作用,主要就是将服务依赖的lib全部复制到jar包外面,并在打包的MANIFEST.MF文件中添加了Class-Path参数,指定lib的位置。
相关代码
现在通过 Java -ajr启动后报错,目前查到的原因是
在BeanPostProcessor#postProcessAfterInitialization的后置处理中,AspectJAwareAdvisorAutoProxyCreator创建代理类报错
@Override public Object postProcessAfterInitialization(Object bean, String beanName) {
if (this.advisor == null || bean instanceof AopInfrastructureBean) {
// Ignore AOP infrastructure such as scoped proxies.
return bean;
}
if (bean instanceof Advised) {
Advised advised = (Advised) bean;
if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
// Add our local Advisor to the existing proxy's Advisor chain...
if (this.beforeExistingAdvisors) {
advised.addAdvisor(0, this.advisor);
}
else {
advised.addAdvisor(this.advisor);
}
return bean;
}
}
if (isEligible(bean, beanName)) {
ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
if (!proxyFactory.isProxyTargetClass()) {
evaluateProxyInterfaces(bean.getClass(), proxyFactory);
}
proxyFactory.addAdvisor(this.advisor);
customizeProxyFactory(proxyFactory);
//报错就是这里,原因是 获取的classLoader和bean实际的classLoader不一致
//导致没有办法获取对应的父类
return proxyFactory.getProxy(getProxyClassLoader());
}
// No proxy needed.
return bean;
}
初步排查
初步排查是类加载器不一致导致proxy获取不到对应的类导致的,但是为什么导致不一致,目前还不清楚。
查询网络,有说是spring-boot-devtools导致的,可项目本身并没有依赖该jar,我个人怀疑是和
MANIFEST.MF的参数Class-Path导致的,但是我曾在别的项目上也这样配置过jar包瘦身,并没有什么问题。
临时解决
通过以下两个自定义在BeanPostProcessor,来还原classLoader解决,但是治标不治本呀。
@Component//在AspectJAwareAdvisorAutoProxyCreator 之前
@Order(Ordered.LOWEST_PRECEDENCE+1)
public class FeignBeanPostProcessor implements BeanPostProcessor ,Ordered, ApplicationContextAware {
private ApplicationContext applicationContext;
// public static ThreadLocal<ClassLoader> originClassloader=new ThreadLocal<>();
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if ("feignTargeter".equals(beanName)){
ConfigurableApplicationContext ctx= (ConfigurableApplicationContext) applicationContext;
DefaultListableBeanFactory bf= (DefaultListableBeanFactory) ctx.getBeanFactory();
bf.getBeanPostProcessors().stream()
.filter(AspectJAwareAdvisorAutoProxyCreator.class::isInstance)
.forEach(item->
((AspectJAwareAdvisorAutoProxyCreator) item).setBeanClassLoader(bean.getClass().getClassLoader())
);
}
return bean;
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE+1;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
一个在AspectJAwareAdvisorAutoProxyCreator执行之前替换classLoader,一个在执行之后换回当前的classLoader
@Component//在AspectJAwareAdvisorAutoProxyCreator 之后
public class FeignBeanPostProcessor2 implements BeanPostProcessor ,Ordered, ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if ("feignTargeter".equals(beanName)){
ConfigurableApplicationContext ctx= (ConfigurableApplicationContext) applicationContext;
DefaultListableBeanFactory bf= (DefaultListableBeanFactory) ctx.getBeanFactory();
bf.getBeanPostProcessors().stream()
.filter(AspectJAwareAdvisorAutoProxyCreator.class::isInstance)
.forEach(item-> ((AspectJAwareAdvisorAutoProxyCreator) item).setBeanClassLoader(Thread.currentThread().getContextClassLoader()));
}
return bean;
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
但是问题的根本原因还是不知道
回答:
去掉spring-boot-maven-plugin插件,在maven-dependency-plugin配置中添加main-class和outputdir配置。即可解决。
<plugin> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<useUniqueVersions>false</useUniqueVersions>
<mainClass>com.bdip.cost.CostApplication</mainClass>
</manifest>
</archive>
<!--指定输出jar目录-->
<outputDirectory>${boot-jar-output}</outputDirectory>
</configuration>
</plugin>
以上是 springboot jar包瘦身后启动提示 IllegalAccessError ? 的全部内容, 来源链接: utcz.com/p/945065.html