javassist

原文地址:http://www.tianshouzhi.com/api/tutorials/bytecode/354

Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。

Javassist的官方网站:http://jboss-javassist.github.io/javassist/

 通过javasssit,我们可以:

  • 动态创建新类或新接口的二进制字节码

  • 动态扩展现有类或接口的二进制字节码(AOP)

1、动态创建新类或新接口的二进制字节码

假设我们需要生成一个User类:

 
 
  1. package com.tianshouzhi;
  2.  
  3. public class User {
  4.     private String name;
  5.  
  6.     public User(String name) {
  7.         this.name = name;
  8.     }
  9.  
  10.     public User() {
  11.     }
  12.  
  13.     public String getName() {
  14.         return name;
  15.     }
  16.  
  17.     public void setName(String name) {
  18.         this.name = name;
  19.     }
  20.  
  21.     @Override
  22.     public String toString() {
  23.         return "name="+name;
  24.     }
  25. }

javassist创建代码如下:

 
 
  1. package com.tianshouzhi;
  2.  
  3. import javassist.*;
  4.  
  5. import java.lang.reflect.Constructor;
  6. import java.lang.reflect.Method;
  7.  
  8. public class UserGenerator {
  9.     public static void main(String[] args) throws Exception {
  10.         ClassPool classPool = ClassPool.getDefault();
  11.         //定义User类
  12.         CtClass ctClassUser = classPool.makeClass("com.tianshouzhi.User");
  13.  
  14.         //定义name字段
  15.         CtClass fieldType = classPool.get("java.lang.String");//字段类型
  16.         String name = "name";//字段名称
  17.         CtField ctFieldName=new CtField(fieldType, name,ctClassUser);
  18.         ctFieldName.setModifiers(Modifier.PRIVATE);//设置访问修饰符
  19.         ctClassUser.addField(ctFieldName, CtField.Initializer.constant("javasssit"));//添加name字段,赋值为javassist
  20.  
  21.         //定义构造方法
  22.         CtClass[] parameters = new CtClass[]{classPool.get("java.lang.String")};//构造方法参数
  23.         CtConstructor constructor=new CtConstructor(parameters,ctClassUser);
  24.         String body = "{this.name=$1;}";//方法体 $1表示的第一个参数
  25.         constructor.setBody(body);
  26.         ctClassUser.addConstructor(constructor);
  27.  
  28.         //setName getName方法
  29.         ctClassUser.addMethod(CtNewMethod.setter("setName",ctFieldName));
  30.         ctClassUser.addMethod(CtNewMethod.getter("getName",ctFieldName));
  31.  
  32.         //toString方法
  33.         CtClass returnType = classPool.get("java.lang.String");
  34.         String methodName = "toString";
  35.         CtMethod toStringMethod=new CtMethod(returnType, methodName, null,ctClassUser);
  36.         toStringMethod.setModifiers(Modifier.PUBLIC);
  37.         String methodBody = "{return \"name=\"+$0.name;}";//$0表示的是this
  38.         toStringMethod.setBody(methodBody);
  39.         ctClassUser.addMethod(toStringMethod);
  40.  
  41.         //代表class文件的CtClass创建完成,现在将其转换成class对象
  42.         Class clazz = ctClassUser.toClass();
  43.         Constructor cons = clazz.getConstructor(String.class);
  44.         Object user = cons.newInstance("wangxiaoxiao");
  45.         Method toString = clazz.getMethod("toString");
  46.         System.out.println(toString.invoke(user));
  47.  
  48.         ctClassUser.writeFile(".");//在当前目录下,生成com/tianshouzhi/User.class文件
  49.     }
  50. }

运行程序后输出:

name=wangxiaoxiao

通过反编译工具(例如:JD-gui )打开User.class 可以看到类似以下代码:

Image.png

2.  动态扩展现有类或接口的二进制字节码(AOP)

假设我们现在有如下一个类

 
 
  1. package com.tianshouzhi;
  2.  
  3. public class Looper {
  4.     public void loop(){
  5.         try {
  6.             System.out.println("Looper.loop() invoked");
  7.             Thread.sleep(1000L);
  8.         } catch (InterruptedException e) {
  9.             e.printStackTrace();
  10.         }
  11.     }
  12. }

现在我们想统计Looper的loop方法的耗时时间。最简单的思路是使用javassist修改loop方法的源码:

在最前加入:long start=System.currentTimeMillis();

在最后插入:System.out.println("耗时:"+(System.currentTimeMillis()-start)+"ms");

如下:

 
 
  1. public class Looper {
  2.  
  3.     public void loop(){
  4.  
  5.         //记录方法调用的开始时间
  6.  
  7.         long start=System.currentTimeMillis();
  8.  
  9.         try {
  10.  
  11.             System.out.println("Looper.loop() invoked");
  12.  
  13.             Thread.sleep(1000L);
  14.  
  15.         } catch (InterruptedException e) {
  16.  
  17.             e.printStackTrace();
  18.  
  19.         }
  20.  
  21.         //方法结束时打印耗时
  22.  
  23.         System.out.println("耗时:"+(System.currentTimeMillis()-start)+"ms");
  24.  
  25.     }
  26.  
  27. }

javassist的CtClass方法提供的insertBefore和insertAfter方法,允许我们在一个方法开始和结束添加自己的代码。

 
 
  1. public class JavassisTimingWrong {
  2.  
  3.     public static void main(String[] args) throws Exception {
  4.  
  5.         //需要修改的已有的类名和方法名
  6.  
  7.         String className="com.tianshouzhi.javassist.Looper";
  8.  
  9.         String methodName="loop";
  10.  
  11.         ClassPool classPool = ClassPool.getDefault();
  12.  
  13.         CtClass clazz = classPool.get(className);
  14.  
  15.         CtMethod method = clazz.getDeclaredMethod(methodName);
  16.  
  17.         method.insertBefore("long start=System.currentTimeMillis();");
  18.  
  19.         method.insertAfter("System.out.println(\"耗时:\"+(System.currentTimeMillis()-start)+\"ms\");");
  20.  
  21.         //调用修改的Looper类的loop方法
  22.  
  23.         Looper looper = (Looper) clazz.toClass().newInstance();
  24.  
  25.         looper.loop();
  26.  
  27.     }
  28.  
  29. }

此时:

因此运行时,会爆出类似以下的错

Image.png

这是因为,javassist插入的代码片段中,每次插入操作的代码,称之为一个插入代码块,后面的插入块不能使用前面的插入块定义的局部变量,而言且也不能使用方法中的原有局部变量。而上述代码中,我们分表调用了insertBefore和insertAfter插入了两个代码块,而后面的插入块不能使用前面的插入块定义的局部变量start,因此爆出了上面的错。

而如果代码片段都位于一个插入块中,则局部变量是可以引用的。因此考虑使用如下的方法实现:

具体的思路是:将原有的loop方法名改为loop$impl,然后再定义一个loop方法,新的loop方法内部会调用loop$impl,在调用之前和调用之后分别加入上述的代码片段。

实现如下:

 
 
  1. package com.tianshouzhi;
  2.  
  3. import javassist.ClassPool;
  4. import javassist.CtClass;
  5. import javassist.CtMethod;
  6. import javassist.CtNewMethod;
  7.  
  8. public class JavassisTiming {
  9.     public static void main(String[] args) throws Exception{
  10.         //需要修改的已有的类名和方法名
  11.         String className="com.tianshouzhi.Looper";
  12.         String methodName="loop";
  13.  
  14.         //修改为原有类的方法名为loop$impl
  15.         CtClass clazz = ClassPool.getDefault().get(className);
  16.         CtMethod method = clazz.getDeclaredMethod(methodName);
  17.         String newname = methodName + "$impl";
  18.         method.setName(newname);
  19.  
  20.         //使用原始方法名loop,定义一个新方法,在这个方法内部调用loop$impl
  21.         CtMethod newMethod = CtNewMethod.make("public void "+methodName+"(){" +
  22.                         "long start=System.currentTimeMillis();" +
  23.                         ""+newname+"();" +//调用loop$impl
  24.                         "System.out.println(\"耗时:\"+(System.currentTimeMillis()-start)+\"ms\");" +
  25.                         "}"
  26.                 , clazz);
  27.         clazz.addMethod(newMethod);
  28.  
  29.         //调用修改的Looper类的loop方法
  30.         Looper looper = (Looper) clazz.toClass().newInstance();
  31.         looper.loop();
  32.     }
  33. }

输出:

Looper.loop() invoked耗时:1000ms

此外还有一种更加简单的方式

 
 
  1. package com.tianshouzhi;
  2.  
  3. import javassist.util.proxy.MethodFilter;
  4. import javassist.util.proxy.MethodHandler;
  5. import javassist.util.proxy.ProxyFactory;
  6.  
  7. import java.lang.reflect.Method;
  8.  
  9. public class JavassistAop {
  10.     public static void main(String[] args) throws IllegalAccessException, InstantiationException {
  11.         ProxyFactory factory=new ProxyFactory();
  12.         //设置父类,ProxyFactory将会动态生成一个类,继承该父类
  13.         factory.setSuperclass(Looper.class);
  14.         factory.setFilter(new MethodFilter() {
  15.             @Override
  16.             public boolean isHandled(Method m) {
  17.                 if(m.getName().equals("loop")){
  18.                     return true;
  19.                 }
  20.                 return false;
  21.             }
  22.         });
  23.         //设置拦截处理
  24.         factory.setHandler(new MethodHandler() {
  25.             @Override
  26.             public Object invoke(Object self, Method thisMethod, Method proceed,
  27.                                  Object[] args) throws Throwable {
  28.                 long start=System.currentTimeMillis();
  29.                 Object result = proceed.invoke(self, args);
  30.                 System.out.println("耗时:"+(System.currentTimeMillis()-start)+"ms");
  31.                 return result;
  32.             }
  33.         });
  34.         Class<?> c=factory.createClass();
  35.         Looper object=(Looper) c.newInstance();
  36.         object.loop();
  37.     }
  38. }

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值