Spring AOP

本篇学习Spring AOP的相关概念和实现原理


AOP相关概念

什么是 Spring AOP?

AOP(Aspect Oriented Programming):面向切面编程,它是⼀种思想,它是对某⼀类事情的集中处理。比如用户登录权限的效验,没学 AOP 之前,我们所有需要判断用户登录的页面(中的方法),都要各自实现或调用用户验证的方法,然而有了 AOP 之后,我们只需要在某一处配置一下,所有需要判断用户登录页面(中的方法)就全部可以实现用登录验证了,不再需要每个方法中都写相同的用户登录验证了。
而 AOP 是⼀种思想,而 Spring AOP 是⼀个框架,提供了⼀种对 AOP 思想的实现,它们的关系和IoC 与 DI 类似。

对于这种功能统⼀,且使用的地方较多的功能,就可以考虑 AOP来统⼀处理了

除了统一的用户登录判断之外,AOP 还可以实现:

  1. 统⼀日志记录
  2. 统⼀方法执行时间统计
  3. 统⼀的返回格式设置
  4. 统⼀的异常处理
  5. 事务的开启和提交等

也就是说使用 AOP 可以扩充多个对象的某个能力,所以 AOP 可以说是 OOP(Object Oriented Programming,面向对象编程)的补充和完善。

AOP的组成

切面(类)

切面(Aspect)由切点(Pointcut)和通知(Advice)组成,它既包含了横切逻辑的定义,也包括了连接点的定义。

切面是包含了:通知、切点和切面的类,相当于 AOP 实现的某个功能的集合

指的就是某一方面的具体内容就是一个切面

切点(方法)

切点相当于保存了众多连接点的⼀个集合(如果把切点看成⼀个表,而连接点就是表中⼀条⼀条的数据)

切点就是类里面的方法,定义一个拦截规则,满足切面使用

连接点

应用执行过程中能够插入切面的⼀个点,这个点可以是方法调用时,抛出异常时,甚至修改字段时。切面代码可以利用这些点插⼊到应用的正常流程之中,并添加新的行为。

连接点相当于需要被增强的某个 AOP 功能的所有方法

所以可触发切点的点叫做连接点

通知(方法的具体实现代码)

执行AOP逻辑业务

切面也是有目标的 ——它必须完成的工作。在 AOP 术语中,切面的工作被称之为通知。

Spring 切⾯类中,可以在方法上使用以下注解,设置方法为通知方法,在满足条件后会通知本方法进行调用:
前置通知:使用 @Before:通知方法会在目标方法调用之前执行。(在目标方法和实际要执行的方法调用之前执行的通知)
后置通知:使用 @After:通知方法会在目标方法返回或者抛出异常后调用。(在目标方法调用之后执行的通知)
返回之后通知:使用 @AfterReturning:通知方法会在目标方法返回后调用。
异常通知:使用 @AfterThrowing:通知方法会在目标方法抛出异常后调用。
环绕通知:使用 @Around:通知包裹了被通知的方法,在被通知的方法通知之前和调⽤之后执行自定义的行为。

织入

将切面应用到目标对象的过程,可以在编译时,加载时或者运行时。

实现Spring AOP

添加依赖

首先添加框架依赖,在pom.xml中添加配置:
选择合适的版本进行添加
maven仓库地址

创建切面

添加两个注解

@Aspect//切面的意思
@Component//不能省略,随着项目启动而启动

创建切点(定义拦截规则)

@Aspect//切面的意思
@Component
public class UserAOP {
    //定义切点,这里使用AspectJ语法(配置拦截规则)
    @Pointcut("execution( * com.example.demo.controller.UserController.*(..))")
    public void pointcut(){
        
    }
}

AspectJ 支持三种通配符:
’ * ’ :匹配任意字符,只匹配⼀个元素(包,类,或⽅法,方法参数)
‘. .’ :匹配任意字符,可以匹配多个元素 ,在表示类时,必须和 * 联合使用。
’ + ’ :表示按照类型匹配指定类的所有类,必须跟在类名后面,如 com.cad.Car+ ,表示继承该类的所有子类包括本身
切点表达式由切点函数组成,其中 execution() 是最常⽤的切点函数,用来匹配方法,语法为:

execution(<修饰符><返回类型><..⽅法(参数)><异常>)

修饰符和异常可以省略,具体含义如下:
修饰符,一般省略:public : 公共方法;’ * ':任意
返回值,不能省略:void : 返回没有值;String : 返回值字符串;* : 任意
包,com.a.b : 固定包 ;com.a. * .b : a下面子包任意 ;com.a.b. . :b包下面所有子包(包括自己)、
类,a:指定类;* a:以a结尾的类;a * :以a开头的类; * :所有类
方法名,不能省略,a:指定方法; * a:以a结尾的方法;a * :以a开头的方法;* :所有方法
参数,():无参;(int):一个参数;(int,int):两个参数;(…):任意参数
异常:通常省略

表达示例:
execution( * com.cad.demo.User. * ( . . )) :匹配 User 类里的所有方法。
execution( * com.cad.demo.User + .* ( . . )) :匹配该类的子类包括该类的所有方法。
execution( * com.cad. * . * ( . .)) :匹配 com.cad 包下的所有类的所有方法。
execution( * com . cad. . * . * (. .)) :匹配 com.cad 包下、子孙包下所有类的所有方法。
execution( * addUser(String, int)) :匹配 addUser 方法,且第⼀个参数类型是 String,第二个参数类型是 int

创建通知

//前置通知
   //前置通知
    @Before("pointcut()")
    public void doBefore(){
        System.out.println("执行了前置通知"+ LocalDateTime.now());
    }

    //后置通知
    @After("pointcut()")
    public void doAfter(){
        System.out.println("执行了后置通知"+ LocalDateTime.now());
    }
    //环绕通知
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("开始执行环绕通知");
        Object obj  = joinPoint.proceed();
        System.out.println("结束环绕通知");
        return obj;
    }
    

创建连接点

@RequestMapping("/sayhi")
    public String sayHi(){
        System.out.println("执行了sayHi方法");
        return "hi,spring boot aop.";
    }

    @RequestMapping("/login")
    public String login(){
        System.out.println("执行了login方法");
        return "hi,login.";
    }
    

Spring AOP实现原理

Spring AOP的实现原理基于动态代理。

Spring AOP 是构建在动态代理基础上,因此 Spring 对 AOP 的支持局限于方法级别的拦截

在这里插入图片描述
Spring的切面由包裹了目标对象的代理类实现,代理类的处理方法的调用,执行了额外的切面逻辑,并且调用目标方法。

Spring AOP 支持 JDK Proxy 和 CGLIB 方式实现动态代理。
默认情况下,实现了接口的类,使用 AOP 会基于 JDK 生成代理类,没有实现接口的类,会基于 CGLIB 生成代理类

织入(代理生成的时机)

在目标对象的生命周期里有多个点可以进行织入:

  1. 编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。
  2. 类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织⼊(load-time weaving. LTW)就支持以这种方式织入切面。
  3. 运行期:切面在应用运行的某⼀时刻被织入。⼀般情况下,在织入切面时,AOP容器会为目标对象动态创建⼀个代理对象。SpringAOP就是以这种方式织入切面的。

两个底层实现:

1.JDK动态代理:通过反射实现,要求:被代理的类一定要实现接口
2.CGLIB:字节码增强技术(生成子类),通过继承被代理类,在运行时动态的生成代理类对象

JDK 和 CGLIB 实现的区别

  1. JDK 实现,要求被代理类必须实现接口,之后是通过 InvocationHandler 及 Proxy,在运行时动态的在内存中生成了代理类对象,该代理对象是通过实现同样的接口实现(类似静态代理接口实现的方式),只是该代理类是在运行期时,动态的织入统⼀的业务逻辑字节码来完成。
  2. CGLIB 实现,是基于类的,被代理类可以不实现接口,是通过继承被代理类,在运行时动态的⽣成代理类对象

1.JDK基于接口,CGLIB基于类
2.JDK生成的代理对象是目标对象的接口,CGLIB生成的是目标对象的子类
3.JDK性能相对较高,生成代理速度较快,CGLIB性能相对较低,生成代理速度较慢(1.7以前)
4.CGLIB无法处理fianl的类和方法,JDK则可以处理任意类的方法

如果既使用了JDK又使用了CGLIB,默认情况下使用JDK,如果目标对象没有实现接口,则使用CGLIB

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值