Spring Framework 学习笔记4:AOP

news2025/1/19 20:31:00

Spring Framework 学习笔记4:AOP

1.概念

AOP(Aspect Oriented Programming,面向切面编程)是一种编程思想。它要解决的问题是:如何在不改变代码的情况下增强代码的功能。

AOP 有一些核心概念:

  • 连接点(JoinPoint):理论上可以是代码运行的任意位置,比如变量声明。但在 Spring AOP 的实现中,只能是方法。
  • 切入点(Pointcut):要增强功能的地方,对应一个或多个连接点。
  • 通知(Advice):所增强的功能会在通知中定义。
  • 切面(Aspect):在切面中关联接入点和所执行的通知。

更详细的说明可以观看这个视频。

2.快速入门

下面通过一个简单示例项目说明如何在 Spring 框架中实现 AOP。

2.1.准备工作

先下载示例项目 aop-demo 并解压。

这是一个用 Maven 搭建的 Spring 项目,有一些基本的实体类、Service 以及测试用例。

UserServiceTests内容如下:

@Service
public class UserServiceImpl implements UserService {
    @Override
    public void add(User user) {
        user.setId(1);
        System.out.println("%s was added.".formatted(user));
    }

    @Override
    public void deleteById(int id) {
        System.out.println("User(%d) was deleted.");
    }
}

可以执行测试套件UserServiceTestsUserService的两个方法进行测试。这两个方法没有实际功能,只是输出一些模拟信息:

User(id=1, name=icexmoon, age=18) was added.
User(%d) was deleted.

现在我们用 Spring AOP 为这两个方法添加上额外功能:在方法执行前输出当前时间。

2.2.依赖

Spring AOP 使用的是 spring-aop 这个依赖,不过我们并不需要添加,因为该依赖已经包含在 Spring 框架( spring-context 这个依赖)中:

image-20230824155624648

但我们还需要添加一个 AspectJ 的依赖,因为 Spring AOP 使用了 AspectJ 定义的一系列注解:

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.19</version>
</dependency>

注意,从 MavenRepository 检索出来的依赖指定了scoperuntime,要去掉。否则无法在编码阶段使用 AspectJ 的一系列注解。

2.3.切入点

定义一个切入点:

public class TimeAspect {
    @Pointcut("execution(public void cn.icexmoon.aopdemo.service.UserService.add(..))")
    private void userAdd(){}
}

切入点本身是一个空方法,只不过在这个方法上用一个@Pointcut注解定义了切入点关联的连接点信息。

在上边这个示例中,切入点关联的是UserService接口的名称为add的方法,且不限定方法参数列表。

2.4.通知

要想让这个切入点执行一些额外功能,需要定义一个通知:

public class TimeAspect {
	// ...
    @Before("userAdd()")
    public void printTime(){
        String timeString = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
        System.out.println(timeString);
    }
}

通知有多种类型,对应在连接点的不同阶段执行相应的行为,比如在连接点之前执行就需要使用@Before注解定义的通知。其关联的切入点用value属性定义。

2.5.切面

要让 Spring 运行我们定义好的通知,还需要为通知和切入点所在的切面类添加注解:

@Component
@Aspect
public class TimeAspect {
	// ...
}

@Component注解将这个类定义为 Bean,@Aspect注解说明这个类是一个切面,其中定义了切入点和通知。

2.6.开启 AOP 功能

最后,还需要在核心配置类上添加@EnableAspectJAutoProxy注解以开启 Spring AOP 功能:

@EnableAspectJAutoProxy
@Configuration
@ComponentScan(basePackages = "cn.icexmoon.aopdemo")
public class SpringConfig {
}

2.7.测试

现在运行测试用例,就可以看到在UserService.add方法执行前,会输出当前时间:

2023-08-24T16:14:39.0459787
User(id=1, name=icexmoon, age=18) was added.
User(%d) was deleted.

也就是说,我们在没有改变原始代码的情况下增强了代码的功能

这就是 AOP。

3.工作原理

AOP 是用代理实现的,具体流程为:

  1. Spring 容器启动
  2. 读取所有切面配置中的切入点
  3. 初始化 Bean,并判断 Bean 的方法是否与切入点匹配,如果匹配,为其创建代理对象。
  4. 执行 Bean 方法,如果是原始对象,直接执行。如果是代理对象,执行代理对象(被增强过的)方法。

详细说明可以观看这个视频。

4.切入点表达式

切入点上用切入点表达式描述切入点关联的连接点(方法)。

切入点表达式的具体语法可以观看这个视频或阅读这篇文章。

这里只展示一个简单示例,可以将之前的示例改写为:

@Component
@Aspect
public class TimeAspect {
    /**
     * 切入点,匹配任意 service 层方法调用
     */
    @Pointcut("execution(* cn.icexmoon.aopdemo.service.*Service.*(..))")
    private void anyServiceMethods(){}

    @Before("anyServiceMethods()")
    public void printTime(){
        String timeString = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
        System.out.println(timeString);
    }
}

现在任意的 Service 层方法(public)执行前都会打印时间。

5.通知类型

Spring AOP 的通知类型有:

  • @Before
  • @After
  • @Around
  • @AfterReturn
  • @AfterThrow

关于它们的用途和写法可以观看这个视频或阅读这篇文章。

6.案例

6.1.统计方法执行时长

@Component
@Aspect
public class TimeAspect {
	// ...
    @Around("anyServiceMethods()")
    public Object clockExecuteTime(ProceedingJoinPoint pjp) throws Throwable {
        Signature signature = pjp.getSignature();
        String className = signature.getDeclaringTypeName();
        String methodName = signature.getName();
        long begin = System.currentTimeMillis();
        Object result = pjp.proceed();
        long end = System.currentTimeMillis();
        System.out.printf("Method %s.%s() is executed, use %d mills.%n",
                className,
                methodName,
                end - begin);
        return result;
    }
}

6.2.处理方法参数

有时候,一些内容来自用户录入,用户可能会在有意或无意间在有效信息前后添加一些空白符,通常我们需要手动调用String.trim()方法对参数进行处理。

可以利用 AOP 简化这种处理:

@Component
@Aspect
public class StrAspect {
    /**
     * 任意方法
     */
    @Pointcut("execution(* *..*(..))")
    private void anyMethod() {
    }

    /**
     * 对任意使用了 @TrimParams 注解的方法,检查其参数,如果是 String,进行 trim 处理
     *
     * @param pjp
     * @param annotation
     * @return
     * @throws Throwable
     */
    @Around(value = "anyMethod() && @annotation(annotation)")
    public Object trimParams(ProceedingJoinPoint pjp, TrimParams annotation) throws Throwable {
        Object[] args = pjp.getArgs();
        for (int i = 0; i < args.length; i++) {
            Object currentArg = args[i];
            // 如果参数类型是字符串,进行 trim 处理
            if (currentArg instanceof String) {
                String strArg = (String) currentArg;
                args[i] = strArg.trim();
            }
        }
        Object result = pjp.proceed(args);
        return result;
    }
}

这里定义了一个通知,用于处理方法中的字符串类型的参数,并去除其前后的空白符。

为了便于控制,这里引入了一个自定义注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TrimParams {
}

现在只要添加了该注解的方法,就会被上面定义的通知处理:

@Service
public class UserServiceImpl implements UserService {
	// ...
    @Override
    @TrimParams
    public void printMsg(String msg) {
        System.out.printf("msg:[%s]%n", msg);
    }
}

可以用下面的测试用例观察是否生效:

// ...
public class UserServiceTests {
	// ...
    @Test
    public void testPrintMsg(){
        userService.printMsg(" 123  ");
    }
}

The End,谢谢阅读。

本文的完整示例可以从这里获取。

7.参考资料

  • 从零开始 Spring Boot 32:AOP II - 红茶的个人站点 (icexmoon.cn)
  • 黑马程序员SSM框架教程

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

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

相关文章

OpenGL之光照贴图

我们需要拓展之前的系统&#xff0c;引入漫反射和镜面光贴图(Map)。这允许我们对物体的漫反射分量和镜面光分量有着更精确的控制。 漫反射贴图 我们希望通过某种方式对物体的每个片段单独设置漫反射颜色。我们仅仅是对同样的原理使用了不同的名字&#xff1a;其实都是使用一张…

5.图形的连续绘制

到目前为止我们已经学习了折线&#xff0c;圆弧&#xff0c;矩形的基本绘制&#xff0c;但是我们每次都是单独绘制并且只绘制一个图形的。 不知道大家有没有试过在一个画布上绘制多个图形&#xff0c;例如我现在要画一个矩形&#xff0c;一个圆形和一段折线&#xff0c;代码如下…

03. 人工智能核心基础 - 导论(2)

文章目录 从方法论上来讲从问题范式上来讲从研究对象来讲 Hi&#xff0c;你好。我是茶桁。 上一章中&#xff0c;我们谈论了人工智能在时间维度上的不同时间不同的侧重点&#xff0c;这只是一个片面的方面。当然除此之外&#xff0c;我们还要从其他方向来认识人工智能&#xf…

解决大模型行业落地三大挑战,华为云GaussDB向量数据库正式发布

随着AI大模型产品及应用呈现爆发式增长,新的AI时代已经到来。向量数据库可与大语言模型配合使用,解决大模型落地过程中的痛点,已成为企业数据处理和应用大模型的必选项。在近日举行的华为全联接大会2023期间,华为云正式发布GaussDB向量数据库。GaussDB向量数据库基于GaussD…

python:bottle + eel 模仿 mdict 查英汉词典

Eel 是一个轻量的 Python 库&#xff0c;用于制作简单的类似于离线 HTML/JS GUI 应用程序&#xff0c;并具有对 Python 功能和库的完全访问权限。 Eel 托管一个本地 Web 服务器&#xff0c;允许您使用 Python 注释函数&#xff08;annotate functions&#xff09;&#xff0c;…

IDLE、Anaconda安装与使用

博主&#xff1a;命运之光 专栏&#xff1a;Python程序设计​​​​​ 目录 Python下载和安装 Anaconda安装和使用 Python程序运行方式 Python下载和安装 常用集成开发环境 IDE 默认编程环境&#xff1a; IDLE---初学者&#xff08; Download Python | Python.org &#x…

自动群发节日祝福,1 行 Python 代码搞定,小白可用

想了解更多精彩内容&#xff0c;快来关注程序员晚枫 大家节日快乐&#xff0c;这里是程序员晚枫&#xff0c;小红薯也叫这个名字。 今天给大家分享一个实用功能&#xff1a;自动群发祝福消息。 我相信社会人都体会过&#xff0c;过年过节给别人群发祝福消息的无奈&#xff0…

前沿研究|16s+宏基因组binning揭示大型藻类附生微生物群落核心组成

发表期刊&#xff1a;Microbiome 发表时间&#xff1a;2023 影响因子&#xff1a;15.5 DOI: 10.1186/s40168-023-01559-1 研究背景 大型藻类附生微生物群落是新型酶类和化合物的丰富资源&#xff0c;在维持沿海系统的高生物生产力和生物多样性方面发挥着重要的作用。但迄今…

华为乾坤区县教育安全云服务解决方案(2)

本文承接&#xff1a; https://blog.csdn.net/qq_37633855/article/details/133276200?spm1001.2014.3001.5501 重点讲解华为乾坤区县教育安全云服务解决方案的部署流程。 华为乾坤区县教育安全云服务解决方案&#xff08;2&#xff09; 课程地址解决方案部署整体流程组网规划…

Prettier - Code formatter格式化规则文件

文章目录 前言安装使用 前言 先前公司在规范代码时,由于个人业务繁忙跟技术总监是后端出身用的IDEA不熟悉vsCode;以及大多数时都自己一个人负责一个项目,当时并不看重这些;最近在整理vue3tsvite的脚手架模板(平时工作用的react),开始整理格式化代码,方便之后 vue 和 react 中应…

(附源码)springboot体检预约APP 计算机毕设16370

目 录 摘要 1 绪论 1.1开发背景 1.2研究现状 1.3springboot框架介绍 1.4论文结构与章节安排 2 Springboot体检预约APP系统分析 2.1 可行性分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 操作可行性分析 2.2 系统流程分析 2.2.1 数据添加流程 2.2.2 数据…

什么是Promise链(Promise chaining)?它在异步编程中的作用是什么?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 什么是 Promise 链&#xff1f;⭐ 异步编程中的作用⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、…

Java后端接口编写流程

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; Java后端接口编写流程 Java后端接口编写流程&#xff0c;更具业务逻辑编写Java后端接口&#xff0c;提供给前端访问 实现逻辑流程 POJO&#xff1a;实体类编写 Data B…

架构设计第七讲:数据巡检系统之daily线上表结构自动化比对

架构设计第七讲&#xff1a;数据巡检系统之daily&线上表结构自动化比对 本文是架构设计第七讲&#xff0c;数据巡检系统之daily&线上表结构自动化比对&#xff0c;避免正式环境与测试环境数据库/表、列结构不一致带来问题。 文章目录 架构设计第七讲&#xff1a;数据巡…

36 二叉树中序遍历

二叉树中序遍历 题解1 递归题解2 迭代 给定一个二叉树的根节点 root &#xff0c;返回它的 中序 遍历 。 提示&#xff1a; 树中节点数目在范围 [0, 100] 内-100 < Node.val < 100 进阶: 递归算法很简单&#xff0c;你可以通过迭代算法完成吗&#xff1f; 题解1 递归…

Python绘图系统23:导入多个坐标轴的数据

文章目录 单轴导入多轴导入多文件导入合并导入源代码 Python绘图系统&#xff1a; 前置源码&#xff1a; Python打造动态绘图系统&#x1f4c8;一 三维绘图系统 &#x1f4c8;二 多图绘制系统&#x1f4c8;三 坐 标 轴 定 制&#x1f4c8;四 定制绘图风格 &#x1f4c8;五 数据…

第一章 数据可视化和matplotlib

Python数据可视化 第一章 数据可视化和matplotlib 1.数据可视化概述 1.1.什么是数据可视化 ​ 数据可视化旨在借助图形化的手段&#xff0c;将一组数据以图形的形式表示&#xff0c;并利用数据分析和开发工具发现其中未知信息的处理过程。 ​ 数据可视化发展历史 ​ 可视化…

Spring IOC容器实例化Bean整体流程图

SpringBean实例化的基本流程-CSDN博客 Spring容器中的BeanDefinitionReader读取器&#xff0c;读取xml配置文件&#xff0c;解析每一个bean标签&#xff0c;将bean标签中信息封装到BeanDefinition对象中&#xff0c;该对象的集合存储到BeanDefinitionMap中&#xff0c;然后Spri…

Cocos Creator3.8 实战问题(三)去除scrollview背景色和label 对齐方式设置无效问题

1、 scrollview 默认背景是白色的&#xff0c; 我们不想要 scrollview 默认的背景颜色&#xff0c;怎么办&#xff1f; 设置 scrollview的color为透明吗&#xff1f; 不对&#xff0c;这会导致 view节点完全透明。 解决方法&#xff1a;直接删除scrollview 的Spritre frame就…

船用低速发动机缸压在线监测系统

LabVIEW开发船用低速发动机缸压在线监测系统 船用发动机结构复杂&#xff0c;部件相互连接&#xff0c;运行环境恶劣&#xff0c;使其更容易发生故障。如果船用发动机发生故障或工作状态不佳&#xff0c;将增加造成经济损失和威胁船舶安全的机。为了减少故障的发生&#xff0c…