文章目录
1.什么是 Spring AOP?
2、为什么要使用 AOP?
这个其实在前面已经讲得很清楚了。
直白来说:使用 AOP 的主要原因:
1、有些代码通用性较强,并且使用频繁,冗余度高。使用 AOP 可以降低使用的成本。
2、处于对 业务的安全性来考虑,不得不做一个安全的校验。类似的业务有很多,于是 使用 AOP 这种思想,是非常好的。既能降低代码量,又能保证 安全性。
基于这两个主要的原因,所以我们要使用 AOP:在一个统一的位置,进行统一的处理。让后面写代码的时候,程序员没有后顾之优,就是不需要担心安全 性问题。而且,由于是在同一个位置实现的,所以,不会影响到其它代码的执行。
除了统⼀的⽤户登录判断之外,AOP 还可以实现:
1、统⼀⽇志记录
2、统⼀⽅法执⾏时间统计
3、统⼀的返回格式设置
4、统⼀的异常处理
5、事务的开启和提交
也就是说使⽤ AOP 可以扩充多个对象的某个能⼒,所以 AOP 可以说是 OOP(Object Oriented Programming,⾯向对象编程)的补充和完善。
Spring AOP 应该怎么学习呢?
AOP 组成
切⾯(Aspect)
切⾯是包含了:通知、切点和切⾯的类,相当于 AOP 实现的某个功能的集合。
连接点(Join Point)
切点(Pointcut)
切点:就是定义 AOP 拦截的规则。
前面说到:切面 是有 切点 和 通知组成的。
也就是说:切面 不止有一个切点 和 通知。
另外,切面是一个类,类里面具体要实现什么方法,是切面说的算的!
切面,就像公司的总经理,负责发布任务,切点,就是中层领导,规划任务和人员,制定计划。
不同的人,负责工作内容是不一样的,每一个人就是一个 连接点。
通知,告诉每个人负责工作的具体内容是什么,然后他去实现。
通知(Advice)
通知:定义了切⾯是什么,何时使⽤,其描述了切⾯要完成的⼯作,还解决何时执⾏这个⼯作的问题。
Spring 切⾯类中,可以在⽅法上使⽤以下注解,设置⽅法为通知⽅法,在满⾜条件后会通知本⽅法进⾏调⽤:
前置通知使⽤ @Before:通知⽅法会在⽬标⽅法调⽤之前执⾏。
后置通知使⽤ @After:通知⽅法会在⽬标⽅法返回或者抛出异常后调⽤。
环绕通知使⽤ @Around:通知包裹了的⽅法(集合中的连接点),在被通知的⽅法收到通知之前和调⽤之后执⾏⾃定义的⾏为。
AOP 整个组成部分的概念如下图所示,以多个⻚⾯都要访问⽤户登录权限为例:
Spring AOP 实现
Spring AOP 的实现步骤如下:
1、 添加 Spring AOP 框架⽀持。
2、 定义切⾯和切点。
3、 定义通知。
接下来我们使⽤ Spring AOP 来实现⼀下 AOP 的功能,完成的⽬标是拦截所有 UserController ⾥⾯的⽅法,每次调⽤ UserController 中任意⼀个⽅法时,都执⾏相应的通知事件。
1、 添加 Spring AOP 框架⽀持。
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.7.1</version>
</dependency>
2、定义切⾯
3、 定义切点
4、定义通知。
环绕通知使⽤ @Around:通知包裹了的⽅法(集合中的连接点),在被通知的⽅法收到通知之前和调⽤之后执⾏⾃定义的⾏为。
实现通知方法:在什么时机执行什么方法。
下面,我们以前置方法为例,演示一下。
前置通知
验收阶段
小结
AspectJ语法 详解
继续演示定义相关通知
1、前置通知 - 已演示
2、后置通知
后置通知:等目标方法执行完成之后,被调用
下面,我们来看一下效果。
我们可以的出一个结论:
@AfterReturning 修饰的方法执行的优先级 比 @After 修饰的方法执行的优先级更高。
后置通知:如果目标方法在执行期间,抛出异常,会被调用
练习:计算一个方法的执行时间。 - 前篇
那么,问题来了!
前面我不是说: AOP 可以统⼀⽅法执⾏时间的统计嘛。
但是,遇到问题了、
那么,我们该怎么做呢?
下面,我们就来先了解一下 环绕通知。
3、环绕通知
下面我们先来实现一个环绕通知。
下面我们再来具体看一下环绕通知的执行流程
练习(环绕通知):计算一个方法的执行时间。 - 后篇
Spring AOP 实现原理 - 升华
Spring AOP ⽀持 JDK Proxy 和 CGLIB ⽅式实现动态代理。
那么,问题来了: 为什么 Spring AOP 动态代理的实现,会有两种方式呢?’
使用 官方的 JDK Proxy 不好吗?为什么还有再加一个 CGLIB。
默认情况下,实现了接⼝的类,使⽤ AOP 会基于 JDK ⽣成代理类,没有实现接⼝的类,会基于 CGLIB ⽣成代理类。
总结:Spring AOP 实现 动态代理的方式,“主力” 为 CGLIB Proxy。“替补” 为 JDK Proxy。
理由: CGLIB Proxy 的性能更高。
“替补” JDK Proxy 上场情况: 目标对象 为 最终类的时候,也就是不满足 CGLIB Proxy 的执行条件的时候,JDK Proxy 才会 “上场”。
织⼊(Weaving):代理的生成时机
无论是通过哪种方式生成的 动态代理,都会涉及到 代理的生成时机。
在⽬标对象的⽣命周期⾥有多个点可以进⾏织⼊,一共三个点。
也就是说: 我们动态代理生成的时机分为3个部分:
编译期:
类加载器:
运⾏期:
那么,问题来了。
Spring AOP 的动态代理 生成的时机 是在哪一个时期呢?
Spring AOP 的动态代理 生成的时机 是在运行期
(“懒汉模式”:用到的时候,才会去生成。)
JDK 动态代理实现(依靠反射实现) - 了解即可
import org.example.demo.service.AliPayService;
import org.example.demo.service.PayService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//动态代理:使⽤JDK提供的api(InvocationHandler、Proxy实现),此种⽅式实现,要求被代理类必须实现接⼝
public class PayServiceJDKInvocationHandler implements InvocationHandler
{
//⽬标对象即就是被代理对象
private Object target;
public PayServiceJDKInvocationHandler( Object target) {
this.target = target;
}
//proxy代理对象,method 执行的目标方法,args 执行方法所需的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//1.安全检查
System.out.println("安全检查");
//2.记录⽇志
System.out.println("记录⽇志");
//3.时间统计开始
System.out.println("记录开始时间");
//通过反射调⽤被代理类的⽅法 - 重点
// invoke 就是实例反射的意思,把 目标对象 target 和 响应的参数args,传进去
Object retVal = method.invoke(target, args);
//4.时间统计结束
System.out.println("记录结束时间");
return retVal;
}
public static void main(String[] args) {
// PayService 它是一个接口,但对接的类 需要根据实际情况来决定
// 下面就是 对应着 阿里的支付服务的实体类
PayService target= new AliPayService();
//⽅法调⽤处理器
InvocationHandler handler =
new PayServiceJDKInvocationHandler(target);
//创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建
PayService proxy = (PayService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{PayService.class},
handler
);
// 调用 代理类
proxy.pay();
}
}
CGLIB 动态代理实现 - 了解即可
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.example.demo.service.AliPayService;
import org.example.demo.service.PayService;
import java.lang.reflect.Method;
public class PayServiceCGLIBInterceptor implements MethodInterceptor {
//被代理对象
private Object target;
public PayServiceCGLIBInterceptor(Object target){
this.target = target;
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//1.安全检查
System.out.println("安全检查");
//2.记录⽇志
System.out.println("记录⽇志");
//3.时间统计开始
System.out.println("记录开始时间");
//通过cglib的代理⽅法调⽤
Object retVal = methodProxy.invoke(target, args);
//4.时间统计结束
System.out.println("记录结束时间");
return retVal;
}
public static void main(String[] args) {
PayService target= new AliPayService();
PayService proxy= (PayService) Enhancer.create(target.getClass(),
new PayServiceCGLIBInterceptor(target));
proxy.pay();
}
}
JDK 和 CGLIB 实现的区别
总结
Spring AOP 是通过动态代理的⽅式,在运⾏期将 AOP 代码织⼊到程序中的,它的实现⽅式有两种: