Spring核心 AOP

news2024/12/29 9:38:22

1.什么是AOP?

AOP(Aspect Orient Programming),直译过来就是面向切面编程。AOP是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。

比如,在《Spring实战(第4版)》中有如下一张图描述了AOP的大体模型。

在这里插入图片描述

可以看出:所谓切面,其实就相当于应用对象间的横切点,我们可以将其单独抽象为单独的模块

总结:AOP是指在程序的运行期间动态地将某段代码切入到指定方法、指定位置进行运行的编程方式。AOP的底层是使用动态代理实现的。

2.实战案例

2.1.导入AOP依赖

在项目的pom.xml文件中引入AOP的依赖,如下所示:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>4.3.12.RELEASE</version>
</dependency>

Spring AOP对面向切面编程做了一些简化操作,只需要加上几个核心注解,AOP就能工作起来

2.2.定义目标类

在com.tianxia.springannotation.aop包下创建一个业务逻辑类,例如MathCalculator,用于处理数学计算上的一些逻辑。比如,我们在MathCalculator类中定义了一个除法操作,返回两个整数类型值相除之后的结果,如下所示

package com.tianxia.springannotation.aop;

/**
 * 业务逻辑类
 * @author liqb
 * @date 2023-05-09 16:30
 **/
public class MathCalculator {

    /**
     * 除法
     * @author liqb
     * @date 2023-05-09 16:31
     * @param i 除数
     * @param j 被除数
     * @return
     */
    public int div(int i, int j) {
        System.out.println("MathCalculator...div...");
        return i / j;
    }
}

需求:希望在业务逻辑运行的时候将日志进行打印,而且是在方法运行之前、方法运行结束、方法出现异常等等位置,都希望会有日志打印出来。

2.3.定义切面类

在com.tianxia.springannotation.aop包下创建一个切面类,例如LogAspects,在该切面类中定义几个打印日志的方法,以这些方法来动态地感知MathCalculator类中的div()方法的运行情况。如果需要切面类来动态地感知目标类方法的运行情况,那么就需要使用Spring AOP中的一系列通知方法了。

AOP中的通知方法及其对应的注解与含义如下:

  • 前置通知(对应的注解是@Before):在目标方法运行之前运行
  • 后置通知(对应的注解是@After):在目标方法运行结束之后运行,无论目标方法是正常结束还是异常结束都会执行
  • 返回通知(对应的注解是@AfterReturning):在目标方法正常返回之后运行
  • 异常通知(对应的注解是@AfterThrowing):在目标方法运行出现异常之后运行
  • 环绕通知(对应的注解是@Around):动态代理,我们可以直接手动推进目标方法运行(joinPoint.procced())

一开始的写法:

package com.tianxia.springannotation.aop;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Before;

/**
 * 日志切面
 * @author liqb
 * @date 2023-05-09 16:33
 **/
public class LogAspects {

    // @Before:在目标方法(即div方法)运行之前切入
    @Before("public int com.tianxia.springannotation.aop.MathCalculator.*(..)")
    public void logStart() {
        System.out.println("除法运行......@Before,参数列表是:{}");
    }

    // 在目标方法(即div方法)结束时被调用
    @After("public int com.tianxia.springannotation.aop.MathCalculator.*(..)")
    public void logEnd() {
        System.out.println("除法结束......@After");
    }

    // 在目标方法(即div方法)正常返回了,有返回值,被调用
    @AfterReturning("public int com.tianxia.springannotation.aop.MathCalculator.*(..)")
    public void logReturn() {
        System.out.println("除法正常返回......@AfterReturning,运行结果是:{}");
    }

    // 在目标方法(即div方法)出现异常,被调用
    @AfterThrowing("public int com.tianxia.springannotation.aop.MathCalculator.*(..)")
    public void logException() {
        System.out.println("除法出现异常......异常信息:{}");
    }
}

对MathCalculator类中的div()方法进行切入,因此在每一个通知方法上都写了com.tianxia.springannotation.aop.MathCalculator.*(…)"这样一串玩意,其实它就是切入点表达式,即指定在哪个方法切入。

如果切入点表达式都一样的情况下,可以抽取出一个公共的切入点表达式,就像下面这样:

/**
 * 日志切面
 * @author liqb
 * @date 2023-05-09 16:33
 **/
public class LogAspects {

    // 如果切入点表达式都一样的情况下,那么可以抽取出一个公共的切入点表达式
    @Pointcut("execution(public int com.tianxia.springannotation.aop.MathCalculator.*(..))")
    public void pointCut() {
        
    }
}

pointCut()方法就是抽取出来的一个公共的切入点表达式,其实该方法的方法名随便写啥都行,但是方法体中啥都别写

如何在每一个通知方法上引用这个公共的切入点表达式呢?

得分两种情况来讨论,第一种情况,如果是本类引用,那么可以像下面这样写:

/**
 * 日志切面
 * @author liqb
 * @date 2023-05-09 16:33
 **/
public class LogAspects {

    // 如果切入点表达式都一样的情况下,那么可以抽取出一个公共的切入点表达式
    @Pointcut("execution(public int com.tianxia.springannotation.aop.MathCalculator.*(..))")
    public void pointCut() {

    }

    // @Before:在目标方法(即div方法)运行之前切入
    @Before("pointCut()")
    public void logStart() {
        System.out.println("除法运行......@Before,参数列表是:{}");
    }
}

第二种情况,如果是外部类(即其他的切面类)引用,那么就得在通知注解中写方法的全名了,例如:

package com.tianxia.springannotation.aop;

import org.aspectj.lang.annotation.*;

/**
 * 日志切面
 * @author liqb
 * @date 2023-05-09 16:33
 **/
public class LogAspects {

    // 如果切入点表达式都一样的情况下,那么可以抽取出一个公共的切入点表达式
    @Pointcut("execution(public int com.tianxia.springannotation.aop.MathCalculator.*(..))")
    public void pointCut() {

    }

    // @Before:在目标方法(即div方法)运行之前切入
    @Before("com.tianxia.springannotation.aop.LogAspects.pointCut()")
    public void logStart() {
        System.out.println("除法运行......@Before,参数列表是:{}");
    }

    // 在目标方法(即div方法)结束时被调用
    @After("com.tianxia.springannotation.aop.LogAspects.pointCut()")
    public void logEnd() {
        System.out.println("除法结束......@After");
    }

    // 在目标方法(即div方法)正常返回了,有返回值,被调用
    @AfterReturning("com.tianxia.springannotation.aop.LogAspects.pointCut()")
    public void logReturn() {
        System.out.println("除法正常返回......@AfterReturning,运行结果是:{}");
    }

    // 在目标方法(即div方法)出现异常,被调用
    @AfterThrowing("com.tianxia.springannotation.aop.LogAspects.pointCut()")
    public void logException() {
        System.out.println("除法出现异常......异常信息:{}");
    }
}

必须告诉Spring哪个类是切面类,只需要给切面类上加上一个@Aspect注解即可

/**
 * 日志切面
 * @author liqb
 * @date 2023-05-09 16:33
 **/
@Aspect
public class LogAspects {
}

2.4.将目标类和切面类加入到IOC容器

在com.tianxia.springannotation.config包中,新建配置类MainConfigOfAOP,并使用@Configuration注解标注这是一个Spring的配置类,同时使用@EnableAspectJAutoProxy注解开启基于注解的AOP模式。在MainConfigOfAOP配置类中,使用@Bean注解将业务逻辑类(目标方法所在类)和切面类都加入到IOC容器中,如下所示:

package com.tianxia.springannotation.config;

import com.tianxia.springannotation.aop.LogAspects;
import com.tianxia.springannotation.aop.MathCalculator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * AOP配置类
 * @author liqb
 * @date 2023-05-09 16:57
 **/
@Configuration
@EnableAspectJAutoProxy
public class MainConfigOfAOP {

    /**
     * 将业务逻辑类(目标方法所在类)加入到容器中
     * @author liqb
     * @date 2023-05-09 16:58
     * @return
     */
    @Bean
    public MathCalculator calculator() {
        return new MathCalculator();
    }

    /**
     * 将切面类加入到容器中
     * @author liqb
     * @date 2023-05-09 16:58
     * @return
     */
    @Bean
    public LogAspects logAspects() {
        return new LogAspects();
    }
}

2.5.测试

运行测试方法方法,输出的结果信息如下所示:

package com.tianxia.springannotation;

import com.tianxia.springannotation.aop.MathCalculator;
import com.tianxia.springannotation.config.MainConfigOfAOP;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * aop测试类
 * @author liqb
 * @date 2023-05-09 17:03
 **/
@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
public class AopTest {

    /**
     * 测试类
     * @author liqb
     * @date 2023-05-09 17:04
     */
    @Test
    public void test01() {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAOP.class);

        // 我们要使用Spring容器中的组件
        MathCalculator mathCalculator = applicationContext.getBean(MathCalculator.class);
        mathCalculator.div(1, 1);

        // 关闭容器
        applicationContext.close();
    }
}

在这里插入图片描述

并打印出了相关信息,但是并没有打印参数列表和运行结果

要想打印出参数列表和运行结果,就需要对LogAspects切面类中的方法进行优化,优化后的结果如下所示:

package com.tianxia.springannotation.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;

import java.util.Arrays;

/**
 * 日志切面
 * @author liqb
 * @date 2023-05-09 16:33
 **/
@Aspect
public class LogAspects {

    // 如果切入点表达式都一样的情况下,那么可以抽取出一个公共的切入点表达式
    @Pointcut("execution(public int com.tianxia.springannotation.aop.MathCalculator.*(..))")
    public void pointCut() {

    }

    // @Before:在目标方法(即div方法)运行之前切入
    @Before("pointCut()")
    public void logStart(JoinPoint joinPoint) {
        // System.out.println("除法运行......@Before,参数列表是:{}");

        Object[] args = joinPoint.getArgs(); // 拿到参数列表,即目标方法运行需要的参数列表
        System.out.println(joinPoint.getSignature().getName() + "运行......@Before,参数列表是:{" + Arrays.asList(args) + "}");
    }

    // 在目标方法(即div方法)结束时被调用
    @After("com.tianxia.springannotation.aop.LogAspects.pointCut()")
    public void logEnd(JoinPoint joinPoint) {
        // System.out.println("除法结束......@After");

        System.out.println(joinPoint.getSignature().getName() + "结束......@After");
    }

    // 在目标方法(即div方法)正常返回了,有返回值,被调用
    @AfterReturning(value = "com.tianxia.springannotation.aop.LogAspects.pointCut()", returning="result") // returning来指定我们这个方法的参数谁来封装返回值
    public void logReturn(JoinPoint joinPoint, Object result) { // 一定要注意:JoinPoint这个参数要写,一定不能写到后面,它必须出现在参数列表的第一位,否则Spring也是无法识别的,就会报错
        // System.out.println("除法正常返回......@AfterReturning,运行结果是:{}");

        System.out.println(joinPoint.getSignature().getName() + "正常返回......@AfterReturning,运行结果是:{" + result + "}");
    }

    // 在目标方法(即div方法)出现异常,被调用
    @AfterThrowing(value = "com.tianxia.springannotation.aop.LogAspects.pointCut()", throwing = "exception")
    public void logException(JoinPoint joinPoint, Exception exception) {
        System.out.println(joinPoint.getSignature().getName() + "出现异常......异常信息:{" + exception + "}");
    }
}

需要注意的是,JoinPoint参数一定要放在参数列表的第一位,否则Spring是无法识别的,那自然就会报错了

运行测试方法,输出结果如下所示:

在这里插入图片描述

3.小结

搭建AOP测试环境时,虽然步骤繁多,但是我们只要牢牢记住以下三点,就会无往而不利了

  1. 将切面类和业务逻辑组件(目标方法所在类)都加入到容器中,并且要告诉Spring哪个类是切面类(标注了@Aspect注解的那个类)
  2. 在切面类上的每个通知方法上标注通知注解,告诉Spring何时何地运行,当然最主要的是要写好切入点表达式,这个切入点表达式可以参照官方文档来写
  3. 开启基于注解的AOP模式,即加上@EnableAspectJAutoProxy注解,这是最关键的一点

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/506330.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【笔试强训选择题】Day9.习题(错题)解析

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;笔试强训选择题 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01; 文章目录…

open3d Image和numpy互转,PointCloud和numpy互转

目录 1. open3d.geometry.Image转numpy 2. numpy 转 open3d.geometry.Image 3. numpy转PointCloud 4. PointCloud转numpy 1. open3d.geometry.Image转numpy np_x np.asarray(x) # (h,w,3) import numpy as np import matplotlib.pyplot as plt import matplotlib.image…

鸿蒙Hi3861学习十-Huawei LiteOS-M(消息队列)

一、简介 消息队列&#xff0c;是一种常用于任务间通信的数据结构&#xff0c;实现了接收来自任务或中断的不固定长度的消息&#xff0c;并根据不同的接口选择传递消息是否存放在自己空间。任务能够从队列里面读取消息&#xff0c;当队列中的消息是空时&#xff0c;挂起读取任务…

EC6108V9/V9C-Hi3798MV100-当贝纯净桌面-卡刷固件包

EC6108V9&#xff0f;V9C-Hi3798MV100-当贝纯净桌面-卡刷固件包-内有教程 特点&#xff1a; 1、适用于对应型号的电视盒子刷机&#xff1b; 2、开放原厂固件屏蔽的市场安装和u盘安装apk&#xff1b; 3、修改dns&#xff0c;三网通用&#xff1b; 4、大量精简内置的没用的软…

C#学习笔记--实现一个可以重复权重并且能够自动排序的容器--MultiplySortedSet

目录 前言SortedSetC#自带类型自定义类SortedSet权值重复 需求自定义容器 -- MultiplySortedSet核心实现思路 MultiplySortedSet 使用C#自带类型自定义类 前言 最近需要在C#中实现一个功能 有一个容器&#xff0c;该容器能自动对里面的元素进行排序&#xff0c;类似C的优先队列…

fast中user_id如何显示user表中的名称_关联模型

问题&#xff1a;编辑框内的user_id显示的是nickname 列表里如何显示nickname或是username 解决方案如下&#xff1a; 需要更改3个地方&#xff0c;控制器&#xff0c;模型&#xff0c;js文件 1.控制器index list($where, $sort, $order, $offset, $limit) $this->build…

ES6D: 利用对称性进行高效的6D姿态检测

利用对称性进行高效的6D姿态检测 本文参考自CVPR2022的这篇文章&#xff1a;ES6D: A Computation Efficient and Symmetry-Aware 6D Pose Regression Framework Github链接为&#xff1a;https://github.com/GANWANSHUI/ES6D 介绍 在6D姿态检测中&#xff0c;一些具备对称性的…

米哈游的春招实习面经,问的很基础

米哈游的春招实习面经&#xff0c;主要考察了java操作系统mysql网络&#xff0c;这四个方面。 面试流程&#xff0c;共1小时&#xff0c;1min自我介绍&#xff0c;20min写题&#xff0c;剩下问题基础知识。 Java String&#xff0c;StringBuilder&#xff0c; StringBuffer区…

注意力模型

如果拿机器翻译来解释这个分心模型的Encoder-Decoder框架更好理解&#xff0c;比如输入的是英文句子&#xff1a;Tom chase Jerry&#xff0c;Encoder-Decoder框架逐步生成中文单词&#xff1a;“汤姆”&#xff0c;“追逐”&#xff0c;“杰瑞”。 在翻译“杰瑞”这个中文单词…

低代码/0代码(无代码)开发平台如何选型?这篇文章告诉你

随着数字化转型的加速&#xff0c;越来越多的企业开始寻求低代码或零代码开发平台来加速应用程序的开发和部署。选对合适的平台是至关重要的&#xff0c;因为这将决定企业能否在数字化转型中保持竞争优势。 市面上的低/零代码平台五花八门&#xff0c;在选型的时候需要考虑哪些…

搭建本地仓库源

一、如何搭建仓库源 之前讲了定制ISO的方法&#xff1a;使用chroot定制系统&#xff0c;但有时候我们想自定义的安装包不在上游的仓库源中&#xff0c;在我们本地应该怎么办呢&#xff1f;如果我们将deb包拷贝到iso目录再安装有点过于麻烦了&#xff0c;而且还可能需要手动处理…

Linux 常见命令与常见问题解决思路

Linux 常见命令 Linux 基础命令目录相关查看文件&#xff08;日志&#xff09;查看普通的文件查看压缩的文件 解压压缩Linux 系统调优topvmstatpidstatps vi/vim 编辑文件查找文件属性相关定时任务scp 复制文件和目录awk 分隔cutsort 与 uniq常见问题处理思路CPU 高系统平均负载…

数组存储与指针学习笔记(一)数据类型与存储、数据对齐、数据移植、typedef

数据类型与存储 一、数据类型与存储1.1 大端模式与小端模式1.2 有符号数和无符号数1.3 数据溢出1.4 数据类型转换 二、数据对齐2.1 为什么非要地址对齐2.2 结构体对齐2.3 联合体对齐 三、数据的可移植性四、Linux内核中的size_t类型五、typedef5.1 typedef的基本用法5.2 typede…

python操作集合

# 集合 l{1,2,1} print(l) sset(range(5)) print(s)# 判断in 或 not in print(5 not in l) # 集合元素新增操作 l.add(4) l.update(1,3,6) print(l) l.update((1,3,5)) l.update([4,4,6]) # 删除集合元素 l.remove(2) l.discard(2) # 无目的的删除 自己不带参数 l.pop() l.cl…

【跟着陈七一起学C语言】今天总结:C预处理器和C库

友情链接&#xff1a;专栏地址 知识总结顺序参考C Primer Plus&#xff08;第六版&#xff09;和谭浩强老师的C程序设计&#xff08;第五版&#xff09;等&#xff0c;内容以书中为标准&#xff0c;同时参考其它各类书籍以及优质文章&#xff0c;以至减少知识点上的错误&#x…

大语言模型(LLM)和基于人类反馈的强化学习(RLHF)

只需三步&#xff0c;构建你的LLM 预训练语言模型 L L M S S L LLM^{SSL} LLMSSL&#xff08;self-supervised-learning)(指令)监督微调预训练模型 L L M S F T LLM^{SFT} LLMSFT&#xff08;supervised-fine-tuning)基于人类反馈的强化学习微调 L L M R L LLM^{RL} LLMRL&…

K8s常用命令

Namespace 默认情况下&#xff0c;kubernetes集群中的所有的Pod都是可以相互访问的。但是在实际中&#xff0c;可能不想让两个Pod之间进行互相的访问&#xff0c;那此时就可以将两个Pod划分到不同的namespace下。kubernetes通过将集群内部的资源分配到不同的Namespace中&#…

猪场规模怎样划分?类型都有哪些?

养猪场按照经营方式分为大中小猪场&#xff08;猪场规模&#xff09;和集团猪场。集团猪场是指集团化发展与管理的养猪企业&#xff0c;或者简称为集团化养猪企业&#xff0c;重点在于“集团化”。猪场规模则是按照年出栏数量划分。 小规模猪场&#xff1a;年出栏3000头以下&a…

加密芯片在GCP系统的应用方案

物联网&#xff08;IoT&#xff09;设备正在迅速发展&#xff0c;越来越多的设备连接到互联网并与其他设备进行通信。这使得设备的安全变得更加重要&#xff0c;因为它们可能会暴露敏感的数据和功能。Google Cloud IoT Core&#xff08;GCP&#xff09;是一个完全托管的服务&am…

Easydict 简洁易用的翻译词典,带你轻松优雅地查找单词或翻译文本。

Easydict Easydict 是一个简洁易用的翻译词典 macOS App&#xff0c;能够轻松优雅地查找单词或翻译文本。Easydict 开箱即用&#xff0c;能自动识别输入文本语言&#xff0c;支持输入翻译&#xff0c;划词翻译和 OCR 截图翻译&#xff0c;可同时查询多个翻译服务结果&#xff…