JAVA源码解析(11)-java.beans.XMLEncoder、java.beans.XMLDecoder(一)

java版本:1.8
作者出于学习阶段,如有问题,欢迎多多提出。

XMLEncoder目前用的比较多的是用来将Java Bean 和 XML的互相转换。

让我们先看一下XMLEncoder和XMLDecoder的用法:

public class XMLUtils {
    /**
     * 读取XML文件并返回相应的对象
     *
     * @param objSource 输出文件的路径
     * @return 由XML转换出来的对象
     */
    public static Object xml2Object(String objSource) throws FileNotFoundException, IOException, Exception {
        //初始化各种对象
        File fin = new File(objSource);
        FileInputStream fis = new FileInputStream(fin);
        XMLDecoder decoder = new XMLDecoder(fis);
        Object obj = null;
        try {
            //真正的核心部分,用于读取Object
            obj = decoder.readObject();

            //关闭流
            fis.close();
            decoder.close();
        } catch (Exception e) {

        }
        return obj;
    }

    /**
     * 将传入的obj对象转换为XML文件
     * @param obj 需要转换的对象
     * @param fileName 需要写出的文件路径
     */
    public static void objectXmlEncoderList(Object obj,String fileName) throws IOException {
        // 创建输出文件
        File fo = new File(fileName);
        // 文件不存在,就创建该文件
        if (!fo.exists()) {
            // 先创建文件的目录
            String path = fileName.substring(0, fileName.lastIndexOf('.'));
            File pFile = new File(path);
            pFile.mkdirs();
        }
        // 创建文件输出流
        FileOutputStream fos = new FileOutputStream(fo);
        // 创建XML文件对象输出类实例
        XMLEncoder encoder = new XMLEncoder(fos);
        // 对象序列化输出到XML文件
        encoder.writeObject(obj);
        encoder.flush();

        // 关闭序列化工具和输出流
        encoder.close();
        fos.close();
    }
}

从上面的例子可以看出,XMLDecoder是用于读取String转为Object的工具,而XMLEncoder是用于将Object转换为XML字符串对象的的工具,下面来具体解析下这两个类,从XMLEncoder开始。

看一下来自API的解释:
XMLEncoder 类是 ObjectOutputStream 的互补替换,可用于生成 JavaBean 的文本表示形式,所使用方式与用 ObjectOutputStream 创建 Serializable 对象的二进制表示形式的方式相同。例如,可以使用以下代码片段创建所提供的 JavaBean 及其所有属性的文本表示形式:

   XMLEncoder e = new XMLEncoder(
                      new BufferedOutputStream(
                          new FileOutputStream("Test.xml")));
   e.writeObject(new JButton("Hello, world"));
   e.close();

尽管两者的 API 类似,但 XMLEncoder 类仅设计用于将 JavaBean 的图形归档为其公共属性的文本表示形式。与 Java 源文件类似,以这种方式写入的文档在所涉及类的实现中可自然免除更改。在进程间通信和通用序列化中继续推荐使用 ObjectOutputStream。
XMLEncoder 类提供 JavaBean 的默认指示,其中它们被表示为符合 1.0 版的 XML 规范和 Unicode/ISO 10646 字符集 UTF-8 字符编码的 XML 文档。由 XMLEncoder 类生成的 XML 文档如下:

轻便且版本灵活:它们不依赖于任何类的私有实现,因此,与 Java 源文件类似,可以在某些不同版本的类之间或不同的供应商的 VM 之间交换它们。
结构紧凑:XMLEncoder 类在内部使用删减冗余 (redundancy elimination) 算法,因此 Bean 属性的默认值不会被写入流中。
容错性:文件中的非结构性错误(由于文件的破坏或在归档文件中对类进行的 API 更改导致)仍然保持本地化,因此 reader 可以报告错误,并继续加载不受错误影响的那部分文档。
以下是一个 XML 归档文件的示例,它包含 swing 工具包中一些用户界面组件:

 <?xml version="1.0" encoding="UTF-8"?>
 <java version="1.0" class="java.beans.XMLDecoder">
 <object class="javax.swing.JFrame">
   <void property="name">
     <string>frame1</string>
   </void>
   <void property="bounds">
     <object class="java.awt.Rectangle">
       <int>0</int>
       <int>0</int>
       <int>200</int>
       <int>200</int>
     </object>
   </void>
   <void property="contentPane">
     <void method="add">
       <object class="javax.swing.JButton">
         <void property="label">
           <string>Hello</string>
         </void>
       </object>
     </void>
   </void>
   <void property="visible">
     <boolean>true</boolean>
   </void>
 </object>
 </java>

XML 语法使用以下约定:
每个元素表示一个方法调用。
“object” 标记表示一个表达式,其值被用作参数来封闭元素。
“void” 标记表示将要执行的语句,但其结果不会被用作参数来封闭方法。
包含元素的元素使用这些元素作为参数,除非它们有以下标记:”void”。
方法的名称由 “method” 属性表示。
XML 的标准 “id” 和 “idref” 属性用于引用以前的表达式,以便处理对象图形中的环形。
“class” 属性用于显式指定静态方法的目标或构造方法;其值是类的完全限定名。
如果没有通过 “class” 属性定义目标,则使用外部上下文将带有 “void” 标记的元素作为目标执行。
Java 的 String 类被特殊对待并被写入 Hello, world,其中使用 UTF-8 字符编码将字符串的字符转换成字节。
尽管只使用这三个标记就可以写入所有对象图形,但以下定义也包括在内,以便能够更具体地表示普通数据结构:

默认方法名是 “new”。
java 类的引用是以 javax.swing.JButton 形式写入的。
用于 Java 基本类型的包装器类的实例是通过将基本类型的名称用做标记来写入的。例如,Integer 类的实例可以写为:123。注意,XMLEncoder 类使用了 Java 的反射包,在该包中,Java 的基本类型与其相关“包装器类”之间的转换是内部处理的。XMLEncoder 类自身的 API 只处理 Object。
在表示 null 方法(其名称以 “get” 开头)的元素中,”method” 属性被 “property” 属性替代,后者的值是通过移除 “get” 前缀并取消所得结果的大写化得到的。
在表示一元方法(其名称以 “set” 开头)的元素中,”method” 属性被 “property” 属性替代,后者的值是通过移除 “set” 前缀并取消所得结果的大写化得到的。
在表示名为 “get” 且使用一个整数参数的方法的元素中,”method” 属性由 “index” 属性替换,后者的值是第一个参数的值。
在表示名为 “set” 且使用两个参数(第一个参数为整数)的方法的元素中,”method” 属性由 “index” 属性替换,后者的值是第一个参数的值。
数组的引用是使用 “array” 标记来编写的。”class” 和 “length” 属性分别指定数组的子类型和它的长度。

首先来看下构造器:

    XMLEncoder(OutputStream out) {
        this(out, "UTF-8", true, 0);
    }

    XMLEncoder(OutputStream out, String charset, boolean declaration, int indentation);

    第一个构造器调用的第二个构造器,第二个构造器则会进行一些操作,首先,在传入的参数中,out就是需要写出的输出流,charset则是要写出流的字符集,在调用第一个构造器的时候,字符集会默认传入UTF-8,第三个参数declaration则是是否在生成的XML中写出XML的头,例如:<?xml version="1.0" encoding="UTF-8" ?>,第四个参数indentation则是代表一个缩进(TAB)代表多少个空格,对于不需要pretty排版的,传入0即可,即没有缩进。

其次,有一个方法比较重要:

 public void writeObject(Object o) {
        if (internal) {
            super.writeObject(o);
        }
        else {
            writeStatement(new Statement(this, "writeObject", new Object[]{o}));
        }

    }

回顾下父类Encoder的writeObject方法:

    protected void writeObject(Object o) {
        if (o == this) {
            return;
        }
        PersistenceDelegate info = getPersistenceDelegate(o == null ? null : o.getClass());
        info.writeObject(o, this);
    }

看下writeStatement方法:

 public void writeStatement(Statement oldStm) {
        // System.out.println("XMLEncoder::writeStatement: " + oldStm);
        boolean internal = this.internal;
        this.internal = true;
        try {
            super.writeStatement(oldStm);
            /*
               Note we must do the mark first as we may
               require the results of previous values in
               this context for this statement.
               Test case is:
                   os.setOwner(this);
                   os.writeObject(this);
            */
            mark(oldStm);
            Object target = oldStm.getTarget();
            if (target instanceof Field) {
                String method = oldStm.getMethodName();
                Object[] args = oldStm.getArguments();
                if ((method == null) || (args == null)) {
                }
                else if (method.equals("get") && (args.length == 1)) {
                    target = args[0];
                }
                else if (method.equals("set") && (args.length == 2)) {
                    target = args[0];
                }
            }
            statementList(target).add(oldStm);
        }
        catch (Exception e) {
            getExceptionListener().exceptionThrown(new Exception("XMLEncoder: discarding statement " + oldStm, e));
        }
        this.internal = internal;
    }

在writeStatement的注释当中,有这样一句话:This method should only be invoked within the context of initializing a persistence delegate。意思是,writeStatement方法当且仅当委托类被初始化的时候才会被调用。

在writeObject方法中,有一个看起来比较奇怪的变量:internal。那么,这个变量是干嘛用的呢?在程序当中,writeStatement、writeExpression方法在开始执行的时候,会将internal赋值为true(默认为false),等方法执行完毕的时候会重新赋值为false,换句话说,当writeStatement、writeExpression被执行的时候,writeObject会执行父类的writeObject方法,否则会执行writeStatement方法。

而writeStatement方法见名知意就是执行当前传入的Statement,只是在这个方法当中,对set、get方法会做一些特定的限定,最后执行。

而父类Encoder的writeObject,则还是先寻找到委托类,最后去执行writeObject这个方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值