SpringBoot核心框架之AOP详解

SpringBoot核心框架之AOP详解

一、AOP基础

1. AOP概述
  • AOP:Aspect Oriented Programming(面向切面编程,面向方面编程),其实就是面向特定方法编程。

  • 场景:

    • 项目部分功能运行较慢,定位执行耗时较长的业务方法,此时就需要统计每一个业务的执行耗时。

    思路:给每个方法在开始前写一个开始计时的逻辑,在方法结束后写一个计时结束的逻辑,然后相减得到运行时间。

    思路是没问题的,但是有个问题,一个项目是有很多方法的,如果挨个增加逻辑代码,会相当繁琐,造成代码的臃肿,

    所以可以使用AOP编程,将计时提出成一个这样的模板:

    **1. 获取方法运行开始时间 **

    2. 运行原始方法

    3. 获取方法运行结束时间,计算执行耗时。

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

  • 实现:

    • 动态代理是面向切面编程最主流的实现。而SpringAOP是Spring框架的高级技术,旨在管理bean对象的过程中,主要是通过底层的动态代理机制,对特点的方法进行编程。
2. AOP快速入门
  • 统计各个业务层方法执行耗时

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

      <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
      </dependency>
      
    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. 维护方便
3. AOP核心概念
  • 连接点:JoinPoint,可以被连接点控制的方法(暗含方法执行时的信息)。 在此例中就是需要被计算耗时的业务方法。
  • 通知:Advice,指那些重复的逻辑,也就是共性功能(最终体现为一个方法)。在此例中就是计算耗时的逻辑代码。
  • 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用。在此例中就是com.example.service 包下所有类的所有方法。
  • 切面:Aspect,描述通知与切入点的对应关系(通知+切入点)。在此例中就是TimeAspect方法。
  • 目标对象:Target,通知所应用的对象。在此例中就是通知com.example.service 包下所有类的所有方法。
4. AOP的执行流程

因为SpringAOP是基于动态代理实现的,所有在方法运行时就会先为目标对象基于动态代理生成一个代理对象,为什么说AOP可以增强方法,就是因为有一个代理方法,然后在AOP执行时,Spring就会将通知添加到代理对象的方法前面,也就是记录开始时间的那个逻辑代码,然后调用原始方法,也就是需要计时的那个方法,此时代理对象已经把原始方法添加到代理对象里面了,然后执行调用原始方法下面的代码,在此例中就是计算耗时的那部分,AOP会把这部分代码添加到代理对象的执行方法的下面,这样代理对象就完成了对目标方法的增强,也就是添加了计时功能,最后在程序运行时自动注入的也就不是原来的对象,而是代理对象了,不过这些都是AOP自动完成,我们只需要编写AOP代码即可。

二、AOP进阶

1. AOP支持的通知类型
  • 通知类型:

    1. 环绕通知(Around Advice) 重点!!!:
      • 使用 @Around 注解来定义。
      • 包围目标方法的执行,可以在方法执行前后执行自定义逻辑,并且可以控制目标方法的执行。
      • 通过 ProceedingJoinPoint 参数的 proceed() 方法来决定是否执行目标方法。
    2. 前置通知(Before Advice)
      • 使用 @Before 注解来定义。
      • 在目标方法执行之前执行,无论方法是否抛出异常,都会执行。
      • 不能阻止目标方法的执行。
    3. 后置通知(After Advice) 也叫最终通知
      • 使用 @After 注解来定义。
      • 在目标方法执行之后执行,无论方法是否抛出异常,都会执行。
      • 通常用于资源清理工作
      • 返回通知(After Returning Advice) 了解
      • 使用 @AfterReturning 注解来定义。
      • 在目标方法成功执行之后执行,即没有抛出异常时执行。
      • 可以获取方法的返回值。
    4. 异常通知(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. 多个通知之间的执行顺序
  • 当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会执行。那么顺序是怎么的呢?

    • 我们先创建三个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 ...
      
    1. 默认情况:

      ​ 执行顺序是和类名有关系的,对于目标方法前的通知字母越靠前的越先执行,目标方法后的通知则相反,字母越靠前的越晚执行,这和Filter拦截器的规则是一样的。

    2. 也可以使用注解的方式指定顺序。使用@Order(数字)加在切面类上来控制顺序。

      ​ 目标方法前的通知:数字小的先执行。

      ​ 目标方法后的通知:数字小的后执行。

      @Slf4j
      @Component
      @Aspect@Order(10)public class MyAspect3 {...
      }
      
3. 切入点表达式
  • 切入点表达式:描述切入点方法的一种表达式。
  • 作用:主要决定项目中哪些方法需要加入通知。
  • 常见形式:
    • execution(…):根据方法的签名来匹配。
    • @annotation:根据注解匹配。
  1. execution(…)
    1. 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. @annotation:用于匹配标识有特定注解的方法

    语法:@annotation(注解的全类名)

    1. 先新建一个注解:

      @Retention(RetentionPolicy.RUNTIME)  // 用来描述有效时间,RUNTIMW:在运行时有效
      @Target(ElementType.METHOD) // 用来说明这个注解可以运行在哪里, METHOD:方法上
      public @interface MyLog {
      }
      
    2. 在目标方法上添加注解

      @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);
      }
      
    3. 在切入点表达式以注解的方式进行

      @After("@annotation(com.example.aop.MyLog)")
      public void after(){...
      }
      
4. 连接点
  • 在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案例

1. 分析
  • 需求:将项目中的增、删、改、相关接口的操作日志记录到数据库表中
    • 操作日志包含:操作人,操作时间,执行方法的全类名,执行方法名,方法运行时的参数,返回值,方法运行时长。
  • 思路分析:
    • 需要对方法添加统一的功能,使用AOP最方便,并且需要计算运行时长,所以使用 环绕通知
    • 因为增删改的方法名没有规则,所以使用注解的方式写切入表达式
  • 步骤:
    • 准备:
      • 案例中引入AOP的起步依赖
      • 设计数据表结构,并且引入对应的实体类
    • 编码:
      • 自定义注解:@Log
      • 定义切面类,完成记录操作日志的逻辑代码
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 '操作日志表';
    
  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. 新建实体类

    @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; //操作耗时
    }
    
  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);
    }
    
  5. 新建注解

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Log {
    }
    
  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;}
    }
    
  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. 查看结果

刚刚进行了部门的增删改以及员工的增删改操作,我们查看数据库,看有没有被记录。

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.rhkb.cn/news/458289.html

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

相关文章

CSS_定位_网页布局总结_元素的显示与隐藏

目录 目标 1. 定位 1.1 为什么需要定位 1.2 定位组成 1. 定位模式 2. 边偏移 1.3 静态定位 static&#xff08;了解&#xff09; 1.4 相对定位 relative&#xff08;重要&#xff09; 1.5 绝对定位 absolute&#xff08;重要&#xff09; 1.6 子绝父相的由来&#xff…

【CCL】浅析 CFX Command Language

【CCL】浅析 CFX Command Language 文章目录 【CCL】浅析 CFX Command LanguageI - 前言ActionsPower Syntax II - Perl 语法概述变量定义注释字符串操作符其他 CCL 语法概述参考链接 I - 前言 CCL 全称是 CFX Command Language &#xff0c;是 CFX-Post 软件内部通讯的命令行…

LLM | 论文精读 | NeurIPS 2023 | SWIFTSAGE: 结合快思考与慢思考的生成智能体

论文标题&#xff1a;SWIFTSAGE: A Generative Agent with Fast and Slow Thinking for Complex Interactive Tasks 作者&#xff1a;Bill Yuchen Lin, Yicheng Fu, Karina Yang, Faeze Brahman, Shiyu Huang, Chandra Bhagavatula, Prithviraj Ammanabrolu, Yejin Choi, Xian…

Python的协程与传统的线程相比,是否能更有效地利用计算资源?在多大程度上,这种效率是可测量的?如何量化Python协程的优势|协程|线程|性能优化

目录 1. 协程与线程的基本概念 1.1 线程 1.2 协程 2. 协程的实现原理 2.1 基本示例 3. 协程与线程的效率对比 3.1 资源利用率 3.2 性能测试 4. 使用场景分析 4.1 适用场景 4.2 不适用场景 5. 性能监测与测量 5.1 使用时间记录 5.2 使用第三方库 6. 总结与展望 P…

入门 | Prometheus+Grafana 普罗米修斯

#1024程序员节&#xff5c;征文# 一、prometheus介绍 1、监控系统组成 一个完整的监控系统需要包括如下功能&#xff1a;数据产生、数据采集、数据存储、数据处理、数据展示、分析、告警等。 &#xff08;1&#xff09;、数据来源 数据来源&#xff0c;也就是需要监控的数据…

【认知智能】编译器1

深度学习编译器是一种专门设计用来优化和加速深度学习模型在各种硬件平台上执行的工具。它们通过将高级深度学习框架&#xff08;如TensorFlow, PyTorch等&#xff09;中的计算图转换为针对特定硬件架构优化过的低级代码来实现这一目标。基础架构通常包括以下几个关键组件&…

ML 系列:机器学习和深度学习的深层次总结(17)从样本空间到概率规则概率

一、说明 概率是支撑大部分统计分析的基本概念。从本质上讲&#xff0c;概率提供了一个框架&#xff0c;用于量化不确定性并对未来事件做出明智的预测。无论您是在掷骰子、预测天气还是评估金融市场的风险&#xff0c;概率都是帮助您驾驭不确定性的工具。本篇将讲授概率的原理和…

STM32 第17章 EXIT--外部中断/事件控制器

时间:2024.10.23 参考资料:《零死角玩转STM32》“EXTI-外部中断/事件控制器” STM32里每一个GPIO都能产生中断,中断的产生体现在GPIO的电平的变化,电平的变化需要一个外设来管理,最终传给NVIC(内核里的嵌套中断控制器)来处理。 一、学习内容 1、EXTI功能框图+EXTI初始…

51单片机完全学习——红外遥控

一、红外接收模块原理 红外接收头内部本身有一个反相&#xff0c;意思就是&#xff1a;平时发送方无信号时接收到的是1&#xff0c;发送方有发送载波时接收头引脚输出的是0&#xff0c;写代码的时候注意这一点。红外协议&#xff0c;你也可以理解成&#xff0c;他对0和1重新做…

HarmonyOS 5.0应用开发——Navigation实现页面路由

【高心星出品】 文章目录 Navigation实现页面路由完整的Navigation入口页面子页面 页面跳转路由拦截其他的 Navigation实现页面路由 Navigation&#xff1a;路由导航的根视图容器&#xff0c;一般作为页面&#xff08;Entry&#xff09;的根容器去使用&#xff0c;包括单页面&…

iPhone SE 4:定了

万众期待的iPhone SE 4&#xff0c;近日传来了确定的消息。 近日&#xff0c;屏幕供应链分析师Ross Young透露&#xff1a;iPhone SE 4的屏幕面板&#xff0c;预计在 11 月份开始出货&#xff0c;并将于 2025 年年初正式发布。 来了来了&#xff0c;终于来了。 和以往不同&am…

【C++打怪之路Lv12】-- 模板进阶

#1024程序员节&#xff5c;征文# &#x1f308; 个人主页&#xff1a;白子寰 &#x1f525; 分类专栏&#xff1a;重生之我在学Linux&#xff0c;C打怪之路&#xff0c;python从入门到精通&#xff0c;数据结构&#xff0c;C语言&#xff0c;C语言题集&#x1f448; 希望得到您…

【jvm】堆的内部结构

目录 1. 说明2. 年轻代&#xff08;Young Generation&#xff09;2.1 说明2.2 Eden区2.3 Survivor区 3. 老年代&#xff08;Old Generation&#xff09;3.1 说明3.2 对象存放3.3 垃圾回收 4. jdk7及之前5. jdk8及之后 1. 说明 1.JVM堆的内部结构主要包括年轻代&#xff08;You…

SpringBoot poi-tl通过模板占位符生成word文件

简介&#xff1a; 开发中我们需要通过在word中使用占位符来动态渲染一些数据&#xff0c;本文讲解poi-tl实现动态生成word文档&#xff0c;包括表格循环&#xff0c;对象嵌套。 poi-tl官网文档 Poi-tl Documentation 1. word格式 这是我的test.word 这是导出后的out.docx文件 …

详解Pectra升级:如何影响以太坊价值及利益相关者

Pectra很可能是最后几个会直接影响用户和ETH持有者的升级之一。 原文&#xff1a;Galaxy Research&#xff1b;编译&#xff1a;Golem&#xff1b;编辑&#xff1a;郝方舟 出品 | Odaily星球日报&#xff08;ID&#xff1a;o-daily&#xff09; 编者按&#xff1a;以太坊 Pectr…

了解 WebSocket

了解 WebSocket 轮询方式、短轮询长轮询SSE WebSocket为什么说 WebSocket 是基于 Http 协议的&#xff1f;如何通过 Sec-WebSocket-Key 与 验证 Sec-WebSocket-Accept验证 demo SpringBoot 中使用 WebSocket引入依赖增加 WebSocketConfig修改 ServerEndpointConfig定义 ServerE…

保研考研机试攻略:python笔记(1)

&#x1f428;&#x1f428;&#x1f428;宝子们好呀 ~ 我来更新欠大家的python笔记了&#xff0c;从这一篇开始我们来学下python&#xff0c;当然&#xff0c;如果只是想应对机试并且应试语言以C和C为主&#xff0c;那么大家对python了解一点就好&#xff0c;重点可以看高分篇…

易基因:Nat Commun:ATAC-seq等揭示恒河猴大脑高分辨率解剖区域的转录组和开放染色质图谱

大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。 恒河猴是神经科学研究中常用的模型动物&#xff0c;其大脑结构和功能与人类大脑相似。大脑中复杂的遗传网络是灵长类动物行为、认知和情感的基础&#xff0c;一直是神经科学的核心。大脑…

禾川SV-X2E A伺服驱动器参数设置——脉冲型

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家&#xff01;人工智能学习网站 前言&#xff1a; 大家好&#xff0c;我是上位机马工&#xff0c;硕士毕业4年年入40万&#xff0c;目前在一家自动化公司担任…

【Android】基础回顾--四大组件

1. 四大组件是什么&#xff1f; 四大组件&#xff1a;Activity、Service、BroadcastReceiver、ContentProvider。 2. 四大组件的生命周期和简单用法 Activity&#xff1a; 特殊情况下的生命周期&#xff1a; 典型的生命周期好像没什么可说的&#xff0c;主要说一下特殊情况…