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

但是问题的根本原因还是不知道

阅读 2k
1 个回答
✓ 已被采纳

去掉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>
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题