問題
系統(tǒng)采用spring data+mongodb driver方式進(jìn)行對(duì)象的保存加勤,以及進(jìn)行相關(guān)的序列化以及反序列化火本。
由于在業(yè)務(wù)系統(tǒng)設(shè)計(jì)過程中主籍,需要根據(jù)業(yè)務(wù)不同保存不同的子類讨彼,然后展示時(shí)也要相應(yīng)的展示鲤嫡。這就要求mongo在保存時(shí)保存完整的數(shù)據(jù)送挑,并且反序列化時(shí)也要能映射到正常的類。
方案
fastjson的JSONType_seeAlso_cn
在fastjson-1.2.11版本中暖眼,@JSONType支持seeAlso配置惕耕,類似于JAXB中的XmlSeeAlso。
從用法上看基本能滿足需求诫肠,相比jackson方案比較簡(jiǎn)潔司澎,也支持隱藏類的展示。不過在序列化類的時(shí)候需要加上@type
關(guān)鍵字注明類名或者別名栋豫。如果要改變關(guān)鍵字挤安,可以使用setDefaultTypeKey
來改變。
JavaBean Config
@JSONType(seeAlso={Dog.class, Cat.class})
public static class Animal {
}
@JSONType(typeName = "dog")
public static class Dog extends Animal {
public String dogName;
}
@JSONType(typeName = "cat")
public static class Cat extends Animal {
public String catName;
}
Usage
Dog dog = new Dog();
dog.dogName = "dog1001";
String text = JSON.toJSONString(dog, SerializerFeature.WriteClassName);
Assert.assertEquals("{\"@type\":\"dog\",\"dogName\":\"dog1001\"}", text);
jackson的XmlSeeAlso
Jackson可以輕松的將Java對(duì)象轉(zhuǎn)換成json對(duì)象和xml文檔丧鸯,同樣也可以將json蛤铜、xml轉(zhuǎn)換成Java對(duì)象。
可以通過配置多態(tài)類型處理,配置相對(duì)復(fù)雜一點(diǎn)围肥,不過比較靈活剿干。支持使用多種識(shí)別碼以及識(shí)別碼可以選擇多種方式,包括兄弟屬性穆刻,擴(kuò)展屬性以及已存在屬性等置尔,并且也支持隱藏類名,以及自定義每個(gè)類的識(shí)別碼氢伟。
JavaBean Config
public class Car extends Vehicle {
private int seatingCapacity;
private double topSpeed;
public Car(String make, String model, int seatingCapacity, double topSpeed) {
super(make, model);
this.seatingCapacity = seatingCapacity;
this.topSpeed = topSpeed;
}
// no-arg constructor, getters and setters
}
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
@JsonSubTypes({
@Type(value = Car.class, name = "car"))
public abstract class Vehicle {
// fields, constructors, getters and setters
}
Usage
- use:定義使用哪一種類型識(shí)別碼榜轿,它有下面幾個(gè)可選值:
-
JsonTypeInfo.Id.CLASS
:使用完全限定類名做識(shí)別 -
JsonTypeInfo.Id.MINIMAL_CLASS
:若基類和子類在同一包類,使用類名(忽略包名)作為識(shí)別碼 -
JsonTypeInfo.Id.NAME
:一個(gè)合乎邏輯的指定名稱 -
JsonTypeInfo.Id.CUSTOM
:自定義識(shí)別碼腐芍,由@JsonTypeIdResolver對(duì)應(yīng)差导,稍后解釋 -
JsonTypeInfo.Id.NONE
:不使用識(shí)別碼
-
- include(可選):指定識(shí)別碼是如何被包含進(jìn)去的,它有下面幾個(gè)可選值:
-
JsonTypeInfo.As.PROPERTY
:作為數(shù)據(jù)的兄弟屬性 -
JsonTypeInfo.As.EXISTING_PROPERTY
:作為POJO中已經(jīng)存在的屬性 -
JsonTypeInfo.As.EXTERNAL_PROPERTY
:作為擴(kuò)展屬性 -
JsonTypeInfo.As.WRAPPER_OBJECT
:作為一個(gè)包裝的對(duì)象 -
JsonTypeInfo.As.WRAPPER_ARRAY
:作為一個(gè)包裝的數(shù)組
property(可選):制定識(shí)別碼的屬性名稱
此屬性只有當(dāng)use為JsonTypeInfo.Id.CLASS(若不指定property則默認(rèn)為@class)猪勇、JsonTypeInfo.Id.MINIMAL_CLASS(若不指定property則默認(rèn)為@c)设褐、JsonTypeInfo.Id.NAME(若不指定property默認(rèn)為@type),include為JsonTypeInfo.As.PROPERTY泣刹、JsonTypeInfo.As.EXISTING_PROPERTY助析、JsonTypeInfo.As.EXTERNAL_PROPERTY時(shí)才有效
-
- defaultImpl(可選):如果類型識(shí)別碼不存在或者無效,可以使用該屬性來制定反序列化時(shí)使用的默認(rèn)類型
- visible(可選椅您,默認(rèn)為false):是否可見
屬性定義了類型標(biāo)識(shí)符的值是否會(huì)通過JSON流成為反序列化器的一部分外冀,默認(rèn)為fale,也就是說,jackson會(huì)從JSON內(nèi)容中處理和刪除類型標(biāo)識(shí)符再傳遞給JsonDeserializer。
結(jié)果
通過對(duì)這兩種方法的調(diào)研掀泳,都能滿足在序列化以及反序列化時(shí)實(shí)現(xiàn)子類多態(tài)雪隧,并且不會(huì)有泄漏類名的風(fēng)險(xiǎn)。
發(fā)現(xiàn)在mongodb保存類時(shí)正常员舵,但是當(dāng)查詢類的內(nèi)容時(shí)脑沿,mongodb會(huì)直接使用定義的父類進(jìn)行反序列化。后面通過對(duì)mongodb存儲(chǔ)以及查詢?cè)淼恼{(diào)研發(fā)現(xiàn)马僻,mongodb在存儲(chǔ)時(shí)并不是采用json進(jìn)行保存庄拇,而是采用了BSON這種格式。也就是拓展了JSON的相關(guān)信息韭邓。
JSON can only represent a subset of the types supported by BSON. To preserve type information, MongoDB adds the following extensions to the JSON format:
* Strict mode. Strict mode representations of BSON types conform to the JSON RFC. Any JSON parser can parse these strict mode representations as key/value pairs; however, only the MongoDB internal JSON parser recognizes the type information conveyed by the format.
* mongo Shell mode. The MongoDB internal JSON parser and the mongo shell can parse this mode.
而mongodb的driver在解析保存的信息時(shí)措近,也是使用的BSON的相關(guān)解析方案。于是想是否可以修改相關(guān)解析器來實(shí)現(xiàn)女淑。發(fā)現(xiàn)mongodb在進(jìn)行序列化以及凡序列化時(shí)使用MappingMongoConverter的行為進(jìn)行定義瞭郑。于是修改相關(guān)定義
@Override
public MappingMongoConverter mappingMongoConverter() throws Exception {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory());
ObjectMapper mapper = new ObjectMapper()
.configure(FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerModule(new SimpleModule() {
{
addDeserializer(ObjectId.class, new JsonDeserializer<ObjectId>() {
@Override
public ObjectId deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
TreeNode oid = p.readValueAsTree().get("$oid");
String string = oid.toString().replaceAll("\"", "");
return new ObjectId(string);
}
});
}
});
MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mongoMappingContext()) {
@Override
public <S> S read(Class<S> clazz, DBObject dbo) {
String string = JSON.serialize(dbo);
try {
return mapper.readValue(string, clazz);
} catch (IOException e) {
throw new RuntimeException(string, e);
}
}
@Override
public void write(Object obj, DBObject dbo) {
String string = null;
try {
string = mapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
throw new RuntimeException(string, e);
}
dbo.putAll((DBObject) JSON.parse(string));
}
};
return converter;
}
然后修改driver相關(guān)的Converter,但是測(cè)試發(fā)現(xiàn)鸭你,獲取到的bson數(shù)據(jù)會(huì)帶有額外的元數(shù)據(jù)凰浮,導(dǎo)致類無法正常反序列化我抠。后面通過查看文檔發(fā)現(xiàn),需要增加JsonWriterSettings
來對(duì)元數(shù)據(jù)進(jìn)行處理袜茧。
JsonWriterSettings settings = JsonWriterSettings.builder()
.int64Converter((value, writer) -> writer.writeNumber(value.toString()))
.objectIdConverter((value,write)->write.writeString(value.toString()))
.dateTimeConverter((value, writer) -> writer.writeString(value.toString())
.build();
然后進(jìn)行相關(guān)測(cè)試,沒發(fā)現(xiàn)什么問題瓣窄。但是后面排查發(fā)現(xiàn)笛厦,系統(tǒng)還采用了mongoGridFSd的方案,Converter
也是采用的此方案俺夕,但是bson在轉(zhuǎn)換時(shí)裳凸,對(duì)于binary對(duì)象是沒有數(shù)據(jù)展示的,于是修改相關(guān)Converter
為原來的方案劝贸。至此問題解決姨谷。