java - How to make an existing class (present in jar file) implement Serializable at runtime (by javassist, bytebuddy, etc)? - S

I have a class in the source codeclass BillPughSingleton {private BillPughSingleton() {System.out.prin

I have a class in the source code

class BillPughSingleton {

    private BillPughSingleton() {
        System.out.println("BillPughSingleton private constructor called");
    }

    // Lớp nội bộ static chứa instance duy nhất của Singleton
    private static class SingletonHelper {
        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }

    public static BillPughSingleton getInstance() {
        return SingletonHelper.INSTANCE;
    }
}

Compiling works fine!!

Now I want to override the jvm bytecode to add serializable capability to this class by using Instrumentation API and Javassist

public class Agent {

  private static final Logger LOGGER = LoggerFactory.getLogger(Agent.class);
  private static Integer count = 0;

  public static void premain(String agentArgs, Instrumentation inst) {
    LOGGER.info("[Agent] In premain method");
    String className = ".example.genericapplicationeventlistener.BillPughSingleton";
    transformClass2( className, inst );
  }

  private static void transformClass2(String className, Instrumentation inst) {
    System.out.println(count);
    Class<?> targetCls = null;
    ClassLoader targetClassLoader = null;
    // see if we can get the class using forName
    try {
      targetCls = Class.forName(className);
      targetClassLoader = targetCls.getClassLoader();
      transform2(targetCls, targetClassLoader, inst);
      return;
    } catch (Exception ex) {
      LOGGER.error("Class [{}] not found with Class.forName", className);
    }
    // otherwise iterate all loaded classes and find what we want
    for (Class<?> clazz : inst.getAllLoadedClasses()) {
      if (clazz.getName().equals(className)) {
        targetCls = clazz;
        targetClassLoader = targetCls.getClassLoader();
        transform2(targetCls, targetClassLoader, inst);
        return;
      }
    }
    throw new RuntimeException("Failed to find class [" + className + "]");
  }

  private static void transform2(Class<?> targetCls, ClassLoader targetClassLoader, Instrumentation inst) {
    SerializableAdder dt = new SerializableAdder(targetCls.getName(), targetClassLoader);
    inst.addTransformer(dt, true);
    try {
      inst.retransformClasses(targetCls);
    } catch (Exception ex) {
      throw new RuntimeException("Transform failed for class: [" + targetCls.getName() + "]", ex);
    }
  }

  static class SerializableAdder implements ClassFileTransformer {
    private final String targetClassName;
    /** The class loader of the class we want to transform */
    private final ClassLoader targetClassLoader;

    SerializableAdder(String targetClassName, ClassLoader targetClassLoader) {
      this.targetClassName = targetClassName;
      this.targetClassLoader = targetClassLoader;
    }

    @Override
    public byte[] transform(
        ClassLoader loader,
        String className,
        Class<?> classBeingRedefined,
        ProtectionDomain protectionDomain,
        byte[] classfileBuffer) {
      byte[] byteCode = classfileBuffer;

      String finalTargetClassName = this.targetClassName.replaceAll("\\.", "/"); // replace . with /
      if (!className.equals(finalTargetClassName)) {
        return byteCode;
      }

      if (className.equals(finalTargetClassName) && loader.equals(targetClassLoader)) {
        try {
          ClassPool pool = ClassPool.getDefault();
          CtClass ctClass = pool.get(targetClassName);

          if (ctClass.isFrozen()) {
            ctClass.defrost();
          }

          ClassFile classFile = ctClass.getClassFile();
          if ( !Set.of(classFile.getInterfaces()).contains( "java.io.Serializable" ) ) {
            classFile.addInterface( "java.io.Serializable" );

          }
          byteCode = ctClass.toBytecode();

          System.out.println();
        } catch (Exception e) {
          e.printStackTrace();
        }
      }

      return byteCode;
    }
  }
}

return byteCode;

But i got this

Exception in thread "main" java.lang.reflect.InvocationTargetException
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:574)
    at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:491)
    at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndCallPremain(InstrumentationImpl.java:503)
Caused by: java.lang.RuntimeException: Transform failed for class: [.example.genericapplicationeventlistener.BillPughSingleton]
    at com.aeris.changelog.spring.instrumentation.Agent.transform2(Agent.java:204)
    at com.aeris.changelog.spring.instrumentation.Agent.transformClass2(Agent.java:191)
    at com.aeris.changelog.spring.instrumentation.Agent.premain(Agent.java:101)
    ... 6 more
Caused by: java.lang.UnsupportedOperationException: class redefinition failed: attempted to change superclass or interfaces
    at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
    at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:169)
    at com.aeris.changelog.spring.instrumentation.Agent.transform2(Agent.java:202)
    ... 8 more
*** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message Outstanding error when calling method in invokeJavaAgentMainMethod at s\src\java.instrument\share\native\libinstrument\JPLISAgent.c line: 619
*** java.lang.instrument ASSERTION FAILED ***: "success" with message invokeJavaAgentMainMethod failed at s\src\java.instrument\share\native\libinstrument\JPLISAgent.c line: 459
*** java.lang.instrument ASSERTION FAILED ***: "result" with message agent load/premain call failed at s\src\java.instrument\share\native\libinstrument\JPLISAgent.c line: 422

Fatal error: processing of -javaagent failed, processJavaStart failed

This is for learning purpose, so please ignore any stupidity!!

Was I wrong somewhere? Please help!!


UPDATE: Code changed according to rzwitserloot

public class Agent {

  private static final Logger LOGGER = LoggerFactory.getLogger(Agent.class);
  private static Integer count = 0;

  public static void premain(String agentArgs, Instrumentation inst) {
    LOGGER.info("[Agent] In premain method");
    String className = ".example.genericapplicationeventlistener.BillPughSingleton";
    transformClass2( className, inst );
  }

  private static void transformClass2(String className, Instrumentation inst) {
    Class<?> targetCls = null;
    ClassLoader targetClassLoader = null;
    // see if we can get the class using forName
    try {
      targetCls = Class.forName(className);
      targetClassLoader = targetCls.getClassLoader();
      transform2(targetCls, targetClassLoader, inst);
    } catch (Exception ex) {
      LOGGER.error("Class [{}] not found with Class.forName", className);
      ex.printStackTrace();
    }
  }

  private static void transform2(Class<?> targetCls, ClassLoader targetClassLoader, Instrumentation inst) {
    SerializableAdder dt = new SerializableAdder(targetCls.getName(), targetClassLoader);
    inst.addTransformer(dt, true);
    try {
      inst.retransformClasses(targetCls);
    } catch (Exception ex) {
      throw new RuntimeException("Transform failed for class: [" + targetCls.getName() + "]", ex);
    }
  }

  static class SerializableAdder implements ClassFileTransformer {
    private final String targetClassName;
    /** The class loader of the class we want to transform */
    private final ClassLoader targetClassLoader;

    SerializableAdder(String targetClassName, ClassLoader targetClassLoader) {
      this.targetClassName = targetClassName;
      this.targetClassLoader = targetClassLoader;
    }

    @Override
    public byte[] transform(
        ClassLoader loader,
        String className,
        Class<?> classBeingRedefined,
        ProtectionDomain protectionDomain,
        byte[] classfileBuffer) {
      byte[] byteCode = classfileBuffer;

      String finalTargetClassName = this.targetClassName.replaceAll("\\.", "/"); // replace . with /
      if (!className.equals(finalTargetClassName)) {
        return byteCode;
      }

      if (className.equals(finalTargetClassName) && loader.equals(targetClassLoader)) {
        try {
          ClassPool pool = ClassPool.getDefault();
          CtClass ctClass = pool.get(targetClassName);

          if (ctClass.isFrozen()) {
            ctClass.defrost();
          }

          ClassFile classFile = ctClass.getClassFile();
          if ( !Set.of(classFile.getInterfaces()).contains( "java.io.Serializable" ) ) {
            classFile.addInterface( "java.io.Serializable" );

          }
          byteCode = ctClass.toBytecode();

          System.out.println();
        } catch (Exception e) {
          e.printStackTrace();
        }
      }

      return byteCode;
    }
  }
}

In short, removed the for loop. Still the same error!!

I have a class in the source code

class BillPughSingleton {

    private BillPughSingleton() {
        System.out.println("BillPughSingleton private constructor called");
    }

    // Lớp nội bộ static chứa instance duy nhất của Singleton
    private static class SingletonHelper {
        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }

    public static BillPughSingleton getInstance() {
        return SingletonHelper.INSTANCE;
    }
}

Compiling works fine!!

Now I want to override the jvm bytecode to add serializable capability to this class by using Instrumentation API and Javassist

public class Agent {

  private static final Logger LOGGER = LoggerFactory.getLogger(Agent.class);
  private static Integer count = 0;

  public static void premain(String agentArgs, Instrumentation inst) {
    LOGGER.info("[Agent] In premain method");
    String className = ".example.genericapplicationeventlistener.BillPughSingleton";
    transformClass2( className, inst );
  }

  private static void transformClass2(String className, Instrumentation inst) {
    System.out.println(count);
    Class<?> targetCls = null;
    ClassLoader targetClassLoader = null;
    // see if we can get the class using forName
    try {
      targetCls = Class.forName(className);
      targetClassLoader = targetCls.getClassLoader();
      transform2(targetCls, targetClassLoader, inst);
      return;
    } catch (Exception ex) {
      LOGGER.error("Class [{}] not found with Class.forName", className);
    }
    // otherwise iterate all loaded classes and find what we want
    for (Class<?> clazz : inst.getAllLoadedClasses()) {
      if (clazz.getName().equals(className)) {
        targetCls = clazz;
        targetClassLoader = targetCls.getClassLoader();
        transform2(targetCls, targetClassLoader, inst);
        return;
      }
    }
    throw new RuntimeException("Failed to find class [" + className + "]");
  }

  private static void transform2(Class<?> targetCls, ClassLoader targetClassLoader, Instrumentation inst) {
    SerializableAdder dt = new SerializableAdder(targetCls.getName(), targetClassLoader);
    inst.addTransformer(dt, true);
    try {
      inst.retransformClasses(targetCls);
    } catch (Exception ex) {
      throw new RuntimeException("Transform failed for class: [" + targetCls.getName() + "]", ex);
    }
  }

  static class SerializableAdder implements ClassFileTransformer {
    private final String targetClassName;
    /** The class loader of the class we want to transform */
    private final ClassLoader targetClassLoader;

    SerializableAdder(String targetClassName, ClassLoader targetClassLoader) {
      this.targetClassName = targetClassName;
      this.targetClassLoader = targetClassLoader;
    }

    @Override
    public byte[] transform(
        ClassLoader loader,
        String className,
        Class<?> classBeingRedefined,
        ProtectionDomain protectionDomain,
        byte[] classfileBuffer) {
      byte[] byteCode = classfileBuffer;

      String finalTargetClassName = this.targetClassName.replaceAll("\\.", "/"); // replace . with /
      if (!className.equals(finalTargetClassName)) {
        return byteCode;
      }

      if (className.equals(finalTargetClassName) && loader.equals(targetClassLoader)) {
        try {
          ClassPool pool = ClassPool.getDefault();
          CtClass ctClass = pool.get(targetClassName);

          if (ctClass.isFrozen()) {
            ctClass.defrost();
          }

          ClassFile classFile = ctClass.getClassFile();
          if ( !Set.of(classFile.getInterfaces()).contains( "java.io.Serializable" ) ) {
            classFile.addInterface( "java.io.Serializable" );

          }
          byteCode = ctClass.toBytecode();

          System.out.println();
        } catch (Exception e) {
          e.printStackTrace();
        }
      }

      return byteCode;
    }
  }
}

return byteCode;

But i got this

Exception in thread "main" java.lang.reflect.InvocationTargetException
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:574)
    at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:491)
    at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndCallPremain(InstrumentationImpl.java:503)
Caused by: java.lang.RuntimeException: Transform failed for class: [.example.genericapplicationeventlistener.BillPughSingleton]
    at com.aeris.changelog.spring.instrumentation.Agent.transform2(Agent.java:204)
    at com.aeris.changelog.spring.instrumentation.Agent.transformClass2(Agent.java:191)
    at com.aeris.changelog.spring.instrumentation.Agent.premain(Agent.java:101)
    ... 6 more
Caused by: java.lang.UnsupportedOperationException: class redefinition failed: attempted to change superclass or interfaces
    at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
    at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:169)
    at com.aeris.changelog.spring.instrumentation.Agent.transform2(Agent.java:202)
    ... 8 more
*** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message Outstanding error when calling method in invokeJavaAgentMainMethod at s\src\java.instrument\share\native\libinstrument\JPLISAgent.c line: 619
*** java.lang.instrument ASSERTION FAILED ***: "success" with message invokeJavaAgentMainMethod failed at s\src\java.instrument\share\native\libinstrument\JPLISAgent.c line: 459
*** java.lang.instrument ASSERTION FAILED ***: "result" with message agent load/premain call failed at s\src\java.instrument\share\native\libinstrument\JPLISAgent.c line: 422

Fatal error: processing of -javaagent failed, processJavaStart failed

This is for learning purpose, so please ignore any stupidity!!

Was I wrong somewhere? Please help!!


UPDATE: Code changed according to rzwitserloot

public class Agent {

  private static final Logger LOGGER = LoggerFactory.getLogger(Agent.class);
  private static Integer count = 0;

  public static void premain(String agentArgs, Instrumentation inst) {
    LOGGER.info("[Agent] In premain method");
    String className = ".example.genericapplicationeventlistener.BillPughSingleton";
    transformClass2( className, inst );
  }

  private static void transformClass2(String className, Instrumentation inst) {
    Class<?> targetCls = null;
    ClassLoader targetClassLoader = null;
    // see if we can get the class using forName
    try {
      targetCls = Class.forName(className);
      targetClassLoader = targetCls.getClassLoader();
      transform2(targetCls, targetClassLoader, inst);
    } catch (Exception ex) {
      LOGGER.error("Class [{}] not found with Class.forName", className);
      ex.printStackTrace();
    }
  }

  private static void transform2(Class<?> targetCls, ClassLoader targetClassLoader, Instrumentation inst) {
    SerializableAdder dt = new SerializableAdder(targetCls.getName(), targetClassLoader);
    inst.addTransformer(dt, true);
    try {
      inst.retransformClasses(targetCls);
    } catch (Exception ex) {
      throw new RuntimeException("Transform failed for class: [" + targetCls.getName() + "]", ex);
    }
  }

  static class SerializableAdder implements ClassFileTransformer {
    private final String targetClassName;
    /** The class loader of the class we want to transform */
    private final ClassLoader targetClassLoader;

    SerializableAdder(String targetClassName, ClassLoader targetClassLoader) {
      this.targetClassName = targetClassName;
      this.targetClassLoader = targetClassLoader;
    }

    @Override
    public byte[] transform(
        ClassLoader loader,
        String className,
        Class<?> classBeingRedefined,
        ProtectionDomain protectionDomain,
        byte[] classfileBuffer) {
      byte[] byteCode = classfileBuffer;

      String finalTargetClassName = this.targetClassName.replaceAll("\\.", "/"); // replace . with /
      if (!className.equals(finalTargetClassName)) {
        return byteCode;
      }

      if (className.equals(finalTargetClassName) && loader.equals(targetClassLoader)) {
        try {
          ClassPool pool = ClassPool.getDefault();
          CtClass ctClass = pool.get(targetClassName);

          if (ctClass.isFrozen()) {
            ctClass.defrost();
          }

          ClassFile classFile = ctClass.getClassFile();
          if ( !Set.of(classFile.getInterfaces()).contains( "java.io.Serializable" ) ) {
            classFile.addInterface( "java.io.Serializable" );

          }
          byteCode = ctClass.toBytecode();

          System.out.println();
        } catch (Exception e) {
          e.printStackTrace();
        }
      }

      return byteCode;
    }
  }
}

In short, removed the for loop. Still the same error!!

Share Improve this question edited 2 days ago SoT asked 2 days ago SoTSoT 1,2631 gold badge20 silver badges53 bronze badges 7
  • Where is this modification code? Is it possible that your BillPugh class is being loaded before your modification? – m-doescode Commented 2 days ago
  • I will update the whole code, please check after 1-2 minutes – SoT Commented 2 days ago
  • I updated, kindly check – SoT Commented 2 days ago
  • I could be wrong but Class.forName might be loading your class early. If you need to validate it early, use pool.get() as you did in SerializableAdder. Seems like you already have a for loop to do that though right after, so its just about getting rid of the early check – m-doescode Commented 2 days ago
  • 1 This sounds like a terrible idea. Many classes don't implement Serializable for a reason. – Louis Wasserman Commented 2 days ago
 |  Show 2 more comments

1 Answer 1

Reset to default -1

You are using the 'retransform' / 'reload' functionality for no reason. What you want to do, is inspect all classes as they are loaded and transform them if it is the one you are interested in. If you call reload/retransform/'re' anything, you've messed up. There is no need and it causes the errors you are seeing.

To do this, you call instrumentation.addTransformer in your agentmain (just the one line). The transformer you pass is your own - it is invoked for everything. The first line it should have is 'is this BillPughClass'? If no, just return null which indicates you don't want to transform it.

Otherwise apply your bytecode modification magic and voila.

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1740986506a4286882.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信