使用javassist更改类(java reflexion)

时间:2016-10-18 10:38:10

标签: java javassist

我有以下代码。我想改变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"

4 个答案:

答案 0 :(得分:8)

代码:

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.   
  3. 使用Instrumentation API重新定义已加载的课程   运行时。
  4.   
  5. 确保加载了输入类和输出类   不同的ClassLoaders。 (不推荐)
  6.   

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

在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

答案 1 :(得分:0)

不知道但不知何故“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();

答案 2 :(得分:0)

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

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)

 //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 .