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.1.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.1.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 也可以定义在构造方法上,定义在构造方法就为该类实现了单实例缓存

字段名默认值说明
debugOutputfalse是否输出调试信息
debugOutputDetailfalse是否输出详细调试信息
weakKeysfalse指定存储在缓存中的每个键(而不是值)都应封装在WeakReference中(默认情况下,使用强引用)。
weakValuesfalse指定存储在缓存中的每个值(而不是键)都应包装在SoftReference中(默认情况下,使用强引用)。软引用的对象将以全局最近最少使用的方式进行垃圾收集,以响应内存需求。
softValuesfalse指定存储在缓存中的每个值(而不是键)都应包装在SoftReference中(默认情况下,使用强引用)。软引用的对象将以全局最近最少使用的方式进行垃圾收集,以响应内存需求。
initialCapacity-1设置内部哈希表的最小总大小。例如,如果初始容量为60,并发级别为8,则创建八个段,每个段都有一个大小为8的哈希表。在构建时提供足够大的估计值可以避免以后进行昂贵的调整大小操作,但不必设置此值—过高会浪费内存。
concurrencyLevel0指定更新操作之间允许的并发性。用作内部大小调整的提示。该表在内部进行了分区,以尝试在没有争用的情况下允许指定数量的并发更新。
maximumSize-1指定缓存可以包含的最大条目数。请注意,缓存可能会在超过此限制之前收回一个条目。随着缓存大小接近最大值,缓存会收回不太可能再次使用的条目。例如,缓存可能会因为最近或不经常使用而被逐出。
maximumWeight-1指定缓存中可能包含的项的最大权重。重量是使用地磅指定的秤来确定的,使用这种方法需要在调用构建之前对地磅进行响应调用。
expireAfterWrite-1指定在创建条目或最近替换其值后经过固定的持续时间后,应自动从缓存中删除每个条目。
expireAfterWriteTimeUnitMINUTES(分钟)expireAfterWrite的时间单位
expireAfterAccess-1指定在创建条目、最近一次替换其值或最后一次访问之后经过固定的持续时间后,应自动从缓存中删除每个条目。访问时间由所有缓存读取和写入操作(包括cache.asMap().get(Object)和cache.asMop().put(K,V))重置,但不由cache.asMap的集合视图上的操作重置。
expireAfterAccessTimeUnitMINUTES(分钟)expireAfterAccess的时间单位
refreshAfterWrite-1指定活动条目的创建或其值的最新替换后经过固定的持续时间后,活动条目的自动刷新条件。刷新的语义在LoadingCache.refresh中指定,并通过调用CacheLoader.reload来执行。
由于CacheLoader.reload的默认实现是同步的,建议此方法的用户使用异步实现覆盖CacheLoader.reload;否则将在不相关的缓存读取和写入操作期间执行刷新。
当前自动刷新是在对条目的第一个过时请求发生时执行的。触发刷新的请求将对CacheLoader.reload进行阻塞调用,如果返回的future完成,则立即返回新值,否则返回旧值。
refreshAfterWriteTimeUnitMINUTES(分钟)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》

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

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

相关文章

Java面试问题(一)

一.Java语言具有的哪些特点 1.Java是纯面向对象语言&#xff0c;能够直接反应现实生活中的对象 2.具有平台无关性&#xff0c;利用Java虚拟机运行字节码文件&#xff0c;无论是在window、Linux还是macOS等其他平台对Java程序进行编译&#xff0c;编译后的程序可在其他平台上运行…

HuatuoGPT2本地运行失败

transformer 版本不能太高 transformer<4.3.32 查看config.json文件 其实 https://huggingface.co/FreedomIntelligence/HuatuoGPT2-7B/blob/main/config.json

2024年03月Python六级真题+答案(中国电子学会 )

青少年软件编程&#xff08;Python&#xff09;等级考试试卷&#xff08;六级&#xff09; 分数&#xff1a;100 题数&#xff1a;38 一、单选题(共25题&#xff0c;共50分) 1.以下选项中&#xff0c;创建类正确的是&#xff1f;&#xff08; &#xff09; A. class test1…

利用opencv自带的Haar级联分类器模型

OpenCV自带的Haar级联分类器模型&#xff1a; haarcascade_eye.xml: 这个模型用于检测眼睛。 haarcascade_eye_tree_eyeglasses.xml: 这个模型用于检测眼镜。 haarcascade_frontalcatface.xml: 这个模型用于检测猫脸。 haarcascade_frontalcatface_extended.xml: 这个模型用…

企业成功的秘诀:全面解读ISO三大管理体系的卓越效益

在现代商业环境中&#xff0c;企业若想在激烈竞争中脱颖而出并保持可持续经营&#xff0c;必须关注质量、环境以及职业健康安全管理。ISO三大管理体系——质量管理体系、环境管理体系和职业健康安全管理体系&#xff0c;为企业提供了实现这一目标的有力工具。本文将详细介绍ISO…

存储故障导致Oracle 19c 数据文件处于recover状态的恢复案例

1.背景 某次平台分布式存储故障&#xff0c;导致数据库出现ORA-00376、ORA-01110数据文件不可读报错&#xff0c;本文将整个恢复过程进行整理记录。 2.报错信息 在进行租户数据库打开操作时&#xff0c;出现了如下报错&#xff1a; ORA-00376: file 17 cannot be read at t…

Vue3抽屉(Drawer)

效果如下图&#xff1a;在线预览 APIs 参数说明类型默认值必传width宽度&#xff0c;在 placement 为 right 或 left 时使用string | number378falseheight高度&#xff0c;在 placement 为 top 或 bottom 时使用string | number378falsetitle标题string | slotundefinedfalse…

iCloud备份的智能压缩与优化:释放存储空间的艺术

iCloud备份的智能压缩与优化&#xff1a;释放存储空间的艺术 iCloud作为Apple提供的云服务&#xff0c;不仅为用户带来了便捷的数据同步功能&#xff0c;还通过智能的备份压缩和优化存储选项&#xff0c;帮助用户高效管理存储空间。本文将详细解析iCloud备份的压缩和优化存储功…

《Nest系列 - 4. 听说人人都会CRUD,可是我还不会怎么办???-《4.2结合前端使用实现CRUD》

终于到了这一步&#xff0c;今天我们就将实现一个CRUD&#xff0c;主要是编写nest 部分&#xff0c;前端部分后面可以看git 代码 下面是效果演示&#xff08;大部分是参考满哥实现&#xff0c;&#x1f923;&#x1f923;&#x1f923;&#xff09; 前期准备 前端接口处理 im…

产业升级具体可从哪些方面入手?

产业升级是一个广泛而复杂的过程&#xff0c;涉及多个方面。以下是产业升级的主要方面&#xff0c;结合参考文章中的相关信息进行清晰分点表示和归纳&#xff1a; 技术创新&#xff1a; 研发和技术改造&#xff1a;通过不断投入研发和技术改造&#xff0c;推动企业生产技术的升…

RT-Thread Studio实现动态线程

1创建项目 我的板子为STM32F03ZET6 点击RT-Thread项目 2选择板子&#xff08;根据自己的板子选择&#xff09; 3找到主函数 4编写代码 4-1创建函数入口 // 线程入口函数 static void thread_entry(void *parameter) {rt_uint32_t count 0;while (1){// 线程执行的代码rt_k…

互斥锁并不能保证任务不能被调度

互斥锁不能保证在临界区的时候&#xff0c;不发送任务调度&#xff0c;所以为了保护共享的资源不被调度访问&#xff0c;需要在两个线程都加互斥锁来保证任务不调度 #include <stdio.h> #include <pthread.h> #include <unistd.h> int shared_resource 0;p…

2025中国(宁波)出口跨境电商博览会

2025中国(宁波)出口跨境电商博览会 时间&#xff1a;2025年5月28-30日 地点&#xff1a;中国宁波国际会展中心 组织单位&#xff1a; 宁波欧德国际商务咨询服务有限公司 凤麟展览(宁波)有限公司 宁波市跨境电子商务协会 宁波市家居产业协会 详询主办方陆先生 I38&…

Java yield()方法在多线程编程中的应用

目录 背景: 代码解释: 1.类定义 2.主方法(main) 3.自定义线程类(MyThread3) 总结: 背景: 在Java中&#xff0c;多线程是构建并发应用程序的关键技术。当我们多个线程需要同时执行时&#xff0c;操作系统必须决定何时为每个线程分配 CPU时。这个过程被称为线程调度&#x…

php中interface接口类,abstract抽象类和trait详解

在PHP中&#xff0c;interface&#xff08;接口&#xff09;、abstract class&#xff08;抽象类&#xff09;和trait都是为了实现代码的复用、提高可维护性和灵活性而设计的。它们各自有不同的用途和特点&#xff1a; Interface&#xff08;接口&#xff09; 用途&#xff1…

上海市计算机学会竞赛平台2023年7月月赛丙组模糊匹配(二)

题目描述 有两个仅包含大写英文字母的字符串 &#x1d446;,&#x1d447;S,T&#xff0c;且字符串 &#x1d447;T 是 &#x1d446;S 的一个子串。 但由于字符串 &#x1d446;S 字迹模糊不清&#xff0c;其某些位置上的字符没有办法进行辨认&#xff0c;这些模糊的位置&am…

星戈瑞DSPE-FITC细胞成像:一种细胞可视化技术

细胞成像技术是现代生物医学研究中的工具&#xff0c;它为我们提供了一种直观、深入地了解细胞结构和功能的方法。其中&#xff0c;DSPE-FITC作为一种荧光标记分子&#xff0c;在细胞成像领域展现出了优势。 DSPE-FITC细胞成像的原理 DSPE-FITC是由磷脂酰丝氨酸&#xff08;DS…

【openmpi】怎样使用openmpi并行运行python脚本?

创作日志&#xff1a; 装过一次openmpi&#xff0c;但是半年之后就忘记怎么用了&#xff0c;所以记录一下 1. 测试openmpi是否安装好 cd /home/xxxx/SnapHiC_Call_Loop/openmpi-4.1.6/examples make mpirun -np 4 hello_c得到如下输出就说明是装好的了 2. 没有导入路径的话导…

rtthread stm32h743的使用(十)i2c设备使用

我们要在rtthread studio 开发环境中建立stm32h743xih6芯片的工程。我们使用一块stm32h743及fpga的核心板完成相关实验&#xff0c;核心板如图&#xff1a; 1.建立新工程&#xff0c;选择相应的芯片型号及debug引脚及调试器 2.打开cubemux&#xff0c;设置外部时钟及串口外设…

劳务工程元宇宙:未来建筑行业的新趋势

随着科技的不断发展&#xff0c;虚拟现实、增强现实、区块链等技术逐渐渗透到各个行业&#xff0c;为传统产业带来了革命性的变革。在建筑行业&#xff0c;劳务工程元宇宙的概念应运而生&#xff0c;预示着未来建筑行业的新趋势。 一、劳务工程元宇宙的定义 劳务工程元宇宙是…