spring-cache框架使用笔记

spring-cache框架使用笔记

什么是spring-cache框架

spring-cache是spring框架中的一个缓存抽象层,
它提供了一种简便的方式来集成不同的底层缓存实现,
如内存缓存(concurrentMap/ehcache/caffeine)/分布式缓存(redis/couchbase)等
它简化了在app中使用缓存的逻辑,并提供了一组注解和API来实现缓存功能

springCache的特点和功能

1.声明性缓存
spring-cache通过注解的方式,允许开发者在方法级别上声明方法的结果要被缓存。
相关注解有@Cacheable 读取缓存+不存在则缓存
@CacheEvict 清除缓存
@CachePut 强制缓存
@Caching 复合功能注解==(@Cacheable+@CacheEvict+@CachePut)
2.缓存的透明性
spring-cache提供了一致的编程接口,
无论底层使用哪种缓存技术,开发者都可以使用相同的方式访问和管理缓存。
这可以使app轻松切换/替换不同的缓存实现技术,而无需更改业务代码3.注解定义缓存策略
可以使用注解@Cacheable/@CacheEvict/@CachePut 声明缓存的行为和策略
如声明缓存名称,缓存key
@Cacheable(cacheNames = "user", key = "#id")
会按照key=user::id缓存数据 (可以参见spring-cache-redis缓存效果)4.支持SpEL表达式
使用SpEL表达式,可以定义缓存的键、条件等。
这允许开发者根据方法参数、方法返回值等动态生成缓存键或决定是否应用缓存。
@Cacheable(cacheNames = "user", key = "#id",condition = "#id != null ")public UserDO getById(Long id) {returnuserRepo.findById(id).orElse(null);}
这个案例只有当id不为空时,才进行缓存
condition属性可以设置缓存的条件,如
#id >=100
#userInfo.id >10
#id%2!=0
等等,必须确保condition能正确返回布尔值,才能决定当前方法最终是否进行缓存

springCache+caffeine配合使用

什么是caffeine

caffeine是一种java内存缓存技术,支持多种缓存策略,
caffeine可以单独使用于普通java项目/springboot/springcloud项目,
这里引入caffeine来作为spring-cache存取缓存的数据区。

xml依赖配置

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId><version>2.8.0</version></dependency>

yml配置

需要在application.yml配置spring-cache使用caffeine

spring:cache:type: caffeine #设置spring-cache框架使用caffeine存取数据  

caffeine配置类

package cn.test.cache;import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.concurrent.TimeUnit;@EnableCaching
@Configuration
public class CacheCfg {@Beanpublic CacheManager cacheManager(){//定义要缓存的caccheName有哪些CaffeineCacheManager caffeineCacheManager =new CaffeineCacheManager("user");//设置缓存配置caffeineCacheManager.setCaffeine(Caffeine.newBuilder()//初始容量10.initialCapacity(10)//最大容量200.maximumSize(200)//写入过期时间30s.expireAfterWrite(30, TimeUnit.SECONDS));return caffeineCacheManager;}
}

业务代码使用spring-cache相关注解

/*** @Cacheable注解标记一个方法时,spring会在执行方法之前,先检查缓存中是否已存在该方法的返回结果* 如果存在,则直接返回缓存的结果,不执行方法的实际逻辑* 如果不存在,则执行方法并将结果保存到缓存中*/@Cacheable(cacheNames = "user", key = "#id")public UserDO getById(Long id) {return userRepo.findById(id).orElse(null);}/*** @CacheEvict注解标记一个方法时,spring会在方法执行成功后,* 清空指定的缓存项,以确保下次访问时可以重新计算或查询最新的结果*/@CacheEvict(cacheNames = "user", key = "#id")public void delUser(Long id) {UserDO userDO = userRepo.findById(id).orElse(null);if (userDO != null) {userRepo.delete(userDO);}}/*** @CachePut注解标记一个方法时,spring会在方法执行后,* 将返回值放入指定的缓存中,* 以便将来的访问可以直接从缓存中获取结果,而不需要再执行方法的实际逻辑** @CachePut注解,适用于 创建新缓存 或 强制更新缓存 操作*/@CachePut(cacheNames = "user", key = "#userInfo.id")public UserDO addUser(UserDO userInfo) {return userRepo.save(userInfo);}/*** 执行updateUserInfo方法时,这里会按key 强制更新caffeine缓存*/@CachePut(cacheNames = "user", key = "#userInfo.id")public UserDO updateUserInfo(UserDO userInfo) {UserDO userDO = userRepo.findById(userInfo.getId()).orElse(null);if (userDO != null) {BeanUtils.copyProperties(userInfo,userDO);userRepo.save(userDO);}return userDO;}

springCache+redis配合使用

什么是redis

redis是一种分布式缓存技术,不限语言,C/S架构,支持可视化观测,
redis可以单独使用于普通java项目/springboot/springcloud项目/其他语言项目等,
这里引入redis来作为spring-cache框架存取缓存的数据区。

xml依赖配置

 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.5.0</version>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId>
</dependency>

yml配置

spring:redis:# iphost: localhost# 端口6379port: 6379#密码,没有密码则不配置这一项password:#指定使用redis 16个库中的哪一个,不配置的话,默认配置为0database: 2lettuce:pool:min-idle: 0   #连接池最新空闲时间max-wait: -1ms  #最大等待时间max-active: 8   #最大活跃时间max-idle: 8    #最大空闲时间shutdown-timeout: 100ms  #连接池关闭超时时间timeout: 1000ms  #redis连接超时时间cache:type: redis     #设置spring-cache框架使用redis存取数据  

redis配置类

package cn.test.cache;import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;import java.time.Duration;@EnableCaching
@Configuration
public class RedisConfig {/*** 设置RedisTemplate使用的序列化器,* 这里使用string作为key的序列化,使用jackson作为value的序列化* @param redisConnectionFactory* @return*/@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(redisConnectionFactory);RedisSerializer<String> stringRedisSerializer = new StringRedisSerializer();RedisSerializer<Object> jsonSerializer = new GenericJackson2JsonRedisSerializer();template.setKeySerializer(stringRedisSerializer);template.setValueSerializer(jsonSerializer);template.setHashKeySerializer(stringRedisSerializer);template.setHashValueSerializer(jsonSerializer);return template;}/*** 设置spring-cache使用redis后,要配置cacheManager缓存管理器** @param redisConnectionFactory* @return*/@Beanpublic RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {RedisSerializer<String> stringRedisSerializer = new StringRedisSerializer();RedisSerializer<Object> jsonSerializer = new GenericJackson2JsonRedisSerializer();RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()//设置spring-cache缓存到redis的数据有效期是60s.entryTtl(Duration.ofSeconds(60))//key的序列化使用字符串.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(stringRedisSerializer))//value的序列化使用jackson.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jsonSerializer));return RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(cacheConfiguration).build();}}

业务代码使用spring-cache相关注解

 @Cacheable(cacheNames = "user", key = "#id")public UserDO getById(Long id) {return userRepo.findById(id).orElse(null);}@CacheEvict(cacheNames = "user", key = "#id")public void delUser(Long id) {UserDO userDO = userRepo.findById(id).orElse(null);if (userDO != null) {userRepo.delete(userDO);}}@CachePut(cacheNames = "user", key = "#userInfo.id")public UserDO addUser(UserDO userInfo) {return userRepo.save(userInfo);}@CachePut(cacheNames = "user", key = "#userInfo.id")public UserDO updateUserInfo(UserDO userInfo) {UserDO userDO = userRepo.findById(userInfo.getId()).orElse(null);if (userDO != null) {BeanUtils.copyProperties(userInfo,userDO);userRepo.save(userDO);}return userDO;}

使用RDM工具观察redis存储的数据

因为RedisCacheManager配置了使用jackson序列化,这里缓存数据值是以json格式存储到redis的。

可以观察到spring-cache存入redis的缓存有效期是60s (因为RedisCacheManager配置了60秒);

缓存的key是

user::1

缓存的value是

{"@class": "cn.test.orm.user.UserDO","id": 1,"account": "ewr3","pwd": "23r3r","remark": "23r3r","createTime": ["java.sql.Timestamp","2023-08-02 11:14:02"],"updateTime": ["java.sql.Timestamp","2023-08-02 11:14:02"],"isDel": 0
}

在这里插入图片描述

SpringCache+Ehcache配合使用

xml依赖配置

<!--   ehcache缓存依赖     --><dependency><groupId>net.sf.ehcache</groupId><artifactId>ehcache-core</artifactId><version>2.6.11</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency>

yml配置

spring:cache:type: ehcache

Ehcache配置类

package cn.ath.knowwikibackend.cache;import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;@EnableCaching
@Configuration
public class EhcacheCfg {@Beanpublic CacheManager cacheManager(){EhCacheManagerFactoryBean factoryBean = new EhCacheManagerFactoryBean();factoryBean.setConfigLocation(new ClassPathResource("ehcache.xml"));factoryBean.setShared(true);return new EhCacheCacheManager(factoryBean.getObject());}
}

ehcache.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"updateCheck="false"><!--diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:user.home – 用户主目录user.dir  – 用户当前工作目录java.io.tmpdir – 默认临时文件路径 win7里是 C:\Users\Administrator\AppData\Local\Temp 目录--><diskStore path="java.io.tmpdir/Tmp_EhCache"/><!--defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。--><!--name:缓存名称。maxElementsInMemory:缓存最大数目maxElementsOnDisk:硬盘最大缓存个数。eternal:对象是否永久有效,一但设置了,timeout将不起作用。overflowToDisk:是否保存到磁盘,当系统当机时timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。clearOnFlush:内存数量最大时是否清除。memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。FIFO,first in first out,这个是大家最熟的,先进先出。LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。--><defaultCacheeternal="false"maxElementsInMemory="10000"overflowToDisk="true"diskPersistent="true"timeToIdleSeconds="1800"timeToLiveSeconds="259200"memoryStoreEvictionPolicy="LRU"/><cachename="user"eternal="false"maxElementsInMemory="5000"overflowToDisk="true"diskPersistent="true"timeToIdleSeconds="300"timeToLiveSeconds="800"memoryStoreEvictionPolicy="LRU"/></ehcache>

被Ehcache缓存的实体类要实现序列化接口

public class UserDO implements Serializable {private static final long serialVersionUID = -2035988554861976400L;@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Column(length = 255)private String account;@Column(length = 255)private String pwd;@Column(length = 255)private String remark;private Integer isDel=0;
}    

业务代码使用spring-cache相关注解

 @Cacheable(cacheNames = "user", key = "#id")public UserDO getById(Long id) {return userRepo.findById(id).orElse(null);}@CacheEvict(cacheNames = "user", key = "#id")public void delUser(Long id) {UserDO userDO = userRepo.findById(id).orElse(null);if (userDO != null) {userRepo.delete(userDO);}}@CachePut(cacheNames = "user", key = "#userInfo.id")public UserDO addUser(UserDO userInfo) {return userRepo.save(userInfo);}@CachePut(cacheNames = "user", key = "#userInfo.id")public UserDO updateUserInfo(UserDO userInfo) {UserDO userDO = userRepo.findById(userInfo.getId()).orElse(null);if (userDO != null) {BeanUtils.copyProperties(userInfo,userDO);userRepo.save(userDO);}return userDO;}

观察log日志中的spring-cache效果

先配置打印spring-cache日志

logging:# 设置自定义日志组group:rest: cn.ath.knowwikibackend.rest.testsyscache: org.springframework.cachelevel:# 为对应组设置日志级别rest: infosyscache: trace # spring-cache要设置trace级别才能看到日志

然后观察log日志,发现cache已经命中

2023-08-04 18:51:06,428  TRACE [16028]  [http-nio-8080-exec-4] [e0e8cc29-f027-4325-b68a-62441e54c094] o.s.c.i.CacheInterceptor [CacheAspectSupport.java : 598] Computed cache key '1' for operation Builder[public cn.ath.knowwikibackend.orm.user.UserDO cn.ath.knowwikibackend.biz.UserService.getById(java.lang.Long)] caches=[user] | key='#id' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='#id != null ' | unless='' | sync='false'
2023-08-04 18:51:06,428  TRACE [16028]  [http-nio-8080-exec-4] [e0e8cc29-f027-4325-b68a-62441e54c094] o.s.c.i.CacheInterceptor [CacheAspectSupport.java : 574] Cache entry for key '1' found in cache 'user'

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

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

相关文章

现代C++中的从头开始深度学习【1/8】:基础知识

一、说明 提及机器学习框架与研究和工业的相关性。现在很少有项目不使用Google TensorFlow或Meta PyTorch&#xff0c;在于它们的可扩展性和灵活性。也就是说&#xff0c;花时间从头开始编码机器学习算法似乎违反直觉&#xff0c;即没有任何基本框架。然而&#xff0c;事实并非…

Unity制作护盾——2、力场冲击波护盾

Unity制作力场护盾 大家好&#xff0c;我是阿赵。   继续做护盾&#xff0c;这一期做一个力场冲击波护盾。 一、效果展示 主要的效果并不是这个球&#xff0c;而是护盾在被攻击的时候&#xff0c;会出现一个扩散的冲击波。比如上图在右边出现了冲击波 如果在左边被攻击&am…

C/C++面试总结

一、关键字static、const、extern、volatile作用 1、const 1.修饰常量 用const修饰的变量是不可变的&#xff0c;修饰后的变量只能使用&#xff0c;不能修改。 2.修饰指针 如果const位于*的左侧&#xff0c;eg&#xff1a;const int* a&#xff0c;则const就是用来修饰指针…

Mr. Cappuccino的第56杯咖啡——Mybatis拦截器

Mybatis拦截器 概述应用场景项目结构实现分页查询其它拦截器的使用 概述 Mybatis允许使用者在映射语句执行过程中的某一些指定的节点进行拦截调用&#xff0c;通过织入拦截器&#xff0c;在不同节点修改一些执行过程中的关键属性&#xff0c;从而影响SQL的生成、执行和返回结果…

LLVM笔记2 Intermediate Representation (IR)

参考链接&#xff1a;https://llvm.org/devmtg/2019-04/slides/Tutorial-Bridgers-LLVM_IR_tutorial.pdf https://zhuanlan.zhihu.com/p/163063995 https://zhuanlan.zhihu.com/p/163328574 文章目录 IR的布局1. IR语法2.IR递归函数3.使用迭代的方式4.全局变量5.LLVM’s type s…

(03)Unity HTC VRTK 基于 URP 开发记录

1.简介 本篇主要内容为&#xff1a;URP如何与VRTK结合、URP需要注意的地方、VRTK的功能进行阐述。 因项目本身要求要渲染出比较好的画质&#xff0c;所以抛弃了Unity默认渲染管线Built-in&#xff0c;使用URP进行渲染&#xff0c;当然也可以选HDRP&#xff0c;但考虑到后期项目…

Stable Diffusion教程(9) - AI视频转动漫

配套抖音视频教程&#xff1a;https://v.douyin.com/UfTcrcJ/ 安装mov2mov插件 打开webui点击扩展->从网址安装输入地址&#xff0c;然后点击安装 https://github.com/Scholar01/sd-webui-mov2mov 最后重启webui 下载模型 从国内liblib AI 模型站下载模型 LiblibAI哩…

通过anvt X6和vue3实现图编辑

通过anvt X6 X6地址&#xff1a;https://x6.antv.antgroup.com/tutorial/about&#xff1b; 由于节点比较复杂&#xff0c;使用vue实现的节点&#xff1b; x6提供了一个独立的包 antv/x6-vue-shape 来使用 Vue 组件渲染节点。 VUE3的案例&#xff1a; <template><div…

软工导论知识框架(五)面向对象方法学

传统软件工程方法学适用于中小型软件产品开发&#xff1b; 面向对象软件工程方法学适用于大型软件产品开发。 一.四要素 对象&#xff0b;类&#xff0b;继承&#xff0b;传递消息实现通信 二.概念 1.对象&#xff1a;具有相同状态的一组操作的集合&#xff0c;对状态和操作…

数据挖掘全流程解析

数据挖掘全流程解析 数据指标选择 在这一阶段&#xff0c;使用直方图和柱状图的方式对数据进行分析&#xff0c;观察什么数据属性对于因变量会产生更加明显的结果。 如何绘制直方图和条形统计图 数据清洗 观察数据是否存在数据缺失或者离群点的情况。 数据异常的两种情况…

成功解决ubuntu-22.04的sudo apt-get update一直卡在【0% [Waiting for headers]】

成功解决ubuntu-22.04的sudo apt-get update一直卡在【0% [Waiting for headers]】 问题描述解决方案 问题描述 在下载安装包的时候一直卡在0% [Waiting for headers]&#xff0c;报错信息如下&#xff1a; Get:1 file:/var/cudnn-local-repo-ubuntu1804-8.5.0.96 InRelease […

【目标检测系列】YOLOV1解读

前言 从R-CNN到Fast-RCNN&#xff0c;之前的目标检测工作都是分成两阶段&#xff0c;先提供位置信息在进行目标分类&#xff0c;精度很高但无法满足实时检测的要求。 而YoLo将目标检测看作回归问题&#xff0c;输入为一张图片&#xff0c;输出为S*S*(5*BC)的三维向量。该向量…

docker菜谱大全

记录docker常用软件安装&#xff0c;感谢小马哥和杨师傅的投稿。&#x1f60e;&#x1f60e;&#x1f60e; 相关文档&#xff1a; DockerHub&#xff1a;https://hub.docker.com/Linux手册&#xff1a;https://linuxcool.com/Docker文档&#xff1a;https://docs.docker.com/Do…

ubuntu 暂时不能解析域名 解决办法

需要修改系统DNS 打开终端&#xff1a;输入 sudo vi /etc/resolv.conf 回车 在打开的配置文件中添加DNS信息 nameserver 114.114.114.114 nameserver 8.8.8.8 保存退出&#xff0c;重启系统即可。

20230802-下载并安装android-studio

下载 android-studio 安装包 https://developer.android.google.cn/studio/ 安装android-studio 双击安装包 D:\Android Studio

Android平台一对一音视频通话方案对比:WebRTC VS RTMP VS RTSP

一对一音视频通话使用场景 一对一音视频通话都需要稳定、清晰和流畅&#xff0c;以确保良好的用户体验&#xff0c;常用的使用场景如下&#xff1a; 社交应用&#xff1a;社交应用是一种常见的使用场景&#xff0c;用户可以通过音视频通话进行面对面的交流&#xff1b;在线教…

Redis 6.0的新特性:多线程、客户端缓存与安全

2020年5月份&#xff0c;6.0版本。 面向网络处理的多IO线程可以提高网络请求处理的速度&#xff0c;而客户端缓存可以让应用直接在客户端本地读取数据&#xff0c;这两个特性可以提升Redis的性能。 细粒度权限控制让Redis可以按照命令粒度控制不同用户的访问权限&#xff0c;…

基于MATLAB小波变换的信号突变点检测

之前在不经意间也有接触过求突变点的问题。在我看来&#xff0c;与其说是求突变点&#xff0c;不如说是我们常常玩的"找不同"。给你两幅图像&#xff0c;让你找出两个图像中不同的地方&#xff0c;我认为这其实也是找突变点在生活中的应用之一吧。回到找突变点位置上…

区块链学习6-长安链部署:如何创建特定共识节点数和同步节点数的链

正常prepare的时候只支持4 7 13 16个节点个数&#xff0c;想要创建10个节点&#xff0c;其中5个是共识节点&#xff0c;如何实现&#xff1f; 1. 注释掉prepare.sh的这几行&#xff1a; 2. 修改 crytogen的模板文件&#xff1a; 如果是cert模式&#xff1a;chainmaker-crypt…

AI lightning学习

真的是没有mmlab的框架好理解&#xff0c;hook调用没问题&#xff0c;就是代码写的不整洁&#xff0c;hook放的到处都是&#xff0c;而且hook的名字和run的名字也不好对应。 又是捧mmengine的一天 &#x1f603;