本方法在几年前均已实现,这里将其重新整理一下,以作备忘
在典型的应用场景中,经常会有这样的需求,即当业务返回单个pojo对象时,需要临时追加几个属性在这个对象中并一起返回至前端。如下例子所示:
public class Abc {
String username;
}
此对象仅有1个属性,但返回至前端时,需要返回类似如下的数据结构:
{
"username": "张三",
"sex": "MALE",
"age": 20
}
有一些作法通过再定义新的类,如 AbcVO 通过继承原类并添加新字段来支持;或者不再使用对象,则是直接使用map代替. 前者会造成类爆炸,后者会造成API语义不清晰.
本文描述了一种标准的 Attr 接口结构,并通过json序列化器(如jackson或fastjson)支持的注解,通过简单的default 方法定义,完成属性添加。并同时支持序列化和反序列化的应用场景.
这里采用接口,以及default 方法设计,原有的类通过一个简单的额外implements,即可支持此特性,无需其它的改造,也不需要额外实现接口方法,即可使用此特性.
对应于典型的setter和getter设计,此接口需要有set,get以及与map语义相类似的定义,以支持json化处理,典型的定义如下所示(类及方法名仅供参考):
public interface Attr {
default Map<String, Object> all() {
//实现
}
default void set(String k, Object v) {
//实现
}
default Object get(String k) {
//实现
}
}
针对之前的定义,那么相应的业务代码应如下使用
val v = new Abc();
v.setUsername(); //正常字段处理
v.set("sex", "MALE"); //扩展字段处理
val s = toJson(v); //序列化
//相应数据为 {username:"xxx", sex:"MALE"}
val v2 = parseJson(s); //反序列化
String extV1 = v2.get("sex");
//这里反序列化同样能拿到 序列化之前的 MALE 值
上面接口的定义中,还需要考虑各类json框架的支持,其中 jackson 中可以使用 @JsonAnySetter 和 @JsonAnyGetter 来处理。而在fastjson 中,可以使用 JSONField(unwrapped=true) 来支持。因此,完整方法定义如下
public interface Attr {
@JsonAnyGetter
@JSONField(name = "_any", unwrapped = true, serialize = true, deserialize = false)
default Map<String, Object> all() {
//实现
}
@JsonAnySetter
@JSONField(name = "_any", unwrapped = true, serialize = false, deserialize = false)
default void set(String k, Object v) {
//实现
}
}
其中fastjson的注解定义可参考此文 https://github.com/alibaba/fastjson/wiki/JSONField_unwrapped_cn
接下来是 default 方法的实现,由于接口方法内不允许有字段,因此相应的实现均转换为外部静态方法调用,可以以 this 为key, 额外的map 为 value 来模拟相应的实现. 比如 set 的实现如下所示
default void set(String k, Object v) {
val map = Utils.computeIfAbsent(this, "_v", Maps::newHashMap);
map.put(name, value);
}
public class Utils {
public static final Cache<Object, Map<String, Object>> cache; //全局 static 大cache map
public static <A, H> A computeIfAbsent(H holder, String attrName, Supplier<A> absentSupplier) {
Map<String, Object> map = cache.get(holder, t -> Maps.newHashMap());
return (A) map.computeIfAbsent(attrName, noused -> absentSupplier.get());
}
}
以上代码仅供参考,有一定的优化空间.
后记
经过以上的处理,通过 全局cache, weak引用, json注解处理,可以完成一个基本的可临时扩展属性类的设计。在实际的使用过程中,还需要考虑 cache 中对象创建和回收压力。在某些场景下,还可能出现循环引用导致 cache 中数据不能回收的情况. 当然,也有一些通过 extends 默认实现类来规避 cache的实现. 这里描述相应的思路.
转载请标明出处:i flym
本文地址:https://www.iflym.com/index.php/code/202103220001.html