Spring AOP笔记
一、认识AOP
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
要理解切面编程,就需要先理解什么是切面。用刀把一个西瓜分成两瓣,切开的切口就是切面;炒菜,锅与炉子共同来完成炒菜,锅与炉子就是切面。web层级设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面。编程中,对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面。
我们一般做活动的时候,一般对每一个接口都会做活动的有效性校验(是否开始、是否结束等等)、以及这个接口是不是需要用户登录。
按照正常的逻辑,我们可以这么做:
这有个问题就是,有多少接口,就要多少次代码copy。对于一个“懒人”,这是不可容忍的。好,提出一个公共方法,每个接口都来调用这个接口。这里有点切面的味道了。
虽然不用每次都copy代码了,但是,每个接口总得要调用这个方法吧。于是就有了切面的概念,我将方法注入到接口调用的某个地方(切点)。
这样接口只需要关心具体的业务,而不需要关注其他非该接口关注的逻辑或处理。红框处,就是面向切面编程。
二、AOP中的相关概念
AOP中专业术语介绍:
Aspect
(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。Joint point
(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。Pointcut
(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。Advice
(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。Target
(目标对象):织入 Advice 的目标对象.。Weaving
(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程
在网上找到的一张图,可以帮助理解以上的概念
上AOP的代码,加深一下理解:
@Component //注入IOC容器
@Aspect //声明这个类是切面,也就是被抽取出的公共部分
public class LogAspect {
@Pointcut("execution(* com.zhou.project..*.*(..))") //声明切点,表示该方法作为切点,注解内的路径代表切点注入的位置
private void pointcut() {
}
@Before(value = "pointcut()") //切点的前置通知
public void before() {
System.out.println("前置通知gogogo!");
}
@After(value = "pointcut()") //切点后置通知
public void after() {
System.out.println("后置通知gogogo!");
}
}
创建一个这样的类,即代表“打印日志”功能切面已经创建完毕了,接下来只需要告诉spring容器我们需要开启切面代理就可以了,所以我们需要在创建一个AspectConfig
配置类就行
@EnableAspectJAutoProxy
@Configuration
@Component
public class AspectConfig{
//什么内容都不用写
}
这样aop切面就可以使用啦
三、Spring AOP& AspectJ AOP 的区别
AOP实现的关键在于 代理模式,代理主要分为静态代理和动态代理。静态代理的代表为
AspectJ;动态代理则以Spring AOP为代表。
- AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
- Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
- Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理
JDK动态代理和CGLIB动态代理的区别
- JDK代理使用的是反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
CGLIB代理使用字节码处理框架asm,对代理对象类的class文件加载进来,通过修改字节码生成子类。 - JDK创建代理对象效率较高,执行效率较低;
CGLIB创建代理对象效率较低,执行效率高。 - JDK动态代理机制是委托机制,只能对实现接口的类生成代理,通过反射动态实现接口类;
CGLIB则使用的继承机制,针对类实现代理,被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的,因为是继承机制,不能代理final修饰的类。 - JDK代理是不需要依赖第三方的库,只要JDK环境就可以进行代理,需要满足以下要求:
1.实现InvocationHandler接口,重写invoke()
2.使用Proxy.newProxyInstance()产生代理对象
3.被代理的对象必须要实现接口 - CGLib 必须依赖于CGLib的类库,需要满足以下要求:
1.实现MethodInterceptor接口,重写intercept()
2.使用Enhancer对象.create()产生代理对象
CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
那么如何在这两者之间做出选择呢,总结如下:
- 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP,可以强制使用CGLIB实现AOP
- 如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
四、补充
AspectJ支持三种通配符
*:匹配任意字符,只匹配一个元素(包,类,或方法,方法参数)
..:匹配任意字符,可以匹配多个元素,在表示类时,必须和*联合使用
+:表示按照类型匹配指定类的所有类,必须跟在类名后面,如com.cad.Car+,表示继承该类的所有子类包括本身
表达式示例如下:
- execution(* com.example.demo.UserController. *(..)):匹配UserController类里的所有方法。*
- execution( * com.example.demo.UserController+.*(..)):匹配UserController类的子类包括该类的所有方法
- *execution(* com.example.demo.*.*(..)):匹配com.example.demo包下的所有类的所有方法
- execution(* com.example.demo..*.*(..)):匹配com.example.demo包下、子孙包下所有类的所有方法
- execution(* addUser(String,int)):匹配addUser方法,且第一个参数类型是String,第二个参数类型是int
Advice 的类型
before advice
, 在 join point 前被执行的 advice. 虽然 before advice 是在 join point 前被执行, 但是它并不能够阻止 join point 的执行, 除非发生了异常(即我们在 before advice 代码中, 不能人为地决定是否继续执行 join point 中的代码)after return advice
, 在一个 join point 正常返回后执行的 adviceafter throwing advice
, 当一个 join point 抛出异常后执行的 adviceafter(final) advice
, 无论一个 join point 是正常退出还是发生了异常, 都会被执行的 advice.around advice
, 在 join point 前和 joint point 退出后都执行的 advice. 这个是最常用的 advice.
inal) advice`, 无论一个 join point 是正常退出还是发生了异常, 都会被执行的 advice.around advice
, 在 join point 前和 joint point 退出后都执行的 advice. 这个是最常用的 advice.introduction
,introduction可以为原有的对象增加新的属性和方法。