springboot 使用spring cache缓存 和 使用fastjson配置redis系列化
此文档,是上篇文档"springboot 使用spring cache缓存 和 缓存数据落地到redis"的继续
此文使用fastjson完成spring cache对象的系列化
springboot 2.7.3
一、maven依赖
<properties>
<java.version>1.8</java.version>
<fastjson.version>1.2.83</fastjson.version>
<codec.version>1.15</codec.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- spring boot 缓存 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- Spring boot Redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- 加解密 使用到了DigestUtils.sha256Hex(jsonString) -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>${codec.version}</version>
</dependency>
</dependencies>
二、redis 默认配置文件
application.yml配置文件,使用redis的默认配置,可以不设置
#application.yml
## redis
# redis 定义变量
REDIS_HOST: localhost
REDIS_PORT: 6379
REDIS_PWD:
redis:
#数据库索引
database: ${REDIS_DB:0}
host: ${REDIS_HOST:127.0.0.1}
port: ${REDIS_PORT:6379}
password: ${REDIS_PWD:}
#连接超时时间
timeout: 5000
三、spring cache redis配置类
主要做了两方面的内容:
-
对spring cache 进行使用fastjson配置系列化处理,解决了redis中显示的乱码,redis中可以正常显示内容了。
配置了spring cache 异常处理
配置了spring cache的KeyGenerator自定义生成
-
对Redis系列化,使用了fastjson进行系列化的配置,自定义了redis的 key-value系列化。
package space.goldchen.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.Cache;
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.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.Map;
/**
* Redis系列化等配置类:
* 主要有对spring cache 的配置 和 使用fastjson系列化的配置
* @author Goldchen
* @create 2022-08-25 14:47
*/
@Slf4j
@Configuration
@EnableCaching
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig extends CachingConfigurerSupport {
/**
* 设置spring cache: 设置@Cacheable 序列化方式 和 设置 spring cache 数据默认过期时间,默认2小时;解决了redis中显示乱码的问题;
*/
@Bean
public RedisCacheConfiguration redisCacheConfiguration() {
// 使用 fastjson替换默认系列化
FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
configuration = configuration.serializeValuesWith(
// 设置使用 fastjson系列化
RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer))
//设置为2小时redis值过期
.entryTtl(Duration.ofHours(2));
return configuration;
}
/**
* 设置spring cache: 注解@cacheable 重写 redis cache异常,只打印日志,当redis失败时,会重新请求应用
*
* @return
*/
@Bean
@Override
public CacheErrorHandler errorHandler() {
// 异常处理,当redis异常时,只打印日志,但是程序正常走
log.info("初始化 -> [{}]", "Redis CacheErrorHandler");
return new CacheErrorHandler() {
@Override
public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
log.error("Redis occur handleCacheGetError:key -> [{}]", key, exception);
}
@Override
public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
log.error("Redis occur handleCachePutError:key -> [{}]", key, value, exception);
}
@Override
public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
log.error("Redis occur handleCacheEvictError:key ->[{}]", key, exception);
}
@Override
public void handleCacheClearError(RuntimeException exception, Cache cache) {
log.error("Redis occur handleCacheClearError:", exception);
}
};
}
/**
* 设置spring cache: 注解 @Cacheable 的 redisKey 的自定义生成器,缓存时,会用这个生成key
* 用法 :@Cacheable(cacheNames = "redisUser_cache", keyGenerator = "keyGenerator")
*/
@Bean
@Override
public KeyGenerator keyGenerator() {
return (target, method, params) -> {
Map<String, Object> container = new HashMap<>(8);
Class<?> targetClass = target.getClass();
// 包名称
container.put("package", targetClass.getPackage());
// 类地址
container.put("class", targetClass.toGenericString());
// 方法名
container.put("methodName", method.getName());
// 参数列表
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);
};
}
/**
* 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);
// value值的序列化采用fastJsonRedisSerializer
template.setValueSerializer(fastJsonRedisSerializer);
template.setHashValueSerializer(fastJsonRedisSerializer);
// key的序列化,使用StringRedisSerializer
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
// fastjson 升级到 1.2.83 后需要指定序列化白名单.不然会报错:
// com.alibaba.fastjson.JSONException: autoType is not support. space.goldchen.pojo.RedisUser
ParserConfig.getGlobalInstance().addAccept("space.goldchen");
return template;
}
/**
* 重写序列化器: fastjson对redis中value的序列化
*/
class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
private final Class<T> clazz;
FastJsonRedisSerializer(Class<T> clazz) {
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) {
if (t == null) {
return new byte[0];
}
// 加上SerializerFeature.WriteClassName后,生成的json中会有对象的标记:
// 如:@class:"space.goldchen.pojo.RedisUser"
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);
}
}
/**
* 重写序列化器: fastjson对redis中key的序列化
*/
class StringRedisSerializer implements RedisSerializer<Object> {
private final Charset charset;
private StringRedisSerializer(Charset charset) {
Assert.notNull(charset, "Charset must not be null!");
this.charset = charset;
}
StringRedisSerializer() {
this(StandardCharsets.UTF_8);
}
@Override
public byte[] serialize(Object o) {
String string = JSON.toJSONString(o);
if (org.apache.commons.lang3.StringUtils.isBlank(string)) {
return null;
}
string = string.replace("\"", "");
return string.getBytes(charset);
}
@Override
public Object deserialize(byte[] bytes) {
return (bytes == null ? null : new String(bytes, charset));
}
}
}
四、测试
测试用的主体类
package space.goldchen.pojo;
import lombok.Data;
import org.apache.commons.lang3.builder.ToStringBuilder;
import java.lang.reflect.Field;
/**
* 测试redis的对象
* @author Goldchen
* @create 2022-09-03 16:02
*/
@Data
public class RedisUser {
private Integer id;
private String name;
private Integer age;
@Override
public String toString() {
ToStringBuilder builder = new ToStringBuilder(this);
Field[] fields = this.getClass().getDeclaredFields();
try {
for (Field f : fields) {
f.setAccessible(true);
builder.append(f.getName(), f.get(this)).append("\n");
}
} catch (Exception e) {
builder.append("toString builder encounter an error");
}
return builder.toString();
}
}
package space.goldchen.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.web.bind.annotation.*;
import space.goldchen.pojo.RedisUser;
/**
* cache 演示
* @author goldchen
* @create 2022-09-4 23:57
* 类上设置后,相当于每个方法设置
* @CacheConfig(cacheNames = "test_cache")
*/
@Slf4j
@RestController
@RequestMapping("/cache")
public class TestSpringCacheController {
/**
* 测试对象的缓存,redis是否正常
*/
@GetMapping("/getUser")
@Cacheable(cacheNames = "redisUser_cache", key = "#user.id")
public RedisUser getUser(@RequestBody RedisUser user){
RedisUser redisUser = new RedisUser();
redisUser.setId(user.getId());
redisUser.setAge(18);
redisUser.setName("空间1");
return redisUser;
}
/**
* 测试使用自定义Key生成
* 测试对象的缓存,redis是否正常,KeyGenerator
*/
@GetMapping("/getUser2")
@Cacheable(cacheNames = "redisUser_cache", keyGenerator = "keyGenerator")
public RedisUser getUser2(@RequestBody RedisUser user){
RedisUser redisUser = new RedisUser();
redisUser.setId(user.getId());
redisUser.setAge(18);
redisUser.setName("空间1");
return redisUser;
}
}
redis的配置完成,下一篇文章将进行redis的使用演示。