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

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
【Unity-AI开发篇】| Unity-MCP最新指南:让AI接管游戏开发

【Unity-AI开发篇】| Unity-MCP最新指南:让AI接管游戏开发

* 前言 * 【Unity-AI开发篇】| Unity-MCP最新指南:让AI接管游戏开发 * 一、🧐 MCP是什么? * 1.1 MCP介绍 * 1.2 为什么要配置MCP? * 1.3 效果展示 * 1.4 使用说明及下载 * 二、🚀MCP安装步骤 * 2.1 前提条件 * 2.2 安装 Unity-MCP包(桥接组件) * 2.2 MCP配置 * 三、🎈Trae配置 * 3.1 添加MCP配置 * 3.2 创建一个智能体并添加Unity-MCP * 3.3 使用AI开发功能 * 总结 前言 * 在人工智能飞速发展的今天,大语言模型早已不仅限于聊天和文本生成。 * 它们开始能够使用工具,与环境进行交互,从而执行复杂任务。 * 对于广大游戏开发者而言,

By Ne0inhk
2026本地AI Agent王炸组合:Qwen3.5+OpenClaw,30分钟打造你的24小时赛博打工人

2026本地AI Agent王炸组合:Qwen3.5+OpenClaw,30分钟打造你的24小时赛博打工人

文章目录 * 前言 * 一、先搞明白:啥是长了手脚的AI? * 二、Qwen3.5:不只是参数大,而是「眼力见儿」好 * 三、OpenClaw:给AI装上「手脚」的脚手架 * 四、实战:30分钟搭一个「赛博秘书」 * 4.1 环境准备:两条路线任选 * 路线A:本地Ollama部署(零费用,适合N卡用户) * 路线B:阿里云百炼云端(即开即用,适合A卡/集显用户) * 4.2 安装OpenClaw本体 * 4.3 配置「大脑」连接 * 4.4 给AI「装手装脚」 * 五、玩法进阶:从「问答」

By Ne0inhk
(第二篇)Spring AI 基础入门:从环境搭建到模型接入全攻略(覆盖国内外模型 + 本地部署)

(第二篇)Spring AI 基础入门:从环境搭建到模型接入全攻略(覆盖国内外模型 + 本地部署)

前言:为什么要学 Spring AI?         最近在做 AI 应用开发时,发现很多朋友卡在了工具链整合这一步:用原生 SDK 调用 OpenAI 要处理一堆 HTTP 请求,切换到通义千问又得改大量代码,本地部署 Llama3 更是不知道怎么和 Spring 项目结合…         直到接触了 Spring AI 才发现,这个框架简直是为 Java 开发者量身定做的 AI 开发工具 —— 它把不同模型的调用逻辑标准化了,不管是 OpenAI、通义千问还是本地 Llama3,都能用几乎一样的 API 调用。         这篇教程从基础环境讲到实战接口,全程手把手操作,哪怕是 AI 开发新手,跟着走也能跑通第一个 Spring AI 应用。 目录 基础环境搭建:JDK17+

By Ne0inhk