• 【Spring AOP】面向切面编程的概念,实践,原理详解


    AOP概念

    AOP的全名是aspect-oriented programming面向切面编程,它是面对对象编程(OOP)的一种方式

    这个AOP的思想主要是指对一个事务的集中处理.将多个类都要完成的功能都在一个类中统一完成.如用户登录的校验功能,每一个页面其实都要进行这个校验,AOP技术就是单独在一个另外的类中统一的进行校验

    AOP技术是一种思想,Spring AOP是这个思想的一个技术实现

    AOP主要完成的事务

    AOP主要可以完成以下几种事务:

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

    组成

    切面Aspect

    切面是由切点和通知组成的.

    主要就是一个包含了一些实现功能的AOP集合类,不仅包括了切点和通知,还包含了连接点的定义

    连接点 Join Point

    程序中调用切面的一个位置,叫做连接点,这个连接点可以是方法调用,可以是抛出异常时,可以是修改参数时

    切点 Pointcut

    Pointcut 的作⽤就是提供⼀组规则(使⽤ AspectJ pointcut expression language 来描述)来匹 配 Join Point,给满⾜规则的 Join Point 添加 Advice。

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

    通配符

    AspectJ ⽀持三种通配符

    • * 匹配任意字符,只匹配⼀个元素(包,类,或⽅法,⽅法参数)
      ..匹配任意字符,可以匹配多个元素 ,在表示类时,必须和 * 联合使⽤。
    • + 表示按照类型匹配指定类的所有类,必须跟在类名后⾯,如 com.cad.Car+ ,表示继承该类的 所有⼦类包括本身

    切点表达式:
    切点表达式由切点函数组成,其中 execution() 是最常⽤的切点函数,⽤来匹配⽅法,语法为:

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

    修饰符,异常一般省略
    包,类一般不省略
    返回类型,方法名,参数不可以省略

    通知 Advice

    切点的方法体的实现就是通知.

    Spring AOP中有以下几种通知:

    前置通知@Before

    通知方法会在目标方法之前进行执行

    后置通知@After

    通知方法在目标方法返回之后或者异常抛出之后

    返回之后通知@AfterReturning

    通知方法在目标方法返回之后通知

    抛异常后通知@AfterThrowing

    通知方法在目标方法抛出异常之后执行

    环绕通知@Around

    通知方法在目标方法执行之前和执行之后都通知

    @Aspect//加上Aspect就表示它是一个切面  
    @Component  
    public class UserAspect {  
      
        @Autowired  
        UserController userController;  
      
        //定义切点,并设置切点的范围  
        @Pointcut("execution(* com.example.demo.controller.UserController.*(..))")  
        public void pointcut(){}//切点没有方法体  
      
        @Before("pointcut()")  
        public void doBefore(){  
            System.out.println("执行before方法");  
        }  
      
        @After("pointcut()")  
        public void doAfter(){  
            System.out.println("执行after方法");  
        }  
      
        @AfterReturning("pointcut()")  
        public void doAfterReturning(){  
            System.out.println("执行AfterReturning");  
        }  
      
        @AfterThrowing("pointcut()")  
        public void doAfterThrow(){  
            System.out.println("执行AfterThrow");  
        }  
      
        public Object doAround(ProceedingJoinPoint joinPoint){  
            Object object=null;  
            System.out.println("执行around方法之前");  
            try {  
                object=joinPoint.proceed();  
            } catch (Throwable throwable) {  
                throwable.printStackTrace();  
            }  
            System.out.println("执行around方法之后");  
            return object;  
        }  
      
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    当有异常抛出时,如果同时有上面的这些通知,那么这些通知的顺序是什么呢?

    执行before方法
    执行AfterThrow
    执行after方法

    先执Before通知,在执行抛出异常通知,最后执行After通知.
    因为程序没有正常的结束,所以不会执行AfterReturn通知

    这也就说明了:

    after通知无论如何都会执行,但是afterReturn在遇到异常之后不会正常执行,这个是两者的区别


    对于Around通知来说,如果加上了它,这些通知的执行顺序是什么呢?

    执行around方法之前
    执行before方法
    目标文件执行
    执行AfterReturning
    执行after方法
    执行around方法之后
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 环绕通知的优先级最高
    2. 接着是前置通知
    3. 执行目标函数
    4. 执行afterReturn通知
    5. 执行后置通知
    6. 最后是有环绕通知结尾

    也就是说,环绕通知最先出现,最晚离开
    在正常执行的情况下,afterReturn出现在后置通知之前


    使用around注解完成执行时间的统计

    使用spring提供的stopWatch和joinPoint来进行统计时间

    @Around("pointcut()")  
    public Object doAround(ProceedingJoinPoint joinPoint){  
        Object object=null;  
        StopWatch stopWatch=new StopWatch();  
        System.out.println("执行around方法之前");  
        try {  
            stopWatch.start();  
            object=joinPoint.proceed();  
            stopWatch.stop();  
        } catch (Throwable throwable) {  
            throwable.printStackTrace();  
        }finally {  
            System.out.println(joinPoint.getSignature().getDeclaringType()+"."+  
                                joinPoint.getSignature().getName()+"执行了"+stopWatch.getTotalTimeMillis()+"ms");  
        }  
      
        System.out.println("执行around方法之后");  
        return object;  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    四大组成的联系

    在这里插入图片描述
    在这里插入图片描述

    Spring AOP的实现

    主要分为以下四个步骤:

    一.创建SpringBoot AOP框架

    还是在pom.xml中引入SPringBoot框架的AOP依赖,我们可以去Maven中央仓库中找到这个依赖,然后赋值到项目的pom.xml中.

    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-aopartifactId>
        <version>2.7.0version>
    dependency>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    记住,一定时SpringBoot的依赖,不要添加成Spring的依赖了,因为现在大多数的项目都是使用SpringBoot较多hh

    二.创建切面

    切面是一个类,在类的前面加上注解@Aspect就表示这是一个切面类.

    同时,它也要加上五大类注解@Component

    @Aspect//加上Aspect就表示它是一个切面  
    @Component  
    public class UserAspect {  
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    三.创建切点

    在函数的前面加上@Pointcut表示是一个切点.

    @Pointcut的后面是指定该切点函数的范围.

        //定义切点,并设置切点的范围  
        @Pointcut("execution(* com.example.demo.controller.UserController.*(..))")  
        public void pointcut(){}//切点没有方法体  
    
    • 1
    • 2
    • 3

    括号里面的内容是AspectJ语言,
    execution(返回类型 包名 类名 方法名 参数类型)

    execution(*(返回类型) com.example.demo.controller(包名).UserController(类名).*(方法名)(..)(参数类型))
    
    • 1

    四.实现通知

    直接在切面类中,创建通知函数就可以,在方法的前面加上通知类型.

    通知类型的括号里面写通知对应的切点是谁.

    @Before("pointcut()")  
        public void UserLoginOrNot(){  
            System.out.println("执行before方法");  
        }  
    
    • 1
    • 2
    • 3
    • 4

    整体代码:

    @Aspect//加上Aspect就表示它是一个切面  
    @Component  
    public class UserAspect {  
      
        @Autowired  
        UserController userController;  
      
        //定义切点,并设置切点的范围  
        @Pointcut("execution(* com.example.demo.controller.UserController.*(..))")  
        public void pointcut(){}//切点没有方法体  
      
        @Before("pointcut()")  
        public void UserLoginOrNot(){  
            System.out.println("执行before方法");  
        }  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    实现原理

    SpringAOP是基于动态代理的方式实现的.

    在没有AOP之前,前端直接和后端进行交互,但是在引入了AOP之后,要先经过AOP的一些通知,这就是动态代理.

    在这里插入图片描述

    主要有两种原理实现:

    1. JDK proxy
    2. CGLIB proxy

    这两个原理相对来说,大部分时间还是使用CGLIB proxy的,因为它的效率高,当CGLIB无法完成的时候再使用JDK proxy

    CGLIB是通过继承目标类来实现动态代理的,对于哪些不可以被继承的类(被final修饰的类) 我们就只好使用JDK proxy

    织入

    织入是创建新的代理对象的过程,切⾯在指定的连接点被织⼊到⽬标对 象中。 在⽬标对象的⽣命周期⾥有多个点可以进⾏织⼊:

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

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

    运⾏期:切⾯在应⽤运⾏的某⼀时刻被织⼊。⼀般情况下,在织⼊切⾯时,AOP容器会为⽬标对象动态创建⼀个代理对象。SpringAOP就是以这种⽅式织⼊切⾯的。

    这两种方法都是在运行时发挥动态代理的

  • 相关阅读:
    数据分析(二):学生成绩预测分析报告
    python小玩意——计算器
    jmeter 性能测试工具的使用(Web性能测试)
    大数据治理的核心是什么
    v-model绑定导致的element UI文本框输入第一次值后被绑定,导致空文本框无法再输入文字
    postman自动化运行接口测试用例
    【JavaScript从入门到入神】代码风格|注释|代码质量
    cnpm的安装与使用
    不会DRF?源码都分析透了确定不来看?
    《了解CV和RoboMaster视觉组》完结啦!
  • 原文地址:https://blog.csdn.net/weixin_51574797/article/details/126707719