使用SpringCache实现缓存,自定义序列化方式,解决序列化bug
在学习SpringCache之前,很多同学会使用硬编码的方式去给代码设置缓存,缓存操作和业务逻辑之间的代码耦合度高,对业务逻辑有较强的侵入性。
当然我们也可以结合Spring的AOP和自定义注解去实现缓存。
那么在Spring中,我们可以直接使用SpringCache来完成我们在项目中数据的缓存操作。
缓存声明
名称 | 解释 |
---|
@Cacheable | 根据方法的请求参数对其结果进行缓存,下次同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法 | @CachePut | 根据方法的请求参数对其结果进行缓存,它每次都会触发真实方法的调用 | @CacheEvict | 根据一定的条件删除缓存 | @Caching | 组合多个缓存注解 | @CacheConfig | 类级别共享缓存相关的公共配置 |
相关demo
Controller
@RequestMapping(value = "/user")
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping(value = "/userInfo")
public JSONObject userInfo (Long id) {
JSONObject jsonRes = new JSONObject();
User user = userService.userInfo(id);
jsonRes.put("userInfo", user);
return jsonRes;
}
@PostMapping(value = "/addUser")
public JSONObject addUser (@RequestBody User user) {
JSONObject jsonRes = new JSONObject();
User userRes = userService.addUser(user);
jsonRes.put("addUser", userRes);
return jsonRes;
}
@PostMapping(value = "/editUser")
public JSONObject editUser (@RequestBody User user) {
JSONObject jsonRes = new JSONObject();
User userRes = userService.editUser(user);
jsonRes.put("editUser", userRes);
return jsonRes;
}
@GetMapping(value = "/delUser")
public JSONObject delUser (Long id) {
JSONObject jsonRes = new JSONObject();
userService.delUser(id);
jsonRes.put("delUser", id);
return jsonRes;
}
}
Service
@Service
public class UserService {
@Cacheable(value = "user_cache", key = "#id")
public User userInfo (Long id) {
User user = new User();
user.setId(id);
user.setName("小黑");
user.setSex("男");
user.setAge(18);
System.out.println("用户详情:" + JSON.toJSONString(user));
return user;
}
public User addUser (User user) {
System.out.println("添加用户:" + JSON.toJSONString(user));
return user;
}
@CachePut(value = "user_cache", key = "#user.id")
public User editUser (User user) {
System.out.println("修改用户:" + JSON.toJSONString(user));
return user;
}
@CacheEvict(value = "user_cache", key = "#id")
public void delUser (Long id) {
System.out.println("删除用户:" + id);
}
}
Configuration
spring.cache.type=redis
spring.redis.host=127.0.0.1
public class MyFastJsonRedisSerializer<T> implements RedisSerializer<T> {
private final Type type;
public MyFastJsonRedisSerializer(Type type) {
this.type = type;
}
@Override
public byte[] serialize(T t) throws SerializationException {
return JSON.toJSONBytes(t);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException {
return JSON.parseObject(bytes,type);
}
}
package com.wazk.demo.conf;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
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.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.ReflectionUtils;
import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@Configuration
public class CacheConfig{
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Autowired
private ApplicationContext applicationContext;
@Bean
public CacheManager cacheManager() {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(120L))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new MyFastJsonRedisSerializer<>(Object.class)));
return RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config).withInitialCacheConfigurations(buildInitCaches()).build();
}
private Map<String, RedisCacheConfiguration> buildInitCaches() {
HashMap<String, RedisCacheConfiguration> cacheConfigMap = new HashMap<>();
Arrays.stream(applicationContext.getBeanNamesForType(Object.class))
.map(applicationContext::getType).filter(Objects::nonNull)
.forEach(clazz -> {
ReflectionUtils.doWithMethods(clazz, method -> {
ReflectionUtils.makeAccessible(method);
Cacheable cacheable = AnnotationUtils.findAnnotation(method, Cacheable.class);
if (Objects.nonNull(cacheable)) {
for (String cache : cacheable.cacheNames()) {
RedisSerializationContext.SerializationPair<Object> sp = RedisSerializationContext.SerializationPair
.fromSerializer(new MyFastJsonRedisSerializer<>(method.getGenericReturnType()));
cacheConfigMap.put(cache, RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(sp));
}
}
});
}
);
return cacheConfigMap;
}
}
序列化问题处理
FastJsonRedisSerializer 和Jackson2JsonRedisSerializer
这两个序列化器在进行非集合的数据缓存后,调用上述代码中获取数据时,会报类型转换异常,但如果是集合数据的时候却是正常的,且是常规的json字符串
- FastJson报
java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to com.cache.demo.SimpleBook - Jackson报
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.cache.demo.SimpleBook
GenericFastJsonRedisSerializer 和GenericJackson2JsonRedisSerializer
这两个序列化器在序列化后的数据中携带了类型的信息@class ,同时为非json格式字符串,当json的工具不一样时会导致解析失败
综合上述的问题,所以改进的思路是自定义一个序列化器,扫描注解上的返回类型进行一一对应的解析
故此上述代码最后进行了自定义序列化方式的操作,也就是用咱们最普通的序列化方式,转为JSON字符串
|