我學習字節好友和我試圖做到以下幾點:錯誤而重新定義與ByteBuddy的方法:「重新定義類失敗:嘗試添加一個方法」
- 從給定創建一個子類類或接口
- 然後在子類中替換的方法
注意,子類是在ClassLoader
之前其方法的一個(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$1.apply(ClassReloadingStrategy.java:293)
at net.bytebuddy.dynamic.loading.ClassReloadingStrategy.load(ClassReloadingStrategy.java:173)
...
下面是一組JUnit測試的代碼。第一個測試shouldReplaceMethodFromClass
通過,因爲Bar
類在重新定義其方法之前未被分類。當給定的Bar
類或Foo
接口被分類時,另外兩個測試失敗。
我讀到應該將新方法委託給一個單獨的類,這就是我使用CustomInterceptor
類所做的工作,並且我還在測試啓動時安裝了ByteBuddy代理並用於加載子類,但即使使用該類,我還是失去了一些東西,我什麼也看不見:(
任何人有一個想法
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
類有:?
public interface Foo {
public String sayHello();
}
和
public class Bar {
public String sayHello() throws Exception {
return null;
}
}
謝謝你的迴應,Raphael!但是我有點困惑,因爲我希望能夠做到類似於Mockito的工作方式:即首先定義一個「模擬」類,然後定製行爲。換句話說,在替換方法之前是否有避免加載子類的方法? –
在這種情況下,您需要遍歷Bar的類層次結構並找到聲明該方法的第一個類。這就是我們在Mockito中所做的。在這個方法中,我們添加類似於'if(this instanceof SomeClass)'的代碼來決定是否應該分派代碼。如果你明確地定義了子類,我會推薦你應用一些'method(any())。instrument(SuperMethodCall.INSTANCE)'什麼讓你稍後重新定義任何方法,如果你有機會的話。 –
太棒了,非常感謝!我使用了一種稍微不同的方法來支持'Foo'接口,通過拋出所有超級方法調用的異常:'.method(ElementMatchers.any()).intercept(ExceptionMethod.throwing(RuntimeException.class));''它效果很好! –