前言:

        系统中使用缓存可以提高系统的执行速度,提高用户的体验。现在一般缓存都是用Redis来完成的,Redis是一款基于内存的非关系型数据库,它的特点就是快,官方数据说可以提供10万+的QBS。所以,我在项目中也使用的是Redis。SpringBoot框架已经对缓存以及Redis做了很好的集成,下面我将介绍一下如何使用。

正文:

        Springboot中提供了缓存相关的注解,例如@Cacheable、@CachePut:、@CacheEvict:等。但是,使用之前只需要简单配置一下,我们这里需要使用Redis作为缓存,也需要对Redis进行配置。

1.引入pom文件:

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

2.缓存及Redis配置类:

package com.hanxiaozhang.redis.config;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.hanxiaozhang.constant.CacheName;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.KeyGenerator;
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.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.util.Assert;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * 〈一句话功能简述〉<br>
 * 〈RedisConfig配置Springboot2.X写法〉
 *
 * @author hanxinghua
 * @create 2020/1/3
 * @since 1.0.0
 */
@Slf4j
@Configuration
@EnableCaching
@ConditionalOnClass(RedisOperations.class)
// @ConditionalOnClass(JedisCluster.class) 集群模式
public class RedisConfig extends CachingConfigurerSupport {

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    /**
     * 设置 redis 数据默认过期时间,默认1h和设置@cacheable 序列化方式
     */
    @Bean
    public RedisCacheConfiguration redisCacheConfiguration() {
        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
        RedisCacheConfiguration configuration = RedisCacheConfiguration
                .defaultCacheConfig()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer))
                .entryTtl(Duration.ofHours(1));
        return configuration;
    }

    @Bean
    @Override
    public CacheManager cacheManager() {

        // 设置一个初始化的缓存空间set集合
        Set<String> cacheNames = new HashSet<>();
        cacheNames.add(CacheName.DICT_12HOUR);
        cacheNames.add(CacheName.CURRENT_USER_BY_USER_ID_2HOUR);
        cacheNames.add(CacheName.ROUTE_BY_USER_ID_2HOUR);

        // 对每个缓存空间应用不同的配置
        Map<String, RedisCacheConfiguration> configMap = new HashMap<>(8);
        configMap.put(CacheName.DICT_12HOUR, redisCacheConfiguration().entryTtl(Duration.ofHours(12)));
        configMap.put(CacheName.CURRENT_USER_BY_USER_ID_2HOUR, redisCacheConfiguration().entryTtl(Duration.ofHours(2)));
        configMap.put(CacheName.ROUTE_BY_USER_ID_2HOUR, redisCacheConfiguration().entryTtl(Duration.ofHours(2)));

        RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
                // Tips: 动态创建出来的都会走默认配置
                .cacheDefaults(redisCacheConfiguration())
                .initialCacheNames(cacheNames)
                .withInitialCacheConfigurations(configMap)
                .build();
        return cacheManager;
    }

    /**
     * RedisTemplate
     *
     * @param redisConnectionFactory
     * @return
     */
    @SuppressWarnings("all")
    @Bean(name = "redisTemplate")
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        //序列化
        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        // key的序列化采用StringRedisSerializer
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        // value值的序列化采用fastJsonRedisSerializer
        template.setValueSerializer(fastJsonRedisSerializer);
        template.setHashValueSerializer(fastJsonRedisSerializer);
        // 设置Redis链接工厂
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    /**
     * 自定义缓存key生成策略,默认将使用该策略
     */
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            Map<String, Object> container = new HashMap<>(3);
            Class<?> targetClassClass = target.getClass();
            // 类地址
            container.put("class", targetClassClass.toGenericString());
            // 方法名称
            container.put("methodName", method.getName());
            // 包名称
            container.put("package", targetClassClass.getPackage());
            // 参数列表
            for (int i = 0; i < params.length; i++) {
                container.put(String.valueOf(i), params[i]);
            }
            // 转为JSON字符串
            String jsonString = JSON.toJSONString(container);
            // 做SHA256 Hash计算,得到一个SHA256摘要作为Key
            return DigestUtils.sha256Hex(jsonString);
        };
    }


    /**
     * 异常处理,当Redis发生异常时,打印日志,但是程序正常走
     *
     * @return
     */
    @Bean
    @Override
    public CacheErrorHandler errorHandler() {
        log.info("初始化 -> [{}]", "Redis CacheErrorHandler");
        return new CacheErrorHandler() {
            @Override
            public void handleCacheGetError(RuntimeException e, Cache cache, Object key) {
                log.error("Redis occur handleCacheGetError:key -> [{}]", key, e);
            }

            @Override
            public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {
                log.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e);
            }

            @Override
            public void handleCacheEvictError(RuntimeException e, Cache cache, Object key) {
                log.error("Redis occur handleCacheEvictError:key -> [{}]", key, e);
            }

            @Override
            public void handleCacheClearError(RuntimeException e, Cache cache) {
                log.error("Redis occur handleCacheClearError:", e);
            }
        };
    }

}

/**
 * Value 序列化
 *
 * @param <T>
 * @author /
 */
class FastJsonRedisSerializer<T> implements RedisSerializer<T> {

    private Class<T> clazz;

    FastJsonRedisSerializer(Class<T> clazz) {
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) {
        if (t == null) {
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(StandardCharsets.UTF_8);
    }

    @Override
    public T deserialize(byte[] bytes) {
        if (bytes == null || bytes.length <= 0) {
            return null;
        }
        String str = new String(bytes, StandardCharsets.UTF_8);
        return JSON.parseObject(str, clazz);
    }

}

/**
 * 重写序列化器-key
 *
 * @author /
 */
class StringRedisSerializer implements RedisSerializer<Object> {

    private final Charset charset;

    StringRedisSerializer() {
        this(StandardCharsets.UTF_8);
    }

    private StringRedisSerializer(Charset charset) {
        Assert.notNull(charset, "Charset must not be null!");
        this.charset = charset;
    }

    @Override
    public String deserialize(byte[] bytes) {
        return (bytes == null ? null : new String(bytes, charset));
    }

    @Override
    public byte[] serialize(Object object) {
        String string = JSON.toJSONString(object);
        if (StringUtils.isBlank(string)) {
            return null;
        }
        string = string.replace("\"", "");
        return string.getBytes(charset);
    }
}

3.配置Yaml文件:

spring:
  redis:
    database: ${REDIS_DB:0}
    host: ${REDIS_HOST:127.0.0.1}
    port: ${REDIS_PORT:6379}
    password: ${REDIS_PWD:123456}
    timeout: 5000
    jedis:
      pool:
        max-idle: 8      # 连接池中的最大空闲连接
        min-idle: 10     # 连接池中的最小空闲连接
        max-active: 100  # 连接池最大连接数(使用负值表示没有限制)
        max-wait: -1     # 连接池最大阻塞等待时间(使用负值表示没有限制)

        完成以上配置,就可以通过注解完成对缓存的操作了,但是有的时候注解使用起来不太灵活,这个时候我们就需要创建一个缓存工具类完成一些对缓存操作。

        例如,用户登录后,将路由信息缓存到Redis中。当路由信息发生变化时,删除脏缓存的场景。这里在缓存路由信息的时候就可以使用注解,如果修改编辑路由信息的方法在同一个类中也可以使用注解。但是不在同一个类中,就需要缓存工具类的帮助。用户登出的时候也需要清除路由缓存,该方法也可能不在创建路由缓存类中,这里也需要缓存工具类。缓存路由演示代码如下:

1.创建路由、增删改菜单的类:

springboot项目启动检测redis是否可用 springboot redisconfig_redis

2.登出删除路由缓存的类:

springboot项目启动检测redis是否可用 springboot redisconfig_spring_02

3.缓存工具类:

springboot项目启动检测redis是否可用 springboot redisconfig_spring_03