【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache功能的开发实战指南(一)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache功能的开发实战指南

CacheManager管理器的扩展支持

Spring的抽象控制机制,即允许绑定不同的缓存解决方案(如Caffeine、Ehcache等),但本身不直接提供缓存功能的实现。它支持注解方式使用缓存,非常方便。

SpringBoot在Annotation的层面实现了数据缓存的功能,基于Spring的AOP技术。所有的缓存配置只是在Annotation层面配置,像声明式事务一样。

Spring定义了CacheManager和Cache接口统一不同的缓存技术。其中CacheManager是Spring提供的各种缓存技术的抽象接口。而Cache接口包含缓存的各种操作。

Cache接口下Spring提供了各种xxxCache的实现,如RedisCache,EhCacheCache ,ConcurrentMapCache等;

缓存技术类型与CacheManger

针对不同的缓存技术,需要实现不同的cacheManager,Spring定义了如下的cacheManger实现。

CacheManger 描述
SimpleCacheManager 使用简单Collection来存储缓存,主要用于测试
ConcurrentMapCacheManager 使用ConcurrentMap作为缓存技术(默认),需要显式的删除缓存,无过期机制
NoOpCacheManager 仅测试用途,不会实际存储缓存
EhCacheCacheManager 使用EhCache作为缓存技术,以前在hibernate的时候经常用
GuavaCacheManager 使用google guava的GuavaCache作为缓存技术(1.5版本已不建议使用)
CaffeineCacheManager 是使用Java8对Guava缓存的重写,spring5(springboot2)开始用Caffeine取代guava
HazelcastCacheManager 使用Hazelcast作为缓存技术
JCacheCacheManager 使用JCache标准的实现作为缓存技术,如Apache Commons JCS
RedisCacheManager 使用Redis作为缓存技术

常规的SpringBoot已经为我们自动配置了EhCache、Collection、Guava、ConcurrentMap等缓存,默认使用ConcurrentMapCacheManager。

SpringBoot的application.properties配置文件,使用spring.cache前缀的属性进行配置。


缓存依赖

开始使用前需要导入依赖spring-boot-starter-cache为基础依赖,其他依赖根据使用不同的缓存技术选择加入,默认情况下使用ConcurrentMapCache不需要引用任何依赖。

xml

复制代码

<!-- 基础依赖 -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- 使用 ehcache -->
<dependency>
   <groupId>net.sf.ehcache</groupId>
   <artifactId>ehcache</artifactId>
</dependency>
<!-- 使用  caffeine https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine -->
<dependency>
   <groupId>com.github.ben-manes.caffeine</groupId>
   <artifactId>caffeine</artifactId>
   <version>2.6.0</version>
</dependency>
<!-- 使用  redis  -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

application配置

yaml

复制代码

spring.cache.type= #缓存的技术类型,可选 generic,ehcache,hazelcast,infinispan,jcache,redis,guava,simple,none
spring.cache.cache-names= #应用程序启动创建缓存的名称,必须将所有注释为@Cacheable缓存name(或value)罗列在这里,否者:Cannot find cache named 'xxx' for Builder[xx] caches=[sysItem] | key='' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false'
#以下根据不同缓存技术选择配置
spring.cache.ehcache.config= #EHCache的配置文件位置
spring.caffeine.spec= #caffeine类型创建缓存的规范。查看CaffeineSpec了解更多关于规格格式的细节
spring.cache.infinispan.config= #infinispan的配置文件位置
spring.cache.jcache.config= #jcache配置文件位置
spring.cache.jcache.provider= #当多个jcache实现类时,指定选择jcache的实现类

缓存注解

下面是缓存公用接口注释,适用于任何缓存类型。

@EnableCaching

在启动类注解@EnableCaching开启缓存。

java

复制代码

@SpringBootApplication
@EnableCaching  //开启缓存
public class DemoApplication{     
   public static void main(String[] args) {
      SpringApplication.run(DemoApplication.class, args);
   }
}

@Cacheable

配置findByName函数的返回值将被加入缓存。同时在查询时,会先从缓存中获取,若不存在才再发起对数据库的访问。

该注解主要有下面几个参数:

  • value、cacheNames:两个等同的参数(cacheNames为Spring4新增,作为value的别名),用于指定缓存存储的集合名。
  • 由于Spring4中新增了@CacheConfig,因此在Spring3中原本必须有的value属性,也成为非必需项了。
  • key:缓存对象存储在Map集合中的key值,非必需,缺省按照函数的所有参数组合作为key值,若自己配置需使用SpEL表达式,比如:@Cacheable(key = “#p0”):使用函数第一个参数作为缓存的key值。
  • condition:缓存对象的条件,非必需,也需使用SpEL表达式,只有满足表达式条件的内容才会被缓存,比如:@Cacheable(key = “#p0”, condition = “#p0.length() < 3”),表示只有当第一个参数的长度小于3的时候才会被缓存
  • unless:另外一个缓存条件参数,非必需,需使用SpEL表达式。它不同于condition参数的地方在于它的判断时机,该条件是在函数被调用之后才做判断的,所以它可以通过对result进行判断。
  • keyGenerator:用于指定key生成器,非必需。若需要指定一个自定义的key生成器,我们需要去实现org.springframework.cache.interceptor.KeyGenerator接口,并使用该参数来指定。
  • 需要注意的是:该参数与key是互斥的。
  • cacheManager:用于指定使用哪个缓存管理器,非必需。只有当有多个时才需要使用
  • cacheResolver:用于指定使用那个缓存解析器,非必需。需通过org.springframework.cache.interceptor.CacheResolver接口来实现自己的缓存解析器,并用该参数指定。

java

复制代码

public class SampleServiceImpl implements SampleService {
     @Override
     @Cacheable(value = {"newJob"},key = "#p0")
     public List<NewJob> findAllLimit(int num) {
         return botRelationRepository.findAllLimit(num);
     }
        .....
}

@CachePut

针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable不同的是,它每次都会触发真实方法的调用 。

简单来说就是用户更新缓存数据。但需要注意的是该注解的value 和key必须与要更新的缓存相同,也就是与@Cacheable 相同。

示例:

java

复制代码

//按条件更新缓存
@CachePut(value = "newJob", key = "#p0") 
public NewJob updata(NewJob job) {
     NewJob newJob = newJobDao.findAllById(job.getId());
     newJob.updata(job);
     return job;
}

@CacheEvict

配置于函数上,通常用在删除方法上,用来从缓存中移除相应数据。除了同@Cacheable一样的参数之外,它还有下面两个参数:

  • allEntries:非必需,默认为false。当为true时,会移除所有数据。如:@CachEvict(value=”testcache”,allEntries=true)
  • beforeInvocation:非必需,默认为false,会在调用方法之后移除数据。当为true时,会在调用方法之前移除数据。 如:

java

复制代码

@CachEvict(value=”testcache”,beforeInvocation=true)
        @Cacheable(value = "emp",key = "#p0.id")
        public NewJob save(NewJob job) {
            newJobDao.save(job);
            return job;
        }
        //清除一条缓存,key为要清空的数据
        @CacheEvict(value="emp",key="#id")
        public void delect(int id) {
            newJobDao.deleteAllById(id);
        }
        //方法调用后清空所有缓存
        @CacheEvict(value="accountCache",allEntries=true)
        public void delectAll() {
            newJobDao.deleteAll();
        }
        //方法调用前清空所有缓存
        @CacheEvict(value="accountCache",beforeInvocation=true)
        public void delectAll() {
            newJobDao.deleteAll();
        }

@CacheConfig

统一配置本类的缓存注解的属性,在类上面统一定义缓存的名字,方法上面就不用标注了,当标记在一个类上时则表示该类所有的方法都是支持缓存的

java

复制代码

@CacheConfig(cacheNames = {"myCache"})
    public class SampleServiceImpl implements SampleService {
        @Override
        @Cacheable(key = "targetClass + methodName +#p0")
        //此处没写value
        public List<BotRelation> findAllLimit(int num) {
            return botRelationRepository.findAllLimit(num);
        }
        .....
    }

SpEL上下文数据

Spring Cache提供了一些供我们使用的SpEL上下文数据,直接摘自Spring官方文档:

名称 位置 描述 示例
methodName root对象 当前被调用的方法名 #root.methodname
method root对象 当前被调用的方法 #root.method.name
target root对象 当前被调用的目标对象实例 #root.target
targetClass root对象 当前被调用的目标对象的类 #root.targetClass
args root对象 当前被调用的方法的参数列表 #root.args[0]
caches root对象 当前方法调用使用的缓存列表 #root.caches[0].name
Argument Name 执行上下文 当前被调用的方法的参数,如findArtisan(Artisan artisan),可以通过#artsian.id获得参数 #artsian.id
result 执行上下文 方法执行后的返回值(仅当方法执行后的判断有效,如 unless cacheEvict的beforeInvocation=false) #result

注意

当我们要使用root对象的属性作为key时,我们也可以将“#root”省略,因为Spring默认使用的就是root对象的属性。 如

ini

复制代码

@Cacheable(key = "targetClass + methodName +#p0")

使用方法参数时,可以直接使用“#参数名”或者“#p参数index”。 如:

java

复制代码

@Cacheable(value="users", key="#id")
@Cacheable(value="users", key="#p0")

SpEL提供了多种运算符

类型 运算符
关系 <,>,<=,>=,==,!=,lt,gt,le,ge,eq,ne
算术 +,- ,* ,/,%,^
逻辑 &&, ,!,and,or,not,between,instanceof
条件 ?: (ternary),?: (elvis)
正则表达式 matches
其他类型 ?.,?[…],![…],^[…],$[…]

不同Cache的实现机制



ConcurrentMap Cache的实现方案

SpringBoot默认使用的是SimpleCacheConfiguration,使用ConcurrentMapCacheManager来实现缓存,ConcurrentMapCache实质是一个ConcurrentHashMap集合对象java内置,所以无需引入其他依赖,也没有额外的配置

ConcurrentMapCache的自动装配声明在SimpleCacheConfiguration中,如果需要也可对它进行额外的装配

java

复制代码

//注册id为cacheManager,类型为ConcurrentMapCacheManager的bean
@Bean
public ConcurrentMapCacheManager cacheManager() {
    ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager(); //实例化ConcurrentMapCacheManager
    List<String> cacheNames = this.cacheProperties.getCacheNames(); //读取配置文件,如果配置有spring.cache.cache-names=xx,xx,则进行配置cacheNames,默认是没有配置的
    if (!cacheNames.isEmpty()) {
       cacheManager.setCacheNames(cacheNames);
    }
    return this.customizerInvoker.customize(cacheManager); 
    }

调用CacheManagerCustomizers#customize 进行个性化设置,在该方法中是遍历其持有的List。

Caffeine Cache

Caffeine是使用Java8对Guava缓存的重写版本,在Spring Boot 2.0中将取代,基于LRU算法实现,支持多种缓存过期策略。具体查看这里。

Caffeine参数说明

ini

复制代码

initialCapacity=[integer]: 初始的缓存空间大小
maximumSize=[long]: 缓存的最大条数
maximumWeight=[long]: 缓存的最大权重
expireAfterAccess=[duration]: 最后一次写入或访问后经过固定时间过期
expireAfterWrite=[duration]: 最后一次写入后经过固定时间过期
refreshAfterWrite=[duration]: 创建缓存或者最近一次更新缓存后经过固定的时间间隔,刷新缓存 refreshAfterWrite requires a LoadingCache
weakKeys: 打开key的弱引用
weakValues:打开value的弱引用
softValues:打开value的软引用
recordStats:开发统计功能

注意:

refreshAfterWrite必须实现LoadingCache,跟expire的区别是,指定时间过后,expire是remove该key,下次访问是同步去获取返回新值,而refresh则是指定时间后,不会remove该key,下次访问会触发刷新,新值没有回来时返回旧值

  • expireAfterWrite和expireAfterAccess同时存在时,以expireAfterWrite为准。
  • maximumSize和maximumWeight不可以同时使用
  • weakValues和softValues不可以同时使用

导入依赖

xml

复制代码

<!-- 使用  caffeine https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine -->
<dependency>
   <groupId>com.github.ben-manes.caffeine</groupId>
   <artifactId>caffeine</artifactId>
   <version>2.6.0</version>
</dependency>

通过yaml配置

通过配置文件来设置Caffeine

yaml

复制代码

spring:
  cache:
    cache-names: outLimit,notOutLimit
    caffeine:
      spec: initialCapacity=50,maximumSize=500,expireAfterWrite=5s,refreshAfterWrite=7s #
      type: caffeine

通过bean装配

java

复制代码

@Bean
@Primary
public CacheManager cacheManagerWithCaffeine() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        Caffeine caffeine = Caffeine.newBuilder()
                .initialCapacity() //cache的初始容量值
                .maximumSize() //maximumSize用来控制cache的最大缓存数量,maximumSize和maximumWeight不可以同时使用,
                .maximumWeight() //控制最大权重
                .expireAfter(customExpireAfter) //自定义过期
                .refreshAfterWrite(, TimeUnit.SECONDS);  //使用refreshAfterWrite必须要设置cacheLoader
        cacheManager.setCaffeine(caffeine);
        cacheManager.setCacheLoader(cacheLoader); //缓存加载方案
        cacheManager.setCacheNames(getNames());   //缓存名称列表
        cacheManager.setAllowNullValues(false);
        return cacheManager;
    }


【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache功能的开发实战指南(二)https://developer.aliyun.com/article/1471012

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
21天前
|
缓存 NoSQL Java
什么是缓存?如何在 Spring Boot 中使用缓存框架
什么是缓存?如何在 Spring Boot 中使用缓存框架
28 0
|
4月前
|
缓存 安全 Android开发
Android经典实战之用Kotlin泛型实现键值对缓存
本文介绍了Kotlin中泛型的基础知识与实际应用。泛型能提升代码的重用性、类型安全及可读性。文中详细解释了泛型的基本语法、泛型函数、泛型约束以及协变和逆变的概念,并通过一个数据缓存系统的实例展示了泛型的强大功能。
45 2
|
4月前
|
缓存 NoSQL Java
【Azure Redis 缓存】示例使用 redisson-spring-boot-starter 连接/使用 Azure Redis 服务
【Azure Redis 缓存】示例使用 redisson-spring-boot-starter 连接/使用 Azure Redis 服务
|
2月前
|
缓存 NoSQL Java
Springboot自定义注解+aop实现redis自动清除缓存功能
通过上述步骤,我们不仅实现了一个高度灵活的缓存管理机制,还保证了代码的整洁与可维护性。自定义注解与AOP的结合,让缓存清除逻辑与业务逻辑分离,便于未来的扩展和修改。这种设计模式非常适合需要频繁更新缓存的应用场景,大大提高了开发效率和系统的响应速度。
83 2
|
4月前
|
缓存 NoSQL 数据库
go-zero微服务实战系列(五、缓存代码怎么写)
go-zero微服务实战系列(五、缓存代码怎么写)
|
4月前
|
缓存 NoSQL 测试技术
【Azure Redis 缓存】Azure Redis 功能性讨论三: 调优参数配置
【Azure Redis 缓存】Azure Redis 功能性讨论三: 调优参数配置
|
4月前
|
缓存 NoSQL Java
惊!Spring Boot遇上Redis,竟开启了一场缓存实战的革命!
【8月更文挑战第29天】在互联网时代,数据的高速读写至关重要。Spring Boot凭借简洁高效的特点广受开发者喜爱,而Redis作为高性能内存数据库,在缓存和消息队列领域表现出色。本文通过电商平台商品推荐系统的实战案例,详细介绍如何在Spring Boot项目中整合Redis,提升系统响应速度和用户体验。
71 0
|
4月前
|
消息中间件 缓存 监控
go-zero微服务实战系列(六、缓存一致性保证)
go-zero微服务实战系列(六、缓存一致性保证)
|
4月前
|
存储 缓存 NoSQL
微服务缓存原理与最佳实践
微服务缓存原理与最佳实践
|
4月前
|
缓存 NoSQL Java
【Azure Redis 缓存】定位Java Spring Boot 使用 Jedis 或 Lettuce 无法连接到 Redis的网络连通性步骤
【Azure Redis 缓存】定位Java Spring Boot 使用 Jedis 或 Lettuce 无法连接到 Redis的网络连通性步骤