Spring Cache简介
Spring Cache 是 Spring 提供的一整套的缓存解决方案。虽然它本身并没有提供缓存的实现,但是它提供了一整套的接口和代码规范、配置、注解等,这样它就可以整合各种缓存方案了,比如 Redis、Ehcache,我们也就不用关心操作缓存的细节。
Cache接口它包含了缓存的各种操作方式,同时还提供了各种xxxCache缓存的实现,比如 RedisCache 针对Redis,EhCacheCache 针对 EhCache,ConcurrentMapCache 针对 ConCurrentMap。
Spring Cache使用效果
每次调用某方法,而此方法又是带有缓存功能时,Spring 框架就会检查
指定参数
的那个方法是否已经被调用过,如果之前调用过,就从缓存中取之前调用的结果;如果没有调用过,则再调用一次这个方法,并缓存结果,然后再返回结果,那下次调用这个方法时,就可以直接从缓存中获取结果了。
Spring Cache自定义CacheManager配置和过期时间
package com.huing.blog.config;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.util.StringUtils;
import java.lang.reflect.Method;
import java.time.Duration;
/**
* Cache注解类,配置了过期时间和序列化
*
* @Author huing
* @Create 2022-07-15 15:42
*/
@Configuration
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager1Minute(RedisConnectionFactory connectionFactory){
RedisCacheConfiguration config = instanceConig(60L);
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.transactionAware()
.build();
}
@Bean
@Primary // 默认的,没有指定采用默认的
public RedisCacheManager cacheManager1Hour(RedisConnectionFactory connectionFactory){
RedisCacheConfiguration config = instanceConig(3600L);
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.transactionAware()
.build();
}
@Bean
public RedisCacheManager cacheManager1Day(RedisConnectionFactory connectionFactory){
RedisCacheConfiguration config = instanceConig(3600 * 24L);
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.transactionAware()
.build();
}
private RedisCacheConfiguration instanceConig(long ttl) {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.registerModule(new JavaTimeModule());
//去掉各种@JsonSerialize注解的解析
objectMapper.configure(MapperFeature.USE_ANNOTATIONS,false);
//只针对非空的值进行序列化
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
//将类型序列化到属性json字符串中
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(ttl))
.disableCachingNullValues() //禁止缓存null的值
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
}
}
Spring Cache自定义KeyGenerator
CacheConfig:
/**
* 自定义缓存Key规则
* @return
*/
@Bean
public KeyGenerator springCacheCustomKeyGenerator(){
return new KeyGenerator() {
@Override
public Object generate(Object o, Method method, Object... objects) {
String key = o.getClass().getSimpleName() + "_" + method.getName() + "_" + StringUtils.arrayToDelimitedString(objects,"_");
System.out.println(key);
return key;
}
};
}
缓存注解
注解描述@Cacheable在调用方法前,首先去缓存中找方法的返回值,如果能找到,则返回缓存的值,否则就执行这个方法,并将返回值放到缓存中。@CachePut在方法调用前不会去缓存中找,无论如何都会执行方法,执行后将缓存数据放入缓存中。@CacheEvict清理缓存中的一个或多个记录@Caching能够同时应用多个缓存注解@CacheConfig在类级别共享相同的缓存的配置
其核心主要是@Cacheable和@CacheEvict。使用@Cacheable标记的方法在执行后,Spring Cache将缓存其返回结果,而使用@CacheEvict标记的方法会在方法执行前或者执行后移除Spring Cache中的某些元素。
Cacheable注解
- 标记在一个方法上,也可以标记在一个类上
- 缓存标注对象的返回结果,标注在方法上缓存该方法的返回值,标注在类上缓存该类所有方法的返回值
- value缓存名称,可以有多个
- key缓存的key规则,可以使用springEL表达式,默认是方法参数组合
- condition缓存条件,使用springEL编写,返回true才缓存
需要注意的是:一个支持缓存的方法在对象内部被调用时是不会触发缓存功能的
示例:
/**
* 最新文章
*
* @return
*/
@PostMapping("new")
@Cacheable(value = {"newArticle"}, key = "#root.methodName",cacheManager = "cacheManager1Minute")
public Result newArticle() {
return articleService.newArticle(limit);
}
其中使用root对象来生成key:
属性名称描述示例methodName当前方法名#root.methodNamemethod当前方法#root.method.nametargett当前被调用的对象#root.targettargetClass当前被调用的对象的class#root.targetClassargs当前方法参数组成的数组#root.args[0]caches当前被调用的方法使用的Cache#root.caches[0].name
使用root对象属性作为key时可以省略“#root”
CachePut注解
- 根据方法的请求参数对其结果进行缓存,每次都会触发真实方法的调用
- value缓存名称,可以有多个
- key缓存的key规则,可以使用springEL表达式,默认是方法参数组合
- condition缓存条件,使用springEL编写,返回true才缓存
与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
可以标注在类上和方法上。使用@CachePut时我们可以指定的属性跟@Cacheable是一样的。
CacheEvict注解
- 从缓存中移除相应数据,触发缓存删除的操作
- value缓存名称,可以有多个
- key缓存的key规则,可以使用springEL表达式,默认是方法参数组合
- beforeinvocation = false(默认) - 缓存的清除是否在方法执行之前执行,默认代表缓存清除操作是在方法执行之后执行;- 如果出现异常缓存就不会清除
- beforeinvocation = true - 代表清除缓存操作是在方法之前执行,无论方法是否出现异常,缓存都会清除
- condition清除缓存条件,使用springEL编写,返回true才缓存
- allEntries = false(默认) - 清除指定key的缓存
- allEntries = true - 忽略key,清除value缓存中的所有元素
示例:
/**
* 写文章
*
* @param articleParm
* @return
*/
@PostMapping("publish")
@CacheEvict(value = {"ArticleById"},key = "#articleParm.id",condition = "#articleParm.id != null") //如果文章id不为空(即编辑时),删除文章对应缓存缓存
public Result publish(@RequestBody ArticleParm articleParm) {
return articleService.publish(articleParm);
}
当articleParm.id != null时,删除缓存名为ArticleById中的key为"#articleParm.id"的元素。
Caching注解
- 组合多个Cache注解使用
- 允许同一方法上使用多个嵌套的@Cacheable、@CachePut和@CacheEvict注解
- 其拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict
示例:
/**
* 写文章
*
* @param articleParm
* @return
*/
@PostMapping("publish")
@Caching(
evict = {
@CacheEvict(value = {"ArticleById"},key = "#articleParm.id",condition = "#articleParm.id != null"), //如果文章id不为空(即编辑时),删除文章对应缓存缓存
@CacheEvict(value = "ArticleList",allEntries = true) //删除ArticleList下的所有缓存
}
)
public Result publish(@RequestBody ArticleParm articleParm) {
return articleService.publish(articleParm);
}
在线博客系统使用SpringCache实现缓存和缓存更新
实现效果
在博客项目中,使用SpringCache,首先我们要实现的效果是:
对文章列表、文章详情和最新文章的接口进行缓存处理,同时,当有新文章发布或者文章被作者编辑后,对相应的缓存内容进行更新。
pop依赖
<!-- cache-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
application.properties配置文件
#cache 配置文件指定缓存类型
spring.cache.type=redis
启动类开启缓存注解
package com.huing.blog;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
/**
* @author huing
* @create 2022-07-03 10:01
*/
@SpringBootApplication
@EnableCaching
public class BlogApp {
public static void main(String[] args) {
SpringApplication.run(BlogApp.class,args);
}
}
CacheConfig配置文件
package com.huing.blog.config;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import java.time.Duration;
/**
* Cache注解类,配置了过期时间和序列化
*
* @Author huing
* @Create 2022-07-15 15:42
*/
@Configuration
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager1Minute(RedisConnectionFactory connectionFactory){
RedisCacheConfiguration config = instanceConig(60L);
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.transactionAware()
.build();
}
@Bean
@Primary // 默认的,没有指定采用默认的
public RedisCacheManager cacheManager1Hour(RedisConnectionFactory connectionFactory){
RedisCacheConfiguration config = instanceConig(3600L);
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.transactionAware()
.build();
}
@Bean
public RedisCacheManager cacheManager1Day(RedisConnectionFactory connectionFactory){
RedisCacheConfiguration config = instanceConig(3600 * 24L);
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.transactionAware()
.build();
}
private RedisCacheConfiguration instanceConig(long ttl) {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.registerModule(new JavaTimeModule());
//去掉各种@JsonSerialize注解的解析
objectMapper.configure(MapperFeature.USE_ANNOTATIONS,false);
//只针对非空的值进行序列化
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
//将类型序列化到属性json字符串中
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(ttl))
.disableCachingNullValues() //禁止缓存null的值
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
}
}
缓存注解实现
对ArticleController控制层进行操作示例
文章列表缓存
/**
* 首页,文章处理
*
* @param pageParams
* @return
*/
@PostMapping()
//加上此注解,代表对此接口记录日志
@LogAnnotation(module = "文章", operation = "获取文章列表")
//对文章列表进行缓存,key为列表的页码数,使用默认的过期时间(一小时)
@Cacheable(value = {"ArticleList"}, key = "#pageParams.page")
public Result<List<ArticleVo>> listArticle(@RequestBody PageParams pageParams) {
return articleService.listArticle(pageParams);
}
最新文章缓存
/**
* 最新文章
*
* @return
*/
@PostMapping("new")
@Cacheable(value = {"newArticle"}, key = "#root.methodName",cacheManager = "cacheManager1Minute")
public Result newArticle() {
return articleService.newArticle(limit);
}
文章详情缓存
/**
* 根据文章id查询文章详情
*
* @param articleId
* @return
*/
@PostMapping("view/{id}")
@Cacheable(value = {"ArticleById"}, key = "#articleId")
public Result findArticleById(@PathVariable("id") Long articleId) {
return articleService.findArticleById(articleId);
}
缓存更新
当对缓存内容进行变更时,删除对应的缓存,实现缓存更新
/**
* 写文章
*
* @param articleParm
* @return
*/
// @RequestBody主要用来接收前端传递给后端的json字符串中的数据的(请求体中的数据的);
// 而最常用的使用请求体传参的无疑是POST请求了,所以使用@RequestBody接收数据时,一般都用POST方式进行提交。
@PostMapping("publish")
// @CacheEvict(value = {"ArticleById"},key = "#articleParm.id",condition = "#articleParm.id != null") //如果文章id不为空(即编辑时),删除文章对应缓存缓存
@Caching(
evict = {
@CacheEvict(value = {"ArticleById"},key = "#articleParm.id",condition = "#articleParm.id != null"), //如果文章id不为空(即编辑时),删除文章对应缓存缓存
@CacheEvict(value = "ArticleList",allEntries = true) //删除ArticleList下的所有缓存
}
)
public Result publish(@RequestBody ArticleParm articleParm) {
return articleService.publish(articleParm);
}
这样我们就实现了:
对文章列表、文章详情和最新文章的接口进行缓存处理,同时,当有新文章发布或者文章被作者编辑后,对相应的缓存内容进行更新。
版权归原作者 hxung 所有, 如有侵权,请联系我们删除。