0


SpringCache

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);
    }

这样我们就实现了:

对文章列表、文章详情和最新文章的接口进行缓存处理,同时,当有新文章发布或者文章被作者编辑后,对相应的缓存内容进行更新。

标签: java spring redis

本文转载自: https://blog.csdn.net/qq_56851614/article/details/125822041
版权归原作者 hxung 所有, 如有侵权,请联系我们删除。

“SpringCache”的评论:

还没有评论