前言:
系统中使用缓存可以提高系统的执行速度,提高用户的体验。现在一般缓存都是用Redis来完成的,Redis是一款基于内存的非关系型数据库,它的特点就是快,官方数据说可以提供10万+的QBS。所以,我在项目中也使用的是Redis。SpringBoot框架已经对缓存以及Redis做了很好的集成,下面我将介绍一下如何使用。
正文:
Springboot中提供了缓存相关的注解,例如@Cacheable、@CachePut:、@CacheEvict:等。但是,使用之前只需要简单配置一下,我们这里需要使用Redis作为缓存,也需要对Redis进行配置。
1.引入pom文件:
org.springframework.boot
spring-boot-starter-data-redis
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;
/**
* 〈一句话功能简述〉
* 〈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 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 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 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 redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate template = new RedisTemplate<>();
//序列化
FastJsonRedisSerializer 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 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
* @author /
*/
class FastJsonRedisSerializer implements RedisSerializer {
private Class clazz;
FastJsonRedisSerializer(Class 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 {
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.创建路由、增删改菜单的类:
2.登出删除路由缓存的类:
3.缓存工具类: