正文
代码不够优雅。业务逻辑有四个典型动作:
存储
,
读取
,
修改
,
删除
。每次操作都需要定义缓存Key ,调用缓存命令的API,产生较多的
重复代码
;
缓存操作和业务逻辑之间的代码
耦合度高
,对业务逻辑有较强的侵入性。
侵入性主要体现如下两点:
开发联调阶段,需要去掉缓存,只能注释或者临时删除缓存操作代码,也容易出错;
某些场景下,需要更换缓存组件,每个缓存组件有自己的API,更换成本颇高。
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/YunaiV/ruoyi-vue-pro
视频教程:https://doc.iocoder.cn/video/
首先需要明确一点:Spring Cache不是一个具体的缓存实现方案,而是一个对缓存使用的抽象(
Cache Abstraction
)。
Spring AOP是基于代理模式(
proxy-based
)。
通常情况下,定义一个对象,调用它的方法的时候,方法是直接被调用的。
Pojo pojo = new SimplePojo(); pojo.foo();
将代码做一些调整,pojo对象的引用修改成代理类。
ProxyFactory factory = new ProxyFactory( new SimplePojo()); factory.addInterface(Pojo . class ) ; factory.addAdvice( new RetryAdvice()); Pojo pojo = (Pojo) factory.getProxy(); //this is a method call on the proxy! pojo.foo();
调用pojo的foo方法的时候,实际上是动态生成的代理类调用foo方法。
代理类在方法调用前可以获取方法的参数,当调用方法结束后,可以获取调用该方法的返回值,通过这种方式就可以实现缓存的逻辑。
缓存声明,也就是标识需要缓存的方法以及
缓存策略
。
Spring Cache 提供了五个注解。
@Cacheable:根据方法的请求参数对其结果进行缓存,下次同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法;
@CachePut:根据方法的请求参数对其结果进行缓存,它每次都会触发真实方法的调用;
@CacheConfig:类级别共享缓存相关的公共配置。
我们重点讲解:@Cacheable,@CachePut,@CacheEvict三个核心注解。
@Cacheble注解表示这个方法有了缓存的功能。
@Cacheable (value= "user_cache" ,key= "#userId" , unless= "#result == null" ) public User getUserById (Long userId) { User user = userMapper.getUserById(userId); return user; }
上面的代码片段里,
getUserById
方法和缓存
user_cache
关联起来,若方法返回的User对象不为空,则缓存起来。第二次相同参数userId调用该方法的时候,直接从缓存中获取数据,并返回。
▍ 缓存key的生成
我们都知道,缓存的本质是
key-value
存储模式,每一次方法的调用都需要生成相应的Key, 才能操作缓存。
通常情况下,@Cacheable有一个属性key可以直接定义缓存key,开发者可以使用SpEL语言定义key值。
若没有指定属性key,缓存抽象提供了
KeyGenerator
来生成key ,默认的生成器代码见下图:
它的算法也很容易理解:
如果没有参数,则直接返回
SimpleKey.EMPTY
;
若有多个参数,则返回包含多个参数的
SimpleKey
对象。
当然Spring Cache也考虑到需要自定义Key生成方式,需要我们实现
org.springframework.cache.interceptor.KeyGenerator
接口。
Object generate (Object target, Method method, Object... params) ;
然后指定@Cacheable的keyGenerator属性。
@Cacheable (value= "user_cache" , keyGenerator= "myKeyGenerator" , unless= "#result == null" ) public User getUserById (Long userId)
▍ 缓存条件
有的时候,方法执行的结果是否需要缓存,依赖于方法的参数或者方法执行后的返回值。
注解里可以通过
condition
属性,通过Spel表达式返回的结果是true 还是false 判断是否需要缓存。
@Cacheable (cacheNames= "book" , condition= "#name.length() < 32" ) public Book findBook (String name)
上面的代码片段里,当参数的长度小于32,方法执行的结果才会缓存。
除了condition,
unless
属性也可以决定结果是否缓存,不过是在执行方法后。
@Cacheable (value= "user_cache" ,key= "#userId" , unless= "#result == null" ) public User getUserById (Long userId) {
上面的代码片段里,当返回的结果为null则不缓存。