使用AOP实现缓存注解
为何重造轮子
半年前写了一个注解驱动的缓存,最近提交到了github。缓存大量的被使用在应用中的多个地方,简单的使用方式就是代码先查询缓存中是否存在数据,如果不存在或者缓存过期再查询数据库,并将查询的结果缓存一段时间,缓存key通常是入参的对象或者入参对象的某些属性,有些时候还需要按照某种条件判断是否缓存。可以看到这种功能性代码和具体的业务代码混合在一起的实现方式有很大的代码冗余,即不便于维护也不灵活。使用切面的方式可以很好的抽取功能相似代码冗余的缓存代码,将缓存代码和业务代码隔离开,这样既做到了对业务的无侵入又可以灵活更换具体缓存组件。
其实从spring3之后spring就提供了@Cacheable注解,但是用起来不爽的地方还是太多,例如缓存时间是由cache本身设置的而非在每个@Cacheable注解中指定,这个粒度有点太大了;没有缓存key的前缀设置,不同方法很容易出现key冲突。
怎样重造轮子
鉴于spring3提供的cache注解不太能满足需求,最后决定自己写一个。目标是构造一个简单好用而不是大而全的缓存注解,整个过程陆陆续续花了3天时间,第一天确定技术方案,构建对象和对象间的关系; 第二天写具体的实现和debug; 第三天写demo和test。
确定技术方案的时候看了spring3的cache注解实现和在阿里时使用过的2个cache注解实现。最大是不同点是创建代理类的方式和动态生成cacheKey的实现。
不同的创建代理类的方式:
- 使用MethodInterceptor+xml配置,最经典的使用方式。缺点是同一个类的方法相互调用时不会被aop拦截,需要使用AopContext.currentProxy()获取代理类。
- 使用@AspectJ注解,可以有效的减少xml配置,缺点和MethodInterceptor相同。
- 基于SmartInstantiationAwareBeanPostProcessor+cglib创建代理类。
不同的生成cacheKey的方式:
- 使用SPEL
- 使用OGNL
- 使用正则表达式
最后选择了@AspectJ+SPEL的实现方式。
虽然具体的实现方式各自不同,类的调用结构和内部功能都是基本相同的。
- cacheManager负责cache的管理,包含cache实现的list。
- cache是具体的缓存实现,可以是redis,ehcache,memcache。
- keyParser负责动态生成cacheKey。
- interceptor负责注解的拦截。
- @Cacheable,@CacheEvict等是具体的缓存注解。
按照上述的功能划分实现相关类后,花了一天的时间来写demo和test,全部的test跑通后就可以使用了。后面增加了一个CacheOperation转换具体的注解,统一对CacheOperation进行处理,代码简化了不少。
实际遇到的问题
实际使用中主要遇到了2个问题,一个是interceptor中catch了所有的Exception并打印错误日志,实际上我们会在应用中定义BizException,当发生预期内的错误时会抛出BizException,而BizException是不需要被拦截打印错误日志的。另一个是问题是并发读写问题,在cache中没有缓存的时候,ThreadA从DB获取数据,ThreadB修改了数据库的数据,ThreadB删除缓存,ThreadA然后put修改之前的数据。原本以为按照业务特点发生并发读写的概率不高,结果发现接口轮询+事务导致频繁发生不一致的情况。缓存失效策略一直是缓存使用中的难题,甚至是计算机科学中两大难题之一。处理数据库并发最常见的2个解决思路是乐观锁和串行化,但是并不适用于解决缓存和数据库的不一致,google了一下也没有找到特别好的解决方案。考虑到应用并没有超高的QPS,短时间的缓存穿透不会造成系统的崩溃,最后通过增加一个redis的缓存删除标识进行解决,这个删除标识会存活5s,在这5s中不会执行put缓存操作从而避免了缓存和数据库的不一致。
使用AOP实现缓存注解的更多相关文章
- 使用AOP 实现Redis缓存注解,支持SPEL
公司项目对Redis使用比较多,因为之前没有做AOP,所以缓存逻辑和业务逻辑交织在一起,维护比较艰难所以最近实现了针对于Redis的@Cacheable,把缓存的对象依照类别分别存放到redis的Ha ...
- Spring缓存注解@Cacheable、@CacheEvict、@CachePut使用
从3.1开始,Spring引入了对Cache的支持.其使用方法和原理都类似于Spring对事务管理的支持.Spring Cache是作用在方法上的,其核心思想是这样的:当我们在调用一个缓存方法时会把该 ...
- Spring缓存注解
从3.1开始,Spring引入了对Cache的支持.其使用方法和原理都类似于Spring对事务管理的支持.Spring Cache是作用在方法上的,其核心思想是这样的:当我们在调用一个缓存方法时会把该 ...
- Spring之缓存注解@Cacheable
https://www.cnblogs.com/fashflying/p/6908028.html https://blog.csdn.net/syani/article/details/522399 ...
- Spring – 缓存注解
Spring缓存抽象概述 Spring框架自身并没有实现缓存解决方案,但是从3.1开始定义了org.springframework.cache.Cache和org.springframework.ca ...
- springboot:自定义缓存注解,实现生存时间需求
需求背景:在使用springbot cache时,发现@cacheabe不能设置缓存时间,导致生成的缓存始终在redis中. 环境:springboot 2.1.5 + redis 解决办法:利用AO ...
- 缓存注解@Cacheable、@CacheEvict、@CachePut使用及注解失效时间
从3.1开始,Spring引入了对Cache的支持.其使用方法和原理都类似于Spring对事务管理的支持.Spring Cache是作用在方法上的,其核心思想是这样的:当我们在调用一个缓存方法时会把该 ...
- Spring缓存注解@Cache使用
参考资料 http://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/ http://swiftlet.net/archive ...
- springIOC、AOP的一些注解
springIOC.AOP的一些注解(使用这些注解之前要导入spring框架的一些依赖): 1.注入IOC容器 @Compontent:使用注解的方式添加到ioc容器需要在配置文件 ...
随机推荐
- 学习笔记TF059:自然语言处理、智能聊天机器人
自然语言处理,语音处理.文本处理.语音识别(speech recognition),让计算机能够"听懂"人类语音,语音的文字信息"提取". 日本富国生命保险公司 ...
- Dapper.Contrib——更加优雅地使用Dapper进行增删改查
简介 Dapper是介于Entity framework与ADO的折中选择.既满足手写查询的高性能需求,又简化了数据库对象映射为内存对象的繁杂工作.Dapper.Contrib是对Dapper的进一步 ...
- 启动Apache出现问题:一直停留在启动界面
问题描述: 由于需要php_curl模块,因此直接在php.ini文件将前面的分号去掉 ,但是重启Apache时出现:一直停留在启动界面,Apache无法正常启动,查看错误日志,显示如下: 解决方 ...
- jQuery 插件格式 规范
方式一(自定义对象): (function($, window, document) { var Plugin, defaults, pluginName; 调用时的函数名: pluginN ...
- div内长串数字或字母不断行处理
比如: <div>1111tryrt645645rt4554111112324353453454364</div> <div>qwewretrytuytuiyiuo ...
- C#中float, double的精度问题
在工作中我发现了一个C#浮点数的精度问题,以下的程序运行结果并未得到我预期的结果: view source print? 01 namespace FloatTest 02 03 class ...
- 六:Ioc和AOP使用拓展
Ioc和AOP使用拓展 一:1.构造注入 一个<constructor-arg>元素表示构造方法的一个参数,且使用时不区分顺序,index指定元素,位置从0开始,Type用来指定参数,避免 ...
- Python之re正则模块
正则表达式可以帮助我们更好的描述复制的文本格式,可以更好地利用它们对文本数据进行检索.替换.提取和修改操作. http://www.cnblogs.com/huxi/archive/2010/07/0 ...
- smartClient 1--框架介绍
一.是什么(以下简称SC) smartClient 是一个基于web技术的开发框架,主要包括: 一个无需安装的 Ajax/HTML5 客户端引擎 UI组件和服务(采用富客户端RIA)--- 提 ...
- 2017湖湘杯Writeup
RE部分 0x01 Re4newer 解题思路: Step1:die打开,发现有upx壳. Step2:脱壳,执行upx -d 文件名即可. Step3:IDA打开,shift+F12看字符串. 点进 ...