spring-aop 在 apple silicon (M1) 上:非法方法名称

spring-aop On apple silicon (M1) : Illegal method name

我的工作项目使用 kotlin + spring-boot.

我在 MacBook apple-m1 机器上尝试 运行 具有 space 的测试用例时发现了这个问题,例如 should success WHEN setting key is api updatable

例子

@Test
@Transactional
fun `should success when setting key is api updatable`()

然后 JVM 抛出包含堆栈跟踪的异常如下所示

Caused by: java.lang.ClassFormatError: Illegal method name "should success when setting key is api updatable" in class com/xxx/xxx/web/rest/CompanySettingResourceIntTest$$EnhancerBySpringCGLIB$$f0902682

这发生在我的 MacBook m1 机器上,但从未发生在 intel 机器上。

我试图调查这个问题,然后我发现在 bean 中的方法上使用 @Transactional 时,spring-aop 将通过使用方法 java.lang.ClassLoader.defineClass 创建一个代理 class .

我有很多这样的方法,而且很容易阅读。我可以在 m1 机器中使用包含 space 运行 的方法名称,如何修复它?

谢谢。


环境

(我尝试使用 spring-boot2+JUnit5 测试用例名称包含 space 的 POC 很好。但是 bean 中的方法也会抛出 Illegal method name。)


JUnit日志(运行测试)


Could not generate CGLIB subclass of class com.xxx.xxx.web.rest.CompanySettingResourceIntTest: Common causes of this problem include using a final class or a non-visible class; nested exception is org.springframework.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null
org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class com.xxx.xxx.web.rest.CompanySettingResourceIntTest: Common causes of this problem include using a final class or a non-visible class; nested exception is org.springframework.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null
    at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:204)
    at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:109)
    at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.createProxy(AbstractAutoProxyCreator.java:466)
    at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(AbstractAutoProxyCreator.java:349)
    at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessAfterInitialization(AbstractAutoProxyCreator.java:298)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:421)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1635)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:398)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:119)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83)
    at org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener.prepareTestInstance(SpringBootDependencyInjectionTestExecutionListener.java:44)
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:230)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:228)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runReflectiveCall(SpringJUnit4ClassRunner.java:287)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:289)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:247)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access[=13=]0(ParentRunner.java:58)
    at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
    at org.junit.vintage.engine.execution.RunnerExecutor.execute(RunnerExecutor.java:40)
    at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
    at java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:193)
    at java.util.Iterator.forEachRemaining(Iterator.java:116)
    at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
    at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
    at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:485)
    at org.junit.vintage.engine.VintageTestEngine.executeAllChildren(VintageTestEngine.java:80)
    at org.junit.vintage.engine.VintageTestEngine.execute(VintageTestEngine.java:71)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:170)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:154)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:90)
    at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:92)
    at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access0(JUnitPlatformTestClassProcessor.java:77)
    at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:73)
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
    at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
    at com.sun.proxy.$Proxy2.stop(Unknown Source)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.stop(TestWorker.java:131)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:155)
    at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:137)
    at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:404)
    at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63)
    at org.gradle.internal.concurrent.ManagedExecutorImpl.run(ManagedExecutorImpl.java:46)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55)
    at java.lang.Thread.run(Thread.java:748)
Caused by: org.springframework.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null
    at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:345)
    at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:492)
    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.apply(AbstractClassGenerator.java:93)
    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.apply(AbstractClassGenerator.java:91)
    at org.springframework.cglib.core.internal.LoadingCache.call(LoadingCache.java:54)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at org.springframework.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:61)
    at org.springframework.cglib.core.internal.LoadingCache.get(LoadingCache.java:34)
    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:116)
    at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:291)
    at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:480)
    at org.springframework.cglib.proxy.Enhancer.createClass(Enhancer.java:337)
    at org.springframework.aop.framework.ObjenesisCglibAopProxy.createProxyClassAndInstance(ObjenesisCglibAopProxy.java:55)
    at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:201)
    ... 73 more
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.GeneratedMethodAccessor82.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:459)
    at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:336)
    ... 86 more
Caused by: java.lang.ClassFormatError: Illegal method name "should success when setting key is api updatable" in class com/xxx/xxx/web/rest/CompanySettingResourceIntTest$$EnhancerBySpringCGLIB$$f0902682
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:756)
    ... 91 more

您可能会点击 Spring issue #21674,它已在 Spring 5.1 中修复。如果可以的话,您可能想要升级。似乎有一个忽略这些方法的修复程序。不知你是否接受。

一些背景信息:您的目标方法是事务性的,因此 Spring 尝试为其创建一个 CGLIB 代理。我在一个简单的 Spock (Groovy) 测试中快速验证了 CGLIB 似乎无法正确处理包含空格的方法——可能是因为它根本不知道像 Groovy 和 Kotlin 这样的语言允许这样的事情。这是一个概念证明。我用了 Groovy 和 Spock,因为我比较懒,不想搭建 Kotlin 项目。但我希望结果是相同的。

Java class 测试中:

package de.scrum_master.Whosebug.q70654015;

public class SampleJava {
  public String greet(String input) {
    return "Hello " + input + "!";
  }
}

Groovy class 测试中:

package de.scrum_master.Whosebug.q70654015

class SampleGroovy {
  String "say hello to"(String input) {
    "Hello $input!"
  }
}

创建 CGLIB 代理的 Spock 测试:

package de.scrum_master.Whosebug.q70654015

import net.sf.cglib.proxy.Enhancer
import net.sf.cglib.proxy.MethodInterceptor
import spock.lang.Specification

class CGLIBProxyTest extends Specification {
  def "create CGLIB proxy for Java class"() {
    given:
    Enhancer enhancer = new Enhancer()
    enhancer.superclass = SampleJava
    enhancer.callback = { obj, method, args, proxy ->
      method.getDeclaringClass() != Object.class && method.getReturnType() == String.class
        ? "Hello cglib!"
        : proxy.invokeSuper(obj, args)
    } as MethodInterceptor

    when:
    SampleJava proxy = enhancer.create()

    then:
    proxy.greet("world") == "Hello cglib!"
  }

  def "create CGLIB proxy for Groovy class with spaces in method name"() {
    given:
    Enhancer enhancer = new Enhancer()
    enhancer.superclass = SampleGroovy
    enhancer.callback = { obj, method, args, proxy ->
      method.getDeclaringClass() != Object.class && method.getReturnType() == String.class
        ? "Hello cglib!"
        : proxy.invokeSuper(obj, args)
    } as MethodInterceptor

    when:
    SampleGroovy proxy = enhancer.create()

    then:
    proxy."say hello to"("world") == "Hello cglib!"
  }
}

第一个测试通过,第二个失败:

net.sf.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null

    at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:345)
    at net.sf.cglib.proxy.Enhancer.generate(Enhancer.java:492)
    at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.apply(AbstractClassGenerator.java:93)
    at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.apply(AbstractClassGenerator.java:91)
    at net.sf.cglib.core.internal.LoadingCache.call(LoadingCache.java:54)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at net.sf.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:61)
    at net.sf.cglib.core.internal.LoadingCache.get(LoadingCache.java:34)
    at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:116)
    at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:291)
    at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:480)
    at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:305)
    at de.scrum_master.Whosebug.q70654015.CGLIBProxyTest.create CGLIB proxy for Groovy class with spaces in method name(CGLIBProxyTest.groovy:36)
Caused by: java.lang.reflect.InvocationTargetException
    at net.sf.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:459)
    at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:336)
    ... 12 more
Caused by: java.lang.ClassFormatError: Illegal method name "say hello to" in class de/scrum_master/Whosebug/q70654015/SampleGroovy$$EnhancerByCGLIB$$ef703cf1
    at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1016)
    ... 14 more

这与您遇到的错误相同。只要将 Groovy 方法重命名为 JVM 中合法的名称,测试就会通过。

关于您声称行为应该取决于硬件或 OS 平台的说法,我不同意。我认为您的 运行 时间环境可能有所不同。您如何构建和 运行ning 您的项目? Maven,Gradle,还有什么?


更新: 我有个好消息要告诉你:我在 JDK 8 和 15 以及 cglib:cglib-nodep:3.2.6 上进行了 运行 测试。当升级到 CGLIB 3.3.0 时,测试开始通过。因此,您可能希望在 Maven POM 的 <dependencyManagement> 部分(或任何 Gradle 的等价物)中对该版本进行版本管理。也许有帮助。请注意,某些工具可能还依赖于 cglib:cglib,因此您也希望对其进行版本管理。即,确保您使用这些版本:

  • cglib:cglib-nodep:3.3.0
  • cglib:cglib:3.3.0(如果在您的应用程序中使用)

更新 2: 我不是普通 Spring 用户,所以我忘记了 Spring 核心(神器 org.springframework:spring-core)包括它自己的 CGLIB 版本,重新定位到基本包名称 org.springframework.cglib。对于我的独立示例,请注意堆栈跟踪与原始 net.sf.cglib 的包名称差异。

我帮你查了一下,发现从版本 5.2.0.RELEASE 开始,Spring Core 是 upgraded to CGLIB 3.3.0。因此,如果您可以使用 Spring 版本 5.2.0 或更高版本,它很可能适合您。