Сбой ByteBuddy при попытке переопределить sun.reflect.GeneratedMethodAccessor1
Руководствуясь любопытством, я попытался экспортировать байт-код GeneratedMethodAccessor1 (сгенерированный JVM при использовании отражения).
Я пытаюсь получить байт-код класса следующим образом:
public class MethodExtractor {
public static void main(String[] args) throws Exception {
ExampleClass example = new ExampleClass();
Method exampleMethod = ExampleClass.class
.getDeclaredMethod("exampleMethod");
exampleMethod.setAccessible(true);
int rndSum = 0;
for (int i = 0; i < 20; i++) {
rndSum += (Integer) exampleMethod.invoke(example);
}
Field field = Method.class.getDeclaredField("methodAccessor");
field.setAccessible(true);
Object methodAccessor = field.get(exampleMethod);
Field delegate = methodAccessor.getClass().getDeclaredField("delegate");
delegate.setAccessible(true);
Object gma = delegate.get(methodAccessor);
ByteBuddyAgent.installOnOpenJDK();
try {
ClassFileLocator classFileLocator = ClassFileLocator.AgentBased
.fromInstalledAgent(gma.getClass().getClassLoader());
Unloaded<? extends Object> unloaded = new ByteBuddy().redefine(
gma.getClass(), classFileLocator).make();
Map<TypeDescription, File> saved = unloaded.saveIn(Files
.createTempDirectory("javaproxy").toFile());
saved.forEach((t, u) -> System.out.println(u.getAbsolutePath()));
} catch (IOException e) {
throw new RuntimeException("Failed to save class to file");
}
}
}
Однако я получаю следующую ошибку при выполнении этого класса:
Exception in thread "main" java.lang.NullPointerException
at net.bytebuddy.dynamic.scaffold.TypeWriter$Engine$ForRedefinition.create(TypeWriter.java:172)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default.make(TypeWriter.java:1182)
at net.bytebuddy.dynamic.scaffold.inline.InlineDynamicTypeBuilder.make(InlineDynamicTypeBuilder.java:244)
at reegnz.dyna.proxy.extractor.MethodExtractor.main(MethodExtractor.java:48)
По сути, я сначала итерирую вызов метода достаточно раз, чтобы JVM раздула метод (сгенерировал GeneratedMethodAccessor), а затем попытался переопределить класс, чтобы получить байт-код.
Я попробовал тот же метод для экспорта сгенерированного класса Proxy, и он работал безупречно. Вот что заставило меня попробовать это.
Кажется, что DelegatingClassLoader класса GeneratedMethodAccessor1 не может даже перезагрузить класс, когда я пытаюсь загрузить класс с помощью метода loadClass.
Любые идеи, как я мог бы получить байт-код для классов GeneratedMethodAccessor?
1 ответ
Прежде всего, NullPointerException
это ошибка, я только что исправил это. Загрузчик должен был бросить IllegalArgumentException
вместо этого, но это никогда не зашло так далеко. Спасибо, что обратили на это мое внимание.
Сложившаяся проблема Байта Бадди в том, что
gma.getClass().getClassLoader().findClass(gma.getClass().getName());
бросает ClassNotFoundException
, Это является следствием использования DelegatingClassLoader
для классов доступа. Как образованное предположение, я думаю, что этот загрузчик классов намеревается защитить свои классы извне, чтобы сделать их легко собираемыми мусором. Однако, не позволяя искать класс, что несколько нарушает контракт на ClassLoader
, Кроме того, я предполагаю, что эта подпрограмма загрузки будет реорганизована для использования в будущем анонимных загрузчиков классов JDK (аналогично классам, представляющим лямбда-выражения). Как ни странно, похоже, что исходный код DelegatingClassLoader
недоступен в JDK, хотя я могу найти его в дистрибутиве. Возможно, ВМ обрабатывает эти загрузчики специально в каком-то месте.
На данный момент вы можете использовать следующие ClassFileTransformer
который использует магию отражения в загрузчике классов, чтобы найти загруженный класс и затем извлечь массив байтов. (The ClassFileLocator
interface только берет имя вместо загруженного класса, чтобы позволить работать с незагруженными типами, что обычно всегда имеет место. Не знаю, почему это не работает в этом случае.)
class DelegateExtractor extends ClassFileLocator.AgentBased {
private final ClassLoader classLoader;
private final Instrumentation instrumentation;
public DelegateExtractor(ClassLoader classLoader, Instrumentation instrumentation) {
super(classLoader, instrumentation);
this.classLoader = classLoader;
this.instrumentation = instrumentation;
}
@Override
public Resolution locate(String typeName) {
try {
ExtractionClassFileTransformer classFileTransformer =
new ExtractionClassFileTransformer(classLoader, typeName);
try {
instrumentation.addTransformer(classFileTransformer, true);
// Start nasty hack
Field field = ClassLoader.class.getDeclaredField("classes");
field.setAccessible(true);
instrumentation.retransformClasses(
(Class<?>) ((Vector<?>) field.get(classLoader)).get(0));
// End nasty hack
byte[] binaryRepresentation = classFileTransformer.getBinaryRepresentation();
return binaryRepresentation == null
? Resolution.Illegal.INSTANCE
: new Resolution.Explicit(binaryRepresentation);
} finally {
instrumentation.removeTransformer(classFileTransformer);
}
} catch (Exception ignored) {
return Resolution.Illegal.INSTANCE;
}
}
}
Чтобы еще больше упростить ваш код, вы можете использовать ClassFileLocator
непосредственно вместо применения перезаписи, которая может немного изменить файл класса, даже если вы не применяете никаких изменений к классу.