Я учусь Byte Бадди, и я пытаюсь сделать следующее:Ошибки при переопределении метода с 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;
}
}
Спасибо за ваш отзыв, Рафаэль! Но я немного смущен, потому что я надеялся сделать что-то похожее на то, как работает Mockito: т. Е. Сначала определить класс «Mock», а затем настроить поведение. Другими словами, существует ли способ избежать загрузки подкласса перед заменой метода? –
В этом случае вам нужно пройти иерархию классов «Бар» и найти первый класс, объявляющий метод. Это то, что мы делаем в Мокито. В этом методе мы затем добавляем код, похожий на: 'if (this instanceof SomeClass)', чтобы решить, следует ли отправлять код. Если вы явно определите подклассы, я бы рекомендовал вам применить некоторый метод method (any()). (SuperMethodCall.INSTANCE), что позволяет вам позже переопределить любой метод, если у вас есть такая возможность. –
Удивительный, спасибо большое! Я использовал несколько иной подход для поддержки интерфейса Foo, вызывая исключение для всех вызовов супер-методов: '.method (ElementMatchers.any()). Intercept (ExceptionMethod.throwing (RuntimeException.class)); и он отлично работает! –