• SpringBoot中AOP


    Spring AOP

    简介

    AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

    白话(自我理解):就是对符合切入点表示的方法进行功能扩展

    核心概念

    • 连接点:程序执行过程中的任意位置,在Spirng中是方法
    • 切入点:哪些方法需要追加功能的,匹配通知的方法,叫切入点,符合切入点表达式的方法
    • 通知:各个方法共用的功能,叫通知。通知存在于通知类中。
    • 切面:切面描述的是通知共用的功能与所对应切入点的关系。在哪些切入点上执行哪些通知叫切面
    • 通知类:定义通知处理类
    • 目标(Target):原对象,被通知对象。
    • 代理(Proxy):向目标对象应用通知之后创建的对象。

    代码实现

    1.导入maven依赖

    因为这里使用的SpringBoot项目所以导入SpringBoot中的依赖

    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-testartifactId>
    dependency>
    
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-aopartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2.业务层和实现层代码

    业务层,继承mybatis-plus

    package com.sky.service;
    
    import com.baomidou.mybatisplus.extension.service.IService;
    import com.sky.pojo.SysUser;
    
    /**
     * @author 尹稳健~
     * @version 1.0
     * @time 2022/9/6
     */
    public interface SysUserService extends IService<SysUser> {
    
        void add();
    
        void delete();
    
        void updateUser();
    
        SysUser queryById(Integer id);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    实现层

    package com.sky.service.impl;
    
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.sky.mapper.SysUserMapper;
    import com.sky.pojo.SysUser;
    import com.sky.service.SysUserService;
    import org.springframework.stereotype.Service;
    
    /**
     * @author 尹稳健~
     * @version 1.0
     * @time 2022/9/6
     */
    @Service
    public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser>
                implements SysUserService {
        @Override
        public void add() {
            System.out.println("add方法调用");
        }
    
        @Override
        public void delete() {
            System.out.println("delete方法调用");
        }
    
        @Override
        public void updateUser() {
            System.out.println("updateUser方法调用");
        }
    
        @Override
        public SysUser queryById(Integer id) {
            System.out.println("queryById方法调用");
            return null;
        }
    }
    
    • 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

    3.在业务层的方法上进行AOP功能扩展

    3.1 切入点表达式

    动作关键字(访问修饰符 返回值 包名.类/接口.方法名(参数)异常名)

    • 动作关键词:描述切入点的行为动作,例如execution表示执行到指定切入点
    • 访问修饰符:public ,private等等,可以省略
    • 返回值
    • 包名
    • 类/接口名
    • 方法名
    • 参数
    • 异常名:方法定义中抛出指定异常,可以省略

    如果把包名写死,那么就需要写特别多的切入点表达式,所以一般我们都是使用通配符来描述切入点,可以快速大量匹配连接点方法

    • *:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
    execution(public * com.sky.*.UserService.select*(*))
    
    • 1
    • … : 多个连续的任意符号,可以独立出现,冲用于简化包名和参数的书写
    execution(public * com.sky..UserService.select*(..))
    
    • 1
    • +:专用于匹配子类类型(了解)
    execution(public * com..*Service+.select*(..))
    
    • 1

    书写技巧

    • 所有代码按照标准规范开发,否则一下技巧全部失效
    • 描述切入点通常描述接口,而不是描述实现类
    • 访问控制修饰符针对接口开发均采用public描述(一般可以省略访问控制修饰符)
    • 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*通配符来快速描述
    • 包名书写尽量不使用 …匹配,效率过低,常用*做当个包描述,或精准匹配
    • 接口名/类名书写名称与模块相关的采用星号匹配,例如UserService书写成*Service,绑定业务层接口名
    • 方法名书写以动词进行精准匹配,名称采用星号匹配,例如getById书写成getBy*,
    • 参数规则较为复杂,根据业务方法灵活调整
    • 通常不使用异常作为匹配规则
    3.2通知类型
    通知类型连接点
    前置通知方法前
    后置通知方法后,发生异常不执行
    环绕通知方法前后
    返回后通知方法后,如果发生异常不会执行
    抛出异常后通知只有发生异常后会通知
    前置通知
    package com.sky.aspect;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    
    /**
     * @author 尹稳健~
     * @version 1.0
     * @time 2022/9/6
     */
    @Aspect
    @Component
    public class LogAspect {
    
        /** 切入点表达式 */
        @Pointcut("execution(* com.sky.service.*Service.*(..))")
        public void logPointCut(){}
    
        /** 前置通知 */
        @Before("logPointCut()")
        public void before(){
            System.out.println("前置通知");
        }
    
    }
    
    • 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

    测试:

    package com.sky;
    
    import com.sky.service.SysUserService;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    /**
     * @author 尹稳健~
     * @version 1.0
     * @time 2022/9/6
     */
    @SpringBootTest
    public class ApplicationTest {
    
        @Autowired
        private SysUserService sysUserService;
    
        @Test
        public void testBefore(){
            sysUserService.add();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    结果:

    前置通知
    add方法调用
    
    • 1
    • 2
    后置通知
    package com.sky.aspect;
    
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    
    /**
     * @author 尹稳健~
     * @version 1.0
     * @time 2022/9/6
     */
    @Aspect
    @Component
    public class LogAspect {
    
        /** 切入点表达式 */
        @Pointcut("execution(* com.sky.service.*Service.*(..))")
        public void logPointCut(){}
    
        /** 前置通知 */
        @Before("logPointCut()")
        public void before(){
            System.out.println("前置通知");
        }
    
        /** 后置通知 */
        @After("logPointCut()")
        public void after(){
    //        int i = 1/0;
            System.out.println("后置通知");
        }
    
    }
    
    • 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

    结果:

    前置通知
    add方法调用
    后置通知
    
    • 1
    • 2
    • 3

    如果我们在后置通知中埋了一个异常,那么会怎么样呢?

    package com.sky.aspect;
    
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    
    /**
     * @author 尹稳健~
     * @version 1.0
     * @time 2022/9/6
     */
    @Aspect
    @Component
    public class LogAspect {
    
        /** 切入点表达式 */
        @Pointcut("execution(* com.sky.service.*Service.*(..))")
        public void logPointCut(){}
    
        /** 前置通知 */
        @Before("logPointCut()")
        public void before(){
            System.out.println("前置通知");
        }
    
        /** 后置通知 */
        @After("logPointCut()")
        public void after(){
            int i = 1/0;
            System.out.println("后置通知");
        }
    
    }
    
    • 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

    结果:发生异常不执行

    前置通知
    add方法调用
    java.lang.ArithmeticException: / by zero
    
    • 1
    • 2
    • 3
    环绕通知
    package com.sky.aspect;
    
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    /**
     * @author 尹稳健~
     * @version 1.0
     * @time 2022/9/6
     */
    @Aspect
    @Component
    public class LogAspect {
    
        /** 切入点表达式 */
        @Pointcut("execution(* com.sky.service.*Service.*(..))")
        public void logPointCut(){}
    
        /** 环绕通知 */
        @Around("logPointCut()")
        public Object around(){
            System.out.println("环绕通知");
            return null;
        }
    
    }
    
    • 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

    调用:

    package com.sky;
    
    import com.sky.service.SysUserService;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    /**
     * @author 尹稳健~
     * @version 1.0
     * @time 2022/9/6
     */
    @SpringBootTest
    public class ApplicationTest {
    
        @Autowired
        private SysUserService sysUserService;
    
        @Test
        public void testBefore(){
            sysUserService.add();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    结果:

    环绕通知
    
    • 1

    为什么这里的结果只有环绕通知中的方法呢,而add方法没有执行呢?

    ​ 因为环绕通知和其他的通知不同,需要自己调用

    环绕通知的正确写法:

    package com.sky.aspect;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    /**
     * @author 尹稳健~
     * @version 1.0
     * @time 2022/9/6
     */
    @Aspect
    @Component
    public class LogAspect {
    
        /** 切入点表达式 */
        @Pointcut("execution(* com.sky.service.*Service.*(..))")
        public void logPointCut(){}
    
        /** 环绕通知 */
        @Around("logPointCut()")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("环绕通知前置");
            Object result = joinPoint.proceed();
            System.out.println("环绕通知后置");
            return result;
        }
    
    }
    
    • 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

    记住一定要将结果返回,不然是没有执行结果的

    Object result = joinPoint.proceed();
    return result;
    
    • 1
    • 2

    介绍ProceedingJoinPoint

    getArgs:可以获取方法的参数

    getTarget: 可以获取目标对象,通过反射可以获取其他信息

    Object[] args = joinPoint.getArgs();
    System.out.println(args);
    Object target = joinPoint.getTarget();
    System.out.println(target.getClass().getName());
    
    • 1
    • 2
    • 3
    • 4

    结果:

    环绕通知前置
    add方法调用
    [Ljava.lang.Object;@3af36922
    com.sky.service.impl.SysUserServiceImpl
    环绕通知后置
    
    • 1
    • 2
    • 3
    • 4
    • 5
    返回后通知

    和后置通知类似,略

    异常通知

    发生异常时,触发:

    package com.sky.service.impl;
    
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.sky.mapper.SysUserMapper;
    import com.sky.pojo.SysUser;
    import com.sky.service.SysUserService;
    import org.springframework.stereotype.Service;
    
    /**
     * @author 尹稳健~
     * @version 1.0
     * @time 2022/9/6
     */
    @Service
    public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser>
                implements SysUserService {
        @Override
        public void add() {
            System.out.println("add方法调用");
            int i = 1 /0;
        }
    
        @Override
        public void delete() {
            System.out.println("delete方法调用");
        }
    
        @Override
        public void updateUser() {
            System.out.println("updateUser方法调用");
        }
    
        @Override
        public SysUser queryById(Integer id) {
            System.out.println("queryById方法调用");
            return null;
        }
    }
    
    
    • 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

    切面类

    package com.sky.aspect;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    import java.util.Arrays;
    
    /**
     * @author 尹稳健~
     * @version 1.0
     * @time 2022/9/6
     */
    @Aspect
    @Component
    public class LogAspect {
    
        /** 切入点表达式 */
        @Pointcut("execution(* com.sky.service.*Service.*(..))")
        public void logPointCut(){}
    
        /** 异常通知 */
        @AfterThrowing(value = "logPointCut()",throwing = "t")
        public void afterThrowing(Throwable t){
            System.out.println("异常通知"+t.getMessage());
        }
    
    }
    
    
    • 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

    调用:

    package com.sky;
    
    import com.sky.service.SysUserService;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    /**
     * @author 尹稳健~
     * @version 1.0
     * @time 2022/9/6
     */
    @SpringBootTest
    public class ApplicationTest {
    
        @Autowired
        private SysUserService sysUserService;
    
        @Test
        public void testBefore(){
            sysUserService.add();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    结果:

    add方法调用
    异常通知/ by zero
    java.lang.ArithmeticException: / by zero
    
    • 1
    • 2
    • 3
  • 相关阅读:
    数风流人物还看今朝|前后端分离微服务项目常用中间件以及指令
    算法-版本号升级
    成熟企业级开源监控解决方案Zabbix6.2关键功能实战-上
    交换机与路由器技术-03-交换机基本配置
    【python】内置库函数大集合 ❢ 这不得点赞收藏一波~
    高效数据传输:Java通过绑定快速将数据导出至Excel
    【字符串】后缀数组
    经营管理者杂志经营管理者杂志社经营管理者编辑部2022年第7期目录
    【Web开发】Python实现Web服务器(Flask打包部署上线)
    【枚举】AcWing 1236. 递增三元组
  • 原文地址:https://blog.csdn.net/weixin_46073538/article/details/126723780