单元测试报错Class $CLASS is already instrumented

360 阅读1分钟

最近在做项目迁移时,发现了一个非常小众的报错。涉及到了字节码增强,单元测试插件配置,pom插件继承关系,这个报错还挺有意思的,记录下来分享给大家做个参考。

一、问题解析

Caused by: java.lang.IllegalStateException: Class $CLASS is already instrumented

这个错误通常出现在使用字节码操作库(如 ASM, Javassist, Byte Buddy 等)进行类转换或增强时。这个错误表明你尝试对一个已经被修改(或称为“已被增强”或“已被仪器化”)的类再次进行相同的操作,但是操作的方式或上下文不允许这样的重复操作。

我一开始以为是我的某个类写的有问题,导致的重复instrumented。但是多次执行下来,我发现报错的类并不是总是同一个。

事实上,对于字节码增强,我只是听说过,但是没有一点了解。所以,这个报错解决起来就异常艰难。尤其是,在百度搜不到这个问题。

二、寻找问题解决方案

好在,我及时改变了策略,终于在stackOverFlow中看到一个老哥跟我遇到了同样的问题。

**并且有热心的老大哥贴心的指明了解决方向。**

按照老哥的指引,我们来到了github的Issue。

在一通翻译后,我看到了具体的报错原因:

最终也在末尾找到了解决思路。 首先,我们需要定位到那里出现了问题。在哪里重复进行了字节码增强?

三、问题定位

我能保证自己的插件版本和插件配置没有问题,毕竟这个东西是从其他地方复制来的。

**但是一定有一个配置,多次执行了instrument目标。虽然我的配置中只明确指定了一次instrument目标。 **

但Maven的默认行为或其他插件的配置可能会无意中再次触发它,如父POM或插件管理部分也配置了jacoco-maven-plugin的instrument目标。

果不其然,我在父pom中找到了另一个插件的配置。

所以,破案了。父子pom都配置了对应的jacoco-maven-plugin插件导致进行重复的字节码增强。

四、具体解决方案

可以将子pom中插件配置进行删除,或者将其改为和父pom同样的配置。 我的解决方案是将原来的配置进行了替换,改为和父pom同样的配置

<!--jacoco-maven-plugin-->
<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>${jacoco.version}</version>
    <executions>
       <execution>
          <id>default-instrument</id>
          <goals>
             <goal>instrument</goal>
          </goals>
       </execution>
       <execution>
          <id>default-restore-instrumented-classes</id>
          <goals>
             <goal>restore-instrumented-classes</goal>
          </goals>
       </execution>
       <execution>
          <id>report</id>
          <phase>prepare-package</phase>
          <goals>
             <goal>report</goal>
          </goals>
          <configuration>
             <dataFile>${project.build.directory}/jacoco.exec</dataFile>
          </configuration>
       </execution>
    </executions>
</plugin>

以上配置改为下面的配置

<!--jacoco-maven-plugin-->
<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>${jacoco.version}</version>
    <executions>
       <execution>
          <id>pre-unit-test</id>
          <goals>
             <goal>prepare-agent</goal>
          </goals>
          <configuration>
             <!-- Sets the path to the file which contains the execution data. -->
             <destFile>${project.build.directory}/jacoco.exec</destFile>
             <!--Sets the name of the property containing the settings for JaCoCo runtime agent.-->
             <propertyName>surefireArgLine</propertyName>
          </configuration>
       </execution>

       <execution>
          <id>post-unit-test</id>
          <phase>test</phase>
          <goals>
             <goal>report</goal>
          </goals>
          <configuration>
             <dataFile>${project.build.directory}/jacoco.exec</dataFile>
             <!-- Sets the output directory for the code coverage report. -->
             <!--<outputDirectory>${project.reporting.outputDirectory}/jacoco-ut</outputDirectory>-->
          </configuration>
       </execution>
    </executions>
</plugin>

五、关于pom插件配置的延伸

子pom可以继承父pom插件配置的版本、配置,但是子pom与父pom配置不同时,可能覆盖掉父pom,也可能进行合并父pom的配置。

这里的问题就是典型的合并配置导致的问题。