vlambda博客
学习文章列表

使用Javassist对字节码操作为JBoss实现动态"AOP"框架

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 可以看到类似以下代码:

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

此时:

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

使用Javassist对字节码操作为JBoss实现动态"AOP"框架

这是因为,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. }

使用Javassist对字节码操作为JBoss实现动态"AOP"框架

使用Javassist对字节码操作为JBoss实现动态"AOP"框架 使用Javassist对字节码操作为JBoss实现动态"AOP"框架 使用Javassist对字节码操作为JBoss实现动态"AOP"框架 使用Javassist对字节码操作为JBoss实现动态"AOP"框架 使用Javassist对字节码操作为JBoss实现动态"AOP"框架 使用Javassist对字节码操作为JBoss实现动态"AOP"框架 使用Javassist对字节码操作为JBoss实现动态"AOP"框架 使用Javassist对字节码操作为JBoss实现动态"AOP"框架

使用Javassist对字节码操作为JBoss实现动态"AOP"框架 使用Javassist对字节码操作为JBoss实现动态"AOP"框架 使用Javassist对字节码操作为JBoss实现动态"AOP"框架 使用Javassist对字节码操作为JBoss实现动态"AOP"框架 使用Javassist对字节码操作为JBoss实现动态"AOP"框架 使用Javassist对字节码操作为JBoss实现动态"AOP"框架