java:aocache:基于aspectJ实现的方法缓存工具
背景
最近一直在做一些服务端的设计,经常遇到常量计算的问题,比如获取查找一个类的所有方法,获取有指定注解(Annnotation)的方法并查找注解的上特定的元注解是否有特定的值 。。。。总之逻辑很复杂,而且会频繁调用。
比如在服务端方法拦截器上经常执行这样的计算,事实上在运行时,对于一个类(Class)或方法(Method),它在运行时就是个常量,上述复杂计算返回结果也是一个恒定的值不会改变,称之为常量计算。
如果每次请求同一个服务方法都要重复执行这样的计算,无疑是对性能的浪费。
FunctionCached
为此,我很久之前就实现了一个解决个问题的工具类FunctionCached
完整代码参见 :https://gitee.com/l0km/common-java/blob/master/common-base2/src/main/java/net/gdface/cache/FunctionCached.java
相关说明参见之前的博客:《java:基于guava缓存(LoadingCache)实现结果缓存避免重复计算》
《java:基于弱引用(WeakReference)的FunctionCached实现》
有了FunctionCached,实现一个方法的缓存就比较方便了,大概是如下的样子:
/** 缓存对象 */private final FunctionCached<String, Class<?>> CACHED_CLASS_FORNAME = FunctionCached.builder()/** 弱引用值模式 */.weakValues().nullable().getterFunction(new Function<String, Class<?>>() {@Overridepublic Class<?> apply(String suffix) {/** 执行计算 */return classFormName0(suffix);}}).build();/** 真正负责计算的方法 */private Class<?> classFormName0(String className){// DO SOMETHING}/** * 对外提供的计算方法,* 方法实现就是从缓存获取参数为key的值,* 如果值不存在缓存自动执行 classFormName0,将结果再存入缓存,避免下次再计算。 */public Class<?> classFormName(String className){return CACHED_CLASS_FORNAME.get(className);}
如上的模式大概就是一内一外两个方法,内部方法负责真正的计算由缓存对象调用,外部方法则直接从缓存读取数据提供给调用方。
烦恼
话说这个FunctionCached我用了好长时间,一直觉得还不错。上个月写了一个新项目casban,项目中用FunctionCached写了十几个不同的缓存方法,写得我不胜其烦。
两周前才结束的项目beanfilter又涉及到大量的方法缓存。
这境遇,让我深感在大量需要计算结果缓存提高性能的场景下,FunctionCached用起来还是不顺手。要是能更简单就好了。
不满的种子开始发芽----必须要做点什么改变现状。
设计目标
为了彻底解决这个麻烦,前阵子断断续续的开始构思一个新的工具,上周开始设计,目标设计是一个更方便使用且通用的方法(Method)缓存工具,如果也能实现单实例(Constructor)缓存则更好。
项目定名aocache(Aspect Oriented Cache),定下了如下的设计目标:
- 支持 1.7及以上JDK版本
这样才有更广泛的通用性,相对底层的通用工具设计时也能用得上。
- 依赖库少
最好没有依赖库,目的同上。
- 方便使用
以注解(Annotaion)标记为使用主要方式。
- 支持对不同方法个性化配置缓存,
比如支持弱引用模式(WeakReference),软引用模式(SoftReference)以减少不必要的资源占用。
效果
经过一周的设计,总算完成,发布第一个版本0.2.0,达到预期设计目标。以前面的classFormName方法的例子来对比的话,最终的使用效果如果就是这样:
/** 注解定义该方法由aocache提供结果缓存 */@AoCacheablepublic Class<?> classFormName(String className){// DO SOMETHING}
是不是很简单?十几行代码缩减为一个注解@AoCacheable
aocache
现在开始正式介绍 aocache:
aocache(Aspect Oriented Cache)是一个基于aspectJ实现的方法(Method/Constructor)缓存工具。
设计用于对方法计算返回的结果缓存,以实现同的输入参数只计算一次,第二次以后调用法则返回第一次计算的结果,以减少重复计算提高系统运行效率,适用于运行时(runtime)输入参数与输出结果保持恒定映射的场景。
aocache基于切面编程(Aspect-Oriented Programming)框架实现方法调用拦截,保存计算结果的缓存基于guava的LoadingCache缓存实现。
快速入门
pom.xml配置
引入依赖
<dependency><groupId>com.gitee.l0km</groupId><artifactId>aocache</artifactId><version>0.2.0</version></dependency>
aocache不会传递依赖任何其他依赖库
增加maven插件aspectj-maven-plugin用于项目编译时织入(Compile Time Weaving)。
Compile Time Weaving:
编译时织入也叫静态织入,是aspectj最简单的织入形式。aspectj-maven-plugin插件执行aspectj编译器AJC 在源码编译过程中将切面拦截代码添加到Java编译器生成的的.class。
<build><plugins><plugin><groupId>org.codehaus.mojo</groupId><artifactId>aspectj-maven-plugin</artifactId><version>1.10</version><configuration><source>1.7</source><target>1.7</target><encoding>UTF-8</encoding><complianceLevel>1.7</complianceLevel><verbose>true</verbose><showWeaveInfo>true</showWeaveInfo><aspectLibraries><aspectLibrary><groupId>com.gitee.l0km</groupId><artifactId>aocache</artifactId></aspectLibrary></aspectLibraries></configuration><executions><execution><goals><goal>compile</goal><goal>test-compile</goal></goals></execution></executions></plugin></plugins></build>
源码注解
如下在AocacheTest.hello(String)方法上增加注解@AoCacheable就激活了该方法的aocache缓存能力。
如果IDE环境没有配置aspectj的合适环境,下面的单元测试可能无法在IDE中正确运行。我的eclipse目前还不行。
示例如下:
AocacheTest.java
import static org.junit.Assert.*;import org.junit.Test;import com.gitee.l0km.aocache.MemberCache;
import com.gitee.l0km.aocache.annotations.AoCacheable;public class AocacheTest {@AoCacheableprivate String hello(String name) {return "hello,"+String.valueOf(name);}@Testpublic void test() {try {String s = hello("jerry");System.out.println(s);/** 第二次以后调用返回结果与第一次是同一个对象 */System.out.printf("%s %b\n",hello("jerry"),hello("jerry")==s);System.out.printf("%s %b\n",hello("jerry"),hello("jerry")==s);System.out.printf("%s %b\n",hello("jerry"),hello("jerry")==s);System.out.printf("%s %b\n",hello("jerry"),hello("jerry")==s);/** 获取方法实际的的执行次数 */long hitCount = MemberCache.INSTANCE.invokeCountOf(this, "hello",new Class<?>[]{String.class},new Object[]{"jerry"});System.out.printf("调用次数 %d\n", hitCount);/** 断言:hello(String)方法对于参数'jerry'的调用只有一次 */assertEquals(1, hitCount);} catch (Throwable e) {e.printStackTrace();fail(e.getMessage());}}}
编译
mvn clean install
maven插件aspectj-maven-plugin输出显示hello方法已经被增加了拦截点(Join Point)
[INFO] --- aspectj-maven-plugin:1.10:test-compile (default) @ aocache-example-ctw ---
[INFO] Showing AJC message detail for messages of types: [error, warning, fail]
[INFO] Join point 'method-execution(void com.gitee.l0km.aocache.example.ctw.AocacheCtwTest.hello())' in Type 'com.gitee.l0km.aocache.example.ctw.AocacheCtwTest' (AocacheCtwTest.java:24) advised by around advice from 'com.gitee.l0km.aocache.aop.AocacheAnnotatedAspect' (aocache-0.0.0-SNAPSHOT.jar!AocacheAnnotatedAspect.class(from AocacheAnnotatedAspect.java))
执行单元测试:
mvn -Dtest=AocacheTest -DskipTests=false test
输出:
T E S T S
-------------------------------------------------------
Running com.gitee.l0km.aocache.example.ctw.AocacheTest
hello,jerry
hello,jerry
hello,jerry
hello,jerry
hello,jerry
调用次数 1
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.084 secResults :Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
@AoCacheable
aocache为每个方法定义一个独立的缓存对象。aocache允许为每个方法配置不同的缓存策略, 最简单的方法就是通过@AoCacheable注解实现缓存配置
@AoCacheable注解定义在方法指定缓存该方法的的返回值。通过@AoCacheable上定义的字段,可以精确配置每个方法缓存。
@AoCacheable 也可以定义在构造方法上,定义在构造方法就为该类实现了单实例缓存
| 字段名 | 默认值 | 说明 |
|---|---|---|
| debugOutput | false | 是否输出调试信息 |
| debugOutputDetail | false | 是否输出详细调试信息 |
| weakKeys | false | 指定存储在缓存中的每个键(而不是值)都应封装在WeakReference中(默认情况下,使用强引用)。 |
| weakValues | false | 指定存储在缓存中的每个值(而不是键)都应包装在SoftReference中(默认情况下,使用强引用)。软引用的对象将以全局最近最少使用的方式进行垃圾收集,以响应内存需求。 |
| softValues | false | 指定存储在缓存中的每个值(而不是键)都应包装在SoftReference中(默认情况下,使用强引用)。软引用的对象将以全局最近最少使用的方式进行垃圾收集,以响应内存需求。 |
| initialCapacity | -1 | 设置内部哈希表的最小总大小。例如,如果初始容量为60,并发级别为8,则创建八个段,每个段都有一个大小为8的哈希表。在构建时提供足够大的估计值可以避免以后进行昂贵的调整大小操作,但不必设置此值—过高会浪费内存。 |
| concurrencyLevel | 0 | 指定更新操作之间允许的并发性。用作内部大小调整的提示。该表在内部进行了分区,以尝试在没有争用的情况下允许指定数量的并发更新。 |
| maximumSize | -1 | 指定缓存可以包含的最大条目数。请注意,缓存可能会在超过此限制之前收回一个条目。随着缓存大小接近最大值,缓存会收回不太可能再次使用的条目。例如,缓存可能会因为最近或不经常使用而被逐出。 |
| maximumWeight | -1 | 指定缓存中可能包含的项的最大权重。重量是使用地磅指定的秤来确定的,使用这种方法需要在调用构建之前对地磅进行响应调用。 |
| expireAfterWrite | -1 | 指定在创建条目或最近替换其值后经过固定的持续时间后,应自动从缓存中删除每个条目。 |
| expireAfterWriteTimeUnit | MINUTES(分钟) | expireAfterWrite的时间单位 |
| expireAfterAccess | -1 | 指定在创建条目、最近一次替换其值或最后一次访问之后经过固定的持续时间后,应自动从缓存中删除每个条目。访问时间由所有缓存读取和写入操作(包括cache.asMap().get(Object)和cache.asMop().put(K,V))重置,但不由cache.asMap的集合视图上的操作重置。 |
| expireAfterAccessTimeUnit | MINUTES(分钟) | expireAfterAccess的时间单位 |
| refreshAfterWrite | -1 | 指定活动条目的创建或其值的最新替换后经过固定的持续时间后,活动条目的自动刷新条件。刷新的语义在LoadingCache.refresh中指定,并通过调用CacheLoader.reload来执行。 由于CacheLoader.reload的默认实现是同步的,建议此方法的用户使用异步实现覆盖CacheLoader.reload;否则将在不相关的缓存读取和写入操作期间执行刷新。 当前自动刷新是在对条目的第一个过时请求发生时执行的。触发刷新的请求将对CacheLoader.reload进行阻塞调用,如果返回的future完成,则立即返回新值,否则返回旧值。 |
| refreshAfterWriteTimeUnit | MINUTES(分钟) | refreshAfterWrite的时间单位 |
弱引用模式
默认配置使用强引用模式保存Key和Value,这种情况下,计算结果一旦产生常驻内存。
弱引用模式允许在JVM自动清理不再被引用的计算结果(VALUE)或其对应的KEY。下次调用时再重新计算。适用于不需要长期保存的数据。
所以用户可以根据自己的场景选择使用强引用还是弱引用配置缓存。
@Cacheable Of Spring
spring也使用 @Cacheable注解也提供了类似的方法缓存功能,那么aocache与之有什么不同?
显然,spring的方法缓存功能只能用于spring环境下,而aocache只要求JDK 1.7以上任何Java开发环境。
项目地址
码云仓库位置:https://gitee.com/l0km/aocache
请访问git仓库获取完整说明
示例项目
aocach-example 为aocache的示例项目,
项目中实践了aocache在aspectJ的三种织入模式(CTW/LTW/PCW)下的应用。
码云仓库位置:https://gitee.com/l0km/aocache-example
参考资料
《Chapter 5. Load-Time Weaving》
《The AspectJTM 5 Development Kit Developer’s Notebook》
相关文章:
java:aocache:基于aspectJ实现的方法缓存工具
背景 最近一直在做一些服务端的设计,经常遇到常量计算的问题,比如获取查找一个类的所有方法,获取有指定注解(Annnotation)的方法并查找注解的上特定的元注解是否有特定的值 。。。。总之逻辑很复杂,而且会频繁调用。 比如在服务端…...
UE4_材质_湿度着色器及Desaturation算法_ben材质教程
学习笔记,不喜勿喷!侵权立删,祝愿美好生活越来越好。 效果图: 原图: 1、使用初学者内容包的材质 我们这里使用虚幻自带的材质M_Brick_Clay_Old,复制一个更名为M_Brickclayoldwet材质。 2、添加去饱和度Desaturation节…...
AI问答-ERP:理解 ERP / 我国ERP发展现状 / ERP软件有哪些 / 华为自研ERP
一、理解ERP 1.1、定义 ERP(Enterprise Resource Planning)是企业资源计划的缩写,它集成了企业各个业务领域,包括采购、销售、库存、生产制造、财务等多个方面,进行全面管理、智能决策的一种企业管理系统。 1.2、功…...
C语言 | Leetcode C++题解之第199题二叉树的右视图
题目: 题解: #define MAX_NODE_NUM 100 int* rightSideView(struct TreeNode* root, int* returnSize){if (root NULL) {*returnSize 0;return NULL;}int *res (int *)malloc(sizeof(int) * MAX_NODE_NUM);int cnt 0;struct TreeNode **record (st…...
java:aocache的单实例缓存
上一篇博客《java:aocache:基于aspectJ实现的方法缓存工具》介绍了aocache的基本使用, 介绍AoCacheable注解时说过,AoCacheable可以定义在构造方法上,定义在构造方法,该构建方法就成了单实例模式。 也就是说,只要构建…...
c++11 abi 兼容性
理解 _GLIBCXX_USE_CXX11_ABI: 兼容性与现代化之间的平衡 随着 C 标准的不断演进,编译器和标准库实现也在不断更新,以支持新的语言特性和库功能。然而,这些更新有时会引入不兼容的更改,特别是应用程序二进制接口(ABI&…...
获取个人免费版Ubuntu Pro
首先上官网地址:Ubuntu Pro | Ubuntu 点击页面中的"Get Ubuntu Pro now" 将用途选为“Myself”,在此页面中Ubuntu说明了该版本只面向个人开发者,且最终只允许5台设备免费使用;因而部署设备的抉择就不得不慎重考虑了&am…...
Pinia的基本用法
Pinia的安装和引入 1.安装Pinia npm install pinia2. 在vue项目的main.js文件中引入pinia import { createApp } from vue import { createPinia } from pinia import App from ./App.vueconst pinia createPinia() const app createApp(App)app.use(pinia) app.mount(#ap…...
正版软件 | DeskScapes:将您的桌面变成生动的画布
您是否厌倦了静态的桌面背景?Stardock 的 DeskScapes 软件赋予您将任何图片或视频动画化的能力,让您的 Windows 桌面焕发活力。 动画桌面,艺术生活 使用 DeskScapes 您可以将任何静态图片或视频转化为桌面背景。不仅如此,通过 60 …...
OpenCV cv::Mat到 Eigen 的正确转换——cv2eigen
在进行计算机视觉项目时,我们经常需要处理相机位姿的变换。最近,我在项目中遇到了一个看似简单但实际上颇具挑战性的问题:从 OpenCV 的 cv::Mat 格式转换到 Eigen 库的格式。这个过程中遇到了一些问题,但最终找到了一个稳健的解决…...
PostgreSQL的扩展(extensions)-常用的扩展-pg_pathman
PostgreSQL的扩展(extensions)-常用的扩展-pg_pathman pg_pathman 是一个用于 PostgreSQL 的分区管理扩展。它提供了一种高效的方式来管理和使用数据库分区,可以显著提升查询性能,特别是在处理大规模数据集时。 安装 pg_pathman…...
数据结构之树
基础知识: 树是一种非线性结构,其严格的数学定义是:如果一组数据中除了第一个节点(第一个节点称为根节点,没有直接前驱节点)之外,其余任意节点有且仅有一个直接前驱,有零个或多个直接…...
6毛钱SOT-23封装28V、400mA 开关升压转换器,LCD偏置电源和白光LED应用芯片TPS61040
SOT-23-5 封装 TPS61040 丝印PHOI 1 特性 • 1.8V 至 6V 输入电压范围 • 可调节输出电压范围高达 28V • 400mA (TPS61040) 和 250mA (TPS61041) 内部开关电流 • 高达 1MHz 的开关频率 • 28μA 典型空载静态电流 • 1A 典型关断电流 • 内部软启动 • 采用 SOT23-5、TSOT23…...
saga模型
Saga源于Hector Garcaa-Molrna和Kenneth Salem发表的论文Sagas。一个LLT事务(Long Lived Transaction)可以分成若干个小的事务执行单元,这些小执行单元就是saga事务。Saga方案更适合用于长事务场景。Saga模型将一个分布式事务拆分为多个本…...
深度神经网络:解锁智能的密钥
深度神经网络:解锁智能的密钥 在人工智能的浩瀚星空中,深度神经网络(Deep Neural Networks, DNNs)无疑是最耀眼的那颗星。它以其强大的学习能力、高度的适应性和广泛的应用场景,成为了我们解锁智能世界的一把密钥。本…...
国际现货黄金最新价格如何分析?结合较高的时间周期
国际现货黄金投资是一种24小时交易的品种,这意味着,在交易日我们打开电脑图表,分析完走势之后就有机会做交易了。但问题也出在这里,如果对国际现货黄金最新价格把握不住,分析和交易就无从谈起了,下面我们就…...
微服务和kafka
一、微服务简介 1.单体架构 分布式--微服务--云原生 传统架构(单机系统),一个项目一个工程:比如商品、订单、支付、库存、登录、注册等等,统一部署,一个进程 all in one的架构方式,把所有的…...
Jetpack架构组件_Navigaiton组件_1.Navigaiton切换Fragment
1.Navigation主要作用 方便管理Fragment (1)方便我们管理Fragment页面的切换 (2)可视化的页面导航图,便于理清页面间的关系。 (3)通过destination和action完成页面间的导航 (4&a…...
[计算机网络] 虚拟局域网
虚拟局域网 VLAN(Virtual Local Area Network,虚拟局域网)是将一个物理的局域网在逻辑上划分成多个广播域的技术。 通过在交换机上配置VLAN,可以实现在同一个VLAN 内的用户可以进行二层互访,而不同VLAN 间的用户被二…...
LabVIEW遇到无法控制国外设备时怎么办
当使用LabVIEW遇到无法控制国外产品的问题时,解决此类问题需要系统化的分析和处理方法。以下是详细的解决思路和具体办法,以及不同方法的分析和比较,包括寻求代理、国外技术支持、国内用过的人请教等内容。 1. 了解产品的通信接口和协议 思路…...
告别WoMic:用VB-Audio Virtual Cable和TCP Socket自建无线麦克风(含参数配置避坑指南)
无线音频传输方案重构:VB-Audio与TCP Socket的工程实践 在音频处理领域,虚拟麦克风技术一直是个既实用又有趣的话题。许多开发者最初接触这一领域是通过WoMic这样的解决方案,但随着项目复杂度提升,人们往往需要更灵活、更可控的自…...
Dify工作流终极指南:3天从新手到专家的完整免费教程
Dify工作流终极指南:3天从新手到专家的完整免费教程 【免费下载链接】Awesome-Dify-Workflow 分享一些好用的 Dify DSL 工作流程,自用、学习两相宜。 Sharing some Dify workflows. 项目地址: https://gitcode.com/GitHub_Trending/aw/Awesome-Dify-Wo…...
如何通过Vial-QMK打造专属键盘体验:从入门到精通的个性化定制指南
如何通过Vial-QMK打造专属键盘体验:从入门到精通的个性化定制指南 【免费下载链接】vial-qmk QMK fork with Vial-specific features. 项目地址: https://gitcode.com/gh_mirrors/vi/vial-qmk 在数字化时代,键盘作为人与计算机交互的核心工具&…...
PowerBuilder老系统维护指南:PB12.5连接现代数据库(如MySQL 8.0)的避坑实操
PowerBuilder老系统维护实战:PB12.5连接MySQL 8.0的七个关键步骤 当技术栈的代际差异超过十年,每一次数据库连接尝试都可能演变成一场跨越时空的调试马拉松。那些在2006年运行良好的PB12.5应用,今天面对MySQL 8.0的SSL加密要求和UTF8MB4字符集…...
从零搭建AI办公助手:OpenClaw+百川2-13B-4bits七日实践计划
从零搭建AI办公助手:OpenClaw百川2-13B-4bits七日实践计划 1. 为什么选择这个组合? 去年冬天,当我第一次听说OpenClaw这个开源自动化框架时,内心是充满怀疑的。作为一个长期被各种"智能助手"忽悠的技术从业者…...
Web AR技术深度探秘:7个创新案例重构浏览器增强现实体验
Web AR技术深度探秘:7个创新案例重构浏览器增强现实体验 【免费下载链接】AR.js Image tracking, Location Based AR, Marker tracking. All on the Web. 项目地址: https://gitcode.com/gh_mirrors/arj/AR.js 你是一个文章写手,你负责为开源项目…...
别再混淆了!深入对比Vivado中AXI DMA IP核与PS端DMA控制器的角色与分工
深入解析Vivado中AXI DMA与PS端DMA控制器的协同设计 在Zynq/MPSoC平台的软硬件协同开发中,数据搬运效率往往成为系统性能的瓶颈。许多开发者虽然能够熟练使用Vivado中的AXI DMA IP核完成基本数据传输,却对PL端AXI DMA与PS端DMA控制器之间的分工协作机制存…...
OpenHarmony软总线实战:手把手教你实现Wi-Fi/BLE双模设备发现(附避坑指南)
OpenHarmony软总线深度实战:Wi-Fi/BLE双模设备发现的工程化实现与性能调优 在智能家居设备爆发式增长的今天,多模连接已成为终端设备的标配能力。作为OpenHarmony分布式能力的核心支撑,软总线(SoftBus)的混合发现机制直…...
用Docker三分钟搞定Hive伪分布式环境(附本地开发调试技巧)
用Docker三分钟搞定Hive伪分布式环境(附本地开发调试技巧) 在数据分析和处理领域,Hive作为基于Hadoop的数据仓库工具,因其能够处理海量数据并提供类SQL查询能力而广受欢迎。然而,传统的Hive环境搭建往往需要配置复杂的…...
我花了 3 小时吃透:Spring AI 核心三剑客 ChatModel、Prompt、ChatResponse 到底怎么用?
你在学习 Spring AI 的时候,肯定遇到过这三个类:ChatModel、Prompt、ChatResponse看着眼熟,却总搞不清谁负责干嘛、代码里为啥要这么写?接下来就是我的理解。一、先搞懂:这三个东西是什么关系?在开始写代码…...
