SpringBoot2与RedisCacheManager整合

SpringBoot 缓存管理器CacheManager

从3.1开始Spring定义了org.springframework.cache.Cacheorg.springframework.cache.CacheManager接口来统一不同的缓存技术;并支持使用JCache(JSR-107) 注解简化开发.

  • Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
  • Cache接口下Spring提供了各种xxxCache的实现;如RedisCacheEhCacheCache ,ConcurrentMapCache等;

快速开始

1、导入Maven

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

<!-- redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

因为需要用RedisCacheManager 所以导入Redis依赖

2、配置Redis并开启缓存支持

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import javax.annotation.Resource;
import java.time.Duration;

/**
 * 
 * @author rstyro
 * @time 2018-07-31
 *
 */
@Configuration
@EnableCaching // 开启缓存支持
public class RedisConfig extends CachingConfigurerSupport {
    @Resource
    private LettuceConnectionFactory lettuceConnectionFactory;


    /**
     * 配置CacheManager
     * @return
     */
    @Bean
    public CacheManager cacheManager() {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        //解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        // 配置序列化(解决乱码的问题)
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
//                .entryTtl(Duration.ZERO)
                .entryTtl(Duration.ofSeconds(20))   //设置缓存失效时间
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                .disableCachingNullValues();

        RedisCacheManager cacheManager = RedisCacheManager.builder(lettuceConnectionFactory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }


    /**
     * RedisTemplate配置
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
        // 设置序列化
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(
                Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // 配置redisTemplate
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
        redisTemplate.setConnectionFactory(lettuceConnectionFactory);
        RedisSerializer<?> stringSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringSerializer);// key序列化
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);// value序列化
        redisTemplate.setHashKeySerializer(stringSerializer);// Hash key序列化
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);// Hash value序列化
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }


}

3、注解解析

注解的几个属性说明:

//指定缓存组件的名字
@AliasFor("cacheNames")
String[] value() default {};

//指定缓存组件的名字
@AliasFor("value")
String[] cacheNames() default {};

// 缓存数据使用的Key,
String key() default "";

// key的生成器,可以自己指定key的生成组件id; 
// key 与 keyGenerator 两个只能选一个,不能同时指定
String keyGenerator() default "";

// 指定缓存管理器,
String cacheManager() default "";

//指定获得解析器,cacheManager、cacheResolver 两者取其一
String cacheResolver() default "";

// 符合condition条件,才缓存
String condition() default "";

// 和 codition 条件相反才成立
String unless() default "";

// 是否使用异步模式
boolean sync() default false;

// 只有@CacheEvict 有这个属性
// 清空所有缓存信息
boolean allEntries() default false;

// 只有@CacheEvict 有这个属性
// 缓存的清除是否在方法之前执行
// beforeInvocation=false  默认是在方法之后执行
boolean beforeInvocation() default false;
  • @Cacheable
    先查询缓存中是否存在,存在则返回缓存内容,反正执行方法后返回并把返回结果缓存起来
  • @CacheEvict
    删除指定缓存
  • @CachePut
    更新并刷新缓存,先执行方法内容,然后更新缓存
  • @EnableCaching
    这个是一个复合注解,可以拥有同时配置上面3个注解的功能

4、使用方法

@CacheConfig(cacheNames = "act")  //这个注解表示类中共同放入到act 模块中
@Service
public class ActicleService implements IActicleService {

    @Autowired
    private ActicleMapper acticleMapper;

    /**
     * @Cacheable
     * 1、先查缓存,
     * 2、若没有缓存,就执行方法
     * 3、若有缓存。则返回,不执行方法
     *
     * 所以@Cacheable 不能使用result
     *
     * @return
     * @throws Exception
     */
    @Cacheable(key = "#root.methodName")
    public List<Acticle> list() throws Exception {
        return acticleMapper.getActicleList();
    }

    /**
     * @CachePut 更新并刷新缓存
     * 1、先调用目标方法
     * 2、把结果缓存
     * @param acticle
     * @return
     * @throws Exception
     */
    @CachePut(key = "#result.id" ,unless = "#result.id == null" )
    public Acticle save(Acticle acticle) throws Exception {
        acticle.setCreateBy(1l);
        acticle.setCreateTime(LocalDateTime.now());
        acticle.setModifyBy(1l);
        acticle.setModifyTime(LocalDateTime.now());
        acticleMapper.save(acticle);
        System.out.println("acticle="+acticle);
        return acticle;
    }

    /**
     * 删除指定key 的缓存
     * beforeInvocation=false  缓存的清除是否在方法之前执行
     * 默认是在方法之后执行
     * @param id
     * @return
     * @throws Exception
     */
    @CacheEvict(key = "#id",beforeInvocation = true)
    public int del(Long id) throws Exception {
        int isDel = 0;
        isDel = acticleMapper.del(id);
        return isDel;
    }

    /**
     * 删除所有缓存
     * @return
     * @throws Exception
     */
    @CacheEvict(allEntries = true)
    public int delAll() throws Exception {
        return 1;
    }

    @CachePut(key = "#result.id" ,unless = "#result.id == null" )
    public Acticle update(Acticle acticle) throws Exception {
        acticle.setModifyBy(1l);
        acticle.setModifyTime(LocalDateTime.now());
        return acticleMapper.update(acticle);
    }

    @Cacheable(key = "#id",condition = "#id > 0")
    public Acticle queryById(Long id) throws Exception {
        return acticleMapper.queryById(id);
    }

    /**
     * @Caching复杂组合缓存注解
     *
     * @param title
     * @return
     * @throws Exception
     */
    @Caching(cacheable = { @Cacheable(key = "#title")},
            put = {@CachePut(key = "#result.id"),
//            @CachePut(key = "T(String).valueOf(#page).concat('-').concat(#pageSize)")
            @CachePut(key = "T(String).valueOf('tag').concat('-').concat(#result.tagId)")
    })
    public Acticle queryByTitle(String title) throws Exception {
        return acticleMapper.queryByTitle(title);
    }

    @Cacheable(key = "T(String).valueOf('tag').concat('-').concat(#tagId)")
    public Acticle queryByTag(Long tagId) throws Exception {
        return null;
    }
}

缓存工作原理

当我们引入缓存的时候,SpringBoot的缓存自动配置CacheAutoConfiguration就会生效

  • CacheAutoConfiguration 中的CacheConfigurationImportSelector会导入很多缓存组件配置类
  • 通过debug看源码,知道imports 的内容如下
static class CacheConfigurationImportSelector implements ImportSelector {
    CacheConfigurationImportSelector() {
    }

    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        CacheType[] types = CacheType.values();
        String[] imports = new String[types.length];

        for(int i = 0; i < types.length; ++i) {
            imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
        }

        return imports;
    }
}

/**
imports 如下

0 = "org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration"
1 = "org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration"
2 = "org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration"
3 = "org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration"
4 = "org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration"
5 = "org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration"
6 = "org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration"
7 = "org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration"
8 = "org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration"     
9 = "org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration"
*/

  • 如果没有引Redis, 则SimpleCacheConfiguration这个就是默认的配置类
  • SimpleCacheConfiguration注入了一个ConcurrentMapCacheManager
  • ConcurrentMapCacheManager 实现了CacheManager 接口
  • ConcurrentMapCacheManager 通过 ConcurrentHashMap 把数据缓存起来
  • CacheManager 有一个Cache getCache(String var1) 方法换取缓存
  • 流程大概就这里,感兴趣的同学可以打断点阅读源码

本章代码示例地址

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容