Я недавно играл с java API инструментовки и байтовым приятелем. Моя цель - изменить поведение уже загруженного класса. Мне удалось изменить существующий метод, но мне не удалось добавить совершенно новый.
Первый подход:
public static void agentmain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException {
System.out.println(("[Agent] In agentmain/premain method"));
Class<?> clazz = Class.forName("com.example.instrumentation.agent.AppService");
inst.addTransformer(new AppServiceTransformer(), true);
inst.retransformClasses(clazz);
}
public class AppServiceTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
byte[] byteCode = null;
System.out.println("Transformation");
try {
byteCode = new ByteBuddy()
.redefine(classBeingRedefined)
// .defineMethod("getExperimental", String.class, Opcodes.ACC_PUBLIC)
// .intercept(FixedValue.value("This is a message from the ByteBuddy hacker !!!"))
.method(named("getAnswer"))
.intercept(FixedValue.value("Service has been hacked :)"))
.make()
.getBytes();
} catch (Throwable e) {
System.err.println(e);
System.err.println("Failed to transform");
}
return byteCode;
}
}
Приведенный выше код работает, когда я присоединяю этот агент к уже работающей виртуальной машине, он изменяет поведение указанного метода. Однако, когда я раскомментировал код, отвечающий за определение нового метода, я получил
Exception in thread "main" com.sun.tools.attach.AgentInitializationException: Agent JAR loaded but agent failed to initialize
. Я попытался запустить приведенный выше пример в качестве основного агента, загружаемого при запуске приложения. В этом случае изменение поведения метода работает, но добавление нового выдает
Failed to transform
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:567)
at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:513)
at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndCallPremain(InstrumentationImpl.java:525)
Caused by: java.lang.UnsupportedOperationException: class redefinition failed: attempted to add a method
Второй подход:
public static void agentmain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException {
System.out.println(("[Agent] In agentmain/premain method"));
new AgentBuilder.Default()
.type(named("com.jarek.example.instrumentation.agent.AppService"))
.transform(new AgentBuilder.Transformer() {
@Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
System.out.println("Entered transform");
return builder.method(named("getAnswer"))
.intercept(FixedValue.value("Service has been hacked :)"))
.defineMethod("getExperimental", String.class, Opcodes.ACC_PUBLIC)
.intercept(FixedValue.value("This is experimental feature"));
}
})
.with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
.with(AgentBuilder.TypeStrategy.Default.REDEFINE)
.installOn(inst);
}
Я вижу в консоли что агент вошел в метод преобразования, однако новый метод не добавляется в класс и поведение существующего не изменяется. Использование этого решения в качестве основного агента работает отлично в обоих случаях.
Третий подход:
public static void premain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException {
System.out.println(("[Agent] In agentmain/premain method"));
Class<?> clazz = Class.forName("com.example.instrumentation.agent.AppService");
ByteBuddyAgent.install();
new ByteBuddy()
.redefine(clazz)
// .defineMethod("getExperimental", String.class, Opcodes.ACC_PUBLIC)
// .intercept(FixedValue.value("This is a message from the ByteBuddy hacker !!!"))
.method(named("getAnswer"))
.intercept(FixedValue.value("Service has been hacked :)"))
.make()
.load(clazz.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
}
Этот случай работает только для основного агента для изменения существующего метода , При попытке добавить новый метод выдается
Caused by: java.lang.UnsupportedOperationException: class redefinition failed: attempted to add a method
. При подключении этого агента к уже запущенному приложению ничего не происходит.
- Кто-нибудь знает, возможно ли это добиться того, что я тоже пытался?
- Какой из этих подходов верен и почему они ведут себя по-разному?
- Как это так? Возможно, что мы можем изменить поведение класса, который уже загружен в виртуальную машину? Имеет ли этот механизм какое-либо конкретное c имя? Я попытался найти некоторую информацию о inte rnet, но ничего не смог найти.
- Возможно, какая-нибудь другая библиотека, например JavaAssist или ASM, лучше подойдет для этого случая?