springboot2 - 自定义cache
功能
首先要说明一下,这不是教你怎么写一个缓存,而是让项目中的 @Cacheable、@CacheEvict 等注解变得可用。
概要
从编码方面考虑,找到 org.springframework.cache.Cache 接口,实现这个接口就搞定了(结尾附上样例代码)。
这个过程,有很多风险项,本文重点讨论一下这些问题。
基本原理——序列化和反序列化
缓存的实现,至少包含两个技术:序列化和反序列化,也就是把对象转成字节,再将字节转成对象。
计算机专业的同学,应该非常熟悉:java 中有 Serializable 接口可以用。
使用 java 自带的序列化功能,是个很好的选择,性能不会太差,也不需要去思考更多的问题。
JSON 等新工具的反序列化问题
有野心的程序员,可能会思考:能不能用一些更先进的序列化技术?比如:fastjson、jackson。
使用 fastjson 反序列化的时候,就会想到这个函数: JSON.parseObject(String json, Class
实际操作就会发现问题,反序列化函数是 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();
}
}