专栏名称: 芋道源码
纯 Java 源码分享公众号,目前有「Dubbo」「SpringCloud」「Java 并发」「RocketMQ」「Sharding-JDBC」「MyCAT」「Elastic-Job」「SkyWalking」「Spring」等等
目录
相关文章推荐
ImportNew  ·  亚马逊程序员破防:AI ... ·  14 小时前  
芋道源码  ·  面试官:为什么数据库连接很消耗资源? ·  13 小时前  
ImportNew  ·  Java 之父怒斥:AI ... ·  4 天前  
ImportNew  ·  Redis 之父:哪怕被喷我也要说,AI ... ·  2 天前  
51好读  ›  专栏  ›  芋道源码

使用 Spring Cache 实现缓存,这种方式才叫优雅!

芋道源码  · 公众号  · Java  · 2025-05-15 12:16

正文

请到「今天看啥」查看全文


  1. 代码不够优雅。业务逻辑有四个典型动作: 存储 读取 修改 删除 。每次操作都需要定义缓存Key ,调用缓存命令的API,产生较多的 重复代码

  2. 缓存操作和业务逻辑之间的代码 耦合度高 ,对业务逻辑有较强的侵入性。

    侵入性主要体现如下两点:

  • 开发联调阶段,需要去掉缓存,只能注释或者临时删除缓存操作代码,也容易出错;
  • 某些场景下,需要更换缓存组件,每个缓存组件有自己的API,更换成本颇高。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

2 缓存抽象

首先需要明确一点:Spring Cache不是一个具体的缓存实现方案,而是一个对缓存使用的抽象( Cache Abstraction )。

2.1 Spring AOP

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方法。

代理类在方法调用前可以获取方法的参数,当调用方法结束后,可以获取调用该方法的返回值,通过这种方式就可以实现缓存的逻辑。

2.2  缓存声明

缓存声明,也就是标识需要缓存的方法以及 缓存策略

Spring Cache 提供了五个注解。

  • @Cacheable:根据方法的请求参数对其结果进行缓存,下次同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法;
  • @CachePut:根据方法的请求参数对其结果进行缓存,它每次都会触发真实方法的调用;
  • @CacheEvict:根据一定的条件删除缓存;
  • @Caching:组合多个缓存注解;
  • @CacheConfig:类级别共享缓存相关的公共配置。

我们重点讲解:@Cacheable,@CachePut,@CacheEvict三个核心注解。

2.2.1 @Cacheable注解

@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则不缓存。







请到「今天看啥」查看全文