对于AOP,这个概念,不用解释,主要用途很多,我这里主要是为了后续研究如何实现APM做准备。前面研究了动态代理实现AOP,考虑到性能的问题,改用javassist直接修改直接码实现!
javassist的使用,可以参考官网, 在用eclipse开发程序的时候,要将这个javassist的jar包放入classpath下。若基于maven开发的话,也有对应的maven插件,很简单的事情!
下面主要列举一下常用的类以及方法:
1 获取JVM中已经加载的所有的类的集合,即pool 2 ClassPool pool = ClassPool.getDefault(); 3 4 获取指定类名对应的类 5 CtClass cc = pool.get("带有包名的全路径类名"); 6 7 为这个类设置超级类 8 cc.setSuperclass(pool.get("指定带有全路径的类名"));
另外还有CtMethod,CtField等等,这些可以到官网找相关的API文档了解其使用方法。下面通过一个简单的例子看看如何使用javassist来动态编写程序,实现AOP。
先定义一个业务类Feed.java,其中的函数,就好比是我们业务系统中的某个操作。
1 package javassit_aop; 2 3 /** 4 * 5 * @author shihuc 6 * @date Mar 24, 2016 7 * 8 */ 9 public class Feed { 10 public void forTest(){ 11 System.out.println("----------execute function "forTest()"-----------"); 12 } 13 }
接下来,定义一个测试类,在这个测试类里面,通过javassist的函数调用,实现切面织入,也就是AOP的目的所在。这个类TEST.java:
1 package javassit_aop; 2 3 import javassist.CannotCompileException; 4 import javassist.ClassPool; 5 import javassist.CtClass; 6 import javassist.CtMethod; 7 import javassist.CtNewMethod; 8 import javassist.NotFoundException; 9 10 /** 11 * 12 * @author shihuc 13 * @date Mar 24, 2016 14 * 15 */ 16 public class TEST { 17 public static void main(String[] args) throws NotFoundException, CannotCompileException, InstantiationException, IllegalAccessException{ 18 CtClass ctClass=ClassPool.getDefault().get("javassit_aop.Feed"); 19 String oldName="forTest"; 20 CtMethod ctMethod=ctClass.getDeclaredMethod(oldName); 21 String newName=oldName+"$NewImpl"; 22 ctMethod.setName(newName); 23 CtMethod newMethod=CtNewMethod.copy(ctMethod, "forTest", ctClass, null); 24 StringBuffer sb=new StringBuffer(); 25 26 /* 27 * Here, below StringBuffer is to create the new method body, what you read is the source code, 28 * but, it will be translated to byte code which can be interpreted by JVM. 29 * 30 * To some extent, ".append(newName+"($$); ")" can be said as function call to the business function Feed.forTest() 31 */ 32 sb.append("{System.out.println("Here you can do BEFORE operation"); ") 33 .append(newName+"($$); ") 34 .append("System.out.println("Here you can do AFTER operation"); }"); 35 newMethod.setBody(sb.toString()); 36 /* 37 * Add new method 38 */ 39 ctClass.addMethod(newMethod); 40 /* 41 * Class changed, ATTENTION, do not use "A a = new A();" to make a new instance, 42 * because in the same classloader it do not allow to load one class more than once. 43 */ 44 Feed a=(Feed)ctClass.toClass().newInstance(); 45 a.forTest(); 46 } 47 }
上面的代码执行完后,可以看到下面的结果:
1 Here you can do BEFORE operation 2 ----------execute function "forTest()"----------- 3 Here you can do AFTER operation
上述代码中$$表示所有的参数,关于javassist的函数调用中参数传递,可以参考官网的说明,这里截取一部分,可以先有个概念,这个和shell脚本中的function调用是参数传递非常像!
The String object passed to the methods insertBefore(), insertAfter(), addCatch(), and insertAt() are compiled by the compiler included in Javassist. Since the compiler supports language extensions, several identifiers starting with $ have special meaning: $0, $1, $2, ... this and actual parameters $args An array of parameters. The type of $args is Object[]. $$ All actual parameters. For example, m($$) is equivalent to m($1,$2,...) $cflow(...) cflow variable $r The result type. It is used in a cast expression. $w The wrapper type. It is used in a cast expression. $_ The resulting value $sig An array of java.lang.Class objects representing the formal parameter types. $type A java.lang.Class object representing the formal result type. $class A java.lang.Class object representing the class currently edited.
虽然javassist的使用中也涉及到了反射的使用,大家都应该意识到,在产品级的软件中,若大量使用反射,性能是不会好的,当然javassist的官方组织也知道这点,所以,在使用的时候,可以通过定义interface,然后在javassist中动态编程来实现这个接口中的方法,调用方,通过这个接口来调用方法,这样就可以绕开因为反射造成的性能损失。
另外,javassist的深入灵活的使用,后续再继续研究,并及时更新博客!