文章目录
SpringBoot与缓存
一、Spring缓存抽象
1. 概述
Spring从3.1开始定义了Cache和CacheManager接口来统一不同的缓存技术,并支持使用JCache(JSR-107)注解简化开发
- CacheManager管理多个Cache组件,对缓存真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一的名字
- Cache接口为缓存的组件规范定义,包含缓存的各种操作集合
- Cache接口下Spring提供了xxxCache的实现,例如RedisCache,EhCacheCache等
2. 缓存注解以及概念
名称 | 简介 |
---|---|
Cache | 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache等 |
CacheManager | 缓存管理器,管理各种缓存(Cache)组件 |
@Cacheable | 针对方法配置,根据方法的请求参数对其结果进行缓存 |
@CacheEvict | 清空缓存 |
@CachePut | 保证方法被调用,又希望结果被缓存 |
@EnableCaching | 开启基于注解的缓存 |
keyGenerator | 缓存数据时key生成策略 |
serialize | 缓存数据时value序列化策略 |
二、环境搭建
-
导入相关依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
-
编写Controller、Service、Dao层等相关业务接口
三、入门案例
-
开启缓存,使用@EnableCaching注解
@SpringBootApplication @EnableCaching public class SpringBootCacheTestApplication { public static void main(String[] args) { SpringApplication.run(SpringBootCacheTestApplication.class,args); } }
-
以@Cacheable为例
@Slf4j @Service public class UserService { @Autowired public Employee employee; @Cacheable(value = {"user"}, key = "#id") public User getUser(Integer id) { log.info("------------------查询数据库 id=" + id + "------------------"); return employee.getEmployees().get(id); } }
-
运行结果
- 第一次调用该接口时系统会查询数据库
- 之后调用该接口时系统会直接从缓存中取值
-
@Cacheable注解的相关属性
- cacheName/value:指定缓存组件的名字,可以是数组
- key:缓存数据时使用的key(默认使用方法参数的值),可以使用SpEL表达式
- keyGenerator:key的生成器:可以自定义。key和keyGenerator二选一
- cacheManager:指定缓存管理器
- condition:指定符合条件的情况下才缓存
- unless:当unless指定的条件为true则不缓存(例如unless="#result==null",即当返回的结果为空时则不缓存)
- sync:指定缓存是否使用异步模式
四、原理
1. 底层源码
-
缓存自动配置类
@Import({CacheAutoConfiguration.CacheConfigurationImportSelector.class, CacheAutoConfiguration.CacheManagerEntityManagerFactoryDependsOnPostProcessor.class}) public class CacheAutoConfiguration { ...... }
-
缓存的配置类
-
默认SimpleCacheConfiguration该配置类生效
-
该类向容器中注入了一个CacheManager,该缓存管理器为ConcurrentMapCacheManager
@Configuration( proxyBeanMethods = false ) @ConditionalOnMissingBean({CacheManager.class}) @Conditional({CacheCondition.class}) class SimpleCacheConfiguration { SimpleCacheConfiguration() { } @Bean ConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers) { ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager(); List<String> cacheNames = cacheProperties.getCacheNames(); if (!cacheNames.isEmpty()) { cacheManager.setCacheNames(cacheNames); } return (ConcurrentMapCacheManager)cacheManagerCustomizers.customize(cacheManager); } }
-
-
ConcurrentMapCacheManager类
- 可以创建ConcurrentHashMap类型的缓存组件
public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware { //用来保存缓存组件 private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap(16); private boolean dynamic = true; private boolean allowNullValues = true; private boolean storeByValue = false; @Nullable private SerializationDelegate serialization; public ConcurrentMapCacheManager() { } public ConcurrentMapCacheManager(String... cacheNames) { this.setCacheNames(Arrays.asList(cacheNames)); } ...... //根据缓存名获取缓存 @Nullable public Cache getCache(String name) { Cache cache = (Cache)this.cacheMap.get(name); if (cache == null && this.dynamic) { synchronized(this.cacheMap) { cache = (Cache)this.cacheMap.get(name); if (cache == null) { cache = this.createConcurrentMapCache(name); this.cacheMap.put(name, cache); } } } return cache; } ...... //创建ConcurrentHashMap类型的缓存 protected Cache createConcurrentMapCache(String name) { SerializationDelegate actualSerialization = this.isStoreByValue() ? this.serialization : null; return new ConcurrentMapCache(name, new ConcurrentHashMap(256), this.isAllowNullValues(), actualSerialization); } }
- ConcurrentMapCache类,该类中有lookup、get、put等方法,会向ConcurrentMap中取出或存入数据
2. 运行流程
- 以Cacheable为例,方法运行之前先去查Cache缓存组件(CacheManager先获取相应缓存),按照CacheNames指定的缓存名获取。第一次如果没有缓存组件则会自动创建
- 去Cache中查找缓存的内容,使用key来查找。key默认使用keyGenerator生成
- 当缓存中没有值时则会调用目标方法
- 将目标方法的返回值存入缓存中
五、测试
-
自定义key的生成策略
- 编写配置类
- 实现KeyGenerator接口并返回
//标记配置类注解 @Configuration public class MyCacheConfig { @Bean("myKeyGenerator")//KeyGenerator的id public KeyGenerator profileKeyGenerator() { return new KeyGenerator() { @Override public Object generate(Object target, Method method, Object... params) { //自定义的key生成策略 return method.getName()+"["+Arrays.asList(params).toString()+"]"; } }; } }
- 在@Cacheable等注解中指定keyGenerator的id即可,例如@Cacheable(value={“缓存名”},keyGenerator=“myKeyGenerator”)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AM6njQDR-1630051212594)(C:\Users\T450\AppData\Roaming\Typora\typora-user-images\image-20210827114528344.png)]
-
@Cacheable的condition和unless属性
//当员工id大于1时才缓存 @Cacheable(value = {"user"}, keyGenerator = "myKeyGenerator",condition = "#id>1") //当员工id为2时不缓存 @Cacheable(value = {"user"}, keyGenerator = "myKeyGenerator",condition = "#id>1",unless = "#id==2")
-
@CachePut注解:既调用方法又更新缓存
@CachePut(value = "user", key = "#id") public User updateEmployee(Integer id, Integer age, String name) { log.info("------------------Service层更新用户,id=" + id + "------------------"); return userDao.updateEmployee(id, age, name); }
- 该方法运行结束后,将返回值放入缓存中
-
@CacheEvict注解:清除缓存中的数据
- 按指定key删除数据
@CacheEvict(value = "user", key = "#id") public void delUser(Integer id) { log.info("------------------Service层删除用户,id=" + id + "------------------"); }
- 删除缓存中的所有数据
@CacheEvict(value = "user", allEntries = true,beforeInvocation=true) public void delUser(Integer id) { log.info("------------------Service层删除用户,id=" + id + "------------------"); } //beforeInvocation=true:在方法执行之前清空缓存,默认为false(当方法执行过程中出现异常时缓存也会被清除) //allEntries=true:清空缓存中的所有内容
-
@Caching注解:定义复杂的缓存规则、
//源码 public @interface Caching { Cacheable[] cacheable() default {}; CachePut[] put() default {}; CacheEvict[] evict() default {}; } //示例 @Caching( evict = { @CacheEvict(value = "user",allEntries = true,condition = "#id==某个值"), @CacheEvict(value = "user",key = "#id") } ) //当id==某个值时会清空缓存,当id!=某个值时只会清空key==id的缓存
-
@CacheConfig注解
//源码 public @interface CacheConfig { String[] cacheNames() default {}; String keyGenerator() default ""; String cacheManager() default ""; String cacheResolver() default ""; }
- 该注解配置在类上,抽取缓存的公共配置。可以配置缓存名、缓存管理器等属性,类中的缓存方法则默认使用类上配置的缓存属性值
六、整合Redis
1. 环境配置
-
引入相应的包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>2.5.3</version> </dependency>
-
配置文件中配置Redis
2. 自定义缓存管理器
-
底层原理
- 当引入Redis时,底层Redis的相关配置就会被匹配到
- 引入Redis的starter后,容器中保存的是RedisCacheManager
- RedisCacheManager帮我们创建RedisCache来作为缓存组件
- 默认保存数据时会对数据进行序列化
-
自定义CacheManager
- 系统中的缓存管理器创建条件为当系统中没有缓存管理器时才创建,当我们自定义之后,相当于系统中已有缓存管理器,则系统就会使用我们创建的缓存管理器
@Configuration public class MyRedisConfig extends CachingConfigurerSupport { @Resource private RedisConnectionFactory redisFactory; /** * 配置CacheManager * * @return */ @Bean public CacheManager cacheManager() { RedisSerializer<String> redisSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer<User> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(User.class); // 配置序列化(解决乱码的问题) RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofSeconds(300)) //设置缓存失效时间 .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) .disableCachingNullValues(); return RedisCacheManager.builder(redisFactory) .cacheDefaults(config) .build(); } /** * RedisTemplate配置 */ @Bean public RedisTemplate<String, User> redisTemplate(RedisConnectionFactory redisConnectionFactory) { // 设置序列化 Jackson2JsonRedisSerializer<User> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>( User.class); // 配置redisTemplate RedisTemplate<String, User> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.setDefaultSerializer(jackson2JsonRedisSerializer); return redisTemplate; } }
-
测试
@Slf4j @Service @CacheConfig(cacheNames = "user") public class UserService { @Autowired public Employee employee; @Autowired public UserDao userDao; @Cacheable(value = "user", key = "#id") public User getUser(Integer id) { log.info("------------------Service层查询用户,id=" + id + "------------------"); return employee.getEmployees().get(id); } }
-
结果