Java外功精要(5)——Spring AOP

Java外功精要(5)——Spring AOP
在这里插入图片描述

1.概述

面向切面编程(Aspect Orient Programming,AOP):是一种编程范式,旨在将 横切关注点(Cross-Cutting Concerns,如日志、事务、安全等) 从业务逻辑中分离出来,通过模块化的方式增强代码的可维护性和复用性。核心思想是通过“切面”定义通用功能,并在运行时动态织入到目标代码中横切关注点(Cross-Cutting Concerns):指的是在系统中"横向"跨越多个模块、多个层次的功能需求,它们无法很好地被封装在单个类或模块中

1.1 场景举例:监控业务性能

1.1.1 硬编码实现

@Slf4jpublicclassHardCoding{publicvoiddemo(){long startTime =System.currentTimeMillis();//业务代码 log.info("消耗时间:{}",System.currentTimeMillis()- startTime);}publicstaticvoidmain(String[] args){newHardCoding().demo();}}
使用这种硬编码方式监控业务性能主要有以下缺点:代码侵入性强:业务代码与监控代码耦合,修改监控代码会影响业务代码重复代码多:每个方法都要重复编写监控代码,维护困难不利于管理:监控逻辑分散,难以统一管理

容易遗漏:开发人员可能忘记添加监控代码

在这里插入图片描述

1.1.2 AOP实现

引入依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId><version>3.4.8</version><!-- 应与SpringBoot版本一致 --></dependency>
@Slf4j@RestController@RequestMapping("/demo")publicclassController{@RequestMapping("/a")publicvoidmethodA(){ log.info("执行methodA");}@RequestMapping("/b")publicvoidmethodB(){ log.info("执行methodB");}@RequestMapping("/c")publicvoidmethodC(){ log.info("执行methodC");}}
@Component@Slf4j@AspectpublicclassAOP{@Around("execution(* org.example.springaop.blog_demo.Controller.*(..))")publicObjectdoAround(ProceedingJoinPoint joinPoint)throwsThrowable{//记录开始时间long startTime =System.currentTimeMillis();//执行业务Object result = joinPoint.proceed();//计算时间 log.info("消耗时间:{}",System.currentTimeMillis()- startTime);return result;}}

执行结果

在这里插入图片描述
相较于硬编码的方式,使用Spring AOP监控业务性能有很多显著好处:代码解耦:业务代码与监控代码完全分离,易于维护统一管理:所有监控逻辑集中处理,保持一致性和规范性无侵入性:对现有代码零侵入,新增监控不影响业务逻辑

复用性强:一套监控方案可以应用到整个项目中的所有方法

在这里插入图片描述

1.2 核心术语

切入点(Pointcut):提供一个表达式,用于指定对哪些方法进行增强。例如,上图箭头切到的ABC业务的集合为一个切入点,具体能切到哪些方法与表达式有关连接点(Join Point):满足切点表达式的方法。上图中处理业务A/B/C的执行前后均为连接点(切入点包含连接点)通知(Advice):连接点的共性功能(重复的逻辑)。如上图箭头执行的逻辑切面(Aspect):定义了在何处(切入点)和何时(通知)执行额外逻辑,即切入点+通知

2.Spring AOP

2.1 @Aspect

作用:用于标识一个类为切面(Aspect)。切面包含切入点(Pointcut)和通知(Advice),用于模块化横切关注点(如日志、事务、权限等)

2.2 切入点

execution:是Spring AOP中定义切点的一种表达式,用于指定在哪些方法或类上应用通知(Advice)。它将横切关注点(如日志、事务)与业务逻辑分离,通过表达式匹配目标方法或类
//语法结构execution(<访问限定修饰符><返回类型><包名.类名.方法(方法参数)><异常>)

*:匹配任意字符(除包分隔符外)
..:匹配任意子包或多级目录;匹配任意数量参数

@Pointcut:是Spring AOP中的一个注解,用于定义一个可重用的切点表达式

2.3 通知类型

①Around注解·:最强大的通知类型,可以在目标方法执行前后完全控制其行为。它需要接收一个ProceedingJoinPoint类型参数,通过调用proceedingJoinPoint.proceed()来执行目标方法。Around通知可以修改返回值、处理异常或完全阻止目标方法执行②Before注解:在目标方法执行前触发,无法阻止方法执行(除非抛出异常)③After注解:在目标方法完成后执行,无论方法是正常返回还是抛出异常④AfterReturning注解:在目标方法正常返回后执行,可以通过访问(不能修改)返回值returning属性:指定接收返回值的参数名,参数类型必须与目标方法返回类型兼容⑤AfterThrowing注解:只在目标方法抛出异常时执行throwing属性:用于绑定目标方法抛出的异常对象
@Slf4j@RestController@RequestMapping("/demo")publicclassController{@RequestMapping("/a")publicObjectmethodA(Integer id){ log.info("执行methodA");return id;}@RequestMapping("/b")publicvoidmethodB(){ log.info("执行methodB");thrownewRuntimeException("发生异常");}}
@Component@Slf4j@AspectpublicclassAOP{@Pointcut("execution(* org.example.springaop.blog_demo.Controller.*(..))")publicvoidpointcut(){}@Around("pointcut()")//@Around("execution(* org.example.springaop.blog_demo.Controller.*(..))")publicObjectdoAround(ProceedingJoinPoint joinPoint)throwsThrowable{ log.info("doAround:业务执行前");//执行业务Object result = joinPoint.proceed(); log.info("doAround:业务执行后,result:{}", result);return"success";}@Before("pointcut()")//@Before("execution(* org.example.springaop.blog_demo.Controller.*(..))")publicvoiddoBefore(){ log.info("doBefore");}@After("pointcut()")//@After("execution(* org.example.springaop.blog_demo.Controller.*(..))")publicvoiddoAfter(){ log.info("doAfter");}@AfterReturning(value ="pointcut()",returning ="id")//@AfterReturning("execution(* org.example.springaop.blog_demo.Controller.*(..))")publicvoiddoAfterReturning(Integer id){//发生异常时不执行 log.info("doAfterReturning,id:{}", id);}@AfterThrowing(value ="pointcut()",throwing ="throwable")//@AfterThrowing("execution(* org.example.springaop.blog_demo.Controller.*(..))")publicvoiddoAfterThrowing(Throwable throwable){ log.info("doAfterThrowing,throwable:{}", throwable.getMessage());}}

2.有异常抛出时,URL:127.0.0.1:8080/demo/b

在这里插入图片描述


在这里插入图片描述

1.无异常抛出时,URL:127.0.0.1:8080/demo/a?id=1

在这里插入图片描述


在这里插入图片描述

2.4 @annotation

作用:@annotation表达式用于匹配带有指定注解的方法,是AOP中实现精准切入的关键方式之一。通过该表达式,可以拦截被特定注解标记的方法,实现逻辑增强

1.创建自定义注解

@Target(ElementType.METHOD)//注解级别:方法注解@Retention(RetentionPolicy.RUNTIME)//生命周期:运行时public@interfaceCustomizeAspect{}

2.使用@annotation表达式描述切点

//添加到切面类中@Around("@annotation(org.example.springaop.config.CustomizeAspect)")publicObjectcustomize(ProceedingJoinPoint joinPoint)throwsThrowable{ log.info("customize before");Object result = joinPoint.proceed(); log.info("customize after,result:{}", result);return"success";}

3.将自定义注解添加到连接点上

//添加到Controller类中@RequestMapping("/c")@CustomizeAspectpublicObjectmethodC(Integer id){ log.info("执行methodC");return id;}
执行结果

2.5 @Order

作用:用于指定Bean的加载顺序,其核心作用是通过数值定义优先级,数值越小优先级越高
@Component@Slf4j@Aspect@Order(1)publicclassAOP1{@Pointcut("execution(* org.example.springaop.controller.Controller.*(..))")publicvoidpointcut(){}@Around("pointcut()")//@Around("execution(* org.example.springaop.controller.Controller.*(..))")publicObjectdoAround(ProceedingJoinPoint joinPoint)throwsThrowable{ log.info("doAround1:业务执行前");//执行业务Object result = joinPoint.proceed();//计算时间 log.info("doAround1:业务执行后,result:{}", result);return"success1";}@Before("pointcut()")publicvoiddoBefore(){ log.info("doBefore1");}@After("pointcut()")publicvoiddoAfter(){ log.info("doAfter1");}@AfterReturning(value ="pointcut()",returning ="id")publicvoiddoAfterReturning(Integer id){//发生异常时不执行 log.info("doAfterReturning1,id:{}", id);}@AfterThrowing(value ="pointcut()",throwing ="throwable")publicvoiddoAfterThrowing(Throwable throwable){ log.info("doAfterThrowing1,throwable:{}", throwable.getMessage());}@Around("@annotation(org.example.springaop.config.CustomizeAspect)")publicObjectcustomize(ProceedingJoinPoint joinPoint)throwsThrowable{ log.info("customize before1");Object result = joinPoint.proceed(); log.info("customize after1,result:{}", result);return"success1";}}
@Component@Slf4j@Aspect@Order(2)publicclassAOP2{@Pointcut("execution(* org.example.springaop.controller.Controller.*(..))")publicvoidpointcut(){}@Around("pointcut()")//@Around("execution(* org.example.springaop.controller.Controller.*(..))")publicObjectdoAround(ProceedingJoinPoint joinPoint)throwsThrowable{ log.info("doAround2:业务执行前");//执行业务Object result = joinPoint.proceed();//计算时间 log.info("doAround2:业务执行后,result:{}", result);return"success2";}@Before("pointcut()")publicvoiddoBefore(){ log.info("doBefore2");}@After("pointcut()")publicvoiddoAfter(){ log.info("doAfter2");}@AfterReturning(value ="pointcut()",returning ="id")publicvoiddoAfterReturning(Integer id){//发生异常时不执行 log.info("doAfterReturning2,id:{}", id);}@AfterThrowing(value ="pointcut()",throwing ="throwable")publicvoiddoAfterThrowing(Throwable throwable){ log.info("doAfterThrowing2,throwable:{}", throwable.getMessage());}@Around("@annotation(org.example.springaop.config.CustomizeAspect)")publicObjectcustomize(ProceedingJoinPoint joinPoint)throwsThrowable{ log.info("customize before2");Object result = joinPoint.proceed(); log.info("customize after2,result:{}", result);return"success2";}}
执行结果

3.AOP底层原理

3.1 代理模式

代理模式(Proxy Pattern)通过创建一个代理对象来控制对目标对象的访问。代理对象作为目标对象的替代品,可以在访问前后添加额外逻辑(如权限控制、性能监控等)

3.2 静态代理

代理类在编译期确定,需要手动为每个目标类编写代理类

以租房为例:租客(调用方)、房东(目标对象)、中介(代理对象)

publicinterfaceIHouse{voidrent();voidsell();}
//房东publicclassRealHouseimplementsIHouse{@Overridepublicvoidrent(){System.out.println("房东出租房子");}@Overridepublicvoidsell(){System.out.println("房东售卖房子");}}
//中介publicclassHouseProxyimplementsIHouse{privatefinalIHouse realHouse;publicHouseProxy(IHouse realHouse){this.realHouse = realHouse;}@Overridepublicvoidrent(){System.out.println("开始代理"); realHouse.rent();System.out.println("结束代理");}@Overridepublicvoidsell(){System.out.println("开始代理"); realHouse.sell();System.out.println("结束代理");}publicstaticvoidmain(String[] args){IHouse iHouse =newHouseProxy(newRealHouse()); iHouse.rent(); iHouse.sell();}}
执行结果
开始代理
房东出租房子
结束代理
开始代理
房东售卖房子
结束代理

3.3 动态代理

AOP的底层原理依赖于动态代理:不需要针对每一个目标对象创建一个代理对象,而是将代理对象的创建时机推迟到程序运行时交由JVM完成

3.3.1 JDK动态代理

JDK动态代理是Java标准库提供的方式,要求目标对象必须实现接口。通过java.lang.reflect.Proxy类java.lang.reflect.InvocationHandler接口动态生成代理类,生成的代理对象和目标对象实现自同一接口(而非与目标类本身有直接继承关系),与上述静态代理类似,但把代理对象的生成时机推迟到程序运行时Proxy.newProxyInstance()是Java动态代理的核心方法,用于在运行时创建代理对象。该方法接收三个参数:ClassLoader loader:用于加载代理类的类加载器。通常传入目标类的类加载器,确保代理类与目标类在同一个类加载器环境中代理对象需要实现目标接口,其字节码由Proxy工具类动态生成。通过目标类加载器创建代理类,可保证类型系统的一致性Class<?>[] interfaces:目标对象实现的接口数组。代理类会实现这些接口,并将方法调用转发到InvocationHandlerInvocationHandler h:负责处理代理对象的方法调用
@Slf4jpublicclassJDKInvocationHandlerimplementsInvocationHandler{privatefinalObject realHouse;publicJDKInvocationHandler(Object realHouse){this.realHouse = realHouse;}@Override//Object proxy:生成的代理对象实例,Method method:被调用的目标方法对象,Object[] args:调用目标方法时传入的参数数组publicObjectinvoke(Object proxy,Method method,Object[] args)throwsThrowable{ log.info("JDK开始代理");//调用realHouse的方法Object invoke = method.invoke(realHouse, args); log.info("JDK结束代理,invoke:{}", invoke);return invoke;}}
publicclassMain{publicstaticvoidmain(String[] args){IHouse target =newRealHouse();//JDK动态代理,只能代理接口IHouse iHouse =(IHouse)Proxy.newProxyInstance(RealHouse.class.getClassLoader(),newClass[]{IHouse.class},newJDKInvocationHandler(target)); iHouse.rent(); iHouse.sell();}}
运行结果

3.3.2 CGLIB动态代理

CGLIB(Code Generation Library)是第三方库,基于字节码增强(动态生成目标类的子类字节码,重写方法逻辑),通过继承方式实现代理,不要求目标对象实现接口。通过org.springframework.cglib.proxy.Enhancer类动态生成目标类的子类作为代理
//interface MethodInterceptor extends Callback@Slf4jpublicclassCGLibMethodInterceptorimplementsMethodInterceptor{privatefinalObject realHouse;publicCGLibMethodInterceptor(Object realHouse){this.realHouse = realHouse;}@Override//Object obj:动态生成的代理对象实例,Method method:当前被拦截的目标方法对象,Object[] args:方法调用时传入的参数数组,MethodProxy proxy:方法代理对象publicObjectintercept(Object obj,Method method,Object[] args,MethodProxy proxy)throwsThrowable{ log.info("开始代理");IHouse result =(IHouse)method.invoke(realHouse, args); log.info("结束代理");return result;}}
publicclassMain{publicstaticvoidmain(String[] args){RealHouse target =newRealHouse();RealHouse realHouse =(RealHouse)Enhancer.create(RealHouse.class,newCGLibMethodInterceptor(target)); realHouse.rent(); realHouse.sell();}}
执行结果

Read more

OpenClaw 实战:让 AI 拥有“眼睛“——摄像头访问完全指南

OpenClaw 实战:让 AI 拥有“眼睛“——摄像头访问完全指南

今天冒出个想法,想让openclaw能控制摄像头分析图片。原因是我有本书,网上还没有电子版,想让openclaw分析然后把重点内容讲给我听。 📖让运行在 WSL2 里的 OpenClaw AI 助手能够"看见"摄像头画面。 🚧 探索过程 第一阶段:OpenClaw Node 配对(失败)折腾了 3 小时+,最终因为 WSL2 网络隔离问题放弃。 我在wsl里安了openclaw,他说要控制摄像头,必须在windows上安装node.js,安装npm,折腾了好久,就是报错。结论就是windows和wsl就是隔离的。 具体过程: **安装 Node.js:** 最开始下载了绿色版 Node.js(v24.14.0),遇到了一系列问题: ```powershell # 绿色版 Node.js

By Ne0inhk
你以为你在部署 AI 助手,其实也可能在打开一扇“数据侧门”:OpenClaw 安全风险全解析

你以为你在部署 AI 助手,其实也可能在打开一扇“数据侧门”:OpenClaw 安全风险全解析

🔥 个人主页:杨利杰YJlio❄️ 个人专栏:《Sysinternals实战教程》《Windows PowerShell 实战》《WINDOWS教程》《IOS教程》《微信助手》《锤子助手》《Python》《Kali Linux》《那些年未解决的Windows疑难杂症》🌟 让复杂的事情更简单,让重复的工作自动化 你以为你在部署 AI 助手,其实也可能在打开一扇“数据侧门”:OpenClaw 安全风险全解析 * * 1、你以为你在装 AI 助手,其实你可能在给系统加一个“高权限自动化入口” * 2、OpenClaw 和普通 AI 最大的区别,到底在哪里? * 3、我为什么说:OpenClaw 更像“拿到部分权限的数字操作员”? * 4、为什么说 AI 助手不是“更聪明的搜索框”? * 5、OpenClaw 的 5

By Ne0inhk
人工智能:大模型高效推理与部署技术实战

人工智能:大模型高效推理与部署技术实战

人工智能:大模型高效推理与部署技术实战 1.1 本章学习目标与重点 💡 学习目标:掌握大语言模型推理与部署的核心技术,理解模型量化、推理加速、服务化部署的原理,能够完成开源大模型的高性能生产级部署。 💡 学习重点:精通INT4/INT8量化技术的应用,掌握vLLM等高性能推理框架的使用方法,学会搭建高并发的大模型API服务。 1.2 大模型推理部署的核心挑战 1.2.1 大模型推理的痛点分析 💡 预训练大模型通常具备数十亿甚至上百亿的参数量,直接进行推理会面临显存占用高、推理速度慢、并发能力弱三大核心问题。 * 显存占用高:以LLaMA-2-7B模型为例,FP16精度下显存占用约14GB,单张消费级显卡难以承载;而70B模型FP16精度显存占用更是超过140GB,普通硬件完全无法运行。 * 推理速度慢:自回归生成的特性导致模型需要逐token计算,单条长文本生成可能需要数十秒,无法满足实时应用需求。 * 并发能力弱:传统推理方式下,单卡同时处理的请求数极少,高并发场景下会出现严重的排队和延迟问题。 这些问题直接制约了大模型从实验室走向实际生产环境,因此高效

By Ne0inhk
Flutter 组件 tavily_dart 的适配 鸿蒙Harmony 深度进阶 - 驾驭 AI 原生聚合搜索、实现鸿蒙端跨域知识发现与垂直领域语义降噪方案

Flutter 组件 tavily_dart 的适配 鸿蒙Harmony 深度进阶 - 驾驭 AI 原生聚合搜索、实现鸿蒙端跨域知识发现与垂直领域语义降噪方案

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 tavily_dart 的适配 鸿蒙Harmony 深度进阶 - 驾驭 AI 原生聚合搜索、实现鸿蒙端跨域知识发现与垂直领域语义降噪方案 前言 在前文中,我们领略了 tavily_dart 在鸿蒙(OpenHarmony)生态中实现基础互联网 AI 搜索集成的魅力。但在真正的“跨国科研智能辅助”、“政务决策舆情态势感知”以及“需要接入高精密专业数据库”的场景中。简单的单次查询往往不足以触达知识的核心。面对需要在大规模并发环境下,针对特定行业域名(如 .gov / .edu)执行深层内容的并行嗅探,并且要求对回显的数万字内容执行基于 AI 强语义的重排序(Re-ranking)与引用链路审计的高阶需求。如果缺乏一套完善的聚合搜索策略与语义降噪模型。不仅会导致 AI 智能体出现由于“信息泛滥”

By Ne0inhk