需求描述

解决方案

Optional 支持

由于 Optional 并不支持序列化,因此无法直接将其作为方法返回值、托管给 JetCache。必须对其进行拓展,添加自定义的编码器以及解码器。

环境

依赖于 com.alicp.jetcache:jetcache-core:2.5.16。

配置

只要将自定义的编码器注册为 bean 即可。

1
2
3
4
jetcache:
remote:
default:
valueEncoder: bean:jetValueEncoder

编码器实现

  1. 在序列化之前,将 Optional 中的值取出,并在打上自定义的标记(IDENTITY_NUMBER 的值),对于非 Optional 类型的数据,则沿用默认的编码器器(JavaValueEncoder);
  2. 编码器需要注册成 spring bean,启用自定义标记之后,解码器则通过读取自定义标记进行匹配;
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
@Component
public class JetValueEncoder extends JavaValueEncoder {

private static final int INIT_BUF_SIZE = 256;
private static final ThreadLocal<WeakReference<ByteArrayOutputStream>> THREAD_LOCAL =
ThreadLocal.withInitial(() -> new WeakReference<>(new ByteArrayOutputStream(INIT_BUF_SIZE)));
protected static int IDENTITY_NUMBER = 0x4A953A84;


public JetValueEncoder() {
this(true);
}


public JetValueEncoder(boolean useIdentityNumber) {
super(useIdentityNumber);
registerDecoder();
}

// 注册解码器,此处为匿名内部类,直接实例并注册到 DecoderMap,
// 键值为JetValueEncoder.IDENTITY_NUMBER,自定义标记
// 实现逻辑,在反序列化之后,将标记匹配的值封装到 Optional 中
// 标记不匹配的,则被其他解码器处理,不用考虑
protected void registerDecoder() {
SpringJavaValueDecoder decoder = new SpringJavaValueDecoder(true) {
@Override
@SuppressWarnings("unchecked")
public Object doApply(byte[] buffer) throws Exception {
Object obj = super.doApply(buffer);
if (obj instanceof CacheValueHolder) {
CacheValueHolder<Object> valueHolder = (CacheValueHolder<Object>) obj;
valueHolder.setValue(Optional.ofNullable(valueHolder.getValue()));
}
return obj;
}
};
DecoderMap.register(JetValueEncoder.IDENTITY_NUMBER, decoder);

}

@Override
@SuppressWarnings("unchecked")
public byte[] apply(Object value) {
if (value instanceof CacheValueHolder) {
CacheValueHolder<Object> valueHolder = (CacheValueHolder<Object>) value;
Object holderValue = ((CacheValueHolder<?>) value).getValue();
if (holderValue instanceof Optional) {
Optional<?> valeOpt = (Optional<?>) holderValue;
valeOpt.ifPresent(valueHolder::setValue);
return doApply(value);
}
}
return super.apply(value);
}

/**
* 从父类中拷贝,由于父类中 IDENTITY_NUMBER 是静态变量,<br/>
* 且没有提供可重构 getter,所以不得不进行代码搬运法。╮(╯▽╰)╭<br/>
* 父类中应该使用 getIdentityNumber() 方法获取 IDENTITY_NUMBER 的值,<br/>
*
* @see JavaValueEncoder#apply(Object)
*/
private byte[] doApply(Object value) {
try {
WeakReference<ByteArrayOutputStream> ref = THREAD_LOCAL.get();
ByteArrayOutputStream bos = ref.get();
if (bos == null) {
bos = new ByteArrayOutputStream(INIT_BUF_SIZE);
THREAD_LOCAL.set(new WeakReference<>(bos));
}

try {
if (useIdentityNumber) {
bos.write((IDENTITY_NUMBER >> 24) & 0xFF);
bos.write((IDENTITY_NUMBER >> 16) & 0xFF);
bos.write((IDENTITY_NUMBER >> 8) & 0xFF);
bos.write(IDENTITY_NUMBER & 0xFF);
}
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(value);
oos.flush();
return bos.toByteArray();
} finally {
bos.reset();
}
} catch (IOException e) {
throw new CacheEncodeException("Java Encode error. " + "msg=" + e.getMessage(), e);
}
}
}

分析

从源码中 SpringConfigProvider#parseValueEncoder 可以找到编码器的加载入口,当 valueEncoder 属性匹配到 “bean:”前缀的编码器时,则启用该 bean。

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 1、ExternalCacheAutoInit#parseGeneralConfig
// 自动初始化配置
protected void parseGeneralConfig(CacheBuilder builder, ConfigTree ct) {
super.parseGeneralConfig(builder, ct);
ExternalCacheBuilder ecb = (ExternalCacheBuilder) builder;
ecb.setKeyPrefix(ct.getProperty("keyPrefix"));
// 设置编码器,指定了配置中的 key:valueEncoder
ecb.setValueEncoder(configProvider.parseValueEncoder(ct.getProperty("valueEncoder", CacheConsts.DEFAULT_SERIAL_POLICY)));
ecb.setValueDecoder(configProvider.parseValueDecoder(ct.getProperty("valueDecoder", CacheConsts.DEFAULT_SERIAL_POLICY)));
}

// 2、SpringConfigProvider#parseValueEncoder
// spring 环境中的配置规则,先尝试搜索是否存在注册为 spring bean 的编码器
public Function<Object, byte[]> parseValueEncoder(String valueEncoder) {
// 进入 bean 加载方法
String beanName = parseBeanName(valueEncoder);
// bean 名称存在时注册 spring 托管的编码器
if (beanName == null) {
return super.parseValueEncoder(valueEncoder);
} else {
Object bean = applicationContext.getBean(beanName);
if (bean instanceof Function) {
return (Function<Object, byte[]>) bean;
} else {
return ((SerialPolicy) bean).encoder();
}
}
}

// 3、SpringConfigProvider#parseBeanName
// bean 名称的解析,需要在配置文件中指定 valueEncode 属性:'bean:anyName'
private String parseBeanName(String str) {
// 通过 “bean:” 前缀匹配 bean 名称
final String beanPrefix = "bean:";
int len = beanPrefix.length();
if (str != null && str.startsWith(beanPrefix) && str.length() > len) {
return str.substring(len);
} else {
return null;
}
}

// 4、AbstractValueDecoder#apply
// 解码器入口,对于 useIdentityNumber = true,及开启自定义标记的序列化数据,
// 需要在 DecoderMap 中通过 identityNumber 进行匹配
public Object apply(byte[] buffer) {
try {
if (useIdentityNumber) {
DecoderMap.registerBuildInDecoder();
int identityNumber = parseHeader(buffer);
AbstractValueDecoder decoder = DecoderMap.getDecoder(identityNumber);
Objects.requireNonNull(decoder, "no decoder for identity number:" + identityNumber);
return decoder.doApply(buffer);
} else {
return doApply(buffer);
}
} catch (Exception e) {
throw new CacheEncodeException("decode error", e);
}
}

评论