结合代码理解Spring AOP的概念(切面、切入点、连接点等)

前情回顾

  • 对AOP的理解

    • 我这篇文章介绍了为什么要有AOP(AOP解决了什么问题)以及如何实现AOP。但在实现AOP的时候,并未探讨AOP相关概念,例如:切面、切入点、连接点等。
    • 因此,本篇文章希望结合代码去理解Spring AOP的相关概念。

Talk is cheap, show me the code.

背景

  • 在使用AOP时,我们大概率遇到了这样的场景:我现在有多个方法,在这多个方法执行前/执行后要做一些统一的操作。

  • 例如:

  • @RequestMapping("/user")
    @RestController
    public class UserController {@GetMapping("/query")public String queryUser() {return "I am a user";}
    }@RequestMapping("/student")
    @RestController
    public class StudentController {@GetMapping("/query")public String queryStudent() {return "I am a student";}
    }
    
    • 我希望在执行这两个方法前,打印一行日志:start execute。
  • 为多个方法增加逻辑,这些代码写在哪里呢?当然是写到一个类里啊(Java嘛,万事万物皆对象,要封装到类里)。

  • public class LogAspect {public void log() {System.out.println("start execute");}
    }
    
    • 这样显然是不够的,因为,Spring并不知道这个类是特殊的类,这些代码要为谁增强。因此,我们要遵循Spring规范,提供一些标记。

      @Aspect
      public class LogAspect {public void log() {System.out.println("start execute");}
      }
      
      • 查看下@Aspect这个注解:

      • @Retention(RetentionPolicy.RUNTIME)
        @Target({ElementType.TYPE})
        public @interface Aspect {String value() default "";
        }
        
        • 非常的简单,就是告知Spring这个类是一个切面类。切面类中的方法,是其他方法的补充逻辑。
        • 然而,仅仅打上@Aspect这个注解是不够的(因为LogAspect没有注入到Spring容器中),还需要打上@Component注解,告诉Spring帮我管理这个Bean。
        • 在Spring容器中,Spring管理着UserController和StudentController这些bean,可以为它们分别生成代理类,然后将Spring容器中的LogAspect合适地织入到代理类中,从而增强了UserController和StudentController的功能。

切面 + 切入点 + 连接点 + 通知

  • 这个切面类中的方法,给谁用呢?显然,这也需要告知Spring。

  • 开发者自己是知道要给谁用的,例如:给UserController的queryUser方法和StudentController的queryStudent方法用。这些方法可以被通俗地理解为一个个 连接点(Joinpoint) 。LogAspect的log方法是给多个连接点使用的,这多个连接点又称为 切入点(Pointcut)

    @Aspect
    @Component
    public class LogAspect {@Pointcut("execution(* com.forrest.learn.springboot.example5.controller.*.*(..))")private void example5Controller() {}public void log() {System.out.println("start execute");}
    }
    
    • execution(* com.forrest.learn.springboot.example5.controller..(..))​切入点表达式,不太好写,而且容易过度拦截连接点。我们只想拦截UserController的queryUser方法和StudentController的queryStudent方法。这时候怎么办?用注解。

    • @Retention(RetentionPolicy.RUNTIME)
      @Target({ElementType.METHOD})
      public @interface LogController {}@RequestMapping("/student")
      @RestController
      public class StudentController {@LogController@GetMapping("/query")public String queryStudent() {return "I am a student";}
      }@RequestMapping("/user")
      @RestController
      public class UserController {@LogController@GetMapping("/query")public String queryUser() {return "I am a user";}
      }@Aspect
      @Component
      public class LogAspect {@Pointcut("@annotation(com.forrest.learn.springboot.example5.annotation.LogController)")private void example5Controller() {}public void log() {System.out.println("start execute");}
      }
      
      • 只要方法打上了@LogController注解,就要被拦截。又引出了另一个问题,什么时候拦截呢?是方法执行前拦截?还是执行后拦截?显然,需要 通知(Advise) ​。

        • @Before​: 拦截方法,在方法执行前增强
        • @AfterReturning​: 拦截方法,在方法执行并正常返回后增强
        • @AfterThrowing​: 拦截方法,在方法执行并异常返回后增强
        • @After​: 拦截方法,在方法执行后增强
        • @Around​:拦截方法,用户自行决定在方法前/后进行增强,也就是包含了前面4个注解的功能了,是最自由的增强。
      • @Aspect
        @Component
        public class LogAspect {@Pointcut("@annotation(com.forrest.learn.springboot.example5.annotation.LogController)")private void example5Controller() {}@Before("example5Controller()")public void log() {System.out.println("start execute");}
        }
        

        image

        • 很清楚地知道了,给哪些连接点增强了。
  • 在Spring Boot应用中,通常不需要手动添加@EnableAspectJAutoProxy​注解来启用AOP功能。这是因为Spring Boot已经为你自动配置了AOP支持。

    • Spring Boot通过@SpringBootApplication​注解(它包含了@EnableAutoConfiguration​)自动开启了AOP功能。具体来说,Spring Boot会自动扫描项目中的@Aspect​注解类,并将其注册为切面(Aspect),同时启用AspectJ代理机制。

小结

@Aspect  // 切面(为多个类提供增强逻辑,逻辑由方法实现,方法写在类中)
@Component // 需要将切面类注入到Spring容器中
public class LogAspect {// 为哪些方法进行增强?靠定义切入点(一组连接点)@Pointcut("@annotation(com.forrest.learn.springboot.example5.annotation.LogController)")private void example5Controller() {}// 什么时候进行增强?靠通知(Advice)@Before("example5Controller()")public void log() {System.out.println("start execute");}
}

连接点的进阶

  • 我需要统计方法执行的耗时,并且打印出方法名、方法入参。
/*** 从连接点中获取方法名,而不是通过注解的字段*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MetricTime {
//    String value() default "";  // 用户可以将方法名称传给value
}@Aspect
@Component
public class MetricTimeAspect {// 切入点作为通知的参数@Around("@annotation(com.forrest.learn.springboot.example5.annotation.MetricTime)")public Object metricTime(ProceedingJoinPoint pjp) throws Throwable {long startAt = System.currentTimeMillis();try {return pjp.proceed();} finally {System.out.println(pjp.getSignature().getName() + " cost " + (System.currentTimeMillis() - startAt) + " ms");System.out.println("入参:" + Arrays.toString(pjp.getArgs()));}}
}/*
queryUser cost 0 ms
入参:[]
*/
  • 切面类中的方法,可以有哪些入参?

    • ProceedingJoinPoint pjp

      • JoinPoint是AOP的核心接口之一,它提供了连接点的信息,例如方法名、参数值等。ProceedingJoinPoint是JoinPoint的子接口,专门用于@Around通知中。在其他通知(如@Before、@After、@AfterReturning、@AfterThrowing)中,通常使用JoinPoint。

        查看源码,就知道ProceedingJoinPoint、JoinPoint提供了哪些方法。

    • 还可以传入注解:

      @Aspect
      @Component
      public class MetricTimeAspect {@Around("@annotation(metricTime)")public Object metricTime(ProceedingJoinPoint pjp, MetricTime metricTime) throws Throwable {long startAt = System.currentTimeMillis();try {return pjp.proceed();} finally {System.out.println(pjp.getSignature().getName() + " cost " + (System.currentTimeMillis() - startAt) + " ms");System.out.println("入参:" + Arrays.toString(pjp.getArgs()));}}
      }
      

思路 > 技术细节

  • Spring AOP在技术细节上还有很多知识。等真正需要用到这些知识时,我们可以查看官方文档,借助AI来帮助落地。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【AI大模型】搭建本地大模型GPT-NeoX:详细步骤及常见问题处理

搭建本地大模型GPT-NeoX:详细步骤及常见问题处理 GPT-NeoX是一个开源的大型语言模型框架,由EleutherAI开发,可用于训练和部署类似GPT-3的大型语言模型。本指南将详细介绍如何在本地环境中搭建GPT-NeoX,并解决过程中可能遇到的常见问题。 1. 系统要求 1.1 硬件要求 1.2 软…

Copilot提示词库用法:调整自己想要的,记住常用的,分享该共用的

不论你是 Microsoft 365 Copilot 的新用户还是熟练运用的老鸟,不论你是使用copilot chat,还是在office365中使用copilot,copilot提示词库都将帮助你充分使用copilot这一划时代的产品。它不仅可以帮助你记住日常工作中常用的prompt提示词&…

Spring:AOP

一、AOP概念的引入 为了更好地介绍AOP,我们以登录作为示例。 首先,我们先来看一下登录的原理: 如图所示,这是一个基本的登录原理图,但是如果我们想要在这个登录过程上再添加一些新的功能,比如权限校验&am…

Ubuntu实时读取音乐软件的音频流

文章目录 一. 前言二. 开发环境三. 具体操作四. 实际效果 一. 前言 起因是这样的,我需要在Ubuntu中,实时读取正在播放音乐的音频流,然后对音频进行相关的处理。本来打算使用的PipewireHelvum的方式实现,好处是可以直接利用Helvum…

CUDA 学习(4)——CUDA 编程模型

CPU 和 GPU 由于结构的不同,具有不同的特点: CPU:擅长流程控制和逻辑处理,不规则数据结构,不可预测存储结构,单线程程序,分支密集型算法GPU:擅长数据并行计算,规则数据结…

前端会话控制技术:cookie/session/token

目录 前端中的 Cookie、Session 和 Token:详解与应用1. Cookie1.1 什么是 Cookie?1.2 Cookie 的工作原理1.3 Cookie 的特点1.4 Cookie 的用途1.5 Cookie 的安全性 2. Session2.1 什么是 Session?2.2 Session 的工作原理2.3 Session 的特点2.4…

MATLAB实现基于“蚁群算法”的AMR路径规划

目录 1 问题描述 2 算法理论 3 求解步骤 4 运行结果 5 代码部分 1 问题描述 移动机器人路径规划是机器人学的一个重要研究领域。它要求机器人依据某个或某些优化原则 (如最小能量消耗,最短行走路线,最短行走时间等),在其工作空间中找到一…

Shopify Checkout UI Extensions

结账界面的UI扩展允许应用开发者构建自定义功能,商家可以在结账流程的定义点安装,包括产品信息、运输、支付、订单摘要和Shop Pay。 Shopify官方在去年2024年使用结账扩展取代了checkout.liquid,并将于2025年8月28日彻底停用checkout.liquid…

电阻的阻值识别

电阻买回来是有偏差的,不同的电阻种类,它的偏差大小会不一样,偏差越小的肯定越贵 主要看要求的精度要求是否越高 色环电阻或者说插件电阻 用来读数的几个色环它是比较靠近的,精度的色环跟用来读数的几个色环的间距会大一点点。 间…

quartz.net条件执行

quartz.net条件执行 在使用Quartz.NET时,你可能需要基于某些条件来决定是否执行一个任务。Quartz.NET本身并不直接支持基于条件执行任务的功能,但你可以通过一些策略来实现这一需求。下面是一些方法来实现基于条件的任务执行: 1. 使用触发器…

计算机操作系统(四) 操作系统的结构与系统调用

计算机操作系统(四) 操作系统的结构与系统调用 前言一、操作系统的结构1.1 简单结构1.2 模块化结构1.3 分层化结构1.4 微内核结构1.5 外核结构 二、系统调用1.1 系统调用的基本概念1.2 系统调用的类型 总结(核心概念速记)&#xf…

NSSCTF(MISC)——[SUCTF 2018 招新赛]single-dog

相应的做题地址:https://www.nssctf.cn/problem/2324 分离图片 在1.txt中得到一段颜文字 http://www.hiencode.com/aaencode.html 解密得到flag

低功耗蓝牙(BLE)方案设计实战指南

一、BLE方案设计工具链 1. 硬件选型与开发平台 TI平台:CC2540/CC2541芯片,使用SmartRF Flash Programmer烧录Nordic平台:nRF51822芯片,使用nRFgo Studio管理协议栈常用调试工具:TI CC Debugger、J-Link(SW…

网络基础(一)

独立模式与网络互联 独立模式: 计算机之间相互独立。 网络互联:多台计算机连接在一起,完成数据共享。 注意:无论是主机内还是主机外,都是通过线来进行连接的,主机内线(线比较短)的连接主要考虑…

用Canvas 画布样式实现旋转的阴阳图

用Canvas 画布样式实现旋转的阴阳图 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Canvas八卦图动画</title><style>/* 重置所有元素的默认样式 */* {padding: 0;margin: 0;box-sizin…

第16届蓝桥杯单片机4T模拟赛三

本次模拟赛涉及的模块&#xff1a;基础三件套&#xff08;Led&Relay&#xff0c;按键、数码管&#xff09; 进阶单件套&#xff08;pcf8591的AD模块&#xff09; 附件&#xff1a; 各模块底层代码在文章的结尾 一、数码管部分 1.页面1 页面1要显示的格式是&#xff1a; …

优选算法的睿智之林:前缀和专题(一)

专栏&#xff1a;算法的魔法世界 个人主页&#xff1a;手握风云 目录 一、前缀和 二、例题讲解 2.1. 一维前缀和 2.2. 二维前缀和 2.3. 寻找数组的中心下标 2.4. 除自身以外数组的乘积 一、前缀和 前缀和算法是一种用于处理数组或序列数据的算法&#xff0c;其核心思想是…

瑞萨RX23E系列开发(二)建立工程

新建工程 使用倒数第二个模板 选择路径 我这里是这个型号。根据型号选择芯片 第一次需要下载FIT

【算法day19】括号生成——数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

括号生成 https://leetcode.cn/problems/generate-parentheses/description/ 数字 n 代表生成括号的对数&#xff0c;请你设计一个函数&#xff0c;用于能够生成所有可能的并且 有效的 括号组合。 左括号数必须大于右括号数&#xff0c;且小于等于n class Solution { publ…

Apache Doris学习

https://doris.apache.org/zh-CN/docs/gettingStarted/what-is-apache-doris 介绍 Apache Doris 是一款基于 MPP 架构&#xff08;大规模并行处理&#xff09;的高性能、实时分析型数据库。它以高效、简单和统一的特性著称&#xff0c;能够在亚秒级的时间内返回海量数据的查询…