最近在做项目迁移时,发现了一个非常小众的报错。涉及到了字节码增强,单元测试插件配置,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的配置。
这里的问题就是典型的合并配置导致的问题。