說幾個風馬牛不相及的詞兒,spring的依賴注入定義,hibernate的數據映射定義,XML的DTD,再就是我們常說的報文格式。
如果對它們不甚了解,請參考章節一《想到哪兒寫到哪兒》。有了基本的了解之后,應當隱約之中有一種感覺,“它們很相似”。
本篇文章要說的就是這個相似性,我管它叫做數據格式\元數據,DataSchema\MetaData。當然,元數據的定義是要大於數據格式的,本文將它們當成同一個概念。
什么是數據格式?看看這段XML(1):
<Bean type=”com.nlo.pojo.User”> <Fields> <Bean name=”name” type=”java.lang.String” default=”Abby”/> <Bean name=”id” type=”int” default=”1”/> </Fields> </Bean>
直覺上,我們可以把它可以這個類聯系起來:
class User{ String name=”Abby”; int id=1; }
對於該User類的實例(User u1=new User()),該XML和該Java類定義,都是用於描述u1這個類實例的元數據。
再深化一點兒,看看如下這段XML(2):
<Element name=”Bean” min=”0” max=”*”> <Attribute name=”type” type=”String”/> <Element name=”fields” min=”1” max=”1” > <Attribute name=”type” type=”String”/> <Attribute name=”name” type=”String”/> <Attribute name=”default” type=”Object”/> </Element> </Element>
能不能得出結論:XML(2)是XML(1)的元數據?
如果你還是學生,相信你一定想起了教科書上的一句話:元數據的元數據是元元數據。
總結一下剛才所說的:
1、元數據本身可以是任何描述性質的結構,比如XML、JAVA類定義、JSON,等等。
2、只要是按照特定規范組合起來的數據,一定具備特定的數據格式。
3、元數據是個相對的概念,提到它就一定要說明,該元數據是什么玩意兒的元數據。就像上面的XML(1),它和User實例對比的時候,是元數據,和XML(2)對比的時候,又是被描述實體。
4、同一種數據實體,可以用不同的元數據結構(XML,JAVA)來描述。
現在再回頭看看章節頭的內容,是不是清晰很多了?
依賴注入和數據映射定義都是框架用於描述JavaBean的,DTD是用來描述XML的,報文格式是用來描述報文的。
它們在相對意義上,都是數據格式。
利用對這個概念的理解,我們可以做到“自動解析指定格式的A實例為B實例”。
這里的A、B可以用XML、Json、Java等等替代。這里以XML轉化為Java實例作為示例講解。
步驟一:
分析Java實例元元數據,得出Java實體類定義結構如下:
類名,包含多個字段
類字段,包含字段名,字段類型,默認值
字段類型,有很多划分方式,這里划分為基本數據類型(含String),集合類型(List、Map或者數組),引用類型(另外一個自定義格式)。
如何用XML來描述它們?回頭看看XML(1)的結構你就懂。
除了XML描述文件,我們還需要一個模型來歸納它們,見接口IDataSchema。

package galaxy.ide.configurable.editor.schema; import galaxy.ide.configurable.editor.persistent.IPersistentHelper; import java.util.Map.Entry; import java.util.Set; /** * 數據結構接口,使用 {@link DataSchemaAnalysts}可以根據數據結構來獲取具體數據模型中的具體值 * * @author caiyu * @date 2014-1-13 */ @SuppressWarnings("rawtypes") public interface IDataSchema<T> { /** * 獲取Class * * @return */ Class<T> getOwner(); void setOwner(Class<T> type); /** * 數據結構關鍵字 * * @return */ String getId(); /** * 添加字段 * * @param memberId * @param member */ void addField(String memberId, IDataSchema<?> member); /** * 獲取字段 * * @param memberId * @return */ IDataSchema<?> getField(String memberId); /** * 獲取全部的字段 * * @return */ Set<Entry<String, IDataSchema<?>>> getFieldEntrySet(); void setName(String name); /** * 獲取名稱 * * @return */ String getName(); /** * 復制目標結構 * * @param schema */ void copy(IDataSchema<?> schema); /** * 根據過濾器復制 * * @param schema * @param filters */ void copy(IDataSchema<?> schema, String... filters); /** * * @return */ String getBundleId(); /** * 設置bundleId * * @param bundleId * @return */ void setBundleId(String bundleId); /** * 獲取Class分類 * * @return */ ClassType getType(); /** * 獲取當前結構默認的持久化助手 * * @return */ IPersistentHelper getDefaultPersistentHelper(); void setDefualtPersistentHelper(IPersistentHelper persistHelper); IDataSchema<?> getFirstField(); boolean containsField(String key); void addProperty(String key, Object property); /** * 獲取額外屬性 * * @param key * @return */ Object getProperty(String key); String[] getPropertyKeys(); }
步驟二:
有了結構說明,還需要一個Xml解析工具,一個結構分析工具。
Xml解析可以使用dom4j,結構分析工具要自己寫,見接口IPersistentHelper。
package galaxy.ide.configurable.editor.persistent; import galaxy.ide.configurable.editor.schema.IDataSchema; /** * 持久化助手接口 * <P> * C->Content object class</br> P->Persistent object class * * * @author caiyu * @date 2013-12-19 */ public interface IPersistentHelper<C, P> { /** * 根據數據結構,從持久化對象中加載內容 * * @return */ C load(P persistentTarget, IDataSchema<C> schema); /** * 根據數據結構,將內容保存為持久化對象 * * @param content */ P save(C content, IDataSchema<C> schema); }
其中有三個對象,C和P是可以互相轉化的對象,這里分別指Java和Xml,schema是dom4j讀取后對象化的結構描述。
參考實現如XmlPersistentHelper(部分源碼)所示。

/** * 留待日后重寫,使用DataSchemaAnalysts分析 * * @author caiyu * @date 2014-1-7 */ public final class XmlPersistentHelper extends AbstractPersistentHelper<Object, Element> { @SuppressWarnings("rawtypes") @Override public Object load(Element persistentTarget, IDataSchema schema) { // TODO load Object instance = deserialCustomType(persistentTarget, schema); if (instance != null) return instance; if (instance == null) instance = deserialBasicType(persistentTarget, schema); if (instance == null) instance = deserialKeyValueType(persistentTarget, schema); if (instance == null) instance = deserialBeanType(persistentTarget, schema); return instance; } @SuppressWarnings("rawtypes") @Override public Element save(Object content, IDataSchema schema) { Assert.isNotNull(schema); // TODO save Element root = serialCustomType(content, schema); if (content != null) { if (root == null) root = serialBasicType(content, schema); if (root == null) root = serialKeyValueType(content, schema); if (root == null) root = serialBeanType(content, schema); } return root; } public Element serialBeanType(Object content, IDataSchema<?> schema) { Assert.isNotNull(schema); Element root = DocumentFactory.getInstance().createElement( schema.getName()); try { Element child = null; for (Entry<String, IDataSchema<?>> field : schema .getFieldEntrySet()) { Field f = DataSchemaAnalysts.getFieldByName(schema.getOwner(), field.getKey()); // schema.getOwner().getDeclaredField(field.getKey()); f.setAccessible(true); IDataSchema<?> subSchema = field.getValue(); Object o = f.get(content); child = save(o, subSchema); if (child != null) root.add(child); f.setAccessible(false); } return root; } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); throw new InvalidBeanSchemaException("can not handle schema " + e.getMessage()); } return null; } @SuppressWarnings("rawtypes") private Element serialBasicType(Object content, IDataSchema<?> schema) { Element root = DocumentFactory.getInstance().createElement( schema.getName()); Class<?> owner = schema.getOwner(); boolean flag = false; if (owner == String.class || owner == Double.class || owner == Long.class || owner == Float.class || owner == Integer.class || owner == Character.class || owner == Boolean.class) { flag = true; root.setText(content.toString()); } else { if (List.class.isAssignableFrom(owner)) { Assert.isTrue(content instanceof List, "unaccept content owner: " + content.getClass()); List list = (List) content; seriaList(list, schema, root); flag = true; } else if (Map.class.isAssignableFrom(owner)) { Assert.isTrue(content instanceof Map, "unaccept content owner: " + content.getClass()); Map map = (Map) content; serialMap(map, schema, root); flag = true; } } if (flag) return root; return null; } @SuppressWarnings("rawtypes") private void serialMap(Map map, IDataSchema<?> schema, Element root) { String primaryKey = (String) schema .getProperty(ExtensionConstants.PRIMARY_KEY); if (primaryKey == null || primaryKey.trim().length() == 0) { Element child = null; Set<Entry<String, IDataSchema<?>>> set = schema.getFieldEntrySet(); for (Entry<String, IDataSchema<?>> entry : set) { Object o = map.get(entry.getValue().getName()); child = save(o, entry.getValue()); if (child != null) root.add(child); } } else { IDataSchema<?> valueSchema = schema.getFieldEntrySet().iterator() .next().getValue(); Element child = null; for (Object value : map.values()) { child = save(value, valueSchema); if (child != null) root.add(child); } } } /** * 反序列化基本數據類型,比如String,Integer,Double等 * * @param schema * @param element * @return */ private Object deserialBasicType(Element persistentTarget, IDataSchema<?> schema) { Class<?> owner = schema.getOwner(); String value = persistentTarget == null ? null : persistentTarget .getText(); ClassType type = schema.getType(); switch (type) { case Map: case List: if (value == null) break; try { // 處理集合型數據 if (List.class.isAssignableFrom(owner)) { Object o = schema.getOwner().newInstance(); Assert.isTrue(o instanceof List, "unaccept content owner: " + o.getClass()); return deserialList(persistentTarget, schema, o); } else if (Map.class.isAssignableFrom(owner)) { Object o = schema.getOwner().newInstance(); Assert.isTrue(o instanceof Map, "unaccept content owner: " + o.getClass()); return deserialMap(persistentTarget, schema, o); } } catch (InstantiationException e1) { e1.printStackTrace(); } catch (IllegalAccessException e1) { e1.printStackTrace(); } break; default: return type.handle(value); } return null; } }
步驟三:
要實現為一個JavaBean存取值,還需要提供一個數據分析工具,利用反射,從JavaBean里抽取指定值,或者設置值。參見DataSchemaAnalysts。

/** * * 數據結構解析器 * * @author caiyu * @date 2014-4-15 */ public class DataSchemaAnalysts { public static Field getFieldByName(Object obj, String fieldName) throws NoSuchFieldException { Assert.isNotNull(obj, fieldName + " should not be null"); for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass .getSuperclass()) { try { return superClass.getDeclaredField(fieldName); } catch (Exception e) { // System.out.println(e.getLocalizedMessage()); } } throw new NoSuchFieldException("can not find field: [" + fieldName + "] in " + obj); } public static Field getFieldByName(Class<?> clazz, String fieldName) throws NoSuchFieldException { for (Class<?> superClass = clazz; superClass != Object.class; superClass = superClass .getSuperclass()) { try { return superClass.getDeclaredField(fieldName); } catch (NoSuchFieldException e) { // System.out.println(e.getLocalizedMessage()); } } throw new NoSuchFieldException(clazz + " " + fieldName); } public static Field[] getAllFields(Class<?> clazz) { List<Field> field = new ArrayList<Field>(20); for (Class<?> superClass = clazz; superClass != Object.class; superClass = superClass .getSuperclass()) { field.addAll(Arrays.asList(superClass.getDeclaredFields())); } return field.toArray(new Field[0]); } /** * 根據父對象(parent)的數據結構(parentSchema),分析抽取關鍵字(key)對應的子對象 * * @param parentSchema * @param parent * @param key * @return * @throws NoSuchFieldException * @throws SecurityException * @throws IllegalArgumentException * @throws IllegalAccessException * @throws InvocationTargetException */ @SuppressWarnings("rawtypes") public static Object analyse(IDataSchema<?> parentSchema, Object parent, String key) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { ClassType type = parentSchema.getType(); if (type == null) throw new AnalyseDataSchemaException("type of " + parentSchema.getName() + " [" + parent + "] can not be null"); switch (type) { case Bean: Field f = getFieldByName(parent, key); f.setAccessible(true); Object o = f.get(parent); f.setAccessible(false); return o; case Map: return ((Map) parent).get(key); case List: int i = Integer.parseInt(key); return ((List) parent).get(i); case Key_Value: DataType dataType = parentSchema.getOwner().getAnnotation( DataType.class); if (dataType != null && dataType.value() == DataTypeValue.MAP) { Method getMethod = extraMethodByAnnotation( parentSchema.getOwner(), get.class); return getMethod.invoke(parent, key); } } return null; } /** * 抽取含有指定注解的方法 * * @param owner * @param annotationClass * @return */ public static Method extraMethodByAnnotation(Class<?> owner, Class<? extends Annotation> annotationClass) { for (Method method : owner.getDeclaredMethods()) { Annotation t = method.getAnnotation(annotationClass); if (t != null) { return method; } } throw new InvalidAnnotationConfigException(owner + " has no annoation " + annotationClass); } public static ClassType analyseClassType(final Class<?> owner) { if (owner == String.class) return ClassType.String; else if (owner == Integer.class) return ClassType.Integer; else if (owner == Double.class) return ClassType.Double; else if (owner == Float.class) return ClassType.Float; else if (owner == Long.class) return ClassType.Long; else if (owner == Character.class) return ClassType.Character; else if (owner == Boolean.class) return ClassType.Boolean; else if (List.class.isAssignableFrom(owner)) return ClassType.List; else if (Map.class.isAssignableFrom(owner)) return ClassType.Map; else if (owner.getAnnotation(DataType.class) != null) { if (owner.getAnnotation(DataType.class).value() == DataTypeValue.MAP) { return ClassType.Key_Value; } } return ClassType.Bean; } /** * 將value應用到指定的target上 * * @param targetSchema * @param target * @param valueSchema * @param value * @throws SecurityException * @throws NoSuchFieldException * @throws IllegalAccessException * @throws IllegalArgumentException * @throws InvocationTargetException */ @SuppressWarnings({ "unchecked", "rawtypes" }) public static void perform(IDataSchema<?> targetSchema, Object target, String key, Object value) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { // TODO Auto-generated method stub ClassType type = targetSchema.getType(); if (type == null) throw new AnalyseDataSchemaException("type of " + targetSchema.getName() + " [" + target + "] can not be null"); switch (type) { case Bean: Field f = getFieldByName(target, key); f.setAccessible(true); if (f.getType().isPrimitive() && value == null) value = DataSchemaUtil.getPrimitiveDefaultValue(f.getType()); f.set(target, value); f.setAccessible(false); break; case Map: String primaryKey = (String) targetSchema .getProperty(ExtensionConstants.PRIMARY_KEY); if (primaryKey != null && primaryKey.length() != 0) { IDataSchema<?> childSchema = targetSchema.getFieldEntrySet() .iterator().next().getValue(); Object k = analyse(childSchema, value, (String) primaryKey); ((Map) target).put(k, value); } else ((Map) target).put(key, value); break; case List: if (target != null) ((List) target).add(value); else throw new IllegalArgumentException( "XML format error, missing element " + targetSchema.getName()); break; case Key_Value: DataType dataType = targetSchema.getOwner().getAnnotation( DataType.class); if (dataType != null && dataType.value() == DataTypeValue.MAP) { Method putMethod = extraMethodByAnnotation( targetSchema.getOwner(), put.class); putMethod.invoke(target, key, value); } break; } } }
擴展思考:完全把Java映射成XML是可能的,因為復雜是由簡單構成,雖然Java里有大量的類型,但一直跟進到代碼深處你會發現,所有的復雜的類結構,最終都是基礎數據類型。可是,完全把Java映射成XML是不可行的,因為這棵定義樹不知道到底有多深,有可能不是人工能完成的工作量,那么,想想,如何做到呢?
最近的三篇都應用到了面向對象相關基礎來分析復雜的需求,下一篇文章將專門講解什么叫萬物皆對象,並應用來解析SQL語言。