[关闭]
@dawn 2016-03-31T08:00:18.000000Z 字数 7109 阅读 314

javassist

未分类


1、为什么要用这个

很多的时候,我们需要根据注解、配置文件或者其它的方式来运行不同的代码,大多数时候我们可能会使用条件判断(if..else,swith..case),或者java中的反射来实现。

这样方式有一个很大的问题是我们需要在设计之初就考虑到所有的情况,并对外提供统一的接口,第三方实现这些接口即可。

但是这并不是一个一劳永逸的方法,在设计一个框架时,我们考虑的指标往往是非常多的,

还有很多很多

2、有哪些项目在用

像spring的注解,使用了javassist与cglib,fastJson的序列化与反序列化,为了达到更快的速度,同样使用了javassist

3、关于javassist

Javassist(Java Programming Assistant)是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。
关于java字节码的处理,目前有很多工具,如bcel,asm。不过这些都需要直接跟虚拟机指令打交道。如果你不想了解虚拟机指令,可以采用javassist。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。

常用的一些类
Classes.png-48.9kB

例如CtClass、CtMember、CtMethod、CtField,可以简单的理解为这是对相应的类、成员封装,提供了一系列基于源码级别的API。
基于javassist开发,不需要了解字节码的一些知识,而且其封装的一些工具类可以简单实现一些高级功能。

参考:
javassist 学习笔记
性能对比

原理

Java编译器编译好Java文件之后,产生.class 文件在磁盘中。这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码。JVM虚拟机读取字节码文件,取出二进制数据,加载到内存中,解析.class 文件内的信息,生成对应的 Class对象。

4、例子

Javassist的使用很简单,首先获取到class定义的容器ClassPool,通过它获取已经编译好的类(Compile time class),并给这个类设置一个父类,而writeFile讲这个类的定义从新写到磁盘,以便后面使用。

简单的例子

  1. public class E1 {
  2. public static void main(String[] args) throws IOException, CannotCompileException, NotFoundException, IllegalAccessException, InstantiationException {
  3. ClassPool pool = ClassPool.getDefault();
  4. CtClass cc = pool.get("com.yodo1.example.T1");
  5. CtMethod cm = cc.getDeclaredMethod("print");
  6. cm.insertBefore("{System.out.println(\"call T1.print\");}");
  7. //cc.writeFile();
  8. T1 t1 = (T1) cc.toClass().newInstance();
  9. t1.print();
  10. T1 t2 = new T1();
  11. t2.print();
  12. }
  13. }
  14. class T1{
  15. void print(){
  16. System.out.println("t1 print");
  17. }
  18. }
  19. 这个例子修改了T1print方法,最终的输出如下
  20. call T1.print
  21. t1 print
  22. call T1.print
  23. t1 print

一个更加复杂的例子

  1. public class E2 {
  2. public static void main(String[] args) throws ClassNotFoundException, CannotCompileException, InstantiationException, NotFoundException, IllegalAccessException {
  3. Handler h = getHandler();
  4. h.handle("login");
  5. h.handle("exit");
  6. h.handle("reg");
  7. h.handle("doSomething");
  8. }
  9. public static Handler getHandler() throws NotFoundException, ClassNotFoundException, CannotCompileException, IllegalAccessException, InstantiationException {
  10. String s1 = "this is s1";
  11. ClassPool pool = ClassPool.getDefault();
  12. CtClass ccBean1 = pool.get("com.yodo1.example.Bean1");
  13. CtClass ccHandler = pool.get("com.yodo1.example.Handler");
  14. ccBean1.addInterface(ccHandler);
  15. StringBuilder invokerStr = new StringBuilder("public void handle(String optString) {\n");
  16. CtMethod[] ctms = ccBean1.getDeclaredMethods();
  17. for (CtMethod cm : ctms) {
  18. if (cm.getAnnotation(Opt.class) != null) {
  19. String methodName = cm.getName();
  20. invokerStr.append("if(optString.equals(\"");
  21. invokerStr.append(methodName);
  22. invokerStr.append("\")){");
  23. invokerStr.append(methodName);
  24. invokerStr.append("(); return;}\n");
  25. }
  26. }
  27. invokerStr.append("System.out.println(\"method: \"+ optString + \"() not exist\");\n}");
  28. System.out.println("code desc...");
  29. System.out.println(invokerStr);
  30. System.out.println();
  31. System.out.println();
  32. CtMethod invokeMethod = CtMethod.make(invokerStr.toString(), ccBean1);
  33. invokeMethod.setModifiers(Modifier.PUBLIC);
  34. ccBean1.addMethod(invokeMethod);
  35. return (Handler) ccBean1.toClass().newInstance();
  36. }
  37. }
  38. //---------
  39. @Target({ElementType.METHOD})
  40. @Retention(RetentionPolicy.RUNTIME)
  41. @Documented
  42. @interface Opt {
  43. }
  44. //---------
  45. interface Handler {
  46. void handle(String optString);
  47. }
  48. //---------
  49. class Bean1 {
  50. @Opt
  51. public void login() {
  52. System.out.println("this is login");
  53. }
  54. @Opt
  55. public void reg() {
  56. System.out.println("this is reg");
  57. }
  58. @Opt
  59. public void exit() {
  60. System.out.println("this is exit");
  61. }
  62. }

最终的输出如下

  1. code desc...
  2. public void handle(String optString) {
  3. if(optString.equals("login")){login(); return;}
  4. if(optString.equals("reg")){reg(); return;}
  5. if(optString.equals("exit")){exit(); return;}
  6. System.out.println("method: "+ optString + "() not exist");
  7. }
  8. this is login
  9. this is exit
  10. this is reg
  11. method: doSomething() not exist
关于泛型,在jvm中是没有泛型概念的,所有的泛型对象最后都会被转成普通对象,所有,javassit是不支持泛型的。

5、与其它的实现方式比较

java字节码操作库:BCEL、cglib、javassit、ObjectWeb ASM、SERP、Package gnu.bytecode 、Cojen、Trove Class File API、Jiapi 、Classfile Reader & Writer 、JBET 、Retroweaver、Jen、Soot、Jdec、JReloader

我们常见的有BCEL、cglib,有的库是直接去操作字节码,在实际的使用中难度稍大,而BCEL、cglib的封装程度与javassit差不多,网上找的两个例子

ObjectWeb ASM

  1. import java.io.FileOutputStream;
  2. import java.io.PrintStream;
  3. import org.objectweb.asm.ClassWriter;
  4. import org.objectweb.asm.MethodVisitor;
  5. import org.objectweb.asm.Opcodes;
  6. import org.objectweb.asm.Type;
  7. import org.objectweb.asm.commons.GeneratorAdapter;
  8. import org.objectweb.asm.commons.Method;
  9. public class Helloworld extends ClassLoader implements Opcodes {
  10. public static void main( final String args[]) throws Exception {
  11. // creates a ClassWriter for the Example public class,
  12. // which inherits from Object
  13. ClassWriter cw = new ClassWriter( 0 );
  14. cw.visit(V1_1, ACC_PUBLIC, "Example" , null , "java/lang/Object" , null );
  15. MethodVisitor mw = cw.visitMethod(ACC_PUBLIC, "<init>" , "()V" , null ,
  16. null );
  17. mw.visitVarInsn(ALOAD, 0 );
  18. mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object" , "<init>" , "()V" );
  19. mw.visitInsn(RETURN);
  20. mw.visitMaxs(1 , 1 );
  21. mw.visitEnd();
  22. mw = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main" ,
  23. "([Ljava/lang/String;)V" , null , null );
  24. mw.visitFieldInsn(GETSTATIC, "java/lang/System" , "out" ,
  25. "Ljava/io/PrintStream;" );
  26. mw.visitLdcInsn("Hello world!" );
  27. mw.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream" , "println" ,
  28. "(Ljava/lang/String;)V" );
  29. mw.visitInsn(RETURN);
  30. mw.visitMaxs(2 , 2 );
  31. mw.visitEnd();
  32. byte [] code = cw.toByteArray();
  33. FileOutputStream fos = new FileOutputStream( "Example.class" );
  34. fos.write(code);
  35. fos.close();
  36. Helloworld loader = new Helloworld();
  37. Class exampleClass = loader
  38. .defineClass("Example" , code, 0 , code.length);
  39. exampleClass.getMethods()[0 ].invoke( null , new Object[] { null });
  40. // ------------------------------------------------------------------------
  41. // Same example with a GeneratorAdapter (more convenient but slower)
  42. // ------------------------------------------------------------------------
  43. cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
  44. cw.visit(V1_1, ACC_PUBLIC, "Example" , null , "java/lang/Object" , null );
  45. Method m = Method.getMethod("void <init> ()" );
  46. GeneratorAdapter mg = new GeneratorAdapter(ACC_PUBLIC, m, null , null ,
  47. cw);
  48. mg.loadThis();
  49. mg.invokeConstructor(Type.getType(Object.class ), m);
  50. mg.returnValue();
  51. mg.endMethod();
  52. m = Method.getMethod("void main (String[])" );
  53. mg = new GeneratorAdapter(ACC_PUBLIC + ACC_STATIC, m, null , null , cw);
  54. mg.getStatic(Type.getType(System.class ), "out" , Type
  55. .getType(PrintStream.class ));
  56. mg.push("Hello world!" );
  57. mg.invokeVirtual(Type.getType(PrintStream.class ), Method
  58. .getMethod("void println (String)" ));
  59. mg.returnValue();
  60. mg.endMethod();
  61. cw.visitEnd();
  62. code = cw.toByteArray();
  63. loader = new Helloworld();
  64. exampleClass = loader.defineClass("Example" , code, 0 , code.length);
  65. exampleClass.getMethods()[0 ].invoke( null , new Object[] { null });
  66. }
  67. }

cglib

  1. public class MyClass {
  2. public void print() {
  3. System.out.println("I'm in MyClass.print!" );
  4. }
  5. }
  6. import java.lang.reflect.Method;
  7. import net.sf.cglib.proxy.Enhancer;
  8. import net.sf.cglib.proxy.MethodInterceptor;
  9. import net.sf.cglib.proxy.MethodProxy;
  10. public class Main {
  11. public static void main(String[] args) {
  12. Enhancer enhancer = new Enhancer();
  13. enhancer.setSuperclass(MyClass.class );
  14. enhancer.setCallback(new MethodInterceptorImpl());
  15. MyClass my = (MyClass) enhancer.create();
  16. my.print();
  17. }
  18. private static class MethodInterceptorImpl implements MethodInterceptor {
  19. public Object intercept(Object obj, Method method, Object[] args,
  20. MethodProxy proxy) throws Throwable {
  21. // log something
  22. System.out.println(method + " intercepted!" );
  23. proxy.invokeSuper(obj, args);
  24. return null ;
  25. }
  26. }
  27. }
  28. 最后输出为
  29. public void MyClass.print() intercepted!
  30. I'm in MyClass.print!
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注