基础知识漫谈(4):讲讲元数据

说几个风马牛不相及的词儿,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语言

时间: 2024-08-02 15:37:24

基础知识漫谈(4):讲讲元数据的相关文章

JAVA 蔡羽 基础知识漫谈

基础知识漫谈(1): 想到哪儿写到哪儿 http://www.cnblogs.com/anrainie/p/5606570.html 基础知识漫谈(2):从设计UI框架开始 http://www.cnblogs.com/anrainie/p/5609958.html 基础知识漫谈(3) 组合基础知识,设计游戏框架 http://www.cnblogs.com/anrainie/p/5614461.html 基础知识漫谈(4):讲讲元数据 http://www.cnblogs.com/anrain

[转]基础知识漫谈(1): 想到哪儿写到哪儿

[转]基础知识漫谈(1): 想到哪儿写到哪儿 本文转自(http://www.cnblogs.com/anrainie/p/5606570.html) 一.想到哪儿写到哪儿 给公司新员工培训,和网上的新手做交流,我最先强调的都是基础. 基础有什么用? 1.节省沟通成本 有天,java群里来了个新人,上来就提了一个问题: “我代码跑不起来,怎么办?” 这一看就是还没入门,没办法提供具体的信息. 于是有个有耐心的老鸟出来了,开始了一连串提问:异常栈看一下?有编译期异常吗?贴出你的main函数看看?

基础知识漫谈(2):从设计UI框架开始

说UI能延展出一丢丢的东西来,光java就有swing,swt/jface乃至javafx等等UI toolkit,在桌面上它们甚至都不是主流,在web端又有canvas.svg等等. 基于这些UI工具包\框架,又产生了大量通用的或者业务性的UI框架,比如Draw2d.GEF.easyUI乃至国内的EChart.白鹭等等. 这些框架的业务范围各异,一个程序员的时间和精力有限,你不可能全部都掌握,又不能预言出是哪一个将来会独步天下,甚至,连当前哪一个最流行,都够打一阵嘴炮. 那,我们应该学什么?

基础知识漫谈(1): 想到哪儿写到哪儿

一.想到哪儿写到哪儿 给公司新员工培训,和网上的新手做交流,我最先强调的都是基础. 基础有什么用? 1.节省沟通成本 有天,java群里来了个新人,上来就提了一个问题: “我代码跑不起来,怎么办?” 这一看就是还没入门,没办法提供具体的信息. 于是有个有耐心的老鸟出来了,开始了一连串提问:异常栈看一下?有编译期异常吗?贴出你的main函数看看? 新人收到了问题并且抛出了你都在说什么异常. “你还是截图吧.”老鸟说. 这里涉及到了异常栈,编译期,main函数等等再基础不过的知识,有那么部分毫不客气

网络基础知识查询

第一章.基础网络概念 1.1 网络是个什么玩意儿 全世界的人种有很多,人类使用的语言种类也多的很.那如果你想要跟外国人沟通时,除了比手划脚之外,你要如何跟对方讲话? 大概只有两种方式啰,一种是强迫他学中文,一种则是我们学他的语言,这样才能沟通啊.在目前世界上的强势语言还是属于英语系国家, 所以啰,不管是啥人种,只要学好英文,那么大家都讲英文,彼此就能够沟通了.希望不久的未来,咱们的中文能够成为强势语言啊! 这个观念延伸到网络上面也是行的通的,全世界的操作系统多的很,不是只有 Windows/Li

三层控件基础知识

一. 基础知识Delphi Multi-tier程序多以MIDAS为基础,因此以MIDAS为基础建立的Delphi Multi-tier程序,客户端和服务器端都要MIDAS.DLL文件的支持.1) 远程数据模块:远程数据模块是一个类似于COM Automation Server或是Corba Server的数据模块,它存在于应用程序服务器中,负责提供应用程序服务器上的数据提供者组件(Provider)接口给客户端应用程序使用.Delphi目前提供的远程数据模块有TRemoteDataModule

hadoop学习笔记——基础知识及安装

1.核心 HDFS  分布式文件系统    主从结构,一个namenoe和多个datanode, 分别对应独立的物理机器 1) NameNode是主服务器,管理文件系统的命名空间和客户端对文件的访问操作.NameNode执行文件系统的命名空间操作,比如打开关闭重命名文件或者目录等,它也负责数据块到具体DataNode的映射 2)集群中的DataNode管理存储的数据.负责处理文件系统客户端的文件读写请求,并在NameNode的统一调度下进行数据块的创建删除和复制工作. 3)NameNode是所有

linux内存基础知识和相关调优方案

内存是计算机中重要的部件之一,它是与CPU进行沟通的桥梁.计算机中所有程序的运行都是在内存中进行的,因此内存的性能对计算机的影响非常大.内存作用是用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据.只要计算机在运行中,CPU就会把需要运算的数据调到内存中进行运算,当运算完成后CPU再将结果传送出来,内存的运行也决定了计算机的稳定运行.对于整个操作系统来说,内存可能是最麻烦的的设备.而其性能的好坏直接影响着整个操作系统. 我们知道CPU是不能与硬盘打交道的,只有数据被载入到内存中才可

.Net Framework基础知识

.net framework是microsoft最新的开发平台,现在最新版本为4.0. .net可以用来开发windows应用程序,web应用程序,web服务和其它各种类型的程序. .net可以用于多种语言,流行的开发语言为C#. .net主要包含了一个非常大的库主要由通用类型系统(common type system(CTS)负责.net在各种语言中进行交互操作)和一个公共语言运行库(common language runtime(CLR)负责管理用.net库开发的所用应用程序的执行). .n