Spring AOP从入门到精通
目录
1. AOP的演化过程
1. 代理模式
2. 动态代理
2.1 JDK动态代理
2.2 Cglib动态代理
3. Spring模式
3.1 ProxyFactory
3.2 ProxyFactoryBean
3.3 AbstractAutoProxyCreator
2. Spring AOP抽象
1. 核心术语
1.1 连接点(JoinPoint)
1.2 切点(Pointcut)
1.3 增强(Advice)
1.4 切面(Aspect)
1.5 目标对象(Target)
1.6 代理对象(Proxy)
2. 核心组件
2.1 AutoProxyCreator工作原理
2.2 @EnableAspectJAutoProxy工作原理
3. Spring AOP案例
1. AOP的演化过程
编程中我们经常会遇到这样的场景,有一段通用逻辑横跨多个业务,不能用继承来解决,比如方法耗时、数据库事务、通用日志等等。一个典型的DAO方法调用分为下面3个步骤:
每一次都手工的开启和提交事务,即显得啰嗦,又影响业务的可读性。AOP就十分擅长解决这类问题,它把开启和提交事务的逻辑抽取到Advice当中,在任务DAO上复用。那么AOP到底是怎么实现的呢?
1. 代理模式
熟悉设计模式的人这时候会想到代理模式,通过新增一个代理类实现,负责处理事务的开启和提交。假设我们要处理的是下面的类图,Staff是我们要处理的实体类,StaffDao是接口,定义两个方法employ、paySalary,StaffDaoImpl是实际的DAO实现,StaffDaoProxy是代理类,负责事务开启和提交。

Dao的操作用伪代码实现,StaffDaoImpl和代理类,核心代码如下
public class StaffDaoImpl implements StaffDao {public void employ(Staff p) {System.out.println("employ staff: " + p);}public void paySalary(Staff p) {System.out.println("pay salary :" + p);}
}
public class StaffDaoProxy implements StaffDao {private StaffDao staffDao;public StaffDaoProxy(StaffDao staffDao) {this.staffDao = staffDao;}public void employ(Staff p) {System.out.println("start transaction...");staffDao.employ(p);System.out.println("commit transaction...");}public void paySalary(Staff p) {System.out.println("start transaction...");staffDao.paySalary(p);System.out.println("commit transaction...");}
}
通过如下代码进行测试,StaffDaoImpl只是完成了数据操作,StaffDaoProxy会额外事务开启和提交操作
Staff staff = new Staff("zhangsan",18); StaffDao staffDao = new StaffDaoImpl(); // 直接调用
staffDao.employ(staff);
staffDao.paySalary(staff);StaffDaoProxy proxy = new StaffDaoProxy(staffDao); // 通过代理类使用
proxy.employ(staff);
proxy.paySalary(staff);
2. 动态代理
代理模式确实达成了我们想要的效果,如果每一个需要横切逻辑的类都需要通过代理模式类实现的话,人力成本过高,而且也不便于维护,后续每一次修改都需要改大量的代理类。通过JDK提供的动态代理,或者字节码操作的第三方库能解决这个问题。
2.1 JDK动态代理
我们来看一个通过JDK的Proxy类实现动态代理的示例,首先要定义一个InvocationHandler的实现
public class TransactionInvocationHandler implements InvocationHandler {private StaffDao staffDao;public TransactionInvocationHandler(StaffDao staffDao) {this.staffDao = staffDao;}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("start transaction...");Object result = method.invoke(staffDao,args);System.out.println("commit transaction...");return result;}
}
接着就可以通过Proxy.newInstance来创建代理类实例,并测试,其中staff和staffDao的实例和代理模式里的创建方式一样,通过proxy.employ和proxy.paySalary调用输出也同之前的案例。
InvocationHandler invocationHandler = new TransactionInvocationHandler(staffDao);
StaffDao proxy = (StaffDao) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{StaffDao.class},invocationHandler);
proxy.employ(staff);
proxy.paySalary(staff);
2.2 Cglib动态代理
JDK动态代理存在的一个问题是,生成代理对象必须基于接口,如果一个类没有实现接口就无法创建代理。好在还有大量的第三方库支持无接口的类生成动态代理。我们来看一个cglib的示例。和JDK代理类似,首先我们要实现一个回调类,不过cglib里回调类接口是MethodInterceptor。
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;import java.lang.reflect.Method;public class TransactionMethodInterceptor implements MethodInterceptor {private StaffDao staffDao;public TransactionMethodInterceptor(StaffDao staffDao) {this.staffDao = staffDao;}public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("start transaction...");Object result = method.invoke(staffDao, args);System.out.println("commit transaction...");return result;}
}
紧接着使用Enhancer类创建代理类对象,并使用测试,也能得到同样的测试结果。
TransactionMethodInterceptor interceptor = new TransactionMethodInterceptor(staffDao);
Enhancer enhancer = new Enhancer();
enhancer.setCallback(interceptor);
enhancer.setSuperclass(StaffDao.class);
StaffDao proxy = (StaffDao) enhancer.create();
3. Spring模式
动态代理解决代理模式带来的部分问题,我不需要再为每个类手动创建子类,所有横切逻辑都集中到InvocationHandler或MethodInterceptor中了。但是每次使用的时候都需要手工创建代理对象,还是比较麻烦。Spring基于自己的能力对这个问题做了简化
3.1 ProxyFactory
Spring提供了ProxyFactory来简化代理对象的创建,频闭通过JDK或Cglib创建代理对象的差异(涉及类如下图),除此以外并没有其他改善。
同样需要实现一个回调类,实现aopaliance提供的MethodInterceptor接口,具体代码如下
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;public class SpringTransactionMethodInterceptor implements MethodInterceptor {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {System.out.println("start transaction...");Object result = invocation.proceed();System.out.println("commit transaction...");return result;}
}
然后使用ProxyFactory来创建代理对象,并进行测试,测试结果同之前的示例
ProxyFactory factory = new ProxyFactory();
factory.setTarget(staffDao);
factory.addAdvice(new SpringTransactionMethodInterceptor());
StaffDao proxy = (StaffDao) factory.getProxy();Staff staff = new Staff("zhangsan", 18);
proxy.employ(staff);
proxy.paySalary(staff);
3.2 ProxyFactoryBean
到目前为止我们依然需要手动创建代理类对象,通过Spring本身的FactoryBean机制,Spring AOP为我们提供了ProxyFactoryBean实现。首先通过xml配置Bean实现
<bean id="staffDao" class="com.lws.designPattern.StaffDaoImpl"/>
<bean id="springTransaction" class="com.lws.spring.SpringTransactionMethodInterceptor"/>
<bean id="staffDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean"><property name="target" ref="staffDao"/><property name="interceptorNames"><list><value>springTransaction</value></list></property>
</bean>
接着通过创建ApplicationContext,获取Bean实例,测试代码如下
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
StaffDao proxy = context.getBean("staffDaoProxy", StaffDao.class);
proxy.employ(staff);
proxy.paySalary(staff);
3.3 AbstractAutoProxyCreator
ProxyFactoryBean已经基本可以使用了,只是每一个Bean要做代理时都需要手工配置一个ProxyFactoryBean,使用起来还是略显繁琐。Spring提供了AbstractAutoProxyCreator来完成AOP代理的自动创建。这里我们以BeanNameAutoProxyCreator为例,看看AutoProxyCreator是如何使用,它是怎么工作的。同样先基于xml配置BeanNameAutoProxyFactory,使用相同interceptorNames的所有Bean都可以通过这一个配置自动创建代理,而且staffDao不需要和之前那样创建staffDao、staffDaoProxy两个Bean。
<bean id="staffDao" class="com.lws.designPattern.StaffDaoImpl"/>
<bean id="springTransaction" class="com.lws.spring.SpringTransactionMethodInterceptor"/><bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"><property name="beanNames"><list><value>staffDao</value></list></property><property name="interceptorNames" value="springTransaction"/>
</bean>
接着只需要正常的获取staffDao的Bean实例,并调用测试即可
Staff staff = new Staff("zhangsan", 18);
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
StaffDao proxy = context.getBean("staffDao", StaffDao.class);
proxy.employ(staff);
proxy.paySalary(staff);
应该承认BeanNameAutoProxyCreator的使用已经比较简单了,对使用相同interceptorNames的AOP场景,只需要把Bean实例名称添加到beanNames就行了。再进一步简化就是通过AnnotationAwareAspectJAutoProxyCreator来实现了,它会自动判断Bean是否匹配AspectJ表达式,对符号条件的Bean完成代理。后面的代码中我们会进行源码级解释,现在我们先来讲讲Spring对AOP做的抽象,以及它实现AOP的核心组件。
2. Spring AOP抽象
1. 核心术语
1.1 连接点(JoinPoint)
连接点是程序中客观存在的特定位置,比如类初始化前、类初始化后、方法执行前、方法执行后等等。Spring AOP中只支持方法的连接点。连接到有两组信息组成,一个是程序的执行点,比如哪个类的哪个方法;一个是相对位置,比如方法执行前、方法返回后等等。Spring使用切点(Pointcut)表示执行点,用增强(Advice)表示相对位置。
1.2 切点(Pointcut)
连接点是程序中客观存在的事务,比如一个程序内有2个方法,方法执行前、方法执行后、抛出异常时,连接点个数就已经确定了,如果只看这3类相对位置的话,连接点就是6个。切点用来表示我们关心的连接点。Spring中使用Pointcut类表示切点。下图是Pointcut类的定义,它有两个成员变量,分别是ClassFilter实例、MethodMatcher实例。通过如下的UML图中的方法定义可以看到,ClassFilter用来表示哪些类是满足当前切点要求,MethodMatcher用来确定哪些方法满足当前切点要求。

Spring内部的核心实现类有下面这些,后面我们会挑几个看一下如何使用。

1.3 增强(Advice)
在介绍连接点的时候我们提供到过,连接点相对位置是通过增强(Advice)来表示的,此外Advice还包含相对位置要执行的代码。下图是Spring提供的Advice的核心类。我们日常使用的较多的就是在图的中心位置的4个类:
- MethodBeforeAdvice,方法执行前增强
- MethodInterceptor,方法环绕增强
- AfterReturningAdvice,方法返回后增强
- ThrowsAdvice,方法抛异常是的正常,这里需要特别注意ThrowsAdvice是一个标记接口,Spring通过反射调用,查看类JavaDoc有描述对方法签名的限制

1.4 切面(Aspect)
从之前的定义中可知,Pointcut和Advice已经完整的定义了在哪里执行什么增强逻辑。切面有切点(Pointcut)和增强(引介)组成,即包括横切逻辑的定义,也包括连接点的定义。
1.5 目标对象(Target)
要进行增强逻辑的目标类,在我们前面的例子里,StaffDao类的实例就是目标对象。
1.6 代理对象(Proxy)
对目标对象进行增强后生成的对象,就称为代理对象,这个对象已经包含增强的执行逻辑。
2. 核心组件
这张图里给出了Spring AOP中的核心流程涉及的组件,包括怎么定义切点(Pointcut),增强(Advice),并且通过组合Pointcut和Advice获得Advisor的定义。拿到Advisor后,我们可以使用ProxyFactory将Advisor和目标对象(target)组合,生成代对象。Spring默认支持两种生成代理对象的实现,一种是基于JDK的实现,JdkDynamicAopProxy,一种是基于Cglib的实现,ObjenesisCglibAopProxy。

每一个组件都有大量的实现类,这里不进行展开。接下来我们对几个关键点做详细的说明
2.1 AutoProxyCreator工作原理
BeanNameAutoProxyCreator继承自AbstractAutoProxyCreator,实现了Bean生命周期里的InstantiationAwareBeanFactoryProcessor、BeanPostProcessor,覆写的是两个方法: postProcessBeforeInstantiation、postProcessAfterInitialization,核心流程图如下:

对于定义了customTargetSource的AbstractAutoProxyCreator,Bean不会走正常的实例化流程,在postProcessBeforeInstantiation就通过TargetSource实例化并创建代理返回。日常开发中我们极少使用TargetSource,TargetSource适用于aop时期望目标对象(target)支持池化或者热替换的场景,更多TargetSource信息可以阅读Spring官方文档中关于Using TargetSources章节。
BeanNameAutoProxy主要覆写了AbstractAutoProxyCreator以下几个方法:
- isSupportedBeanName,基于配置的beanNames判断当前bean是否支持自动创建代理对象
- getAdvicesAndAdvisorsForBean,支持的时候返回空数组,不支持返回null
BeanNameAutoProxyCreator的Advice和Advisor对象的注入主要时通过interceptorNames做为bean名称从容器中查找的,具体实现看AbstractorAutoProxyCreator的buildAdvisors实现
AnnotationAwareAspectJAutoProxyCreator基于AbstractAutoProxyCreator实现,不适用interceptorNames,核心逻辑在于查找bean匹配的Advisor,通过findCandidateAdvisors查找候选Advisor。
以下是AbstractAutoProxyCreator的骨干代码,实现类基于下面的模板修改部分逻辑。
@Overridepublic Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {...TargetSource targetSource = getCustomTargetSource(beanClass, beanName); // 自定义customTargetSourceCreator的Beanif (targetSource != null) {if (StringUtils.hasLength(beanName)) {this.targetSourcedBeans.add(beanName);}Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource); // 通过自定义TargetSource直接返回BeanObject proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);this.proxyTypes.put(cacheKey, proxy.getClass());return proxy;}...}@Overridepublic Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {Object cacheKey = getCacheKey(bean.getClass(), beanName);if (this.earlyBeanReferences.remove(cacheKey) != bean) {return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;}
2.2 @EnableAspectJAutoProxy工作原理
EnableAspectJAutoProxy通过在自身注解@Import引入ImportBeanDefinitionRegistrar实现,ImportBeanDefinitionRegistrar接口允许用户通过代码注册自己的BeanDefinition。
@Import({AspectJAutoProxyRegistrar.class})
public @interface EnableAspectJAutoProxy
AspectJAutoProxyRegistrar的核心代码如下:
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);...}
}
进一步往AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary下面追踪,我们发现自动注册了AnnotationAwareAspectJAutoProxyCreator,结合上面我们对AutoProxyCreator的了解,整个基于注解的配置AOP的流程就通顺了。

3. Spring AOP案例
基于上面对原理的探究,我们能理解怎么从Advice、Pointcut开始创建Advisor,怎么利用ProxyFactoryBean创建代理,通过AutoProxyCreator让Spring自动创建代理,Spring对所有这些元素的抽象,以及相互之间如何协同。然而在显示工作中,仅仅知道这些是不够的,我们不可能每次都从最原始的MethodBeforceAdvice来创建我们的Advice,不可能从ClassFilter、MethodMatcher开始定义Pointcut,这样的话使用成本太高了,Spring提供了大量的内置类型,降低我们使用Spring AOP的成本。下一篇中我们会专注提供实战案例,包括但不限于如下主题
相关文章:
Spring AOP从入门到精通
目录 1. AOP的演化过程 1. 代理模式 2. 动态代理 2.1 JDK动态代理 2.2 Cglib动态代理 3. Spring模式 3.1 ProxyFactory 3.2 ProxyFactoryBean 3.3 AbstractAutoProxyCreator 2. Spring AOP抽象 1. 核心术语 1.1 连接点(JoinPoint) 1.2 切点(Pointcut) 1.3 增强(Ad…...
Tap虚拟网卡
1 概述 Tap设备通常用于虚拟化场景下,其驱动代码位于drivers/net/tun.c,tap与tun复用大部分代码, 注:drivers/net/tap.c并不是tap设备的代码,而是macvtap和ipvtap; 下文中,我们统一称tap&#…...
【数电笔记】53-与非门构成的基本RS触发器
目录 说明: 1. 电路组成 2. 逻辑功能 3. 特性表 4. 特性方程 5. 状态转换图 6. 驱动表 7. 例题 例1 例2 说明: 笔记配套视频来源:B站;本系列笔记并未记录所有章节,只对个人认为重要章节做了笔记;…...
kubernetes(k8s)容器内无法连接同所绑定的Service ClusterIP问题记录
kubernetes(k8s)容器内无法连接同所绑定的Service ClusterIP问题记录 1. k8s环境 k8s使用kubernetes-server-linux-amd64_1.19.10.tar.gz 二进制bin 的方式手动部署 k8s 版本: [rootmaster ~]# kubectl version Client Version: version.Info{Major:"1", Minor:&…...
Hadoop入门学习笔记
视频课程地址:https://www.bilibili.com/video/BV1WY4y197g7 课程资料链接:https://pan.baidu.com/s/15KpnWeKpvExpKmOC8xjmtQ?pwd5ay8 这里写目录标题 一、VMware准备Linux虚拟机1.1. VMware安装Linux虚拟机1.1.1. 修改虚拟机子网IP和网关1.1.2. 安装…...
堆栈,BSS,DATA,TEXT
一、目标文件 首先目标文件的构成,Linux下就是.o 文件 编译器编译源码后生成的文件叫目标文件(Object File)。 目标文件和可执行文件一般采用同一种格式,这种存储格式为 ELF。 目前文件的内容至少有编译后的机器指令代码和数据&a…...
Java八股文面试全套真题【含答案】-JSON篇
什么是JSON? 答案:JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,基于JavaScript的对象字面量表示法,用于在不同语言和平台之间传输数据。JSON的数据结构是怎样的? 答案…...
数据库管理-第119期 记一次迁移和性能优化(202301130)
数据库管理-第119期 记一次迁移和性能优化(202301130) 1 迁移 之前因为DV组件没有迁移成功的那个PDB,后来想着在目标端安装DV组件迁移,结果目标端装不上,而且开了SR也没看出个所以然来。只能换一个方向,尝…...
【云原生-K8s】镜像漏洞安全扫描工具Trivy部署及使用
基础介绍基础描述Trivy特点 部署在线下载百度网盘下载安装 使用扫描nginx镜像扫描结果解析json格式输出 总结 基础介绍 基础描述 Trivy是一个开源的容器镜像漏洞扫描器,可以扫描常见的操作系统和应用程序依赖项的漏洞。它可以与Docker和Kubernetes集成,…...
【Docker】Swarm的ingress网络
Docker Swarm Ingress网络是Docker集群中的一种网络模式,它允许在Swarm集群中运行的服务通过一个公共的入口点进行访问。Ingress网络将外部流量路由到Swarm集群中的适当服务,并提供负载均衡和服务发现功能。 在Docker Swarm中,Ingress网络使…...
gcc安全特性之FORTIFY_SOURCE
GCC 4.0引入了FORTIFY_SOURCE特性,旨在加强程序的安全性,特别是对于字符串和内存操作函数的使用。下面是对FORTIFY_SOURCE机制的深入分析: 1. 功能 FORTIFY_SOURCE旨在检测和防止缓冲区溢出,格式化字符串漏洞以及其他与内存操作…...
【JUC】二十、volatile变量的特点与使用场景
文章目录 1、volatile可见性案例2、线程工作内存与主内存之间的原子操作3、volatile变量不具有原子性案例4、无原子性的原因分析:i5、volatile变量小总结6、重排序7、volatile变量禁重排的案例8、日常使用场景9、总结 volatile变量的特点: 可见性禁重排无…...
软件工程期末复习(2)
学习资料 设计模式与软件体系结构【期末全整理答案】_软件设计模式与体系结构期末考试题_鸽子不二的博客-CSDN博客 软件设计与体系结构(第二版)部分习题_软件设计与体系结构第二版课后答案-CSDN博客 软件体系结构试题库试题和答案 - 豆丁网Docin 软件设计与体系结构复习 - CN…...
[vue3] 使用 vite 创建vue3项目的详细流程
一、vite介绍 Vite(法语意为 “快速的”,发音 /vit/,发音同 “veet”) 是一种新型前端构建工具,能够显著提升前端开发体验(热更新、打包构建速度更快)。 二、使用vite构建项目 【学习指南】学习新技能最…...
#HarmonyOS:软件安装window和mac预览Hello World
Window软件地址 https://developer.harmonyos.com/cn/develop/deveco-studio#download 安装的建议 这个界面这样选,其他界面全部按照默认路径往下走!!! 等待安装… 安装环境错误处理 一般就是本地node配置异常导致ÿ…...
nginx 一键切换停机维护页面 —— 筑梦之路
背景说明 进行停机维护或者系统升级等操作,会影响到用户使用,如果停机维护期间用户未看到停机维护的通知,仍去访问系统,会提示默认不太友好的访问错误界面 ,这时如果在维护的时候直接展示停机公告的具体信息࿰…...
Python作业答疑
1. 旋转字符串 1.1 问题描述 给定一个字符串(以字符数组的形式)和一个偏移量,根据偏移量原地从左向右旋转字符串。 1.2 问题示例 输入str"abcdefg",offset3,输出"efgabcd"。 输入str"ab…...
计算机网络实用工具之Hydra
简介 Hydra 是一个并行登录破解程序,支持多种协议进行攻击。它非常快速且灵活,并且很容易添加新模块。 该工具使研究人员和安全顾问能够展示远程未经授权访问系统是多么容易。 目前该工具支持以下协议: Asterisk, AFP, Cisco AAA, Cisco au…...
AUTOSAR 入门
前言 AUTOSAR是什么Vector DaVinci 工具功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个注脚注释也是必…...
新版IDEA中,module模块无法被识别,类全部变成咖啡杯无法被识
新版IDEA中,module模块无法被识别,类全部变成咖啡杯无法被识 如下图: 解决方法:java的Directory文件没有被设置为根目录,解决方法如下: 这是方法之一,还有很多的原因 可能的原因: …...
别再让反归一化坑了你!用TensorFlow+Keras做LSTM时序预测的完整避坑指南
LSTM时序预测中的归一化陷阱:从原理到实战的完整解决方案 当你兴奋地看着训练好的LSTM模型在测试集上展现出漂亮的损失曲线,却在最后一步——将预测值还原为业务可理解的单位时栽了跟头,这种挫败感我深有体会。归一化是时序预测的标准预处理步…...
【NSudo】功能定位:开源权限管理工具的系统运维解决方案
【NSudo】功能定位:开源权限管理工具的系统运维解决方案 【免费下载链接】NSudo [Deprecated, work in progress alternative: https://github.com/M2Team/NanaRun] Series of System Administration Tools 项目地址: https://gitcode.com/gh_mirrors/ns/NSudo …...
ScintillaNET:打造专业级代码编辑器的终极Windows Forms解决方案
ScintillaNET:打造专业级代码编辑器的终极Windows Forms解决方案 【免费下载链接】ScintillaNET A Windows Forms control, wrapper, and bindings for the Scintilla text editor. 项目地址: https://gitcode.com/gh_mirrors/sc/ScintillaNET ScintillaNET是…...
当I2C总线卡死时我们在debug什么:从复位异常到多设备冲突的故障树分析
当I2C总线卡死时我们在debug什么:从复位异常到多设备冲突的故障树分析 I2C总线作为嵌入式系统中广泛使用的通信协议,其简洁的两线制设计(SCL时钟线与SDA数据线)背后隐藏着复杂的硬件交互逻辑。当系统突然出现I2C通信失败、设备无响…...
告别OpenAI依赖:用智谱AI与轻量本地模型构建RAG评估实战
1. 为什么需要替代OpenAI的RAG评估方案 当我们在构建RAG(检索增强生成)系统时,评估环节至关重要。传统的Ragas框架默认使用OpenAI的GPT模型进行评估,但这会带来几个实际问题: 首先是访问稳定性问题。由于网络环境差异…...
【第四周】论文精读:Frustratingly Simple Retrieval Improves Challenging, Reasoning-Intensive Benchmarks
极简检索即可大幅刷新高难度推理基准主流观点认为简单RAG无法提升MMLU、MATH、GPQA等高难度推理任务,甚至会损害性能;本文推翻这一共识,证明核心瓶颈并非检索范式,而是缺少高质量、广覆盖、可单机部署的检索库;提出COM…...
不会写C代码也能做飞控?手把手教你用Matlab/Simulink和FMT搭建无人机算法模型
零代码飞控开发实战:用Matlab/SimulinkFMT实现无人机算法快速迭代 当无人机行业从极客玩具转向工业级应用时,传统飞控开发模式正面临严峻挑战——某高校研究团队曾花费三个月手工编写PID控制代码,却在首次试飞时因姿态解算模块的数值溢出导致…...
APK Studio安全最佳实践:合规使用逆向工程工具
APK Studio安全最佳实践:合规使用逆向工程工具 【免费下载链接】apkstudio Open-source, cross platform Qt based IDE for reverse-engineering Android application packages. 项目地址: https://gitcode.com/gh_mirrors/ap/apkstudio 在移动应用开发与安全…...
保姆级教程:用BERT微调一个智能家居语音助手的意图识别模型(含完整代码)
智能家居场景下的BERT意图识别实战:从数据标注到模型部署 想象一下,当你对家里的智能音箱说"把客厅灯调暗一点"时,设备能准确理解你的意图并执行操作。这种自然交互的背后,是意图识别技术在发挥作用。不同于通用对话系…...
springboot+vue基于web的大学生课程排课管理系统设计
目录 功能模块分析后台管理系统(SpringBoot)前端系统(Vue) 技术实现要点 项目技术支持源码获取详细视频演示 :文章底部获取博主联系方式!同行可合作 功能模块分析 后台管理系统(SpringBoot&…...



