springboot2 - 自定义cache

功能

首先要说明一下,这不是教你怎么写一个缓存,而是让项目中的 @Cacheable、@CacheEvict 等注解变得可用。

概要

从编码方面考虑,找到 org.springframework.cache.Cache 接口,实现这个接口就搞定了(结尾附上样例代码)。

这个过程,有很多风险项,本文重点讨论一下这些问题。

基本原理——序列化和反序列化

缓存的实现,至少包含两个技术:序列化和反序列化,也就是把对象转成字节,再将字节转成对象。

计算机专业的同学,应该非常熟悉:java 中有 Serializable 接口可以用。

使用 java 自带的序列化功能,是个很好的选择,性能不会太差,也不需要去思考更多的问题。

JSON 等新工具的反序列化问题

有野心的程序员,可能会思考:能不能用一些更先进的序列化技术?比如:fastjson、jackson。

使用 fastjson 反序列化的时候,就会想到这个函数: JSON.parseObject(String json, Class clazz)。

实际操作就会发现问题,反序列化函数是 deserialize(byte[] bytes),没有 class 这个参数的。

(用 RedisSerializer 举例,因为代码更短,更容易说明问题,spring 的 cache 有相同的问题)

没有 class 参数,那要怎么序列化?这是第一个要思考的问题。

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.JSONWriter;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;

/**
 * Redis 使用 FastJson 序列化数据,满足所有数据类型的序列化
 *
 * @author Mr.css
 * @version 2020-01-02 11:24
 */
public class FastJsonRedisSerializer implements RedisSerializer<Object> {

    /**
     * 包含各类序列化配置
     */
    private final JSONWriter.Feature[] features;
    /**
     * 反序列化拦截器
     */
    private final JSONReader.AutoTypeBeforeHandler filter;


    /**
     * 默认序列化的时候,写入全类名
     *
     * @param names 允许自动转型的包
     */
    public FastJsonRedisSerializer(String... names) {
        this.features = new JSONWriter.Feature[]{JSONWriter.Feature.WriteClassName};
        this.filter = JSONReader.autoTypeFilter(names);
    }

    /**
     * 序列化
     *
     * @param obj 对象实体
     * @return 字节数组
     * @throws SerializationException -
     */
    @Override
    public byte[] serialize(Object obj) throws SerializationException {
        if (obj == null) {
            return new byte[0];
        } else {
            return JSON.toJSONBytes(obj, features);
        }
    }

    /**
     * 反序列化
     *
     * 不在白名单的对象,不会反序列化失败,而是返回 com.alibaba.fastjson2.JSONObject,
     *
     * @param bytes 字节数组
     * @return 对象实体
     * @throws SerializationException -
     */
    @Override
    public Object deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length == 0) {
            return null;
        } else {
            return JSON.parseObject(bytes, Object.class, filter);
        }
    }
}

反序列程序漏洞

如果你选用的是 fastjson,很快就能找到资料,它有个 JSON.parse(String json) 函数。

这个函数不需要 class 参数,可以解决我们前面提到的问题,但是与此同时,又引出了一个值得重视的问题:

JSON.parse(String json) 只有一个参数,json 能转换成什么对象,完全由 json 自身控制。

如果掌握了 json 的规律,就能伪造 json,控制程序创建出所需的对象,这是一个十分严重的漏洞。

比如说:你的缓存用的是 redis,redis 被黑客攻破了,他就可以继续攻击你的系统,伪造一段 json,你的程序读到 json 之后,就会 new 出他所需的对象。

针对这个问题,需要考虑一份白名单设计,确定哪些对象是允许反序列化的。

(fastjson 有对应的解决方案,这里不展开说明了)

自定义Cache

CacheConfig

配置注册到容器

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;

/**
 * @author Mr.css
 * @date 2019/12/23
 */
@Configuration
public class CacheConfig extends CachingConfigurerSupport {

  @Bean
  @Override
  public CacheManager cacheManager() {
    return new RedisCacheManager();
  }
}
CacheManager

缓存管理类

import org.springframework.cache.Cache;
import org.springframework.cache.support.AbstractCacheManager;

import java.util.ArrayList;
import java.util.Collection;

public class RedisCacheManager extends AbstractCacheManager {

  @Override
  protected Collection<? extends Cache> loadCaches() {
    return new ArrayList<>();
  }

  @Override
  protected Cache getMissingCache(String name) {
    return new RedisCache();
  }
}

Cache

缓存处理对象,这里展示的是伪代码,需要将 map 换成更有效的数据结构。

import cn.seaboot.admin.consts.SystemConst;
import cn.seaboot.common.core.Converter;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;

/**
 *
 */
public class RedisCache implements Cache {

  Map<Object, Object> map = new HashMap<>();

  /**
   * 简单直白,就是获取Cache的名字
   */
  @Override
  public String getName() {
    return SystemConst.CACHE_DEF;
  }

  /**
   * 获取底层的缓存实现对象
   */
  @Override
  public Object getNativeCache() {
    return SystemConst.CACHE_DEF;
  }

  /**
   * 根据键获取值,把值包装在ValueWrapper里面,如果有必要可以附加额外信息
   */
  @Override
  public ValueWrapper get(Object key) {
    System.out.println("ValueWrapper");
    return map.containsKey(key)?new SimpleValueWrapper(map.get(key)):null;
  }

  /**
   * 这个函数在spring 4以及以上才有效,很新的api,目前我还不知道如何触发此函数。
   */
  @Override
  public <T> T get(Object key, Class<T> aClass) {
    try {
      System.out.println("get(Object o, Class<T> aClass)");
      return map.containsKey(key)?Converter.convert(map.get(key), aClass):null;
    } catch (Exception e) {
      e.printStackTrace();
    }
    return null;
  }

  /**
   * 与sync属性有关
   */
  @Override
  public <T> T get(Object key, Callable<T> valueLoader) {
    try {
      System.out.println("get");
      return valueLoader.call();
    } catch (Exception e) {
      e.printStackTrace();
    }
    return null;
  }

  /**
   * 存放键值对
   */
  @Override
  public void put(Object key, Object value) {
    System.out.println("put(Object key, Object value)");
    map.put(key, value);
  }

  /**
   * 如果键对应的值不存在,则添加键值对
   */
  @Override
  public ValueWrapper putIfAbsent(Object key, Object value) {
    System.out.println("putIfAbsent");
    map.put(key, value);
    return new SimpleValueWrapper(value);
  }

  /**
   * 移除键对应键值对
   */
  @Override
  public void evict(Object key) {
    System.out.println("evict");
    map.remove(key);
  }

  /**
   * 清空缓存
   */
  @Override
  public void clear() {
    System.out.println("clear");
    map.clear();
  }
}

posted on   疯狂的妞妞  阅读(1292)  评论(0编辑  收藏  举报

(评论功能已被禁用)
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

导航

统计

点击右上角即可分享
微信分享提示