AspectJ-AOP和Spring-AOP
AspectJ-AOP
简单介绍
AspectJ是一个java实现的AOP框架。
- 它能够对java代码进行AOP编译(一般在编译期进行)
- 让java代码具有AspectJ的AOP功能(当然需要特殊的编译器)
- 可以这样说AspectJ是目前实现AOP框架中最成熟,功能最丰富的语言,更幸运的是
举例:
这里先进行一个简单案例的演示,然后引出AOP中一些晦涩难懂的抽象概念,放心,通过本篇博客,我们将会非常轻松地理解并掌握它们。编写一个HelloWord的类,然后利用AspectJ技术切入该类的执行过程。
/**
* Created by zejian on 2017/2/15.
* Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
*/
public class HelloWord {
public void sayHello(){
System.out.println("hello world !");
}
public static void main(String args[]){
HelloWord helloWord =new HelloWord();
helloWord.sayHello();
}
}
编写AspectJ类,注意关键字为aspect(MyAspectJDemo.aj,其中aj为AspectJ的后缀),含义与class相同,即定义一个AspectJ的类
/**
* Created by zejian on 2017/2/15.
* Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
* 切面类
*/
public aspect MyAspectJDemo {
/**
* 定义切点,日志记录切点
*/
pointcut recordLog():call(* HelloWord.sayHello(..));
/**
* 定义切点,权限验证(实际开发中日志和权限一般会放在不同的切面中,这里仅为方便演示)
*/
pointcut authCheck():call(* HelloWord.sayHello(..));
/**
* 定义前置通知!
*/
before():authCheck(){
System.out.println("sayHello方法执行前验证权限");
}
/**
* 定义后置通知
*/
after():recordLog(){
System.out.println("sayHello方法执行后记录日志");
}
}
ok~,运行helloworld的main函数:
AspectJ的织入方式及其原理概要
aspect(切面)应用到目标函数(类)的过程,分为两种:
-
动态织入(动态代理):Spring-AOP的动态代理。动态生成代理类。
动态织入的方式是在运行时动态将要增强的代码织入到目标类中,这样往往是通过动态代理技术完成的,如Java JDK的动态代理(Proxy,底层通过反射实现)或者CGLIB的动态代理(底层通过继承实现),Spring AOP采用的就是基于运行时增强的代理技术
-
静态织入(静态代理):ApectJ主要采用的是静态织入。把
aspect类编译成class字节码
后,在java目标类编译时织入
。编译器直接更改了目标类的字节码文件
编译后织入aspect类的HelloWord字节码反编译类:编译器直接更改了目标类的字节码文件。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.zejian.demo;
import com.zejian.demo.MyAspectJDemo;
//编译后织入aspect类的HelloWord字节码反编译类
public class HelloWord {
public HelloWord() {
}
public void sayHello() {
System.out.println("hello world !");
}
public static void main(String[] args) {
HelloWord helloWord = new HelloWord();
HelloWord var10000 = helloWord;
try {
//MyAspectJDemo 切面类的前置通知织入
MyAspectJDemo.aspectOf().ajc$before$com_zejian_demo_MyAspectJDemo$1$22c5541();
//目标类函数的调用
var10000.sayHello();
} catch (Throwable var3) {
MyAspectJDemo.aspectOf().ajc$after$com_zejian_demo_MyAspectJDemo$2$4d789574();
throw var3;
}
//MyAspectJDemo 切面类的后置通知织入
MyAspectJDemo.aspectOf().ajc$after$com_zejian_demo_MyAspectJDemo$2$4d789574();
}
}
两者区别
原理区别
-
AspectJ 属于静态织入,原理是静态代理. 作用于编译期,字节码加载前,性能优于动态织入。直接更改字节码。
-
Cglib、JDK 动态代理属于动态织入,原理是动态代理。作用于运行期,字节码加载后,使用了反射,所以性能较差
首先说说静态织入:作用于编译期,字节码加载前,性能优于动态织入。AspectJ 用一种特定语言编写切面,通过自己的语法编译工具 ajc 编译器来编译,生成一个新的代理类,该代理类增强了业务类。
第二说下动态织入:作用于运行期,字节码加载后,使用了反射,所以性能较差。
Spring 底层的动态代理分为 Cglib 和 JDK 代理,为什么要分两种?
-
JDK 动态代理用于对接口的代理,动态产生一个实现指定接口的类,注意动态代理有个约束:目标对象一定是要有接口的,没有接口就不能实现动态代理,只能为接口创建动态代理实例,而不能对类创建动态代理。
-
CGLIB 用于对类的代理,把被代理对象类的 class 文件加载进来,修改其字节码生成一个继承了被代理类的子类。注意,修改了字节码,所以需要依赖 ASM 包,使用 cglib 就是为了弥补动态代理的不足。
功能区别
AspectJ 更强大。例如Spring AOP不能修改final修饰的类。
相似点
那有的小伙伴可能有疑问,Spring AOP 使用了 AspectJ,怎么是动态代理呢?
Spring AOP只是借鉴了AspectJ的语法。
详细的区别如下:
cglib-AOP 和 Spring-AOP
jdk动态代理
流程
- 拿到被代理对象的引用。**并且获取到它的所有的接口,反射获取。**加入JDK Proxy 类 newInstance方法中
- JDK Proxy 类 newInstance方法生成被代理类。
- 动态生成 Java 代码,类中包含Invocationhandle的引用,重写invoke方法,在invoke方法中调用method.invoke()前后可以添加扩展的业务模块
cjlib动态代理
流程
- 拿到被代理对象的引用,生成一个继承自这个目标类的代理类,一个目标类的FastClass和一个代理类的FastClass,总共生成三个class文件
- 代理类中重写了所有目标类的方法,并且添加每个方法的代理的方法
- 实际调用时,生成代理类与目标类的FastClass,FastClass中的机制是通过int类型数据定位需要调用的方法,这样省去了反射的调用。
CGLib 和 JDK 动态代理对比
- JDK 动态代理是实现了被代理对象的接口,CGLib 是继承了被代理对象。
- CGLib 代理的目标对象不需要实现任何接口,它是通过动态继承目标对象 实现的动态代理
- JDK 和 CGLib 都是在运行期生成字节码,JDK 是直接写 Class 字节码,CGLib 使用 ASM 框架写 Class 字节码,Cglib 代理实现更复杂,生成代理类比 JDK 效率低。
- JDK 调用代理方法,是通过反射机制调用,CGLib 是通过 FastClass 机制直接调用方法, CGLib 执行效率更高
运行时cglib效率为什么高(1.8之后目前差不多)
Cglib 采用了 FastClass 机 制:
- 生成了两个FastClass,目标类的FastClass和一个代理类的FastClass
- FastClass其实就一个数组,装入Method方法的引用。效率更快。(以空间换时间)
注:因为JDK动态代理中代理类中的方法是通过反射调用的,而cglib因为引入了FastClass,可以直接调用代理类对象的方法。