我最近一直在使用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;
}
}
上面的代码有效,当我将该代理附加到已经运行的VM时,它会更改指定方法的行为。但是,当我取消注释负责定义新方法的代码时,我得到的是
Exception in thread "main" com.sun.tools.attach.AgentInitializationException: Agent JAR loaded but agent failed to initialize
我尝试在示例中作为在应用程序启动时加载的premain代理运行。对于这种情况,改变方法的行为是可行的,但是会添加一个新方法
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);
}
我可以在控制台中看到代理已经输入了transform方法,但是新方法并未添加到类中,并且现有方法的行为也没有改变。在两种情况下,使用此解决方案作为主要代理均可以完美地工作。
第三种方法:
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
将其作为代理附加到已经运行的应用程序时,什么都没有发生。
因为您的要求是建立一个框架,您可以使用该框架收集应用程序指标。首先,有VisualVM之类的工具可以帮助您从正在运行的Java应用程序中获取指标并查看相关见解。这将完全在您的应用程序外部,并且不需要任何代码更改。
如果您希望更好地控制指标,则可以使用Spring Boot Admin,这将为您实时进行,而无需更改任何代码。 Spring Boot Admin中存在大量功能。