一文读懂Spring AOP:手把手教你优雅实现“无侵入”代码增强

目录

1.什么是Spring AOP?

2.SpringAOP优点与上手

Spring AOP 的核心术语

3.通知类型注解

4.@PointCut+@Order

5.切点表达式

6.代理模式

7.Spring AOP原理


1.什么是Spring AOP?

AOP=>面向切片编程思想,是一种对一类问题集中处理的思想,比如拦截器,统一返回结果管理,统一异常处理,登录校验......如果使用OOP(面向结果编程)会让相同的代码重复多次出现,业务方法中混杂着非核心的逻辑。

Spring AOP就是为了解决这些问题存在,是AOP思想的其中一种实现方式

2.SpringAOP优点与上手

优点:

  • 不影响原有代码,解耦
  • 便于维护功能
  • 提高开发效率
  • 减少重复代码

快速上手SpringAOP

编写一个使用SpringAOP计算所有方法的运行时长的例子

1.在pom.xml文件引入依赖

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>

注意:SpringBoot使用版本号为4.x.x, AOP依赖需要添加版本号

2.编写AOP程序

package com.example.springbookdemo.aspect; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; @Slf4j @Aspect @Component public class TimeRecordAspect {  @Around("execution(* com.example.springbookdemo.controller.*.*(..))")//该注解()中是要执行方法的路径 //参数部分的ProceedingJoinPoint joinPoint即要执行的方法 //不确定执行方法的返回类型,因此在这里设返回类型为Object public Object timeRecord(ProceedingJoinPoint joinPoint) throws Throwable { long start=System.currentTimeMillis();//记录方法执行前时间 //执行方法 Object proceed = joinPoint.proceed();//proceed()方法返回类型固定为Object long end=System.currentTimeMillis();//记录方法执行完的时间 log.info(joinPoint.getSignature()+"方法耗时:"+(end-start)+"ms");//getSignature()用于获取执行方法名 return proceed; } }

注解解释:

  • Aspect=>标识切面类
  • Component=>将该类交给Spring容器管理,使其能自动扫描并注入
  • Around=>指明对哪些方法生效
  • Slf4j=>自动生成日志对象,可调用log.info()等方法打印日志

Spring AOP 的核心术语

理解这些术语是掌握 AOP 的关键:

  1. Aspect(切面)
    • 是什么:横切关注点的模块化。它是一个,里面包含了需要被织入到业务代码中的各种通知(Advice)。包含了切点,通知,连接点
  2. Join Point(连接点)
    • 是什么:程序执行过程中可以插入切面的点。在 Spring AOP 中,连接点总是代表执行的方法
  3. Pointcut(切点)
    • 是什么:一个表达式,用于定义在哪些连接点上应用通知。它告诉 AOP:“在哪里” 执行切面代码。例如,你可以定义一个切点,匹配所有 com.example.service包下以 delete开头的方法。
  4. Advice(通知)
    • 是什么:切面在特定连接点上执行的动作。指具体要做的⼯作, 指哪些重复的逻辑,它回答了 “什么时候” 和 “做什么” 的问题。
    • 类型
      • @Before:在目标方法执行之前执行。
      • @After:在目标方法执行之后执行(无论是否成功完成)。
      • @AfterReturning:在目标方法成功执行并返回结果后执行。
      • @AfterThrowing:在目标方法抛出异常后执行。
      • @Around最强大的通知,它包围了连接点。可以在方法调用前后执行自定义行为,并决定是否继续执行方法、返回值或抛出异常。

用上面的例子来区分

3.通知类型注解

写两个方法,用于测试

package com.example.springaopdemo.controller; @Slf4j @RestController @RequestMapping("/test") public class TestController { @RequestMapping("/t1") public String t1(){ log.info("执行t1"); return "t1"; } @RequestMapping("/t2") public boolean t2(){ log.info("执行t2"); return true; } }

编写AOP

package com.example.springaopdemo.aspect; @Aspect @Component @Slf4j public class TestAspect { //抽取切点 @Pointcut("execution(* com.example.springaopdemo.controller.*.*(..))") public void pt(){} //前置通知 @Before("pt()") public void doBefore() { log.info("执行doBefore方法"); } //后置通知 @After("pt()") public void doAfter() { log.info("执行 After 方法"); } //返回后通知 @AfterReturning("pt()") public void doAfterReturning() { log.info("执行 AfterReturning 方法"); } //抛出异常后通知 @AfterThrowing("pt()") public void doAfterThrowing() { log.info("执行 doAfterThrowing 方法"); } //添加环绕通知 @Around("pt()") public Object doAround(ProceedingJoinPoint joinPoint) { log.info("Around 方法开始执行"); Object result = null; try { result = joinPoint.proceed(); } catch (Throwable e) { throw new RuntimeException(e); } log.info("Around 方法结束执行"); return result; } } 

结果展示:

访问t1=>即接口正常访问

访问t2=>即接口发生异常且不捕获

访问t2=>即接口发生异常且捕获不throw

4.@PointCut+@Order

@PointCut

在上面中存在大量的同样的切点表达式,使用该注解能够定义可重用的切点表达式,避免在多个通知中重复编写相同的表达式。

通过@PointCut注解定义一个公共切点方法,该方法本身没有实现,仅作为切点表达式的载体:

@PointCut("execution(* com.example.service.*.*(..))") public void serviceLayer() {} 

重用切点表达式

定义好的切点可以在其他通知中通过方法名引用,避免重复编写表达式:

@Before("serviceLayer()") public void beforeService() { // 前置通知逻辑 } @After("serviceLayer()") public void afterService() { // 后置通知逻辑 } 

@Order用于设置优先级,控制多个切面的执行顺序,@Order(),()中的数字越小,优先级越高

@Component @Aspect @Slf4j public class TestAspect2 { @Before("com.example.springaopdemo.aspect.TestAspect.pt()") public void doBefore() { log.info("执行doBefore2方法"); } } @Component @Aspect @Slf4j public class TestAspect3 { @Before("com.example.springaopdemo.aspect.TestAspect.pt()") public void doBefore() { log.info("执行doBefore3方法"); } } @Aspect @Component @Slf4j public class TestAspect { //抽取切点 @Pointcut("execution(* com.example.springaopdemo.controller.*.*(..))") public void pt(){} //前置通知 @Before("pt()") public void doBefore() { log.info("执行doBefore方法"); } }

执行结果:也就是切面默认执行顺序(按照类名排序)

通过添加@Order注解可以人为控制切面执行顺序

@Order(3)

public class TestAspect(){}

@Order(2)

public class TestAspect2(){}

@Order(1)

public class TestAspect3(){}

添加之后执行结果:

5.切点表达式

有两种

第一种execution(<访问修饰符><返回类型><包名.类名.方法名(方法参数)><异常>)

其中访问修饰符和异常可以省略," * "匹配任意字符," .. "匹配多个连续符号

第二种@annotation=>自定义注解

相比较第一种有一定规律来说,想要执行的方法没有规律可以使用这种

//自定义注解 @Retention(RetentionPolicy.RUNTIME)//设置生命周期 @Target({ElementType.METHOD})//设置注解使用位置 public @interface TimeRecord { }
//注解通过切面实现 @Aspect @Component @Slf4j public class TimeRecordAspect { @Around("@annotation(com.example.springaopdemo.aspect.TimeRecord)") public Object timeRecord(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis(); Object result = joinPoint.proceed(); long end = System.currentTimeMillis(); log.info(joinPoint.getSignature() + "耗时" + (end - start) + "ms"); return result; } }

注解完成自定义之后,只要在对应方法添加该注解即可实现计算方法运行时间的功能

6.代理模式

它为另一个对象提供一个替身或占位符,以控制对这个对象的访问。其核心目的是:在不修改原始对象(目标对象)代码的前提下,通过代理对象来增强或控制对目标对象的访问

一个标准的代理模式通常包含三个角色:

  • 抽象主题 (Subject): 定义了目标对象和代理对象的公共接口。这样在任何使用目标对象的地方都可以透明地使用代理对象。
  • 真实主题 (Real Subject): 即“目标对象”,是业务逻辑的真正执行者。
  • 代理 (Proxy): 持有对真实主题的引用,客户端直接与代理交互。代理可以在调用真实主题的前后,添加额外的处理逻辑。

分为静态代理和动态代理,区别在于代理对象的class文件生成的时机

静态代理:程序运行之前就创建好了代理对象

动态代理:程序运行时动态生成代理对象

其中动态代理有两种

  • jdk动态代理=>要求目标类必须实现至少一个接口。代理对象会实现与目标类相同的接口。
  • cGlib字节码增强=>当目标类没有实现接口时,Spring会使用CGLIB。它通过生成目标类的子类来创建代理,因此不能代理 final类或方法。

7.Spring AOP原理

Spring AOP是通过动态代理将横切关注点(如日志,事务)模块化,并织入到目标方法的编程范式

它的实现同时使用了jdk和CGlib

工作流程

  1. Spring容器启动时,会自动扫描被@Aspect注解的类
  2. 通过扫描切点表达式,找到所有匹配的Bean(即目标对象)
  3. 通过ProxyFactory创建目标对象对应的代理对象(jdk/cglib)并将通知封装成拦截器链
  4. 在调用代理对象方法时,拦截器链被触发,按顺序执行通知逻辑,最终调用目标方法

此外ProxyFactory有个很重要的属性proxyTargetClass,

SpringBoot默认为true,Spring默认为false

可以通过在application.properties中进行配置:spring.aop.proxy-target-class=true/false

Read more

前端静态项目快速启动:python -m http.server 4173 与 npx serve . 全解析

前端静态项目快速启动:python -m http.server 4173 与 npx serve . 全解析 在前端开发或文件共享场景中,我们经常会用到 python -m http.server 4173 和 npx serve . 这两个简单命令,它们能快速启动服务器预览前端项目,但很多人会疑惑:前端代码如此复杂,为何这两个简单命令就能实现“启动”?本文将从命令解析、工作原理、核心区别等方面全面拆解,帮你彻底弄懂背后的逻辑。 一、命令一:python -m http.server 4173 详细解释 1. 核心作用 在当前命令行所在的目录下,快速启动一个简单的HTTP文件服务器(静态文件服务器),该服务器会监听本机的4173端口,允许通过浏览器或其他HTTP客户端访问该目录下的文件及子目录。它常用来快速共享文件、本地调试简单静态网页(HTML/CSS/JS)

By Ne0inhk

Qwen3-0.6B-FP8基础教程:6亿参数+FP8量化+多语言支持详解

Qwen3-0.6B-FP8基础教程:6亿参数+FP8量化+多语言支持详解 想快速体验一个既小巧又聪明的AI助手吗?今天要介绍的Qwen3-0.6B-FP8,就是一个让你在普通电脑上也能轻松玩转大模型的“神器”。它只有6亿参数,经过FP8量化后,显存占用不到2GB,却能流畅地进行多轮对话、代码生成甚至复杂推理。 这篇文章,我就带你从零开始,手把手教你如何部署和使用这个模型,让你在10分钟内就能和它愉快地聊天。 1. 为什么选择Qwen3-0.6B-FP8? 在开始动手之前,我们先花一分钟了解一下,这个模型到底有什么特别之处,值不值得你花时间去折腾。 简单来说,Qwen3-0.6B-FP8是阿里通义千问家族的最新成员,主打一个“小而美”。它最大的亮点,就是用上了FP8量化技术。你可以把量化想象成给模型“瘦身”,在不怎么影响它“智商”(性能)的前提下,让它占用的空间(显存)大大减少。 对于咱们普通开发者或者爱好者来说,这意味着什么呢? * 门槛极低:你不再需要昂贵的专业显卡。一张显存大于2GB的消费级显卡(比如RTX 3060)甚至一些集成显卡就能跑起来。

By Ne0inhk

中兴B863AV3.1-M2卡刷固件实战:从萌虎动画到无线网卡全解析

1. 中兴B863AV3.1-M2卡刷固件入门指南 第一次接触中兴B863AV3.1-M2刷机的朋友可能会觉得有些复杂,但其实只要跟着步骤来,整个过程并不难。这个固件最大的亮点就是加入了萌虎动画和无线网卡支持,让原本功能受限的机顶盒焕发新生。 我去年第一次刷这个固件时也踩过不少坑,比如U盘格式不对、刷机按键时机没掌握好等等。后来反复尝试了几次,终于摸清了门道。现在我的盒子开机就能看到可爱的萌虎动画,还能用USB无线网卡连接WiFi,彻底摆脱了网线的束缚。 这个固件适合哪些人呢?首先你得有个中兴B863AV3.1-M2的盒子,或者兼容的魔百盒E900V22C/D系列。其次最好有些基础的刷机经验,至少知道怎么进Recovery模式。如果你是纯小白,建议先看看其他基础教程练练手。 2. 萌虎动画的实现原理与定制 2.1 萌虎动画的技术解析 这个固件最吸引人的就是那个虎年主题的开机动画了。我拆解过这个动画包,发现它其实是由一系列PNG图片组成的bootanimation.zip。这个压缩包放在/system/media/目录下,包含三个关键部分: * desc.txt:定义动

By Ne0inhk

开发者入门必看:AI 印象派艺术工坊WebUI集成部署实战推荐

开发者入门必看:AI 印象派艺术工坊WebUI集成部署实战推荐 1. 引言 1.1 业务场景描述 在图像处理与创意设计领域,将普通照片转化为具有艺术风格的画作一直是用户高度关注的功能需求。无论是社交媒体内容创作、数字艺术展示,还是个性化图像服务,一键生成艺术化图像已成为提升用户体验的重要手段。 然而,当前大多数艺术风格迁移方案依赖深度学习模型(如StyleGAN、Neural Style Transfer),存在部署复杂、资源消耗高、启动依赖网络下载等问题,尤其对初学者和轻量级应用场景不够友好。 1.2 痛点分析 传统基于深度学习的图像风格迁移面临以下挑战: * 模型体积大:动辄数百MB甚至上GB的权重文件,增加存储和加载成本。 * 部署门槛高:需要GPU支持、CUDA环境配置,不利于快速验证和本地测试。 * 启动不稳定:依赖外部模型下载,网络波动可能导致服务初始化失败。 * 可解释性差:黑盒模型难以调试,不利于二次开发和算法理解。 1.3 方案预告 本文介绍一个轻量、高效、零依赖的图像艺术化解决方案 —— AI 印象派艺术工坊(Artistic

By Ne0inhk