SpringBoot核心框架之AOP详解
SpringBoot核心框架之AOP详解
一、AOP基础
1.1 AOP概述
- AOP:Aspect Oriented Programming(面向切面编程,面向方面编程),其实就是面向特定方法编程。
- 场景:项目部分功能运行较慢,定位执行耗时较长的业务方法,此时就需要统计每一个业务的执行耗时。
- 思路:给每个方法在开始前写一个开始计时的逻辑,在方法结束后写一个计时结束的逻辑,然后相减得到运行时间。
思路是没问题的,但是有个问题,一个项目是有很多方法的,如果挨个增加逻辑代码,会相当繁琐,造成代码的臃肿,所以可以使用AOP编程,将计时提出成一个这样的模板:
- 获取方法运行开始时间
- 运行原始方法
- 获取方法运行结束时间,计算执行耗时
原始方法就是我们需要计算时间的方法,并且可以对原始方法进行增强,其实这个技术就是用到了我们在Java基础部分学习的动态代理技术。
实现:动态代理是面向切面编程最主流的实现。而SpringAOP是Spring框架的高级技术,旨在管理bean对象的过程中,主要是通过底层的动态代理机制,对特点的方法进行编程。
1.2 AOP快速入门
统计各个业务层方法执行耗时
- 导入依赖:在pom.xml中导入AOP的依赖。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 编写AOP程序:针对于特定方法根据业务需要进行编程。
@Slf4j // 日志
@Component // 将当前类交给spring管理
@Aspect // 声明这是一个AOP类
public class TimeAspect {@Around("execution(* com.example.service.*.*(..))")// @Around:表示这是一个环绕通知。// "execution(* com.example.service.*.*(..))":切入点表达式,它定义了哪些方法会被这个环绕通知所拦截。这个后面会详细讲解。// execution(* ...):表示拦截执行的方法。// * com.example.service.*.*(..):表示拦截 com.example.service 包下所有类的所有方法(* 表示任意字符的通配符)。// ..:表示方法可以有任意数量和类型的参数。public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {// ProceedingJoinPoint是 Spring AOP 中的一个接口,在使用环绕通知时需要// 它继承自 JoinPoint 接口,并添加了 proceed() 方法。// 这个方法是 AOP 代理链执行的关键部分,它允许你在切面中执行自定义逻辑后继续执行原始方法。// 1. 记录开始时间long start = System.currentTimeMillis();// 2. 调用原始方法Object result = joinPoint.proceed(); // 执行被通知的方法。如果不调用 proceed(),被通知的方法将不会执行。// 3. 记录结束时间,计算耗时long end = System.currentTimeMillis();// getSignature():返回当前连接点的签名。log.info(joinPoint.getSignature()+"方法执行耗时:{}ms",end - start);return result;}
}
- 查看结果

这样我们就完成了,一个AOP的小例子,但是AOP的功能远不能这些,他还有更多的实用的功能。比如:记录操作日志:可以记录谁什么时间操作了什么方法,传了什么参数,返回值是什么都可以很方便的实现。还有比如权限控制,事务管理等等。
我们来总结一下AOP的优势
- 代码无侵入
- 减少重复代码
- 提高开发效率
- 维护方便
1.3. AOP核心概念
连接点:JoinPoint,可以被连接点控制的方法(暗含方法执行时的信息)。 在此例中就是需要被计算耗时的业务方法。
通知:Advice,指那些重复的逻辑,也就是共性功能(最终体现为一个方法)。在此例中就是计算耗时的逻辑代码。
切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用。在此例中就是com.example.service 包下所有类的所有方法。
切面:Aspect,描述通知与切入点的对应关系(通知+切入点)。在此例中就是TimeAspect方法。
目标对象:Target,通知所应用的对象。在此例中就是通知com.example.service 包下所有类的所有方法。
1.4. AOP的执行流程
因为SpringAOP是基于动态代理实现的,所有在方法运行时就会先为目标对象基于动态代理生成一个代理对象,为什么说AOP可以增强方法,就是因为有一个代理方法,然后在AOP执行时,Spring就会将通知添加到代理对象的方法前面,也就是记录开始时间的那个逻辑代码,然后调用原始方法,也就是需要计时的那个方法,此时代理对象已经把原始方法添加到代理对象里面了,然后执行调用原始方法下面的代码,在此例中就是计算耗时的那部分,AOP会把这部分代码添加到代理对象的执行方法的下面,这样代理对象就完成了对目标方法的增强,也就是添加了计时功能,最后在程序运行时自动注入的也就不是原来的对象,而是代理对象了,不过这些都是AOP自动完成,我们只需要编写AOP代码即可。
二、AOP进阶
2.1. AOP支持的通知类型
通知类型:
- 环绕通知(Around Advice)
重点!!!:
- 使用
@Around注解来定义。 - 包围目标方法的执行,可以在方法执行前后执行自定义逻辑,并且可以控制目标方法的执行。
- 通过
ProceedingJoinPoint参数的proceed()方法来决定是否执行目标方法。
- 前置通知(Before Advice):
- 使用
@Before注解来定义。 - 在目标方法执行之前执行,无论方法是否抛出异常,都会执行。
- 不能阻止目标方法的执行。
- 后置通知(After Advice) 也叫最终通知:
- 使用
@After注解来定义。 - 在目标方法执行之后执行,无论方法是否抛出异常,都会执行。
- 通常用于资源清理工作
- 返回通知(After Returning Advice)
了解: - 使用
@AfterReturning注解来定义。 - 在目标方法成功执行之后执行,即没有抛出异常时执行。
- 可以获取方法的返回值。
- 异常通知(After Advice)
了解:
- 使用
@AfterThrowing注解来定义。 - 在目标方法抛出异常后执行。
- 可以获取抛出的异常对象。
注意事项:
- 环绕通知需要自己调用
joinPoint.proceed()来让原始方法执行,其他通知则不需要。 - 环绕通知的返回值必须是
Object,来接受原始方法的返回值。
@Slf4j
@Component
@Aspect
public class MyAspect {// 因为示例中的切入点都是一样的,所以不用写多次切入表达式,创建一个方法即可。// 此方法也可在其他AOP需要切入点的地方使用。@Pointcut("execution(* com.example.service.impl.DeptServiceImpl.*(..))")public void pt(){}// 前置通知@Before("pt()")public void Before(){log.info("before ...");}// 环绕通知@Around("pt()")public Object Around(ProceedingJoinPoint joinPoint) throws Throwable {log.info("around after ...");// 调用原始方法Object proceed = joinPoint.proceed();log.info("around after ...");return proceed;}// 后置通知@After("pt()")public void After(){log.info("after ...");}// 返回通知@AfterReturning("pt()")public void Returning(){log.info("returning ...");}// 异常通知@AfterThrowing("pt()")public void Throwing(){log.info("throwing ...");}
}
2.2. 多个通知之间的执行顺序
当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会执行。那么顺序是怎么的呢?
我们先创建三个AOP程序,分别给他们创建一个前置通知和后置通知,然后启动程序观察他们的输出情况。
// MyAspect2
@Slf4j
@Component
@Aspect
public class MyAspect2 {@Before("execution(* com.example.service.impl.DeptServiceImpl.*(..))")public void befor(){log.info("befor2 ...");}@After("execution(* com.example.service.impl.DeptServiceImpl.*(..))")public void after(){log.info("after2 ...");}
}
// MyAspect3
@Slf4j
@Component
@Aspect
public class MyAspect3 {@Before("execution(* com.example.service.impl.DeptServiceImpl.*(..))")public void befor(){log.info("befor3 ...");}@After("execution(* com.example.service.impl.DeptServiceImpl.*(..))")public void after(){log.info("after3 ...");}
}
// MyAspect4
@Slf4j
@Component
@Aspect
public class MyAspect4 {@Before("execution(* com.example.service.impl.DeptServiceImpl.*(..))")public void befor(){log.info("befor4 ...");}@After("execution(* com.example.service.impl.DeptServiceImpl.*(..))")public void after(){log.info("after4 ...");}
}// 输出结果com.example.aop.MyAspect2 : befor2 ...com.example.aop.MyAspect3 : befor3 ...com.example.aop.MyAspect4 : befor4 ...com.example.aop.MyAspect4 : after4 ...com.example.aop.MyAspect3 : after3 ...com.example.aop.MyAspect2 : after2 ...// 然后我们把MyAspect2改成MyAspect5,但输出内容不变,我们来看一下输出结果com.example.aop.MyAspect3 : befor3 ...com.example.aop.MyAspect4 : befor4 ...com.example.aop.MyAspect5 : befor2 ...com.example.aop.MyAspect5 : after2 ...com.example.aop.MyAspect4 : after4 ...com.example.aop.MyAspect3 : after3 ...
2.2.1 默认情况:
执行顺序是和类名有关系的,对于目标方法前的通知字母越靠前的越先执行,目标方法后的通知则相反,字母越靠前的越晚执行,这和Filter拦截器的规则是一样的。
2.2.2 也可以使用注解的方式指定顺序。使用@Order(数字)加在切面类上来控制顺序。
目标方法前的通知:数字小的先执行。
目标方法后的通知:数字小的后执行。
@Slf4j
@Component
@Aspect@Order(10)public class MyAspect3 {...
}
2.3. 切入点表达式
切入点表达式:描述切入点方法的一种表达式。
作用:主要决定项目中哪些方法需要加入通知。
常见形式:
- execution(…):根据方法的签名来匹配。
- @annotation:根据注解匹配。
2.3.1 execution(…)
execution主要是通过方法的返回值,类名,包名,方法名,方法参数等信息来匹配,语法为:
execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常)
其中带 ? 的表示可以省略的部分
- 访问修饰符:可省略(比如:public private …)
- 包名.类名:可省略 但不推荐
- throws 异常:可省略 (注意是方法上声明可抛出的异常,不是实际抛出的异常)
// 完整的写法:
@Before("execution(public void com.example.service.impl.DeptServiceImpl.add(java.lang.Integer))")
public void befor(){...
}
可以使用通配符描述切入点
- 单个独立的任意符号,可以通配任意返回值,包括包名,类名,方法名,任意一个参数,也可以通配包,类,方法名的一部分。
@After("execution(* com.*.service.*.add*(*))")
- 多个连续的任意符号,可以通配任意层级的包,或任意类型,任意个数的参数。
@After("execution(* com.example..DeptService.*(..))")
- 根据业务的需要,也可以使用 且(&&),或(||),非(!)来组合切入点表达式。
@After("execution(* com.example..DeptService.*(..)) || execution(* com.example.service.DeptService.*(..))")
2.3.2 @annotation:用于匹配标识有特定注解的方法
语法:@annotation(注解的全类名)
先新建一个注解:
@Retention(RetentionPolicy.RUNTIME) // 用来描述有效时间,RUNTIMW:在运行时有效
@Target(ElementType.METHOD) // 用来说明这个注解可以运行在哪里, METHOD:方法上
public @interface MyLog {
}
在目标方法上添加注解
@MyLog
@Override
public void delete(Integer id) {deptMapper.delect(id); // 根据id删除部门
}
@MyLog
@Override
public void add(Dept dept) {dept.setCreateTime(LocalDateTime.now());dept.setUpdateTime(LocalDateTime.now());deptMapper.add(dept);
}
在切入点表达式以注解的方式进行
@After("@annotation(com.example.aop.MyLog)")
public void after(){...
}
3.3. 连接点
在Spring中使用JoinPoint抽象了连接点,用它可以获取方法执行时的相关信息,如目标类目,方法名,方法参数等。
- 对于环绕通知(@around),获取连接点信息只能使用
ProceedingJoinPoint - 对于其他四种通知,获取连接点信息只能使用
JoinPoint,他是ProceedingJoinPoint的父类型。
// 我们只在环绕通知中演示,因为API都是相同的
@Component
@Aspect
@Slf4j
public class MyAspect5 {@Pointcut("@annotation(com.example.aop.MyLog)")public void pt(){}@Before("pt()")public void before(JoinPoint joinPoint){log.info("before ...");}@Around("pt()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {log.info("around ... before");// 1. 获取目标对象的类名log.info("目标对象的类名:"+joinPoint.getTarget().getClass().getName());// 2. 获取目标方法的方法名log.info("目标方法的方法名"+joinPoint.getSignature().getName());// 3. 目标方法运行时传入的参数log.info("目标方法运行时传入的参数"+ Arrays.toString(joinPoint.getArgs())); // 数组不能直接输出// 4. 放行,目标方法执行Object object = joinPoint.proceed();// 5. 获取目标方法的返回值log.info("目标方法的返回值"+ object);log.info("around ... after");return object;}
}
// 查看结果
com.example.aop.MyAspect5 : around ... before
com.example.aop.MyAspect5 : 目标对象的类名:com.example.service.impl.DeptServiceImpl
com.example.aop.MyAspect5 : 目标方法的方法名select
com.example.aop.MyAspect5 : 目标方法运行时传入的参数[1]
com.example.aop.MyAspect5 : before ...
com.example.aop.MyAspect5 : 目标方法的返回值[Dept(id=1, name=学工部, createTime=2023-11-30T13:55:55, updateTime=2023-11-30T13:55:55)]
com.example.aop.MyAspect5 : around ... after
三、AOP案例
3.1. 分析
需求:将项目中的增、删、改、相关接口的操作日志记录到数据库表中
- 操作日志包含:操作人,操作时间,执行方法的全类名,执行方法名,方法运行时的参数,返回值,方法运行时长。
思路分析: - 需要对方法添加统一的功能,使用AOP最方便,并且需要计算运行时长,所以使用 环绕通知
- 因为增删改的方法名没有规则,所以使用注解的方式写切入表达式
步骤:- 准备:
- 案例中引入AOP的起步依赖
- 设计数据表结构,并且引入对应的实体类
- 编码:
- 自定义注解:@Log
- 定义切面类,完成记录操作日志的逻辑代码
- 准备:
3.2. 开始干活
3.2.1. 创建数据库:
create table operate_log
(id int unsigned primary key auto_increment comment 'ID',operate_user int unsigned comment '操作人ID',operate_time datetime comment '操作时间',class_name varchar(100) comment '操作的类名',method_name varchar(100) comment '操作的方法名',method_params varchar(1000) comment '方法参数',return_value varchar(2000) comment '返回值',cost_time bigint comment '方法执行耗时, 单位:ms'
) comment '操作日志表';
3.2.2. 引入依赖
<!-- AOP-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency><!-- fastJSON 阿里巴巴提供的转JSON的工具-->
<!-- 因为返回值是一个json的,但数据库表需要的是字符串,所以使用此工具将json转换成String -->
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.7</version>
</dependency>
3.2.3. 新建实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OperateLog {private Integer id; //IDprivate Integer operateUser; //操作人IDprivate LocalDateTime operateTime; //操作时间private String className; //操作类名private String methodName; //操作方法名private String methodParams; //操作方法参数private String returnValue; //操作方法返回值private Long costTime; //操作耗时
}
3.2.4. 新建Mapper层
@Mapper
public interface OperateLogMapper {//插入日志数据@Insert("insert into operate_log (operate_user, operate_time, class_name, method_name, method_params, return_value, cost_time) " +"values (#{operateUser}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime});")void insert(OperateLog log);
}
3.2.5. 新建注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
}
3.2.6. 定义切面类,完成记录操作日志的逻辑代码
@Component
@Aspect
@Slf4j
public class LogAspect {@Autowiredprivate HttpServletRequest request;@Autowiredprivate OperateLogMapper operateLogMapper;@Around("@annotation(com.example.anno.Log)")public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {//操作人ID 因为jwt令牌有登录人信息,所以解析jwt令牌就可以
// String token = request.getHeader("token");
// Claims claims = JwtUtils.parseJWT(token);
// Integer user = (Integer) claims.get("id");// 使用链式编程 ↓↓↓Integer user = (Integer) JwtUtils.parseJWT(request.getHeader("token")).get("id");//操作时间LocalDateTime optionTime = LocalDateTime.now();//操作类名String className = joinPoint.getTarget().getClass().getName();//操作方法名String methodName = joinPoint.getSignature().getName();//操作方法参数String args = Arrays.toString(joinPoint.getArgs());long start = System.currentTimeMillis(); // 记录方法开始运行时间// 调用原始方法Object result = joinPoint.proceed();long end = System.currentTimeMillis(); // 记录方法结束运行时间//操作方法返回值String returnValue = JSONObject.toJSONString(result);//操作耗时long costTime = end - start;// 记录操作日志OperateLog operateLog = new OperateLog(null, user, optionTime, className, methodName, args, returnValue, costTime);operateLogMapper.insert(operateLog);log.info("AOP记录操作日志:{}", operateLog);return result;}
}
3.2.7. 给需要记录的方法上面添加自定义的注解
// 这里就不一一展示了
/*** 根据id删除部门*/@Log@DeleteMapping("/{id}")public Result delete(@PathVariable Integer id){log.info("根据id删除部门:{}",id);deptService.delete(id);return Result.success();}/*** 添加部门*/@Log@PostMappingpublic Result add(@RequestBody Dept dept){log.info("添加部门{}",dept);deptService.add(dept);return Result.success();}
3.3. 查看结果
刚刚进行了部门的增删改以及员工的增删改操作,我们查看数据库,看有没有被记录。
1,1,2024-10-27 20:20:23,com.example.controller.DeptController,delete,[15],"{""code"":1,""msg"":""success""}",40
2,1,2024-10-27 20:20:45,com.example.controller.DeptController,add,"[Dept(id=null, name=测试部, createTime=null, updateTime=null)]","{""code"":1,""msg"":""success""}",5
3,1,2024-10-27 20:21:00,com.example.controller.EmpController,sava,"[Emp(id=null, username=测试, password=null, name=测试, gender=1, image=, job=1, entrydate=2024-10-20, deptId=16, createTime=null, updateTime=null)]","{""code"":1,""msg"":""success""}",6
4,1,2024-10-27 20:23:01,com.example.controller.DeptController,add,"[Dept(id=null, name=1, createTime=null, updateTime=null)]","{""code"":1,""msg"":""success""}",8
5,1,2024-10-27 20:23:18,com.example.controller.DeptController,delete,[17],"{""code"":1,""msg"":""success""}",12

完全符合要求!!!!!!
相关文章:
SpringBoot核心框架之AOP详解
SpringBoot核心框架之AOP详解 一、AOP基础 1.1 AOP概述 AOP:Aspect Oriented Programming(面向切面编程,面向方面编程),其实就是面向特定方法编程。 场景:项目部分功能运行较慢,定位执行耗时…...
Linux: network: ifconfig已经过时,建议使用ip addr相关命令
最近有一个同事在问网络的问题,在debug的过程中还在使用ifconfig命令查看IP的相关信息。 但是这个ifconfig已经不推荐使用了,最好使用ip 相关的命令做操作。 有些信息使用ifconfig显示不出来 ifconfig eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500ine…...
Flutter 鸿蒙next中的路由使用详解【基础使用】
✅近期推荐:求职神器 https://bbs.csdn.net/topics/619384540 🔥欢迎大家订阅系列专栏:flutter_鸿蒙next 💬淼学派语录:只有不断的否认自己和肯定自己,才能走出弯曲不平的泥泞路,因为平坦的大路…...
基于SSM+小程序民宿短租管理系统(民宿1)
👉文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 基于SSM小程序民宿短租管理系统实现了管理员、用户及房主 1、管理员可以管理民宿信息和订单信息用户管理、房主管理、房间类型管理、预定管理等。 2、房主可以管理自己的民宿和订单 3、…...
SQL LIKE 操作符
SQL LIKE 操作符 在SQL中,LIKE 操作符用于在查询中搜索列中的特定模式。它通常与 % 和 _ 通配符一起使用,分别代表任意数量的字符和单个字符。LIKE 操作符在数据过滤和模式匹配方面非常有用,尤其是在处理大量文本数据时。 LIKE 操作符的基本…...
七款主流图纸加密软件强力推荐|2024年CAD图纸加密保护指南
在当今信息化的设计行业,保护CAD图纸的知识产权和数据安全变得尤为重要。随着越来越多的企业采用数字化设计和共享文件,如何防止CAD图纸被未经授权的访问和窃取成为了许多设计师和企业关注的焦点。为此,选用合适的图纸加密软件是保护CAD文件安…...
【STM32】单片机ADC原理详解及应用编程
本篇文章主要详细讲述单片机的ADC原理和编程应用,希望我的分享对你有所帮助! 目录 一、STM32ADC概述 1、ADC(Analog-to-Digital Converter,模数转换器) 2、STM32工作原理 二、STM32ADC编程实战 (一&am…...
C# 委托简述
1.委托 1.1什么是委托 委托委托 官网解释: 委托是安全封装方法的类型,类似于 C 和 C 中的函数指针。 与 C 函数指针不同的是,委托是面向对象的、类型安全的和可靠的。 委托的类型由委托的名称确定。 个人理解:委托就是一个方法的模板。它可以接收…...
瑞吉外卖项目
目录 Day01业务开发 一、项目总体介绍与展示 二、软件开发整体介绍 (一)软件开发流程 三、瑞吉外卖项目介绍 (一)项目介绍 (二)技术选型功能架构 1.技术选型—— 编辑2.功能架构—— 编辑 &a…...
Docker:4、龙晰(Anolis OS 8.8)宝塔面板安装
接上文Docker:1、基于龙晰 (Anolis OS 8.8 )的基础镜像制作,本节我们介绍:基于Docker的龙晰(Anolis OS 8.8 )宝塔安装。 在第一节中由于我们对 Docker 容器进行了SSH设置,这为我们这…...
多端项目开发全流程详解 - 从需求分析到多端部署
引言 在当今互联网时代,一个完整的产品常常需要覆盖多个终端,包括小程序、Web端(后台管理系统)、App端等。本文将详细介绍一个采用前后端分离架构的多端项目开发流程,重点分析各个终端的特点、功能定位及其开发要点。…...
4.5KB原生html+js+css实现图片打印位置的坐标和尺寸获取
一般用于图片打印文字或图片的坐标获取,代码来自AI有改动。 功能:本地图选择后不上传直接可比划线条作为对角线得到矩形,动态显示坐标 按下鼠标开始松开鼠标结束。有细微BUG但不影响坐标获取。 <!DOCTYPE html> <html lang"en">…...
智诊小助手-记录模式选择
记录模式总共有连续记录、硬件触发、软件触发、错误触发四种模式选择,并且在选择完记录模式后还可以设置保留触发点前报文条数、存储时间、录制通道、保存类型 配置过程如下: 点击下面右图中模式选择即可进入到左图中的参数配置界面 如上图选择的配置…...
JDBC: Java数据库连接的桥梁
什么是JDBC? Java数据库连接(Java Database Connectivity,简称JDBC)是Java提供的一种API,允许Java应用程序与各种数据库进行交互。JDBC提供了一组标准的接口,开发者可以利用这些接口执行SQL语句、处理结果集…...
英伟达GPU算力【自用】
GPU(图形处理单元)算力的提升是驱动当代科技革命的核心力量之一,尤其在人工智能、深度学习、科学计算和超级计算机领域展现出了前所未有的影响力。2024年的GPU技术发展,不仅体现在游戏和图形处理的传统优势上,更在跨行…...
「C/C++」C++11 之 智能指针
✨博客主页何曾参静谧的博客📌文章专栏「C/C」C/C程序设计📚全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasoli…...
算法面试小抄
第一章:算法与数据结构要点速学 1.时间复杂度 (大 O) 首先,我们来谈谈常用操作的时间复杂度,按数据结构/算法划分。然后,我们将讨论给定输入大小的合理复杂性。 数组(动态数组/列表) 规定 n arr.length, 注意: &am…...
当有违法数据时,浏览器不解析,返回了undefined,导致数据不解析
现象:页面上没有看到数据 排查:断点到线上的源码里:1、协议回调确实没有拿到数据是个undefined 2、network里看服务确实响应了数据 3、控制台没有任何报错。 心情:莫名其妙的现象 我本地有json格式化工具,copy进去后&…...
在MySQL中ORDER BY使用的那种排序算法
在 MySQL 中,ORDER BY 子句的排序算法通常根据场景、数据量和表的索引情况而有所不同。MySQL 常用的排序算法包括: 文件排序(File Sort):MySQL 没有使用索引排序的情况下,会进行文件排序,这可以…...
学习threejs,使用粒子实现雨滴特效
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:threejs gis工程师 文章目录 一、🍀前言1.1 ☘️THREE.Points简介1.11 ☘️…...
eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...
UE5 学习系列(三)创建和移动物体
这篇博客是该系列的第三篇,是在之前两篇博客的基础上展开,主要介绍如何在操作界面中创建和拖动物体,这篇博客跟随的视频链接如下: B 站视频:s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...
C++ 基础特性深度解析
目录 引言 一、命名空间(namespace) C 中的命名空间 与 C 语言的对比 二、缺省参数 C 中的缺省参数 与 C 语言的对比 三、引用(reference) C 中的引用 与 C 语言的对比 四、inline(内联函数…...
BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...
uniapp 小程序 学习(一)
利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 :开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置,将微信开发者工具放入到Hbuilder中, 打开后出现 如下 bug 解…...
LLaMA-Factory 微调 Qwen2-VL 进行人脸情感识别(二)
在上一篇文章中,我们详细介绍了如何使用LLaMA-Factory框架对Qwen2-VL大模型进行微调,以实现人脸情感识别的功能。本篇文章将聚焦于微调完成后,如何调用这个模型进行人脸情感识别的具体代码实现,包括详细的步骤和注释。 模型调用步骤 环境准备:确保安装了必要的Python库。…...
车载诊断架构 --- ZEVonUDS(J1979-3)简介第一篇
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 做到欲望极简,了解自己的真实欲望,不受外在潮流的影响,不盲从,不跟风。把自己的精力全部用在自己。一是去掉多余,凡事找规律,基础是诚信;二是…...
