闲人野居
好好学习,天天向上
posts - 57,  comments - 137,  trackbacks - 0
    关于java字节码的处理,目前有很多工具,如bcel,asm。不过这些都需要直接跟虚拟机指令打交道。如果你不想了解虚拟机指令,可以采用javassist。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。
    下面通过一个简单的例子,通过javassist来实现如何动态注入代码。
    假设,存在类A,如下:
public class A {
    public void method() {
        for (int i = 0; i < 1000000; i++) {
        }
        System.out.println("method1");
    }
}
测试类B如下:
public class B {
    public static void main(String[] args) {
        A a = new A();
        a.method();    
    }
}
现在想统计一下method的执行时间,
默认的实现是修改method:
 public void method() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
        }
        System.out.println("method1");
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }
如果A的方法很多,统计方法的执行时间的代码就会相应的增加。为了减少工作量,通过动态注入代码的形式来实现。
修改B的main方法:
    public static void main(String[] args) throws Exception {
      //用于取得字节码类,必须在当前的classpath中,使用全称
        CtClass ctClass = ClassPool.getDefault().get("org.esoft.A");
         //需要修改的方法名称
        String mname = "method";        
        CtMethod mold = ctClass.getDeclaredMethod(mname);
         //修改原有的方法名称
        String nname = mname + "$impl";
        mold.setName(nname);
         //创建新的方法,复制原来的方法
        CtMethod mnew = CtNewMethod.copy(mold, mname, ctClass, null);
         //主要的注入代码
        StringBuffer body = new StringBuffer();
        body.append("{\nlong start = System.currentTimeMillis();\n");
        //调用原有代码,类似于method();($$)表示所有的参数
        body.append(nname + "($$);\n");
        body.append("System.out.println(\"Call to method "
                    + mname
                    + " took \" +\n (System.currentTimeMillis()-start) + "
                    + "\" ms.\");\n");
       
        body.append("}");
         //替换新方法
        mnew.setBody(body.toString());
         //增加新方法
        ctClass.addMethod(mnew);
        //类已经更改,注意不能使用A a=new A();,因为在同一个classloader中,不允许装载同一个类两次
        A a=(A)ctClass.toClass().newInstance();
        a.method();
    }
这只是简单的一个应用。javassist还提供了很多的功能,用于更改类结构。有兴趣的可以参考相关文档

posted on 2007-02-10 21:02 布衣郎 阅读(13739) 评论(9)  编辑  收藏 所属分类: jdk相关

FeedBack:
# re: 使用javassist动态注入代码
2007-04-19 10:06 | noname
good!  回复  更多评论
  
# re: 使用javassist动态注入代码[未登录]
2008-04-08 19:32 | Tester
你编译并运行过你的代码?   回复  更多评论
  
# re: 使用javassist动态注入代码[未登录]
2008-04-08 19:34 | Tester
如果运行的话,

在: A a=(A)ctClass.toClass().newInstance();上将抛出 java.lang.ClassCastException

这是为什么呢?希望你可以解答.  回复  更多评论
  
# re: 使用javassist动态注入代码[未登录]
2008-05-12 17:25 | Tester
http://www.csg.is.titech.ac.jp/~chiba/javassist/tutorial/tutorial.html
上关于为什么会抛出ClassCastException讲的很详细  回复  更多评论
  
# re: 使用javassist动态注入代码[未登录]
2009-05-10 15:16 | 菜鸟
Test.main() inserts a call to println() in the method body of say() in Hello. Then it constructs an instance of the modified Hello class and calls say() on that instance.

Note that the program above depends on the fact that the Hello class is never loaded before toClass() is invoked. If not, the JVM would load the original Hello class before toClass() requests to load the modified Hello class. Hence loading the modified Hello class would be failed (LinkageError is thrown). For example, if main() in Test is something like this:

public static void main(String[] args) throws Exception {
Hello orig = new Hello();
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get("Hello");
:
}

then the original Hello class is loaded at the first line of main and the call to toClass() throws an exception since the class loader cannot load two different versions of the Hello class at the same time.

If the program is running on some application server such as JBoss and Tomcat, the context class loader used by toClass() might be inappropriate. In this case, you would see an unexpected ClassCastException. To avoid this exception, you must explicitly give an appropriate class loader to toClass(). For example, if bean is your session bean object, then the following code:

CtClass cc = ...;
Class c = cc.toClass(bean.getClass().getClassLoader());

would work. You should give toClass() the class loader that has loaded your program (in the above example, the class of the bean object).

toClass() is provided for convenience. If you need more complex functionality, you should write your own class loader.

  回复  更多评论
  
# re: 使用javassist动态注入代码
2009-06-12 12:04 | genle657101519
有收获,谢了。  回复  更多评论
  
# re: 使用javassist动态注入代码
2009-08-05 19:12 | helo
CtMethod mnew = CtNewMethod.copy(mold, mname, ctClass, null);
应该为:CtMethod mnew = CtNewMethod.copy(mold, nname, ctClass, null);

后面的代码也要修改。误人子弟,无语。。。。。  回复  更多评论
  
# re: 使用javassist动态注入代码[未登录]
2009-08-07 07:42 | joe
@helo
确实写错了  回复  更多评论
  
# re: 使用javassist动态注入代码
2010-08-06 17:31 | abettor

只有注册用户登录后才能发表评论。


网站导航:
 

<2007年2月>
28293031123
45678910
11121314151617
18192021222324
25262728123
45678910

常用链接

留言簿(12)

随笔分类(59)

随笔档案(57)

blog

java

uml

搜索

  •  

积分与排名

  • 积分 - 355837
  • 排名 - 154

最新评论

阅读排行榜

评论排行榜