Spring学习笔记12 面向切面编程AOP
Spring学习笔记11 GoF代理模式_biubiubiu0706的博客-CSDN博客
AOP(Aspect Oriented Programming):面向切面编程,面向方面编程.
AOP是对OOP的补充延申.底层使用动态代理实现.
Spring的AOP使用的动态代理是:JDK动态代理_CGLIB动态代理技术.Spring在这两种动态代理中灵活切换.如果是代理接口,会默认使用JDK动态代理,如果要代理某个类,这个类没有实现接口,就会切换使用CGLIB.当然,也可以强制通过一些配置让Spring只使用CGLIB
日志,事务管理,安全这些交叉业务(非业务代码)都属于AOP
AOP是一种思想,JDK代理,CGLIB代理都是AOP思想的实现,Spring AOP底层用的就是JDK代理和CGLIB代理
面向切面编程的七大术语:
连接点 Joinpoint:在程序的整个执行流程中,可以织入切面的位置.方法的执行前,异常抛出之后等位置.----->我的理解连接点就是用户需要被扩展的方法,其实我们将自定义注解放到目标方法上做标识,那么该注解其实就是一个连接点 连接点描述的是位置
切入点 Pointcut:程序执行流程中,真正织入切面的方法.(一个切入点对应多个连接点).---->我的理解:用户实际扩展的方法,确定了连接点,那么该方法就是一个切入点
通知 Advice: 扩展方法的具体实现
1.前置通知:在目标方法执行之前执行 @Before
2.后置通知:在目标方法执行之后,返回时执行 @AfterReturning
3.环绕通知:在目标方法执行前后,都要执行的通知 也可以控制方法是否执行 @Around
4.异常通知:在目标方法执行之后,抛出异常时执行 @AfterThrowing
5.最终通知:无论程序是否执行成功,都要最后执行的通知 @After
切面 Aspect:切入点+通知就是切面
织入 Weaving:把通知应用到目标对象的过程
代理对象 Proxy:一个目标对象被织入通知后产生的新对象
目标对象 Target:被织入通知的对象
切入点表达式:
概念:当程序满足切入点表达式,才能进入切面,执行通知方法
1.bean("Bean的id") 根据Bean名称 进行拦截 只能匹配一个
2.within("com.example") 包名+类名 可以使用通配符*? 能匹配多个
3.execution(返回值类型 包名.类名.方法名(参数列表))
4.annotation(包名.注解名)
使用SpringAOP
Spring对AOP的实现包括已下3种方式:
第一种方式:Spring框架结合AspectJ框架实现的AOP,基于注解方式
第二种方式:Spring框架结合AspectJ框架实现的AOP,基于XML方式
第三种方式:Spring框架自己实现的AOP,基于XML配置方式
实际开发中,都是Spring+AspectJ来实现AOP,重点第一种和第二种方式.
准备环境
新建模块spring-aspectj-xml
导入依赖
<dependencies><!--Spring依赖种包含Spring-aop依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.0.10</version></dependency><!--Spring aspectj依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>6.0.10</version></dependency><!--junit依赖--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope></dependency> </dependencies>
Spring配置文件中添加context命名空间和aop命名空间
先搭个架子
配置文件 注意下 这个注释可能没写对
默认情况下,<aop:aspectj-autoproxy />
会使用Spring的JDK动态代理来代理目标对象。这意味着如果你的目标对象实现了至少一个接口,Spring将会使用JDK动态代理。这种代理方式仅代理实现了接口的方法。
如果目标对象没有实现任何接口,Spring将会使用CGLIB(Code Generation Library)来创建一个子类代理。CGLIB允许代理类继承目标对象,因此可以代理非接口类型的目标对象。
<aop:aspectj-autoproxy proxy-target-class="true" />
是强制使用CGLIB代理意思
测试,当调用业务层login方法时候
业务层新增一个方法
如果我在切入点表达式中修改下
可见切入点表达式很重要
新加一个业务类
修改切入点表达式
测试
下面引入所有通知
将UserService注释掉 避免看着混乱
package com.example.aspect;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;/*** @author hrui* @date 2023/9/26 0:33*/
@Component
@Aspect//标注是一个切面
public class LogAspect {//切面//切面=切入点+通知//通知就是增强,就是具体要编写的增强代码//前置通知 方法执行之前@Before("execution(* com.example.service..*(..))")//里面要写切入点表达式 UserService里的所有方法public void 增强(){System.out.println("我是一个通知,我是一段增强代码");}//后置通知 方法执行之后返回时执行@AfterReturning("execution(* com.example.service..*(..))")public void afterAdvice(){System.out.println("方法执行之后通知");}//环绕通知 目标方法执行之前和执行之后 并且在前置通知之前 在后置通知之后@Around("execution(* com.example.service..*(..))")public Object aroundAdvice(ProceedingJoinPoint joinPoint){//注意多参情况ProceedingJoinPoint要放第一位Object result = null;try {System.out.println("执行方法前");//调用目标方法result=joinPoint.proceed();System.out.println("执行方法后");} catch (Throwable throwable) {throwable.printStackTrace();}finally {}return result;}//异常通知 发生异常之后@AfterThrowing(value = "execution(* com.example.service..*(..))",throwing = "e")//当目标方法执行时,抛出异常时,可以用AfterThrowing记录public void afterThrowingAdvice(){System.out.println("报错了我就执行了");}//最终通知asd@After("execution(* com.example.service..*(..))")public void after(){System.out.println("最后我肯定会执行");}}
当系统有多个切面时候
加个切面
可以用@Order排序 数字小在前面
测试
通用切入点
切面上的代码,每个切入点都要写切入点表达式,代码冗余
package com.example.aspect;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;/*** @author hrui* @date 2023/9/26 0:33*/
@Component
@Aspect//标注是一个切面
@Order(2)
public class LogAspect {//切面//切面=切入点+通知//通知就是增强,就是具体要编写的增强代码@Pointcut("execution(* com.example.service..*(..))")public void 通用切点(){//这个方法只是一个标记,方法名随意,方法体也不需要写任何代码}//前置通知 方法执行之前@Before("通用切点()")//里面要写切入点表达式 UserService里的所有方法public void 增强(){System.out.println("我是一个通知,我是一段增强代码");}//后置通知 方法执行之后返回时执行@AfterReturning("通用切点()")public void afterAdvice(){System.out.println("方法执行之后通知");}//环绕通知 目标方法执行之前和执行之后 并且在前置通知之前 在后置通知之后@Around("通用切点()")public Object aroundAdvice(ProceedingJoinPoint joinPoint){//注意多参情况ProceedingJoinPoint要放第一位Object result = null;try {System.out.println("执行方法前");//调用目标方法result=joinPoint.proceed();System.out.println("执行方法后");} catch (Throwable throwable) {throwable.printStackTrace();}finally {}return result;}//异常通知 发生异常之后@AfterThrowing(value = "通用切点()",throwing = "e")//当目标方法执行时,抛出异常时,可以用AfterThrowing记录public void afterThrowingAdvice(){System.out.println("报错了我就执行了");}//最终通知asd@After("通用切点()")public void after(){System.out.println("最后我肯定会执行");}}
测试
范式
package com.jt.aop;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;import java.util.Arrays;/*** AOP(面向切面编程) 主要利用**动态代理**的模式 **降低程序的耦合度,扩展业务功能方法.*** 1.AOP需要被Spring容器管理 @Component* 2.标识该类是AOP切面 @Aspect* 关于AOP名词介绍* 1).连接点: 用户可以被扩展的方法 其实我们将自定义注解放到目标方法上做标识,那么该注解其实就是个连接点* 2).切入点: 用户实际扩展的方法 确定了连接点,那么该方法也就是个切入点* 3).通知: 扩展方法的具体实现 5个通知* 4).切面: 将通知应用到切入点的过程** 通知类型(必会)* 1. before: 在目标方法执行之前执行* 2. afterReturning: 在目标方法执行之后返回时执行* 3. afterThrowing: 在目标方法执行之后,抛出异常时执行* 4. after: 无论程序是否执行成功,都要最后执行的通知* 5. around: 在目标方法执行前后 都要执行的通知(完美体现了动态代理模式)* 功能最为强大 只有环绕通知可以控制目标方法的执行** 关于通知方法总结:* 1.环绕通知是处理业务的首选. 可以修改程序的执行轨迹* 2.另外的四大通知一般用来做程序的监控.(监控系统) 只做记录* @author TB* @date 2020/2/12 0:24*/
@Component
//虽然标识了该类为AOP切面 但是Spring容器默认不能识别切面注解,需要手动配置
//需要在配置类SpringConfig里加上注解@EnableAspectJAutoProxy
@Aspect
public class SpringAOP {/*** 切入点表达式* 概念:当程序满足切入点表达式,才能进入切面,执行通知方法.** 1.bean("bean的ID") 根据beanId进行拦截 只能匹配一个* 2.within("包名.类名") 可以使用通配符*? 能匹配多个.* 粒度: 上述的切入点表达式 粒度是类级别的. 粗粒度.* 3.execution(返回值类型 包名.类名.方法名(参数列表...))* 粒度: 控制的是方法参数级别. 所以粒度较细. 最常用的.* 4.@annotation(包名.注解名) 只拦截注解.* 粒度: 注解是一种标记 根据规则标识某个方法/属性/类 细粒度*//*** 切入点表达式练习* within:* 1.within(com.jt.*.DeptServiceImpl) 一级包下的类* 2.within(com.jt..*.DeptServiceImpl) ..代表多级包下的类* 3.within(com.jt..*) 包下的所有的类** execution(返回值类型 包名.类名.方法名(参数列表))* 1.execution(* com.jt..*.DeptServiceImpl.add*())* 注释: 返回值类型任意的, com.jt下的所有包中的DeptServiceImpl的类* 的add开头的方法 ,并且没有参数.** 2.execution(* com.jt..*.*(..))* 注释: 返回值类型任意,com.jt包下的所有包的所有类的所有方法 任意参数.** 3.execution(int com.jt..*.*(int))* 4.execution(Integer com.jt..*.*(Integer))* 强调: 在Spring表达式中没有自动拆装箱功能! 注意参数类型** @annotation(包名.注解名)* @Before("@annotation(com.jt.anno.Cache)")* 只拦截特定注解的内容.*///1.定义before通知//@Before("bean(deptServiceImpl)")//扫描的是一个类 因此该类里所有方法都被扩展到了//@Before("within(com.jt.service.DeptServiceImpl)")//和上面效果一样//@Before("execution(* com.jt.service.DeptServiceImpl.add*())")//*表示返回值类型任意 add*表示以add开头的方法名 最后()表示参数是空的//@Before("@annotation(com.jt.anno.Cache)")//意思有该注解 就作为切入点 因此用注解标识最常用(自定义个注解)/*** spring为了AOP动态获取目标对象及方法中的数据,则通过Joinpoint* JoinPoint是所有通知的公共参数,无论哪种通知里都可以使用* 在Before里可以获取* 对象做数据传递获取如:* 1.获取目标对象的类型* 2.获取目标方法的名称* 3.获取目标方法的参数* @param joinPoint*/@Before("pointcut()")public void before(JoinPoint joinPoint){//前置方法一般作用获取参数,方法名,等等System.out.println("目标对象的Class类对象: "+joinPoint.getTarget().getClass());System.out.println("获取目标方法的方法签名: "+joinPoint.getSignature());System.out.println("获取目标对象的类名: "+ joinPoint.getSignature().getDeclaringTypeName());System.out.println("获取目标对象方法名: "+ joinPoint.getSignature().getName());System.out.println("获取目标方法参数: "+ Arrays.toString(joinPoint.getArgs()));System.out.println("我是before通知");}//1.定义一个切入点@Pointcut("@annotation(com.jt.anno.Cache)")public void pointcut(){}//如果每个通知前都加个切入点表达式 那么也太冗余了 因此我们可以定义个切入点 其他通知都围绕切入点//@BafterReturning("@annotation(com.jt.anno.Cache)")/*** JoinPoint参数是所有通知方法公有的*AfterReturning是目标方法返回执行之后返回时执行* 可以记录方法的返回值* AfterReturning注解里 value和pointcut是相同的效果:也就是说* @AfterReturning(value="pointcut()",returning="result")和@AfterReturning(pointcut="pointcut()",returning="result")* 效果一样* returning:将方法的返回值,通过形参result(这个随便取名)来进行传递(Spring会将返回值赋值给你定义的这个变量)*/@AfterReturning(value="pointcut()",returning="result")public void afterReturning(JoinPoint joinPoint,Object result){//这里注意 如果有需要用到JointPoint参数 那么必须放在第一个位置 不用可以去掉System.out.println("目标返回值结果是: "+result);System.out.println("我是AfterReturning的通知");}@AfterThrowing(pointcut = "pointcut()",throwing="e")//当目标方法执行时,抛出异常时,可以用AfterThrowing记录public void afterThrowing(Exception e){System.out.println("获取目标异常信息: "+e.getMessage());System.out.println("获取目标异常类型: "+e.getClass());System.out.println("我是AfterThrowing的通知,出现异常了");}@After("pointcut()")public void after(){System.out.println("我是After的通知");}/*** 关于环绕通知的说明* 作用: 可以控制目标方法是否执行.* 参数: ProceedingJoinPoint 通过proceed方法控制目标方法执行.* 注意事项:* ProceedingJoinPoint 只能适用环绕通知* @return*/@Around("pointcut()")public Object around(ProceedingJoinPoint joinPoint){//注意多参情况ProceedingJoinPoint要放第一位Object result = null;try {System.out.println("环绕通知开始");//1.执行下一个通知 2.执行目标方法 3.接收返回值Long start = System.currentTimeMillis();result = joinPoint.proceed();Long end = System.currentTimeMillis();System.out.println("耗时:"+(end-start));} catch (Throwable throwable) {throwable.printStackTrace();}System.out.println("环绕通知结束");return result;}
}
关于全注解式开发,不用Spring配置文件
测试也需要改下
Spring AOP基于XML方式的实现
新建模块spring-aspectj-xml2
依赖
<dependencies><!--Spring依赖种包含Spring-aop依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.0.10</version></dependency><!--Spring aspectj依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>6.0.10</version></dependency><!--junit依赖--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope></dependency> </dependencies>
目标类
切面类
相关文章:

Spring学习笔记12 面向切面编程AOP
Spring学习笔记11 GoF代理模式_biubiubiu0706的博客-CSDN博客 AOP(Aspect Oriented Programming):面向切面编程,面向方面编程. AOP是对OOP的补充延申.底层使用动态代理实现. Spring的AOP使用的动态代理是:JDK动态代理_CGLIB动态代理技术.Spring在这两种动态代理中灵活切换.如…...
【0225】源码分析postgres磁盘块(disk block)定义
相关阅读: 【0040】 PostgreSQL数据库表文件底层结构布局分析 1. postgres磁盘块定义 在学习本文之前,需要对关系表的结构原理有一定的理解。如果不清楚PG磁盘数据表文件的布局,可阅读:...

第九章 动态规划 part11 123. 买卖股票的最佳时机III 188. 买卖股票的最佳时机IV
第五十天| 第九章 动态规划 part11 123. 买卖股票的最佳时机III 188. 买卖股票的最佳时机IV 一、123. 买卖股票的最佳时机III(难难难难难) 题目链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/ 题目介绍ÿ…...

阿里云服务器共享型和企业级独享有什么区别?
阿里云ECS云服务器共享型和企业级有什么区别?企业级就是独享型,共享型和企业级云的主要区别CPU调度模式,共享型是非绑定CPU调度模式,企业级是固定CPU调度模式,共享型云服务器在高负载时计算性能可能出现波动不稳定&…...

Vue.js基本语法上
🎬 艳艳耶✌️:个人主页 🔥 个人专栏 :《Spring与Mybatis集成整合》《springMvc使用》 ⛺️ 生活的理想,为了不断更新自己 ! 目录 1.插值 1.1 文本 1.2 v-v-html 1.3 数据双向绑定数据(v-model) 1.4 属性ÿ…...
【1333. 餐厅过滤器】
来源:力扣(LeetCode) 描述: 给你一个餐馆信息数组 restaurants,其中 restaurants[i] [idi, ratingi, veganFriendlyi, pricei, distancei]。你必须使用以下三个过滤器来过滤这些餐馆信息。 其中素食者友好过滤器 v…...
wifi7有关的210个提案
[1] TGbe, “Compendium of motions related to the contents of the TGbe specification framework document,” 19/1755r8, September 2020. [2] Bin Tian (Qualcomm), “Discussion on 11be PHY capabilities,” 20/0975r0, July 2020. [3] TGbe, “Compendiu…...

200行C++代码写一个Qt俄罗斯方块小游戏
小小演示一下: 大体思路: 其实很早就想写一个俄罗斯方块了,但是一想到那么多方块还要变形,还要判断落地什么的就脑壳疼。直到现在才写出来。 俄罗斯方块这个小游戏的小难点其实就一个,就是方块的变形,看似…...

蓝桥杯每日一题20223.9.26
4407. 扫雷 - AcWing题库 题目描述 分析 此题目使用map等都会超时,所以我们可以巧妙的使用哈希模拟散列表,哈希表初始化为-1首先将地雷读入哈希表,找到地雷的坐标在哈希表中对应的下标,如果没有则此地雷的位置第一次出现&#…...

查看基站后台信息
查看基站后台信息 电脑配置固定ip: 192.168.1.99: 打开“网络和共享中心”,选择更改适配器设置: 右键“本地连接”,选择属性 基站网线直连电脑网口 Telnet 登录基站 打开dos窗口 windows键R”,输入cmd,点确定&…...

关于坐标的旋转变换和坐标系的旋转变换
不管是坐标的旋转变换还是坐标系下的旋转变换,只和旋转的顺时针和逆时针有关。然坐标系间的顺时针和逆时针是根据当前坐标系在目标坐标系下的相对位置确定。 一。逆时针旋转belta角度的公式 二。顺时针旋转belta角度的公式 三。坐标的旋转变换 1.坐标的旋转变换相…...

2023.9.19 关于 数据链路层 和 DNS 协议 基本知识
目录 数据链路层 MTU DNS 协议 补充 DHCP协议 数据链路层 基本概念: 考虑相邻两个节点之间的传输(通过 网线 / 光纤 / 无线 直接相连的两个设备)以太网协议 规定了 数据链路层 和 物理层 的内容 IP地址 与 mac地址 的相互配合 IP地址 描…...
如何保证接口幂等性
简介 接口幂等性就是说用户使用相同的参数请求同一个接口无论是一次还是多次都应该是一样的。不会因为多次的点击产生不同效果。 举个栗子:一个用户在手机APP上提200块钱,然后一不小心点击了两次,那么就应该只提取出200块钱,不应…...

搭建智能桥梁,Amazon CodeWhisperer助您轻松编程
零:前言 随着时间的推移,人工智能技术以惊人的速度向前发展,正掀起着全新的编程范式革命。不仅仅局限于代码生成,智能编程助手等创新应用也进一步提升了开发效率和代码质量,极大地推动着软件开发领域的快速繁荣。 当前…...

数组和指针笔试题解析之【指针】
目录 🍂笔试题1: 🍂笔试题2: 🍂笔试题3: 🍂笔试题4: 🍂笔试题5: 🍂笔试题6: 🍂笔试题7: 🍂笔试题…...

【Linux】之Centos7卸载KVM虚拟化服务
👨🎓博主简介 🏅云计算领域优质创作者 🏅华为云开发者社区专家博主 🏅阿里云开发者社区专家博主 💊交流社区:运维交流社区 欢迎大家的加入! 🐋 希望大家多多支…...

智能电力运维系统:数字化转型在电力行业的关键应用
随着信息技术、人工智能等的飞速发展,数字化改造已成为各行各业的重要发展趋势。在电力行业中,智能电力运维系统是数字化转型的关键应用之一。 力安科技智能电力运维系统是一种集自动化、智能化、云计算、物联网等先进技术于一体的电力运维管理解决方…...
eslint报错:no-empty-source
问题: 提交代码时,eslint校验没有通过,报错 esc[2m183:9esc[22m esc[31mesc[31m✖esc[39m Unexpected empty source esc[2mno-empty-sourceesc[22m 1 problem (esc[31m1 erroresc[39m, esc[33m0 warnings esc[39m) 原因: …...
图论17(Leetcode864.获取所有钥匙的最短路径)
用二进制表示获得的钥匙,假设n钥匙个数 000000000代表没有钥匙,0000000001代表有idx为1的钥匙,0000000011代表有idx1,2的钥匙 (这方法巧妙又复杂.. 代码: class Solution {static int[][] dirs {{-1,0}…...

vue 脚手架 入门 记录
vue 脚手架 入门 记录 以管理员身份运行PowerShell执行:get-ExecutionPolicy,回复Restricted,表示状态是禁止的 3.执行:set-ExecutionPolicy RemoteSigned 4.选择Y 注意:一定要以管理员的身份运行PowerShellÿ…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...

工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...

全球首个30米分辨率湿地数据集(2000—2022)
数据简介 今天我们分享的数据是全球30米分辨率湿地数据集,包含8种湿地亚类,该数据以0.5X0.5的瓦片存储,我们整理了所有属于中国的瓦片名称与其对应省份,方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...

跨链模式:多链互操作架构与性能扩展方案
跨链模式:多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈:模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展(H2Cross架构): 适配层…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...