有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java动态类在运行时的重新定义

我最近一直在玩java instrumentation API和一个byte buddy。我的目标是更改已加载类的行为。我可以改变现有的方法,但是我没有添加一个全新的方法

第一种方法:

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方法,但是新方法没有添加到类中,现有方法的行为也没有改变。 将此解决方案用作premain代理在这两种情况下都能完美工作

第三种方法:

    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());
}

本例仅适用于修改现有方法的premain代理。尝试添加新方法时抛出

Caused by: java.lang.UnsupportedOperationException: class redefinition failed: attempted to add a method

将此作为代理附加到已运行的应用程序时,不会发生任何事情

  1. 有人知道这是否能实现我一直在努力的目标吗
  2. 这些方法中哪一种是正确的,为什么它们的行为不同
  3. 我们如何能够更改已加载到VM的类的行为? 这个机制有具体的名称吗?我试着在网上寻找一些信息,但什么也找不到
  4. 也许其他一些库,如JavaAssist或ASM更适合这种情况

共 (2) 个答案

  1. # 1 楼答案

    使用AgentBuilder,您应该注册Listener,以查看在重新传输期间是否发生错误。您可能应该设置.disableClassFormatChanges(),因为一般的JVM不支持向已经定义的类添加方法或字段

    添加一个字段或方法是不可能的,就像现在一样,只有the code evolution VM supports it as of today,而且这个特性是否能应用到OpenJDK中还值得怀疑

  2. # 2 楼答案

    因为您的需求是构建一个框架,使用它可以收集应用程序度量。首先,有像VisualVM这样的工具,可以帮助您从运行的java应用程序中获取度量,并查看细节。这将完全在应用程序外部,不需要任何代码更改

    如果你想更好地控制这些指标,你可以加入Spring Boot Admin,这样你就可以在不改变任何代码的情况下实时完成。Spring Boot Admin中有很多功能