运行时动态类重新定义

问题描述 投票:0回答:1

我最近一直在使用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

将其作为代理附加到已经运行的应用程序时,什么都没有发生。

  1. 有人知道这是否有可能实现我一直在尝试的目标吗?
  2. 其中哪些方法是正确的,为什么它们表现不同?
  3. 如何改变已经加载到虚拟机的类的行为?这个机制有特定的名称吗?我试图在互联网上寻找一些信息,但找不到任何东西。
  4. 也许其他一些库,例如JavaAssist或ASM更适合这种情况?
java instrumentation byte-buddy
1个回答
0
投票

因为您的要求是建立一个框架,您可以使用该框架收集应用程序指标。首先,有VisualVM之类的工具可以帮助您从正在运行的Java应用程序中获取指标并查看相关见解。这将完全在您的应用程序外部,并且不需要任何代码更改。

如果您希望更好地控制指标,则可以使用Spring Boot Admin,这将为您实时进行,而无需更改任何代码。 Spring Boot Admin中存在大量功能。

© www.soinside.com 2019 - 2024. All rights reserved.