SpringBoot核心框架之AOP详解

SpringBoot核心框架之AOP详解

一、AOP基础
1.1 AOP概述
  • AOP:Aspect Oriented Programming(面向切面编程,面向方面编程),其实就是面向特定方法编程。
    • 场景:项目部分功能运行较慢,定位执行耗时较长的业务方法,此时就需要统计每一个业务的执行耗时。
    • 思路:给每个方法在开始前写一个开始计时的逻辑,在方法结束后写一个计时结束的逻辑,然后相减得到运行时间。

思路是没问题的,但是有个问题,一个项目是有很多方法的,如果挨个增加逻辑代码,会相当繁琐,造成代码的臃肿,所以可以使用AOP编程,将计时提出成一个这样的模板:

  1. 获取方法运行开始时间
  2. 运行原始方法
  3. 获取方法运行结束时间,计算执行耗时

原始方法就是我们需要计算时间的方法,并且可以对原始方法进行增强,其实这个技术就是用到了我们在Java基础部分学习的动态代理技术

实现:动态代理是面向切面编程最主流的实现。而SpringAOP是Spring框架的高级技术,旨在管理bean对象的过程中,主要是通过底层的动态代理机制,对特点的方法进行编程。

1.2 AOP快速入门

统计各个业务层方法执行耗时

  1. 导入依赖:在pom.xml中导入AOP的依赖。

    org.springframework.boot spring-boot-starter-aop
  2. 编写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;
    

    }
    }

  3. 查看结果在这里插入图片描述

这样我们就完成了,一个AOP的小例子,但是AOP的功能远不能这些,他还有更多的实用的功能。比如:记录操作日志:可以记录谁什么时间操作了什么方法,传了什么参数,返回值是什么都可以很方便的实现。还有比如权限控制,事务管理等等。

我们来总结一下AOP的优势

  1. 代码无侵入
  2. 减少重复代码
  3. 提高开发效率
  4. 维护方便
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支持的通知类型

通知类型:

  1. 环绕通知(Around Advice) 重点!!!:
  • 使用 @Around 注解来定义。
  • 包围目标方法的执行,可以在方法执行前后执行自定义逻辑,并且可以控制目标方法的执行。
  • 通过 ProceedingJoinPoint 参数的 proceed() 方法来决定是否执行目标方法。
  1. 前置通知(Before Advice)
  • 使用 @Before 注解来定义。
  • 在目标方法执行之前执行,无论方法是否抛出异常,都会执行。
  • 不能阻止目标方法的执行。
  1. 后置通知(After Advice) 也叫最终通知
  • 使用 @After 注解来定义。
  • 在目标方法执行之后执行,无论方法是否抛出异常,都会执行。
  • 通常用于资源清理工作
  • 返回通知(After Returning Advice) 了解
  • 使用 @AfterReturning 注解来定义。
  • 在目标方法成功执行之后执行,即没有抛出异常时执行。
  • 可以获取方法的返回值。
  1. 异常通知(After Advice)了解
  • 使用 @AfterThrowing 注解来定义。
  • 在目标方法抛出异常后执行。
  • 可以获取抛出的异常对象。

注意事项:

  1. 环绕通知需要自己调用joinPoint.proceed()来让原始方法执行,其他通知则不需要。

  2. 环绕通知的返回值必须是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

在这里插入图片描述

完全符合要求!!!!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/70550.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【RK3588嵌入式图形编程】-SDL2-构建模块化UI

构建模块化UI 文章目录 构建模块化UI1、概述2、创建UI管理器3、嵌套组件4、继承5、多态子组件6、总结在本文中,将介绍如何使用C++和SDL创建一个灵活且可扩展的UI系统,重点关注组件层次结构和多态性。 1、概述 在前面的文章中,我们介绍了应用程序循环和事件循环,这为我们的…

第四届图像、信号处理与模式识别国际学术会议(ISPP 2025)

重要信息 会议官网&#xff1a;www.icispp.com 会议时间&#xff1a;2025年3月28-30日 会议地点&#xff1a;南京 简介 由河海大学和江苏大学联合主办的第四届图像、信号处理与模式识别国际学术会议&#xff08;ISPP 2025) 将于2025年3月28日-30日在中国南京举行。会议主…

低代码与开发框架的一些整合[2]

1.分析的项目资源说明 经过近期的的不断分析与运行对比&#xff0c;最终把注意力集中在了以下几个框架&#xff1a; 01.dibootdiboot.diboot: 写的更少, 性能更好 -> 为开发人员打造的低代码开发平台。Mybatis-plus关联查询&#xff0c;关联无SQL&#xff0c;性能高10倍&a…

Spring Boot 中事务的用法详解

引言 在 Spring Boot 中&#xff0c;事务管理是一个非常重要的功能&#xff0c;尤其是在涉及数据库操作的业务场景中。Spring 提供了强大的事务管理支持&#xff0c;能够帮助我们简化事务的管理和控制。本文将详细介绍 Spring Boot 中事务的用法&#xff0c;包括事务的基本概…

Java面试——Tomcat

优质博文&#xff1a;IT_BLOG_CN 一、Tomcat 顶层架构 Tomcat中最顶层的容器是Server&#xff0c;代表着整个服务器&#xff0c;从上图中可以看出&#xff0c;一个Server可以包含至少一个Service&#xff0c;用于具体提供服务。Service主要包含两个部分&#xff1a;Connector和…

第4章 信息系统架构(三)

4.3 应用架构 应用架构的主要内容是规划出目标应用分层分域架构&#xff0c;根据业务架构规划目标应用域、应用组和目标应用组件&#xff0c;形成目标应用架构逻辑视图和系统视图。从功能视角出发&#xff0c;阐述应用组件各自及应用架构整体上&#xff0c;如何实现组织的高阶…

python小项目编程-中级(1、图像处理)

目录 图像处理 实现 测试 unittest pytest 图像处理 实现界面化操作&#xff0c;使用PIL库实现简单的图像处理功能&#xff0c;如缩放&#xff08;设置缩放比例&#xff09;、旋转和滤镜、对比度调整、亮度调整、灰度图、二值化图&#xff08;二值图如果使用的是彩色图片需…

【Leetcode 每日一题】2209. 用地毯覆盖后的最少白色砖块

问题背景 给你一个下标从 0 0 0 开始的 二进制 字符串 f l o o r floor floor&#xff0c;它表示地板上砖块的颜色。 f l o o r [ i ] floor[i] floor[i] 为 ‘0’ 表示地板上第 i i i 块砖块的颜色是 黑色 。 f l o o r [ i ] floor[i] floor[i] 为’1’ 表示地板上第 i …

Docker 性能优化指南

Docker 提供了强大的容器化功能&#xff0c;能够帮助开发者在不同的环境中构建、测试和部署应用。然而&#xff0c;随着容器化应用的不断增长&#xff0c;Docker 容器可能会面临一些性能瓶颈&#xff0c;影响其运行效率、资源占用和扩展能力。为了确保容器在生产环境中的高效运…

2025 WE DAY品牌日| 天璇II WE X7 Pro充电桩震撼发布,能效电气开启充电革命

随着新能源产业的迅猛发展,充电桩作为电动汽车能量补给的重要基础设施,正在成为市场关注的焦点。能效电气作为充电桩领域的佼佼者,专注于研发高效、智能的充电解决方案,为电动汽车的普及与可持续发展铺设了坚实的基础。 2025年2月21日,能效电气在深圳盛大举办了以“以创新 引未…

< OS 有关 > Ubuntu 24 SSH 服务器更换端口 in jp/us VPSs

原因&#xff1a; 两台 VPS 的 ssh 端口一直被密码重试&#xff0c; us 这台已经封了 632, jp 这台两周前清过一次 sqlite3 数据&#xff0c;现在赞到 1008 Fail2Ban 是使用 sqlite3 来记录&#xff0c;数据量大后&#xff0c;硬盘的 I/O 会飙升&#xff0c;我有写过一个 app…

MATLAB学习之旅:数据插值与曲线拟合

在MATLAB的奇妙世界里,我们已经走过了一段又一段的学习旅程。从基础的语法和数据处理,到如今,我们即将踏入数据插值与曲线拟合这片充满魅力的领域。这个领域就像是魔法中的艺术创作,能够让我们根据现有的数据点,构建出更加丰富的曲线和曲面,从而更好地理解和描述数据背后…

若依-@Excel新增注解numberFormat

Excel注解中原本的scale会四舍五入小数&#xff0c;导致进度丢失 想要的效果 显示的时候保留两个小数真正的数值是保留之前的数值 还原过程 若以中有一個專門的工具类&#xff0c;用来处理excel的 找到EXCEL导出方法exportExcel()找到writeSheet,写表格的方法找到填充数据的方法…

LeetCode 热题 100_搜索二维矩阵(64_74_中等_C++)(二分查找)(暴力破解法;Z字形查找;一次二分查找)

LeetCode 热题 100_搜索二维矩阵&#xff08;64_74&#xff09; 题目描述&#xff1a;输入输出样例&#xff1a;题解&#xff1a;解题思路&#xff1a;思路一&#xff08;暴力破解法&#xff09;&#xff1a;思路二&#xff08;Z字形查找&#xff09;&#xff1a;思路三&#x…

从CNN到Transformer:遥感影像目标检测的技术演进(矿产勘探、精准农业、城市规划、林业测量、军事目标识别和灾害评估等)

在遥感影像分析领域&#xff0c;目标检测一直是研究热点之一。随着高分辨率对地观测系统的不断发展&#xff0c;遥感影像的分辨率和数据量呈爆发式增长&#xff0c;如何高效、准确地从海量数据中提取有用信息&#xff0c;成为了一个亟待解决的问题。近年来&#xff0c;深度学习…

【rt-thread】rt-thread 控制 led 的两种方式

1. pin设备 #define LED_PIN 3int led(void) {rt_uint8_t count;rt_pin_mode(LED_PIN, PIN_MODE_OUTPUT); for(count 0 ; count < 10 ;count){ rt_pin_write(LED_PIN, PIN_HIGH);rt_kprintf("led on, count : %d %d\r\n", count, rt_pin_read(LED_PIN));…

Excell 代码处理

文章目录 Excell 代码处理cvc格式xlsl格式小结 Excell 代码处理 有时候要对excell进行分析&#xff0c;或者数据的导入导出&#xff0c;这个时候如果可以用代码读写分析操作那么会方便很多 cvc格式 CSV&#xff08;Comma-Separated Values&#xff0c;逗号分隔值&#xff09;是…

新手小白如何挖掘cnvd通用漏洞之存储xss漏洞(利用xss钓鱼)

视频教程和更多福利在我主页简介或专栏里 &#xff08;不懂都可以来问我 专栏找我哦&#xff09; 如果对你有帮助你可以来专栏找我&#xff0c;我可以无偿分享给你对你更有帮助的一些经验和资料哦 目录&#xff1a; 一、XSS的三种类型&#xff1a; 二、XSS攻击的危害&#x…

代码随想录算法【Day52】

Day51 101. 孤岛的总面积 思路 从周边找到陆地然后 通过 dfs或者bfs 将周边靠陆地且相邻的陆地都变成海洋&#xff0c;然后再去重新遍历地图 统计此时还剩下的陆地 代码 #include <iostream> #include <vector> using namespace std; int dir[4][2] {-1, 0, …

Python开源项目月排行 2024年12月

#2024年12月2025年1月21日1DeepSeek-Coder-V2一个开源的专家混合&#xff08;MoE&#xff09;代码语言模型&#xff0c;其在代码特定任务中的性能可与GPT4-Turbo相媲美。具体而言&#xff0c;DeepSeek-Coder-V2是在DeepSeek-V2的一个中间检查点上进一步预训练的&#xff0c;增加…