1 背景介绍
官方入门文档:https://spring.io/guides/gs/caching/
Spring 从3.1 开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager 接口来统一不同的缓存技术; 并支持使用JCache(JSR-107)注解简化我们开发;Cache 接口为缓存的组件规范定义,包含缓存的各种操作集合; Cache 接口下Spring 提供了各种xxxCache 的实现;如RedisCache , EhCacheCache , ConcurrentMapCache 等;
每次调用需要缓存功能的方法时,Spring 会检查检查指定参数的指定的目标方法是否已 经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
如果我们的程序想要使用缓存,就要与这些框架耦合。聪明的架构师已经在利用接口来降低耦合了,利用面向对象的抽象和多态的特性,做到业务代码与具体的框架分离。
但我们仍然需要显式地在代码中去调用与缓存有关的接口和方法,在合适的时候插入数据到缓存里,在合适的时候从缓存中读取数据。
想一想AOP的适用场景,这不就是天生就应该AOP去做的吗?
是的,Spring Cache就是一个这个框架。它利用了AOP,实现了基于注解的缓存功能,并且进行了合理的抽象,业务代码不用关心底层是使用了什么缓存框架,只需要简单地加一个注解,就能实现缓存功能了。而且Spring Cache也提供了很多默认的配置,用户可以3秒钟就使用上一个很不错的缓存功能。
既然有这么好的轮子,干嘛不用呢?
2 使用方式
Spring cache提供了开箱即用的接入方式,只需要若干注解和缓存管理类即可接入.
1.开启缓存能力
引入缓存依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> <version>2.1.3.RELEASE</version> </dependency>
在应用启动类添加@EnableCaching注解:
@SpringBootApplication @EnableCaching public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
2.使用缓存
在业务方法添加@Cacheable注解:
@Cacheable(cacheNames = {"task"}) public TaskInfoDTO getTask(String taskId) { log.info("TestBuzz.getTask mock query from DB......"); TaskInfoDTO taskInfoDTO = new TaskInfoDTO(); taskInfoDTO.setTaskId(taskId); taskInfoDTO.setApplicantId("system"); taskInfoDTO.setDescription("test"); return taskInfoDTO; }
模拟请求:
@GetMapping("/test_cache") public IResp<TaskInfoDTO> testCache() { TaskInfoDTO taskInfoDTO = this.testBuzz.getTask("task123"); return IResp.getSuccessResult(taskInfoDTO); }
连续发送两次查询请求:
curl http://localhost:port/test_cache
从日志中看到只打印了一次DB调用,也就是说明第二次走了缓存。就这么简单我们就开启并使用了spring的缓存能力。
3 常用注解
原理:基于Proxy/AspectJ动态代理技术的AOP思想(面向切面编程)
使用:
SpringCache包含两个顶级接口,Cache(缓存)和CacheManager(缓存管理器),顾名思义,用CacheManager去管理一堆Cache。
spring cache实现有基于XML/注解实现AOP;
CacheManager负责对缓存的增删改查, CacheManager的缓存的介质可配置, 如:ConcurrentMap/EhCache/Redis等。当没有加入EhCache或者Redis依赖时默认采用concurrentMap实现的缓存,是存在内存中,重启服务器则清空缓存
pring Cache中的注解主要有如下五个:
@Cacheable:缓存数据或者获取缓存数据
@CachePut:修改缓存数据
@CacheEvict: 清空缓存
@CacheConfig:统一配置@Cacheable中的value值
@Caching:组合多个Cache注解
3.1 @Cacheable
先从value中获取为key的缓存,如果存在直接返回;如果不存在则执行方法并返回,且把返回输出存入缓存。(注意:保存的数据是return返回的数据)
主要有三个参数:
value:缓存的名称,可以多个 (必填,也可以用@CacheConfig替代)
@Cacheable(value="testcache") @Cacheable(value={"testcache1","testcache2"}
- key:缓存的 key,按照 SpEL 表达式编写,为空时默认为方法的入参(value相当于缓存空间的名称,而key相当于是一个缓存值的名字)
@Cacheable(value="testcache",key="#id")
- condition:缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false
@Cacheable(value="testcache",condition="#id.length()>2")
3.2 @CachePut
根据value中获取为key的缓存,如果存在则修改;不存在则新增
主要是三个参数
value,key,condition如上所示。
注意:保存的数据是return返回的数据,如下返回的有user对象和null,第一个缓存的数据是实体类,第二个缓存的数据是空
3.3 @CacheEvict
根据对应的value和key删除缓存,没有key值则删除value中所有的缓存
主要有五个参数value,key,condition,allEntries,beforeInvocation
- allEntries:是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存
@CachEvict(value="testcache",allEntries=true)
图一、会清空getData下面所有缓存(allEntries=true则删除所有)
图二、只会清空getData下面key值为id的缓存(没有key默认取入参)
图三、不会清空任何缓存
- beforeInvocation: 是否在方法执行前就清空,缺省为 false,如果指定为
true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存(注:作用只有一个,就是先清空缓存再执行方法
)
@CachEvict(value="testcache",beforeInvocation=true)
3.4 @CacheConfig
相当于把类下面所有方法@Cacheable中的value值放到@CacheConfig注解中,
如果@Cacheable中没有value值则用@Cacheable中的值;如果@Cacheable中有value值则以value值为准。
@CacheConfig("testcache") public class UserServiceImpl implements UserService{ @Cacheable public Result findById(Long id) { } @Cacheable public Result findByIdAndName(Long id,String name) { }
3.5 @Caching
组合注解,可以组合多个注解
@Caching(put = { @CachePut(value = "user", key = "#user.id"), @CachePut(value = "user", key = "#user.username"), @CachePut(value = "user", key = "#user.email") }) public User save(User user) { }
4 实战:Spring Cache整合redis缓存
4.1 CacheManager 缓存管理器
public interface CacheManager { //根据缓存名字获取缓存 @Nullable Cache getCache(String name); //获取管理的所有缓存的名字 Collection<String> getCacheNames(); }
支持多种类型的缓存
这里我们使用redis作为缓存
<!-- 引入redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <exclusions> <exclusion> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions> </dependency> <!-- jedis不写版本springboot控制 --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId>
4.2 配置spring-boot-starter-cache
1.缓存的自动配置
CacheProperties封装了配置文件中可以配置的属性
@ConfigurationProperties(prefix = "spring.cache") org.springframework.boot.autoconfigure.cache.CacheProperties CacheConfiguration`会根据缓存类型导入`RedisCacheConfiguration
RedisCacheConfiguration
自动配好了缓存管理器
@Bean public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) { RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory) .cacheDefaults(determineConfiguration(resourceLoader.getClassLoader())); List<String> cacheNames = this.cacheProperties.getCacheNames(); if (!cacheNames.isEmpty()) { builder.initialCacheNames(new LinkedHashSet<>(cacheNames)); } return this.customizerInvoker.customize(builder.build()); }
4.3 手动配置
配置使用Redis作为缓存
spring: cache: #cache-names: 可以自动配置 type: redis
4.4 开启缓存功能 @Cacheable({“…”})
在启动类添加@EnableCaching 注解,不需要重复配Redis
@EnableCaching 注解
2.使用注解 就能完成缓存操作
//每一个需要缓存的数据都需要指定要放到哪个名字的缓存,缓存的分区,按照业务类型分 @Cacheable({"categroy"}) //代表方法的结果需要缓存 ,如果缓存中有 方法都不用调用,如果缓存中没有,调用方法,将结果放入缓存 @Override public List<CategoryEntity> getLevel1Categorys() { long l = System.currentTimeMillis(); List<CategoryEntity> categoryEntityList = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0)); System.out.println("消耗时间:" + (System.currentTimeMillis() - l)); return categoryEntityList; }
使用@Cacheable 默认行为
如果缓存中有缓存 方法都不用调用
key值 自动生成
缓存的Value值默认使用JDK序列化机制,将序列化后的数据存到Redis
默认过期时间TTL -1 永不过去
应该要指定缓存存活时间 在配置文件中修改
cache: #cache-names: 可以自动配置 type: redis redis: time-to-live: 3600000 #设置存活时间毫秒 key-prefix: CACHE_ #key前缀 如果制定了前缀就用指定的前缀,如果没有就默认使用缓存的名字作为前缀 use-key-prefix: true # 是否使用前缀 cache-null-values: true # 是否缓存控制 解决缓存穿透