Java 高級特性之使用反射實(shí)現(xiàn)萬能序列化
很多時(shí)候我們需要將一個(gè)類的實(shí)例變成二進(jìn)制數(shù)據(jù)存儲(chǔ)或是通過網(wǎng)絡(luò)發(fā)送,這個(gè)過程叫序列化。如果將二進(jìn)制數(shù)據(jù)解析成位于內(nèi)存中的類實(shí)例或是相關(guān)數(shù)據(jù)結(jié)構(gòu),那叫反序列化。所有的序列化算法都遵循一定的套路,例如:
- class A {
- public int a = 1;
- public int b = 2;
- protected B b = new B();
- private float c = 3.0;
- }
如果要序列化類A的實(shí)例,那么通常需要將變量a,b的數(shù)值對應(yīng)的二進(jìn)制數(shù)寫入,然后獲得類B實(shí)例序列化后的二進(jìn)制數(shù)據(jù),最后將變量c的數(shù)值的二進(jìn)制數(shù)據(jù),這里可以體會(huì)到,序列化其實(shí)有一種遞歸的性質(zhì),在序列化過程中如果遇到的是基礎(chǔ)類型,那么可以直接獲取其對應(yīng)的二進(jìn)制數(shù)據(jù),如果遇到類實(shí)例,那么需要先序列化它,取得對應(yīng)二進(jìn)制數(shù)據(jù)。
而序列化過程中需要你了解對應(yīng)類的定義,但如果我們不知道要序列化的對象,例如我們看不到類A的定義,我們只拿到了A對應(yīng)的一個(gè)實(shí)例對象,那此時(shí)怎么序列化呢。這就需要用到j(luò)ava語言的反射特性,java編譯器在編譯類A時(shí),不僅僅將它為它的各個(gè)字段分配了內(nèi)存,而且還為類A的相關(guān)信息進(jìn)行了設(shè)置和存儲(chǔ),例如A里面有多少字段,字段的類型是int, float, stirng,還是特定類對象,這些信息都一并設(shè)置并存儲(chǔ)了起來,只要我們使用java反射提供的API就能獲得這些信息,從而就能對任意類實(shí)現(xiàn)序列化。
因此序列化的萬能套路是:
1,獲得要序列化的類實(shí)例;
2,獲得類中各個(gè)字段的屬性,類型等相關(guān)信息。
3,如果字段屬于基礎(chǔ)數(shù)據(jù),那么獲得其數(shù)值的二進(jìn)制數(shù)據(jù)。
4,如果對應(yīng)字段是一個(gè)類實(shí)例,那么先遞歸的序列化該實(shí)例
根據(jù)以上步驟,當(dāng)我們需要序列化任意一個(gè)類實(shí)例時(shí),首先通過getClass獲得其對應(yīng)的Class類實(shí)例,然后調(diào)用getDeclaredFields()接口獲得該實(shí)例所有的字段,其中包括public,protected,private,或者調(diào)用getFields()獲得類實(shí)例聲明或繼承的公有字段。在序列化中,我們不能忘了序列化當(dāng)前類實(shí)例的父類,因此可以調(diào)用getSuperClass()來獲得當(dāng)前實(shí)例的父類,這個(gè)過程會(huì)不斷進(jìn)行直到抵達(dá)根類為止。
每個(gè)字段都會(huì)對應(yīng)一個(gè)元類叫Field,通過該類相關(guān)接口能獲得字段的值。獲取字段的數(shù)據(jù)首先需要確定字段的類型,如果是Boolean類型,那么可以調(diào)用Field類的getBoolean接口獲得數(shù)據(jù),如果是int類型,那么可以通過getInteger()接口獲得數(shù)據(jù),如果字段是類對象,那么就得遞歸的去獲得其二進(jìn)制數(shù)據(jù),如果字段是基礎(chǔ)類型,那么通過調(diào)用其getString()就能獲得其數(shù)值的字符串形式。
在獲取字段類型前,我們還需要知道字段的修飾屬性,例如是public還是private,是不是static等,這些屬性通過Field類的接口getModifier()獲得,調(diào)用它會(huì)返回一個(gè)整形值,該值在相關(guān)比特位上設(shè)置1或0來表示修飾屬性。在java語法中共有11種修飾屬性,因此有11個(gè)比特位來對應(yīng),但我們不需要分析哪個(gè)比特位設(shè)置為1來獲取字段屬性,java反射提供了一個(gè)特定類Modifier,通過getModifier返回的數(shù)值可以輸入Modifier類的isPublic, isPrivate等接口來查詢字段對應(yīng)的修飾屬性。
在序列化時(shí),我們要忽略掉static屬性的字段,因此他們是寫死的,因此通過Modifier.isStatic(field.getModifier())所得結(jié)果就能進(jìn)行字段的static屬性判斷。總所周知,對于protected 或是private類型的字段,外部是不能直接讀取的,但是序列化必須要能讀取這類字段的值,要不然序列化就無法進(jìn)行,F(xiàn)ield類提供了setAccessible(true)來打破這個(gè)限制。
此外還需要考慮的一個(gè)因素是,如果字段是數(shù)組類型的情況。java反射提供了元類Array來應(yīng)對,假設(shè)實(shí)例對象obj是一個(gè)數(shù)組,那么Array.getLength(obj)就能獲得數(shù)組的長度,Array.get(obj, i)就能獲得第i個(gè)元素對象。
最后我們需要考慮序列化后的文件格式,我們使用xml格式來存儲(chǔ)序列化的結(jié)果,例如在上面例子中,字段a在序列化后對應(yīng)為”\1\“,具體的情況我們在后續(xù)代碼中慢慢來觀察。
首先我們使用IntelliJ 創(chuàng)建一個(gè)maven項(xiàng)目,由于我們需要將數(shù)據(jù)序列化成XML文件,因此需要使用JDOM接口,于是在pom.xml中添加如下依賴:
- <!-- https://mvnrepository.com/artifact/org.jdom/jdom -->
- <dependency>
- <groupId>org.jdom</groupId>
- <artifactId>jdom</artifactId>
- <version>2.0.2</version>
- </dependency>
然后點(diǎn)擊一下Maven命令面板的下載按鈕下載jdom包。然后我們創(chuàng)建一個(gè)實(shí)現(xiàn)文件叫ReflectionSerilization,然后先完成一些骨架基礎(chǔ):
- import org.jdom2.Document;
- import org.jdom2.Element;
- import org.jdom2.output.XMLOutputter;
- import java.lang.reflect.*;
- import java.util.IdentityHashMap;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Properties;
- import java.util.Map;
- public class ReflectionSerialization {
- public Document doSerilizeObject(Object objectToSerilized) throws Exception{
- return recursiveSerilizeObject(objectToSerilized, new Document(new Element("serialized")),
- new IdentityHashMap());
- }
- private Document recursiveSerilizeObject(Object objToSerilized, Document target, Map table) throws Exception{
- String id = Integer.toString(table.size()); //為當(dāng)前序列化的對象設(shè)置id標(biāo)號
- table.put(objToSerilized, id);
- Class objClass = objToSerilized.getClass();
- Element elem = new Element("object");
- elem.setAttribute("class", objClass.getName());
- elem.setAttribute("id", id);
- target.getRootElement().addContent(elem);
- /*
- 判斷當(dāng)前要序列化的對象是否是數(shù)組類型,如果不是,那么先遍歷該對象所有字段,然后遞歸的序列化對應(yīng)字段,因?yàn)樽侄斡锌赡苁穷悓ο螅?nbsp;
- 如果是數(shù)組類型,那么遍歷其中每個(gè)元素,然后針對每個(gè)元素進(jìn)行序列化
- */
- if (objClass.isArray()) {
- //TODO
- } else {
- //TODO
- }
- }
- public static void main(String[] args) {
- }
- }
接下來我們針對兩個(gè)TODO進(jìn)行實(shí)現(xiàn),如果當(dāng)前要序列化的對象不是數(shù)組,那么需要遍歷其所有字段,然后序列化各個(gè)字段,如果字段是類對象類型,那么還得遞歸的對他進(jìn)行處理,我們看代碼實(shí)現(xiàn):
- /*
- 判斷當(dāng)前要序列化的對象是否是數(shù)組類型,如果不是,那么先遍歷該對象所有字段,然后遞歸的序列化對應(yīng)字段,因?yàn)樽侄斡锌赡苁穷悓ο螅?nbsp;
- 如果是數(shù)組類型,那么遍歷其中每個(gè)元素,然后針對每個(gè)元素進(jìn)行序列化
- */
- if (objClass.isArray()) {
- handleNoArrayField(objToSerilized, objClass, target, table, elem);
- } else {
- //TODO
- }
- private void handleNoArrayField(Object objToSeerilized, Class cls, Document target, Map table, Element parent) throws Exception {
- Field[] fields = this.iterateClassFields(cls);
- for (int i = 0; i < fields.length; i++) {
- if (!Modifier.isPublic(fields[i].getModifiers())) {
- fields[i].setAccessible(true); //如果不是公有字段,那么需要設(shè)置它的可讀取性
- }
- Element fElt = new Element("field"); //針對該字段插入xml元素
- fElt.setAttribute("name", fields[i].getName());
- Class declClass = fields[i].getDeclaringClass(); //獲取字段對應(yīng)的類
- fElt.setAttribute("declaringclass", declClass.getName());
- Class fieldType = fields[i].getType(); //獲得該字段類型對應(yīng)的元類
- Object child = fields[i].get(objToSeerilized); //獲得字段對應(yīng)的實(shí)例對象
- if (Modifier.isTransient(fields[i].getModifiers())) {
- child = null;
- }
- fElt.addContent(extractContentFromField(fieldType, child, target, table));
- parent.addContent(fElt);
- }
- }
- private Element extractContentFromField(Class fieldType, Object child, Document target, Map table) throws Exception{
- //將字段對應(yīng)的數(shù)據(jù)抽取出來
- if (child == null) {
- return new Element("null");
- }
- else if (!fieldType.isPrimitive()) {
- Element reference = new Element("reference");
- if (table.containsKey(child)) {
- reference.setText(table.get(child).toString()); //任何基礎(chǔ)類型都繼承自O(shè)bject,他們都支持toString來將自身對應(yīng)的數(shù)據(jù)進(jìn)行字符串表達(dá)
- }
- else {
- reference.setText(Integer.toString(table.size()));
- recursiveSerilizeObject(child, target, table); //如果不是基礎(chǔ)類型,那么就遞歸的進(jìn)行序列化
- }
- }
- }
- private Field[] iterateClassFields(Class cls) {
- List fieldsList = new LinkedList(); //用隊(duì)列存儲(chǔ)對象所有字段
- while (cls != null) {
- Field[] fields = cls.getDeclaredFields(); //獲得當(dāng)前實(shí)例對應(yīng)類所聲明的所有字段
- for (int i = 0; i < fields.length; i++) {
- if (!Modifier.isStatic(fields[i].getModifiers())) {
- fieldsList.add(fields[i]); //如果字段不是static修飾那么就加入隊(duì)列
- }
- }
- cls = cls.getSuperclass(); //獲取父類然后遞歸的獲取字段
- }
- Field[] retValue = new Field[fieldsList.size()];
- return (Field[])fieldsList.toArray();
- }
我們先看第一種情況的實(shí)現(xiàn),首先遍歷當(dāng)前實(shí)例對應(yīng)類聲明的所有字段,將所有字段放入到一個(gè)隊(duì)列中然后再一一取出來進(jìn)行處理,這個(gè)功能的實(shí)現(xiàn)就在函數(shù)iterateClassFields,然后對取出的字段進(jìn)行判斷,看它是否具備public屬性,如果不具備,那么要想讀取它的內(nèi)容,我們需要調(diào)用setAccessible進(jìn)行設(shè)置,接下來還有判斷其是否是Transient類型,如果不是,那么就通過extractContentFromField來讀取字段包含的數(shù)據(jù)。
在extractContentFromField中,先判斷字段是否為基礎(chǔ)數(shù)據(jù)類型,如果是,由于基礎(chǔ)數(shù)據(jù)類型都實(shí)現(xiàn)了toString方法,于是我們可以用該方法獲得數(shù)據(jù)的字符串對應(yīng)內(nèi)容,然后寫入到xml文件中,如果它不是基礎(chǔ)類型,那么我們就調(diào)用recursiveSerilizeObject遞歸的去對他進(jìn)行序列化。
由于內(nèi)容相對燒腦,因此我們先在這里暫停,消化一下后再處理下一步,也就是應(yīng)對字段是數(shù)組類型的情況。