Bootstrap

这篇文章带你弄懂AOP,切面,切点,通知,连接点

目录

一、AOP是什么?Spring AOP是什么?

二、为什么使用AOP?AOP可以完成什么功能呢?

三、Spring AOP的学习

3.1AOP的组成

3.2Spring AOP的实现

3.3Spring AOP的实现原理


一、AOP是什么?Spring AOP是什么?

AOP(Aspect Oriented Programming):面向切面编程,是一种思想,它是对某一类事情的集中处理。

Spring AOP是一个框架,提供了一种对AOP思想的实现。

二、为什么使用AOP?AOP可以完成什么功能呢?

比方说我们编写一个后台系统的代码,为保证系统中功能的安全性,每次操作系统中某个功能的时候都需要进行用户验证(除了注册和登录时不用验证),也就代表我们在每个功能模块都需要写上同样的判断代码,不仅会导致程序冗余代码过多,还会导致后期不好进行维护,因为只要对验证规则稍有变化,所有地方都需要去一个一个改动。有人可能会说,编写到一个方法中,每次调用这个方法,代码确实较少了很多,也比较好维护,但是,我们仍会在每个功能中有个方法调用的代码,当我们修改了方法名字咋办呢?所以这就是为啥有AOP,将使用地方比较多的功能,交给AOP统一处理,后期只需要在一个地方进行维护。

AOP可以实现用户的登录判断,统一日志记录,统一方法执行时间统计,统一的返回格式设置,统一的异常处理,事务的开启和提交等。使用AOP可以扩充多个对象的某个能力,AOP是OOP(面向对象)的补充和完善。

三、Spring AOP的学习

3.1AOP的组成

1.切面(Aspect):在程序中是个类。指的是某一方面的具体内容就是切面,如用户的登录判断就是一个切面。

2.切点(Pointcut):类中的一个方法,定义的拦截规则。

3.通知(Advice):方法具体实现代码,执行AOP逻辑业务。

  • 前置通知(@Before):在目标方法(实际要执行方法)调用之前执行的通知;
  • 后置通知(@After):在目标方法之后执行的通知;
  • 环绕通知(@AfterReturning):在目标方法调用前、后都执行的通知;
  • 异常通知(@AfterThrowing):在目标方法抛出异常的时候执行的通知;
  • 返回通知(@Around):在目标方法返回的时候执行的通知。

4.连接点(Join Point)

所有可能触发切点的点就叫做连接点。

(如果大家对这个概念还有点懵,那就根据下面这个例子来弄懂吧!)

3.2Spring AOP的实现

1.添加Spring Boot AOP依赖,大家看自己JDK多少,然后选择适配的AOP。(创建SpringBoot项目的时候,无法快速的直接添加,因为库中没提供,我们需要在加载完的项目中,手动添加这个依赖)


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>3.0.4</version>
</dependency>

2.创建切面,在类上加注解@Aspect表示这个类为切面,但是也要加上类注解,目的是Spring框架需要识别并管理这些切面类,以便在合适的时候将切面逻辑织入到目标对象中。

import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

//创建一个切面
@Aspect
@Component
public class UserAOP {
}

3.创建切点(定义拦截规则),对哪些类进行拦截,对哪些方法进行拦截。

(1)AspectJ ⽀持三种通配符

        *  匹配任意字符, 只匹配⼀个元素( 包, 类, 或⽅法 ⽅法参数)

        ..  匹配任意字符, 可以匹配多个元素 ,在表示类时, 必须和 * 联合使⽤ 

        + :表示按照类型匹配指定类的所有类, 必须跟在类名后⾯,  com.cad.Car+ ,表示继承该类的 所有⼦类包括本身

(2)切点由切点函数组成,execution这个函数经常被使用,表达式如下:

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

@Aspect
@Component
public class UserAOP {
    //创建切点
    //这个拦截规则表示将会拦截任意返回类型的com.example.springaopdemo.controller这个包下的UserController的任意方法以及方法中任意参数
    @Pointcut("execution(* com.example.springaopdemo.controller.UserController.*(..))")
    public void pointCut() {
    }
}

切点的方法可以为空方法,不需要有方法体,因为他就是定义拦截规则,起到个标识的作用,标识通知方法指哪个切点。

4.创建通知(通知类型前面已经说过了)

@Aspect
@Component
public class UserAOP {
    //创建切点
    //这个拦截规则表示将会拦截任意返回类型的com.example.springaopdemo.controller这个包下的UserController的任意方法以及方法中任意参数
    @Pointcut("execution(* com.example.springaopdemo.controller.UserController.*(..))")
    public void pointCut() {
    }
    //前置通知
    @Before("pointCut()")
    public void doBefore() {
        System.out.println("执行了前置通知");
    }
}

5.创建连接点,连接点说白了也就是我们拦截规则拦截的哪些类哪些方法,拦截的那些所有的方法就可以称为所有的连接点。

@RestController
public class UserController {
    @RequestMapping("/test")
    public void test() {
        System.out.println("连接点执行了");
    }
}

6.页面输入url测试,结果如下

注意注意!!!环绕通知的执行以及环绕通知和前置后置的执行顺序

    @Around("pointCut()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) {
        Object obj = null;
        System.out.println("环绕的前置开始执行");
        try {
            //执行连接点(方法)
            obj = proceedingJoinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("环绕的后置开始执行");
        return obj;
    }

3.3Spring AOP的实现原理

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

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

1.JDK动态代理的底层是通过反射机制实现,要求被代理类一定得实现接口。(速度比较块)

2.CGLIB底层是增强字节码技术实现(生成动态子类),通过实现代理类的子类来实现动态代理。被代理类不能被final修饰(无法动态生成子类)

织入:代理的生成时机

织⼊是把切⾯应⽤到⽬标对象并创建新的代理对象的过程 ,切⾯在指定的连接点被织⼊到⽬标对 象中。

在⽬标对象的⽣命周期⾥有多个点可以进⾏织⼊:

●    编译期:切⾯在⽬标类编译时被织⼊ 。这种⽅式需要特殊的编译器 AspectJ的织⼊编译器就 是以这种⽅式织⼊切⾯的。

●    类加载期:切⾯在⽬标类加载到JVM时被织⼊ 。这种⽅式需要特殊的类加载器(ClassLoader ,它可以在⽬标类被引⼊应⽤之前增强该⽬标类的字节码 AspectJ5的加载时织⼊  load-time weaving. LTW 就⽀持以这种⽅式织⼊切⾯ 

●    运⾏期:切⾯在应⽤运⾏的某⼀时刻被织⼊ 。⼀般情况下 ,在织⼊切⾯时 AOP容器会为⽬

标对象动态创建⼀个代理对象 SpringAOP就是以这种⽅式织⼊切⾯的。

JDK和CGLIB都是在运行期,动态织入字节码生成代理类。

;