SpringAOP高频知识梳理
Spring AOP原理深层解析
前言
IOC和AOP是Spring的两个重要组成部分,IOC之前也经过分析(点击跳转)可以抽象认为这是一个容器,那AOP又是什么东西呢?
AOP是Aspect-Oriented Programming(面向方面编程或者面向切面)的简称。它可以看成是OOP(面向对象编程)的一种延续。简单地说就是将代码中重复的部分抽取出来,在需要执行的时候使用动态代理技术,在不修改源码的基础上对方法进行增强。
AOP的知识点特别多,这里简单整理一些比较重要的知识点概念。如果需要深度学习下去,可以参考源码分析:推荐阅读
什么是AOP?
(这里主要是摘录https://gyl-coder.top/spring/spring-ioc-aop/)
下面我们先看一个 OOP 的例子。
例如:现有三个类,Horse
、Pig
、Dog
,这三个类中都有 eat 和 run 两个方法。
通过 OOP 思想中的继承,我们可以提取出一个 Animal 的父类,然后将 eat 和 run 方法放入父类中,Horse
、Pig
、Dog
通过继承Animal
类即可自动获得 eat()
和 run()
方法。这样将会少些很多重复的代码。
OOP 编程思想可以解决大部分的代码重复问题。但是有一些问题是处理不了的。比如在父类 Animal 中的多个方法的相同位置出现了重复的代码,OOP 就解决不了。
/**
* 动物父类
*/
public class Animal {
/** 身高 */
private String height;
/** 体重 */
private double weight;
public void eat() {
// 性能监控代码
long start = System.currentTimeMillis();
// 业务逻辑代码
System.out.println("I can eat...");
// 性能监控代码
System.out.println("执行时长:" + (System.currentTimeMillis() - start)/1000f + "s");
}
public void run() {
// 性能监控代码
long start = System.currentTimeMillis();
// 业务逻辑代码
System.out.println("I can run...");
// 性能监控代码
System.out.println("执行时长:" + (System.currentTimeMillis() - start)/1000f + "s");
}
}
这部分重复的代码,一般统称为 横切逻辑代码。
横切逻辑代码存在的问题:
- 代码重复问题
- 横切逻辑代码和业务代码混杂在一起,代码臃肿,不变维护
AOP 就是用来解决这些问题的
AOP 另辟蹊径,提出横向抽取机制,将横切逻辑代码和业务逻辑代码分离
代码拆分比较容易,难的是如何在不改变原有业务逻辑的情况下,悄无声息的将横向逻辑代码应用到原有的业务逻辑中,达到和原来一样的效果。
AOP 解决了什么问题
通过上面的分析可以发现,AOP 主要用来解决:在不改变原有业务逻辑的情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复。
AOP 为什么叫面向切面编程
切 :指的是横切逻辑,原有业务逻辑代码不动,只能操作横切逻辑代码,所以面向横切逻辑
面 :横切逻辑代码往往要影响的是很多个方法,每个方法如同一个点,多个点构成一个面。这里有一个面的概念
重要术语
连接点(Join point):
- 能够被拦截的地方:Spring AOP是基于动态代理的,所以是方法拦截的。每个成员方法都可以称之为连接点~
切点(Poincut):
- 具体定位的连接点:上面也说了,每个方法都可以称之为连接点,我们具体定位到某一个方法就成为切点。
增强/通知(Advice):
-
表示添加到切点的一段逻辑代码,并定位连接点的方位信息。
-
- 简单来说就定义了是干什么的,具体是在哪干
- Spring AOP提供了5种Advice类型给我们:前置、后置、返回、异常、环绕给我们使用!
-
五种Advice类型如下:
- 前置通知(Before Advice): 在连接点之前执行的Advice,不过除非它抛出异常,否则没有能力中断执行流。使用
@Before
注解使用这个Advice。 - 返回之后通知(After Retuning Advice): 在连接点正常结束之后执行的Advice。例如,如果一个方法没有抛出异常正常返回。通过
@AfterReturning
关注使用它。 - 抛出(异常)后执行通知(After Throwing Advice): 如果一个方法通过抛出异常来退出的话,这个Advice就会被执行。通用
@AfterThrowing
注解来使用。 - 后置通知(After Advice): 无论连接点是通过什么方式退出的(正常返回或者抛出异常)都会执行在结束后执行这些Advice。通过
@After
注解使用。 - 围绕通知(Around Advice): 围绕连接点执行的Advice,就你一个方法调用。这是最强大的Advice。通过
@Around
注解使用。
- 前置通知(Before Advice): 在连接点之前执行的Advice,不过除非它抛出异常,否则没有能力中断执行流。使用
织入(Weaving):
- 将
增强/通知
添加到目标类的具体连接点上的过程。
引入/引介(Introduction):
引入/引介
允许我们向现有的类添加新方法或属性。是一种特殊的增强!
切面(Aspect):
- 切面由切点和
增强/通知
组成,它既包括了横切逻辑的定义、也包括了连接点的定义。
底层实现
我们上面介绍了AOP的一系列相关术语,而这些术语其实也就是为了加深我们对AOP的面向切面的认识。AOP的底层其实是通过动态代理来实现的,其实如果学过设计模式其实看着AOP的思想也能大概猜出来了。将相同逻辑的重复代码横向抽取出来,使用动态代理技术将这些重复代码织入到目标对象方法中,实现和原来一样的功能。
在SpringAOP中,我们底层的通过两种动态代理来实现的,分别是JDK动态代理和CGLib动态代理。
JDK动态代理
关于JDK的动态代理代码演示可以看一下我的这一篇,顺便也可以了解一下动态代理模式(点击跳转)
JDK动态代理是需要实现某个接口了,而我们类未必全部会有接口,于是CGLib代理就有了~~
- CGLib代理其生成的动态代理对象是目标类的子类
- Spring AOP默认是使用JDK动态代理,如果代理的类没有接口则会使用CGLib代理。
那么JDK代理和CGLib代理我们该用哪个呢??在《精通Spring4.x 企业应用开发实战》给出了建议:
- 如果是单例的我们最好使用CGLib代理,如果是多例的我们最好使用JDK代理
原因:
- JDK在创建代理对象时的性能要高于CGLib代理,而生成代理对象的运行性能却比CGLib的低。
- 如果是单例的代理,推荐使用CGLib
CGLib动态代理
字节码生成技术实现AOP,其实就是继承被代理对象,然后Override需要被代理的方法,在覆盖该方法时,自然是可以插入我们自己的代码的。CGLib动态代理需要依赖asm包,把被代理对象类的class文件加载进来,修改其字节码生成子类。
因为需要Override被代理对象的方法,所以自然CGLIB技术实现AOP时,就 必须要求需要被代理的方法不能是final方法,因为final方法不能被子类覆盖 。
现在演示一下如何使用CGLIB动态代理实现开头的情景,CGLIB动态代理不要求被代理类实现接口,先写一个被代理类。
public class MonkeyOperation {
public void put() {
System.out.println("放入猴子...");
}
public void get() {
System.out.println("拿出猴子...");
}
}
在写一个类实现MethodInterceptor接口,并在接口方法intercept()里对被代理对象的方法做增强,并编写生成代理对象的方法
public class FridgeCGLibProxy implements MethodInterceptor {
public String name="hahaha";
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
openDoor();//调用被代理方法做一些操作
Object result = methodProxy.invokeSuper(proxy,args);//执行被代理对象的方法,如果方法有返回值则赋值给result
closeDoor();//调用被代理方法后做一些操作
return result;
}
private void openDoor(){
System.out.println("打开冰箱...");
}
private void closeDoor(){
System.out.println("关闭冰箱...");
}
public Object getProxy(Class cls){//参数为被代理的类对象
Enhancer enhancer = new Enhancer();//创建增强器,用来创建动态代理类
enhancer.setSuperclass(cls);//设置父类,即被代理的类对象
enhancer.setCallback(this);//设置回调,指定为当前对象
return enhancer.create();//返回生成的代理类
}
}
测试及结果:
public static void main(String args[]) {
MonkeyOperation monkeyOperation =(MonkeyOperation)new FridgeCGLibProxy().getProxy(MonkeyOperation.class);
monkeyOperation.put();
monkeyOperation.get();
}
应用场景
通过使用SpringAOP,我们可以将我们与业务无关的代码,分割出来。具体的应用场景主要是有这些:
- 权限控制
- 缓存控制
- 事务控制
- 审计日志
- 性能监控
- 分布式追踪
- 异常处理
如何在这些场景去使用就需要根据情况具体实践了。可以多看看实战书籍!
参考资料
https://gyl-coder.top/spring/spring-ioc-aop/
《Spring技术内幕:深入解析Spring架构与原理》