【问题标题】:Changing a class with javassist (java reflexion)使用 javassist 更改类(java 反射)
【发布时间】:2018-09-20 17:33:31
【问题描述】:

我有以下代码。我想更改 hello 类的 say 方法。我使用 javassist。我有以下错误。

public class TestJavasisit {
/**
 * @param args the command line arguments
 * @throws java.lang.Exception
 */
public static void main(String[] args) throws Exception {
    ClassPool pool = ClassPool.getDefault();
    // version original
    Hello h1 = new Hello();
    h1.say();
    CtClass cc = pool.get("testjavasisit.Hello");
    cc.defrost();
    CtMethod m = cc.getDeclaredMethod("say");
    m.insertBefore("{ System.out.println(\"Hello.say():\"); }");
    cc.writeFile(".");
    cc.toClass();
    // version modifie
    Hello h2 = new Hello();
    h2.say();
}

}

你好类:

public class Hello {

    public void say() {
        System.out.println("Hello");
    }
}

错误信息:

run:
Hello
Exception in thread "main" javassist.CannotCompileException: by java.lang.LinkageError: loader (instance of  sun/misc/Launcher$AppClassLoader): attempted  duplicate class definition for name: "testjavasisit/Hello"

【问题讨论】:

  • 看看 byte buddy,它更强大且易于使用。

标签: java javassist


【解决方案1】:

代码:

package testjavasisit;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

public class TestJavasisit {
    /**
     * @param args
     *            the command line arguments
     * @throws java.lang.Exception
     */
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();

        // version original
         Hello h1 = new Hello(); // remove this line
         h1.say();               // remove this line

        CtClass cc = pool.get("testjavasisit.Hello");
        cc.defrost();
        CtMethod m = cc.getDeclaredMethod("say");
        m.insertBefore("{ System.out.println(\"Hello.say():\"); }");
        cc.writeFile(".");
        // This throws a java.lang.LinkageError ... attempted  duplicate class definition for name: ...
        cc.toClass();
        // version modifie
        Hello h2 = new Hello();
        h2.say();
    }

}

调试及解决方案:

如果你去掉以下2行,那么它会运行成功。

   // Hello h1 = new Hello();
   // h1.say();

输出:

Hello.say():

你好

根本原因分析:

当您第一次使用Hello h1 = new Hello(); 时,类加载器会加载 Hello 类。

之后,当您再次尝试使用 cc.toClass(); 加载 Hello 类时,出现此错误。

发生原因:

Rafael Winterhalter在此link告诉了原因和一些解决方法 作为

cc.toClass() 接受一个加载的类[Hello] 并重新定义它 相同的类而不更改其名称。重新定义后,您 尝试再次加载更改的类。然而这并不是 在 Java 中,任何ClassLoader 都可以only load a class of a given name one single time

要克服您的问题,您有不同的选择:

  1. 创建参数类的子类(或使用接口) 使用随机名称。然后子类的类型与您的兼容 参数类,但从未加载。
  2. 使用Instrumentation API 重新定义加载的类 运行时。
  3. 确保输入类和输出类都加载了 不同的类加载器。 (不推荐)

此处描述了相同类型的问题:

在tomcat中,他们已经解决了这个问题。

  1. https://github.com/brettwooldridge/HikariCP/issues/217
  2. http://forum.spring.io/forum/spring-projects/container/58952-use-javassist-modify-class-error-when-in-lib

【讨论】:

  • 谢谢。对于上面的例子,它可以工作。但是如果我们想添加新方法,它就行不通了。根据 API 文档。重新定义不得添加、删除或重命名字段或方法。那么我们如何添加方法。链接:docs.oracle.com/javase/6/docs/api/java/lang/instrument/…
【解决方案2】:

您应该在不同的类加载器中加载类的原始版本和修改版本。 试试这个:

public static void main(String[] args) {
     try {
         ClassPool pool = ClassPool.getDefault();
         Loader cl = new Loader(pool); //javassist.Loader
         // version original
         Class origin = cl.loadClass("testjavasisit.Hello");
         Object h1 =  origin.newInstance();
         Method sayMethod =  origin.getMethod("say", null);
         sayMethod.invoke(h1);

            CtClass cc = pool.get("testjavasisit.Hello");
            cc.defrost();
            CtMethod m = cc.getDeclaredMethod("say");
            m.insertBefore("{ System.out.println(\"Hello.say():\"); }");
            cc.writeFile(".");
            cc.toClass();
            // version modifie            
            Hello h2 = new Hello();
            h2.say();
    } catch (Throwable e) {
        e.printStackTrace();
    }
}   

如果您的目标是在同一个类加载器中修改已加载的类,您可以使用Java Instrument API javaasist 工作后到retransform 上课

【讨论】:

    【解决方案3】:
     //0.just use javaassist ,gradle dependency :
     //compile 'org.javassist:javassist:3.21.0-GA'
     //1.try to find the class  
     ctClazz.refClasses.forEach {
            if (it == "android.widget.Toast") {
                //yes, you find the target class
            }
        }
     ctClazz.replaceClassName("com.xx.the.class","com.xx.the.target.class")
     ctClazz.writeFile(classDirectory)
     ctClazz.xxx() //if need
    
     //2try to recompile the jar and check out .
    

    【讨论】:

      【解决方案4】:

      不知道,但不知何故“Hello h1 = new Hello();”线创建问题。请参阅下面的更新代码。

          ClassPool pool = ClassPool.getDefault();
          CtClass cc = pool.get("testjavasisit.Hello");
          cc.defrost();
          CtMethod m = cc.getDeclaredMethod("say");
          m.insertBefore("{ System.out.println(\"Hello11.say():\"); }");
          cc.writeFile("build");
          cc.toClass();
          Hello h2 = new Hello();
          h2.say();
      

      【讨论】:

      • 我们特意调用了Hello h1 = new Hello()。因为我们要改变一个已经实例化的类。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-21
      • 1970-01-01
      • 1970-01-01
      • 2019-02-18
      • 1970-01-01
      相关资源
      最近更新 更多