使用 ByteBuddy 重新定义方法时出错:"class redefinition failed: attempted to add a method"
Error while redefining a method with ByteBuddy: "class redefinition failed: attempted to add a method"
我正在学习 Byte Buddy,我正在尝试执行以下操作:
- 从给定的 class 或接口
创建子class
- 然后替换subclass
中的一个方法
注意 subclass 是 'loaded' 在 ClassLoader
before 其中一个方法 (sayHello
) 是重新定义。它失败并显示以下错误消息:
java.lang.UnsupportedOperationException: class redefinition failed: attempted to add a method
at sun.instrument.InstrumentationImpl.redefineClasses0(Native Method)
at sun.instrument.InstrumentationImpl.redefineClasses(InstrumentationImpl.java:170)
at net.bytebuddy.dynamic.loading.ClassReloadingStrategy$Strategy.apply(ClassReloadingStrategy.java:293)
at net.bytebuddy.dynamic.loading.ClassReloadingStrategy.load(ClassReloadingStrategy.java:173)
...
下面是一组 JUnit 测试的代码。第一个测试 shouldReplaceMethodFromClass
通过,因为 Bar
class 在重新定义其方法之前未被子class 编辑。当给定的 Bar
class 或 Foo
接口是 subclassed.
时,其他两个测试失败
我读到我应该在一个单独的 class 中委托新方法,这就是我使用 CustomInterceptor
class 所做的,我还在测试启动并用于加载子class,但即便如此,我仍然遗漏了一些东西,我看不到什么:(
有人有想法吗?
public class ByteBuddyReplaceMethodInClassTest {
private File classDir;
private ByteBuddy bytebuddy;
@BeforeClass
public static void setupByteBuddyAgent() {
ByteBuddyAgent.install();
}
@Before
public void setupTest() throws IOException {
this.classDir = Files.createTempDirectory("test").toFile();
this.bytebuddy = new ByteBuddy().with(Implementation.Context.Disabled.Factory.INSTANCE);
}
@Test
public void shouldReplaceMethodFromClass()
throws InstantiationException, IllegalAccessException, Exception {
// given
final Class<? extends Bar> modifiedClass = replaceMethodInClass(Bar.class,
ClassFileLocator.ForClassLoader.of(Bar.class.getClassLoader()));
// when
final String hello = modifiedClass.newInstance().sayHello();
// then
assertThat(hello).isEqualTo("Hello!");
}
@Test
public void shouldReplaceMethodFromSubclass()
throws InstantiationException, IllegalAccessException, Exception {
// given
final Class<? extends Bar> modifiedClass = replaceMethodInClass(createSubclass(Bar.class),
new ClassFileLocator.ForFolder(this.classDir));
// when
final String hello = modifiedClass.newInstance().sayHello();
// then
assertThat(hello).isEqualTo("Hello!");
}
@Test
public void shouldReplaceMethodFromInterface()
throws InstantiationException, IllegalAccessException, Exception {
// given
final Class<? extends Foo> modifiedClass = replaceMethodInClass(createSubclass(Foo.class),
new ClassFileLocator.ForFolder(this.classDir));
// when
final String hello = modifiedClass.newInstance().sayHello();
// then
assertThat(hello).isEqualTo("Hello!");
}
@SuppressWarnings("unchecked")
private <T> Class<T> createSubclass(final Class<T> baseClass) {
final Builder<T> subclass =
this.bytebuddy.subclass(baseClass);
final Loaded<T> loaded =
subclass.make().load(ByteBuddyReplaceMethodInClassTest.class.getClassLoader(),
ClassReloadingStrategy.fromInstalledAgent());
try {
loaded.saveIn(this.classDir);
return (Class<T>) loaded.getLoaded();
} catch (IOException e) {
throw new RuntimeException("Failed to save subclass in a temporary directory", e);
}
}
private <T> Class<? extends T> replaceMethodInClass(final Class<T> subclass,
final ClassFileLocator classFileLocator) throws IOException {
final Builder<? extends T> rebasedClassBuilder =
this.bytebuddy.redefine(subclass, classFileLocator);
return rebasedClassBuilder.method(ElementMatchers.named("sayHello"))
.intercept(MethodDelegation.to(CustomInterceptor.class)).make()
.load(ByteBuddyReplaceMethodInClassTest.class.getClassLoader(),
ClassReloadingStrategy.fromInstalledAgent())
.getLoaded();
}
static class CustomInterceptor {
public static String intercept() {
return "Hello!";
}
}
}
Foo
接口和Bar
class是:
public interface Foo {
public String sayHello();
}
和
public class Bar {
public String sayHello() throws Exception {
return null;
}
}
问题是您首先创建了 Bar
的子 class,然后加载它,但稍后重新定义它以添加方法 sayHello
。您的 class 进化如下:
子class创作
class Bar$ByteBuddy extends Bar {
Bar$ByteBuddy() { ... }
}
重新定义subclass
class Bar$ByteBuddy extends Bar {
Bar$ByteBuddy() { ... }
String sayHello() { ... }
}
HotSpot VM 和大多数其他虚拟机不允许在 class 加载后添加方法。您可以通过在第一次定义它之前将方法添加到 subclass 来解决这个问题,即设置:
DynamicType.Loaded<T> loaded = bytebuddy.subclass(baseClass)
.method(ElementMatchers.named("sayHello"))
.intercept(SuperMethodCall.INSTANCE) // or StubMethod.INSTANCE
.make()
这样重定义时方法已经存在,字节好友可以直接替换其字节码,无需添加方法。请注意,Byte Buddy 尝试重新定义,因为一些 VM 确实支持它(特别是动态代码演化 VM,希望在某个时候合并到 HotSpot 中,请参阅 JEP 159)。
我正在学习 Byte Buddy,我正在尝试执行以下操作:
- 从给定的 class 或接口 创建子class
- 然后替换subclass 中的一个方法
注意 subclass 是 'loaded' 在 ClassLoader
before 其中一个方法 (sayHello
) 是重新定义。它失败并显示以下错误消息:
java.lang.UnsupportedOperationException: class redefinition failed: attempted to add a method
at sun.instrument.InstrumentationImpl.redefineClasses0(Native Method)
at sun.instrument.InstrumentationImpl.redefineClasses(InstrumentationImpl.java:170)
at net.bytebuddy.dynamic.loading.ClassReloadingStrategy$Strategy.apply(ClassReloadingStrategy.java:293)
at net.bytebuddy.dynamic.loading.ClassReloadingStrategy.load(ClassReloadingStrategy.java:173)
...
下面是一组 JUnit 测试的代码。第一个测试 shouldReplaceMethodFromClass
通过,因为 Bar
class 在重新定义其方法之前未被子class 编辑。当给定的 Bar
class 或 Foo
接口是 subclassed.
我读到我应该在一个单独的 class 中委托新方法,这就是我使用 CustomInterceptor
class 所做的,我还在测试启动并用于加载子class,但即便如此,我仍然遗漏了一些东西,我看不到什么:(
有人有想法吗?
public class ByteBuddyReplaceMethodInClassTest {
private File classDir;
private ByteBuddy bytebuddy;
@BeforeClass
public static void setupByteBuddyAgent() {
ByteBuddyAgent.install();
}
@Before
public void setupTest() throws IOException {
this.classDir = Files.createTempDirectory("test").toFile();
this.bytebuddy = new ByteBuddy().with(Implementation.Context.Disabled.Factory.INSTANCE);
}
@Test
public void shouldReplaceMethodFromClass()
throws InstantiationException, IllegalAccessException, Exception {
// given
final Class<? extends Bar> modifiedClass = replaceMethodInClass(Bar.class,
ClassFileLocator.ForClassLoader.of(Bar.class.getClassLoader()));
// when
final String hello = modifiedClass.newInstance().sayHello();
// then
assertThat(hello).isEqualTo("Hello!");
}
@Test
public void shouldReplaceMethodFromSubclass()
throws InstantiationException, IllegalAccessException, Exception {
// given
final Class<? extends Bar> modifiedClass = replaceMethodInClass(createSubclass(Bar.class),
new ClassFileLocator.ForFolder(this.classDir));
// when
final String hello = modifiedClass.newInstance().sayHello();
// then
assertThat(hello).isEqualTo("Hello!");
}
@Test
public void shouldReplaceMethodFromInterface()
throws InstantiationException, IllegalAccessException, Exception {
// given
final Class<? extends Foo> modifiedClass = replaceMethodInClass(createSubclass(Foo.class),
new ClassFileLocator.ForFolder(this.classDir));
// when
final String hello = modifiedClass.newInstance().sayHello();
// then
assertThat(hello).isEqualTo("Hello!");
}
@SuppressWarnings("unchecked")
private <T> Class<T> createSubclass(final Class<T> baseClass) {
final Builder<T> subclass =
this.bytebuddy.subclass(baseClass);
final Loaded<T> loaded =
subclass.make().load(ByteBuddyReplaceMethodInClassTest.class.getClassLoader(),
ClassReloadingStrategy.fromInstalledAgent());
try {
loaded.saveIn(this.classDir);
return (Class<T>) loaded.getLoaded();
} catch (IOException e) {
throw new RuntimeException("Failed to save subclass in a temporary directory", e);
}
}
private <T> Class<? extends T> replaceMethodInClass(final Class<T> subclass,
final ClassFileLocator classFileLocator) throws IOException {
final Builder<? extends T> rebasedClassBuilder =
this.bytebuddy.redefine(subclass, classFileLocator);
return rebasedClassBuilder.method(ElementMatchers.named("sayHello"))
.intercept(MethodDelegation.to(CustomInterceptor.class)).make()
.load(ByteBuddyReplaceMethodInClassTest.class.getClassLoader(),
ClassReloadingStrategy.fromInstalledAgent())
.getLoaded();
}
static class CustomInterceptor {
public static String intercept() {
return "Hello!";
}
}
}
Foo
接口和Bar
class是:
public interface Foo {
public String sayHello();
}
和
public class Bar {
public String sayHello() throws Exception {
return null;
}
}
问题是您首先创建了 Bar
的子 class,然后加载它,但稍后重新定义它以添加方法 sayHello
。您的 class 进化如下:
子class创作
class Bar$ByteBuddy extends Bar { Bar$ByteBuddy() { ... } }
重新定义subclass
class Bar$ByteBuddy extends Bar { Bar$ByteBuddy() { ... } String sayHello() { ... } }
HotSpot VM 和大多数其他虚拟机不允许在 class 加载后添加方法。您可以通过在第一次定义它之前将方法添加到 subclass 来解决这个问题,即设置:
DynamicType.Loaded<T> loaded = bytebuddy.subclass(baseClass)
.method(ElementMatchers.named("sayHello"))
.intercept(SuperMethodCall.INSTANCE) // or StubMethod.INSTANCE
.make()
这样重定义时方法已经存在,字节好友可以直接替换其字节码,无需添加方法。请注意,Byte Buddy 尝试重新定义,因为一些 VM 确实支持它(特别是动态代码演化 VM,希望在某个时候合并到 HotSpot 中,请参阅 JEP 159)。