AOP就是面向切面编程,我们可以从几个层面来实现AOP。
在编译器修改源代码,在运行期字节码加载前修改字节码或字节码加载后动态创建代理类的字节码,以下是各种实现机制的比较。
类别
|
机制
|
原理
|
优点
|
缺点
|
---|---|---|---|---|
静态AOP | 静态织入 | 在编译期,切面直接以字节码的形式编译到目标字节码文件中。 | 灵活性不够。 | |
动态AOP | 动态代理 | 在运行期,目标类加载后,为接口动态生成代理类,将切面植入到代理类中。 | 切入的关注点需要实现接口。对系统有一点性能影响。 | |
动态字节码生成(cglib) | 在运行期,目标类加载后,动态构建字节码文件生成目标类的子类,将切面逻辑加入到子类中。 | 没有接口也可以织入。 | final Class不支持 | |
自定义类加载器 | 在运行期,目标加载前,将切面逻辑加到目标字节码里。 | 可以对绝大部分类进行织入 | 代码中如果使用了其他类加载器,则这些类将不会被织入。 | |
字节码转换 | 在运行期,所有类加载器加载字节码前,前进行拦截。 | 可以对所有类进行织入。 |
关于Cglib与JDK动态代理
cglib(Code Generation Library)是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。 cglib封装了asm(http://www.ibm.com/developerworks/cn/java/j-lo-asm30/,这篇文章较为通俗详细的介绍了asm),可以在运行期动态生成新的class。 cglib用于AOP,jdk中的proxy必须基于接口,cglib却没有这个限制。
cglib生成代理的大概的过程
- 就是Cglib根据父类,Callback, Filter 及一些相关信息生成key.
- 然后根据key 生成对应的子类的二进制表现形式
- 使用ClassLoader装载对应的二进制,生成Class对象,并缓存
- 最后实例化Class对象,并缓存
JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,其实现就需要才cglib。cglib采用了非常底层的字节码技术。原理是:通过字节码技术为一个类创建子类。并且在子类中采用方法拦截的技术拦截所有父类的方法的调用,顺势织入横切逻辑。Proxy 毕竟是通过反射实现的,必须在效率上付出代价:调用反射比一般的函数开销至少要大 10 倍。而且,从程序实现上可以看出,对 proxy class 的所有方法调用都要通过使用反射的 invoke 方法。因此,对于性能关键的应用,使用 proxy class 是需要精心考虑的,以避免反射成为整个应用的瓶颈。
官方文档关于反射效率的说明:
Because reflection involves types that are dynamically resolved, certain Java virtual machine optimizations can not be performed. Consequently, reflective operations have slower performance than their non-reflective counterparts, and should be avoided in sections of code which are called frequently in performance-sensitive applications. |
虽然在高版本的JDK中,动态代理对象的性能得到了很大的提高,但是,CGLib所创建的动态代理对象的性能依旧比JDK的所创建的代理对象的性能高不少(大概10倍)。但CGLib在创建代理对象(因为需要动态构建字节码)时所花费的时间却比JDK动态代理多(大概8倍),所以对于singleton的代理对象或者具有实例池的代理,因为无须频繁创建代理对象,所以比较适合用CGLib动态代理技术,反之适合用JDK动态代理技术。值得一提的是,由于CGLib采用动态创建子类的方式生成代理对象,所以不能对目标类中的final方法进行代理。