本方法在几年前均已实现,这里将其重新整理一下,以作备忘
在典型的应用场景中,经常会有这样的需求,即当业务返回单个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