使用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;

/**
 * @class: CacheConfig
 * @description: TODO
 * @author: wazk
 * @version: 1.0
 * @date: 2022/4/17 5:42 下午
 */
@Configuration
public class CacheConfig{

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;
    @Autowired
    private ApplicationContext applicationContext;

    // SpringCache缓存的序列化处理
    @Bean
    public CacheManager cacheManager() {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(120L)) //缓存20秒钟
                .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;
    }

}

序列化问题处理

FastJsonRedisSerializerJackson2JsonRedisSerializer

这两个序列化器在进行非集合的数据缓存后,调用上述代码中获取数据时,会报类型转换异常,但如果是集合数据的时候却是正常的,且是常规的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

GenericFastJsonRedisSerializerGenericJackson2JsonRedisSerializer

这两个序列化器在序列化后的数据中携带了类型的信息@class,同时为非json格式字符串,当json的工具不一样时会导致解析失败

综合上述的问题,所以改进的思路是自定义一个序列化器,扫描注解上的返回类型进行一一对应的解析

故此上述代码最后进行了自定义序列化方式的操作,也就是用咱们最普通的序列化方式,转为JSON字符串

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐