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

简介: 【深入浅出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

相关文章
|
10月前
|
缓存 Java 开发者
【Spring】原理:Bean的作用域与生命周期
本文将围绕 Spring Bean 的作用域与生命周期展开深度剖析,系统梳理作用域的类型与应用场景、生命周期的关键阶段与扩展点,并结合实际案例揭示其底层实现原理,为开发者提供从理论到实践的完整指导。
1094 22
|
10月前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
2972 0
|
9月前
|
监控 Cloud Native Java
Spring Boot 3.x 微服务架构实战指南
🌟蒋星熠Jaxonic,技术宇宙中的星际旅人。深耕Spring Boot 3.x与微服务架构,探索云原生、性能优化与高可用系统设计。以代码为笔,在二进制星河中谱写极客诗篇。关注我,共赴技术星辰大海!(238字)
1380 2
Spring Boot 3.x 微服务架构实战指南
|
9月前
|
缓存 并行计算 监控
vLLM 性能优化实战:批处理、量化与缓存配置方案
本文深入解析vLLM高性能部署实践,揭秘如何通过continuous batching、PagedAttention与前缀缓存提升吞吐;详解批处理、量化、并发参数调优,助力实现高TPS与低延迟平衡,真正发挥vLLM生产级潜力。
2315 0
vLLM 性能优化实战:批处理、量化与缓存配置方案
|
9月前
|
XML Java 测试技术
《深入理解Spring》:IoC容器核心原理与实战
Spring IoC通过控制反转与依赖注入实现对象间的解耦,由容器统一管理Bean的生命周期与依赖关系。支持XML、注解和Java配置三种方式,结合作用域、条件化配置与循环依赖处理等机制,提升应用的可维护性与可测试性,是现代Java开发的核心基石。
|
10月前
|
存储 缓存 NoSQL
Redis专题-实战篇二-商户查询缓存
本文介绍了缓存的基本概念、应用场景及实现方式,涵盖Redis缓存设计、缓存更新策略、缓存穿透问题及其解决方案。重点讲解了缓存空对象与布隆过滤器的使用,并通过代码示例演示了商铺查询的缓存优化实践。
391 1
Redis专题-实战篇二-商户查询缓存
|
9月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
775 2
|
缓存 NoSQL 关系型数据库
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
|
缓存 NoSQL Java
Redis+Caffeine构建高性能二级缓存
大家好,我是摘星。今天为大家带来的是Redis+Caffeine构建高性能二级缓存,废话不多说直接开始~
1712 0