SpringBoot2與RedisCacheManager整合

SpringBoot 緩存管理器CacheManager

從3.1開始Spring定義了org.springframework.cache.Cacheorg.springframework.cache.CacheManager接口來統(tǒng)一不同的緩存技術(shù);并支持使用JCache(JSR-107) 注解簡化開發(fā).

  • Cache接口為緩存的組件規(guī)范定義,包含緩存的各種操作集合;
  • Cache接口下Spring提供了各種xxxCache的實(shí)現(xiàn);如RedisCache,EhCacheCache ,ConcurrentMapCache等;

快速開始

1、導(dǎo)入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>

因?yàn)樾枰?code>RedisCacheManager 所以導(dǎo)入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);

        //解決查詢緩存轉(zhuǎn)換異常的問題
        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))   //設(shè)置緩存失效時(shí)間
                .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) {
        // 設(shè)置序列化
        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 {};

// 緩存數(shù)據(jù)使用的Key,
String key() default "";

// key的生成器,可以自己指定key的生成組件id; 
// key 與 keyGenerator 兩個只能選一個,不能同時(shí)指定
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 有這個屬性
// 緩存的清除是否在方法之前執(zhí)行
// beforeInvocation=false  默認(rèn)是在方法之后執(zhí)行
boolean beforeInvocation() default false;
  • @Cacheable
    先查詢緩存中是否存在,存在則返回緩存內(nèi)容,反正執(zhí)行方法后返回并把返回結(jié)果緩存起來
  • @CacheEvict
    刪除指定緩存
  • @CachePut
    更新并刷新緩存,先執(zhí)行方法內(nèi)容,然后更新緩存
  • @EnableCaching
    這個是一個復(fù)合注解,可以擁有同時(shí)配置上面3個注解的功能

4、使用方法

@CacheConfig(cacheNames = "act")  //這個注解表示類中共同放入到act 模塊中
@Service
public class ActicleService implements IActicleService {

    @Autowired
    private ActicleMapper acticleMapper;

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

    /**
     * @CachePut 更新并刷新緩存
     * 1、先調(diào)用目標(biāo)方法
     * 2、把結(jié)果緩存
     * @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  緩存的清除是否在方法之前執(zhí)行
     * 默認(rèn)是在方法之后執(zhí)行
     * @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復(fù)雜組合緩存注解
     *
     * @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;
    }
}

緩存工作原理

當(dāng)我們引入緩存的時(shí)候,SpringBoot的緩存自動配置CacheAutoConfiguration就會生效

  • CacheAutoConfiguration 中的CacheConfigurationImportSelector會導(dǎo)入很多緩存組件配置類
  • 通過debug看源碼,知道imports 的內(nèi)容如下
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這個就是默認(rèn)的配置類
  • SimpleCacheConfiguration注入了一個ConcurrentMapCacheManager
  • ConcurrentMapCacheManager 實(shí)現(xiàn)了CacheManager 接口
  • ConcurrentMapCacheManager 通過 ConcurrentHashMap 把數(shù)據(jù)緩存起來
  • CacheManager 有一個Cache getCache(String var1) 方法換取緩存
  • 流程大概就這里,感興趣的同學(xué)可以打斷點(diǎn)閱讀源碼

本章代碼示例地址

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容