JAXB技術的魔力 —— 二維CAD圖象數據的存儲

JAXB技術的魔力
                                                                              —— 二維CAD圖象數據的存儲

SUN中國軟件技術中心      王昱     [email protected]
   摘要:本文介紹了怎樣使用JAXB技術來快速開發基於XML的應用程序,通過一個簡單的二維圖像數據存儲的例子,展現了使用XML數據表示給應用程序帶來的靈活性,以及怎樣使用JAXB技術來簡化XML操作的複雜程度。
序言
    我們的一個合作伙伴,正在使用Java技術開發專業領域內的CAD二維圖像應用軟件。隨着Java技術的不斷髮展,其虛擬機的速度不斷的提高,越來越多的廠商使用Java語言作爲他們圖形軟件跨平臺的解決方案。特別是JDK1.4推出以後,對圖形圖像的操作,性能有了顯著的提高。在某些二維圖形圖像的類(如VolatileImage)中,能夠直接操縱硬件加速的圖像緩存,甚至使得Java成爲圖形遊戲的開發平臺成爲可能。
    爲了加快開發的速度,我們的合作伙伴選擇了Java本身的對象序列化(serialization)技術來作爲應用數據的存儲。換句話說,應用程序中的所有圖形圖像,不管是按鈕還是線條,都以對象序列化方式存儲到文件系統中;在使用的時候,從文件中讀出數據,恢復成內存中的Java對象。這樣做的好處是簡單,所有對象數據的存儲和恢復都由Java平臺內部的序列化機制來完成,數據保存的格式(二進制)也由Java來制定;並且在性能上,這種方法也是不錯的,特別是在JDK1.4上,對象序列化的速度有了很大的提高,使得序列化技術應用得越來越廣泛,不僅僅用於對象數據的存儲,還大量的應用於各種分佈式網絡計算模型中。
    但是,我們的合作伙伴在產品開發和版本升級過程中遇到了一些問題。
l          數據的兼容性差
在升級的版本中,如果對Java類的改動超出了對象序列化兼容性的範圍,如:改變了類圖結構中的繼承關係和層次結構,改變或增減了類成員變量數量和類型等等,都會造成新老版本的數據不一致性。很有可能新版本的系統不能兼容和使用老版本系統產生的數據。
l          對數據的操作性差。
由於序列化所產生的數據是由JVM內部機制生成的二進制數據,對其進行修改和轉換有一定的困難和風險。而且二進制數據的可讀性比較差。而對這些圖形數據的操作是此係統不可缺少的一部分。例如,不同版本之間的數據轉換,不同格式之間的轉換以及系統數據的導入和導出,都需要對系統數據進行不同程度的操作。
l          數據的保存量大。
用序列化保存的數據通常包含了大量無用的信息。例如,保存一個簡單的"Button",序列化會保存它所有父類對象的所有實例的成員變量,還會保存這個對象所有缺省的其他屬性。而實際上,我們只要關心這個"Button"上面的文本,加上它在圖面中的座標就行了。要控制序列化保存數據量的大小需要較複雜和繁瑣的設置,例如使用transit修飾符等等(詳見參考資料)。
l          JDK版本的影響。
不同的JDK的序列化實現有可能會有差異,保存數據的格式也有所不同,這就使得系統有可能被綁定在某個版本的JDK,而不能使用高版本的JDK所帶來的性能上和功能上的好處!       
一. XML數據表示和JAXB的解決方案
    由於存在以上的問題,需要考慮採用其他的解決方案,以保證即能夠保證數據操作的靈活性,又能夠使用現有的數據格式和成熟的開發工具包,減輕系統開發和維護的負擔。
   XML成爲被考慮的目標之一。顯然,具有通用數據表示之稱的XML能夠帶來很大的靈活性,使得數據在各個平臺之間的交換,各個版本之間的升級變得非常方便;而且,在XML的開發中,Java平臺已經擁有很多成熟的軟件開發工具和解決方案。一般來說,使用XML作爲系統數據存儲的格式有以下兩種解決方案:
l          直接使用Java對象序列化的內部對XML的支持功能:XmlEncoderXMLDecoder兩個類。這個功能其實是對象序列化功能本身的一部分,通過使用XmlEncoderXMLDecoder這兩個類,可以使序列化的數據以XML的格式保存和恢復,而不是二進制格式。這種方式增加了對數據的可讀性和可操作性。但是仍然不能靈活地按照自己的意願和需求對數據進行保存和恢復。
l          自己定義XML的數據格式。這個方案使系統擁有了最大的靈活程度。一旦使用自己定義的XML格式,那麼對此XML格式的文件進行解析、效驗以及反向解析(XML文件的生成)都需要自己去開發了!一旦版本發生更新,XML的數據格式也有可能隨着改變,這些解析和效驗程序都有可能需要做出相應的改動。這樣會大大增加了系統開發的負擔和週期!
    JAXB正是爲解決這些問題而提出來的。在允許你自己靈活定義自己的 XML文件格式的基礎上,由JAXB替你生成操作XML文件的源代碼,使你的應用程序將重點放到Java對象上,而不用直接面對XML操作。這正是JAXB的目的所在。
二. JAXB技術介紹
2.1 .什麼是JAXB?
    Java  Architecture for XML Binding (JAXB) 是一個業界的標準,是一項可以根據XML Schema產生Java類的技術。該過程中,JAXB也提供了將XML實例文檔反向生成Java對象樹的方法,並能將Java對象樹的內容重新寫到XML實例文檔。從另一方面來講,JAXB提供了快速而簡便的方法將XML模式綁定到Java表示,從而使得Java開發者在Java應用程序中能方便地結合XML數據和處理函數。
    這意味着你不需要處理甚至不需要知道XML編程技巧就能在Java應用程序中利用平臺核心XML數據的靈活性。而且,可以充分利用XML的優勢而不用依賴於複雜的XML處理模型如SAXDOMJAXB 隱藏了細節並且取消了SAXDOM中沒用的關係——生成的JAXB類僅描述原始模型中定義的關係。其結果是結合了高度可移植Java代碼和高度可移植的XML數據。其中這些代碼可用來創建靈活、輕便的應用程序和Web服務。
2.2.  JAXB的體系結構
JAXB體系結構
圖 1  JAXB體系結構
      JAXB的體系結構和應用過程如圖1所示,一般來說包含以下幾個步驟:
l          根據你的應用程序所要操作的XML數據格式,撰寫相應的XML Schema,有關XML Schema的知識,請參閱參考資料
l          使用JAXB 所帶的編譯工具(Binding Compiler),將這個XML Schema文件作爲輸入,產生一系列相關的Java ClassInterface
l          在使用JAXB編譯工具的時候,可以有選擇性的提供一個配置文件(圖1的虛線部分),來控制JAXB編譯工具的一些高級屬性。
l          這些Java ClassInterface是你的應用程序操縱XML數據的主要接口和方法。
l          通過JAXBXML文檔進行的操作主要包括:將符合XML Schema規定的XML文檔解析生成一組相應的Java對象;對這些對象進行操作(修改、增加和刪除對象的屬性等等);然後將這些對象的內容保存到這個XML文檔中。
      下面我們結合本文的示例來進一步說明使用JAXB的過程。
 
三. 實例分析
3.1. 示例運行的環境和步驟
本示例運行的Java環境是JDK1.3.1以上。本示例在Jdk1.4.1運行測試通過。
l          展開此壓縮文件,在bin目錄下找到可執行文件rundemo.batfor windows)或rundemo.sh for unix
l          確保環境變量JAVA_HOME已經正確設置,指向你係統所安裝的JDK的目錄。
l          運行rundemo.bat rundemo.sh,就會出現如下圖所示的運行畫面。
3.2. 示例運行的場景
運行
圖2  demo運行場景
               
如圖2所示,本示例演示了一個非常簡單的場景,它允許你在畫布上使用鼠標繪製大小一定的正方形或圓形。
l          你的鼠標點到什麼位置,就會在相應的位置上繪製圖形。
l          你可以通過(Color)菜單改變當前畫筆的顏色:綠色或紅色。
l          你可以通過(Graph)菜單改變當前圖形的形狀:正方形或圓形。
l          你還可以通過(File)菜單中的savesave as子菜單將當前已經繪製的圖形以XML格式保存起來。
l          你還可以通過(File)菜單中的open子菜單,選擇以前保存過的圖形文件,將它顯示在畫布上。
3.3. JAXB使用過程分析
l          下載JAXB開發工具包
      JAXB1.0 的正式版本的一個實現(大家一定要記住,JAXB只是一個標準,Sun公司提供的此工具包只能說是這個標準的一個實現)已經發布了。由於XMLWeb Services中的大量應用,所以,JAXB1.0作爲Web Services 開發包的一部分,可以從WSDP1.1 下載。其中jaxb包含在jaxb-1.0子目錄下。
l          設置環境變量 
    要使用JAXB,在下載JAXB開發包以後,還要設置一些環境變量,主要是設置classpath的路徑,以提供JAXB包所帶的庫文件的位置。一般來說,可以寫一個專門用來設置環境變量的執行文件。例如,在Windows 上可以寫這樣一個setenv.bat的文件:
 
set JAVA_HOME=c:/application/java/jdk1.4.1_01
set JWSDP_HOME=c:/application/wsdp1.1
set JAXB_HOME=%JWSDP_HOME%/jaxb-1.0
set JAXB_LIBS=%JAXB_HOME%/lib
set JAXP_LIBS=%JWSDP_HOME%/jaxp-1.2.2/lib
set JWSDP_LIBS=%JWSDP_HOME%/jwsdp-shared/lib

set PATH=%JAXB_HOME%/bin;%JWSDP_HOME%/jwsdpshared/bin;%PATH%

set CLASSPATH=%JAXB_LIBS%/jaxb-api.jar;%JAXB_LIBS%/jaxb-ri.jar;%JAXB_LIBS%/jaxb-xjc.jar;%JAXB_LIBS%/jaxb-libs.jar;%JAXP_LIBS%/jaxb-api.jar;%JAXP_LIBS%/endorsed/xercesImpl.jar;%JAXP_LIBS%/endorsed/xalan.jar;%JAXP_LIBS%/endorsed/sax.jar;%JAXP_LIBS%/endorsed/dom.jar;%JWSDP_LIBS%/jax-qname.jar;%JWSDP_LIBS%/namespace.jar;.
l          確定XML Schema
    要確定XML Schema意味着你要確定一個規則,來約束你的XML文檔,使所有符合這個規則的XML文檔看上去都很類似。例如,在這個例子中,我們希望XML文檔的格式如下:
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<shapeContainer>
        <shape>
              <xposition>185</xposition>
              <yposition>83</yposition>
              <shapename>circle</shapename>
              <shapecolor>red</shapecolor>
        </shape>
        <shape>
              <xposition>169</xposition>
              <yposition>177</yposition>
              <shapename>circle</shapename>
              <shapecolor>green</shapecolor>
        </shape>
        <shape>
              <xposition>358</xposition>
              <yposition>155</yposition>
              <shapename>rect</shapename>
              <shapecolor>green</shapecolor>
        </shape>
</shapeContainer>
    文如其意,用不着太多的解釋,大家就能明白上面的XML文檔所包含的意思。<shapeContainer>表示了畫布,在畫布中有各種各樣的形狀,每個形狀都包含了一些顯示信息,例如x,y座標,形狀的類型和顏色等。在應用程序中想要操縱此XML文檔,還要根據這個XML文檔,產生一個Schema文檔。例如:(此schema下載
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
        <xsd:element name="appversion" type="xsd:string"/>
        <xsd:element name="shapeContainer" type="ShapeContainerType"/>
        <xsd:complexType name="ShapeContainerType">
              <xsd:sequence>
                      <xsd:element name="shape" type="ShapeType" minOccurs="1" maxOccurs="unbounded"/>
              </xsd:sequence>
        </xsd:complexType>
        <xsd:complexType name="ShapeType">
              <xsd:sequence>
                    <xsd:element name="xposition" type="xsd:int"/>
                    <xsd:element name="yposition" type="xsd:int"/>
                    <xsd:element name="shapename" type="ShapeNameType"/>
                    <xsd:element name="shapecolor" type="ShapeColorType"/>
              </xsd:sequence>
        </xsd:complexType>
        <xsd:simpleType name="ShapeColorType">
              <xsd:restriction base="xsd:string">
                      <xsd:enumeration value="green"/>
                      <xsd:enumeration value="red"/>
              </xsd:restriction>
        </xsd:simpleType>
        <xsd:simpleType name="ShapeNameType">
              <xsd:restriction base="xsd:string">
                    <xsd:enumeration value="circle"/>
                    <xsd:enumeration value="rect"/>
             </xsd:restriction>
        </xsd:simpleType>
</xsd:schema>
     這個Schema描述了對xml文檔的約束。例如,
    <xsd:complexType name="ShapeContainerType">
              <xsd:sequence>
                      <xsd:element name="shape" type="ShapeType" minOccurs="1" maxOccurs="unbounded"/>
              </xsd:sequence>
        </xsd:complexType>
    它規定了<shapeContainer>這個節點內可以包含一個或多個<shape>的節點。而  
<xsd:complexType name="ShapeType">
       <xsd:sequence>
                <xsd:element name="xposition" type="xsd:int"/>
                <xsd:element name="yposition" type="xsd:int"/>
          <xsd:element name="shapename" type="ShapeNameType"/>
                <xsd:element name="shapecolor" type="ShapeColorType"/>
          </xsd:sequence>
   </xsd:complexType>
  則規定了每個<shape>節點必須包含x,y座標、形狀類型和顏色等屬性。
  schema的其他部分還規定了顏色屬性由紅色綠色組成,形狀類型屬性圓形方形組成。另外,在Schema中還使用了一些專用的描述符,例如“complexType”“simpleType”“element”“sequence”以及大量的"NameSpace"的知識,我就不一一介紹了,想要詳細瞭解XML Schema請訪問參考資料
  SchemaXML文檔之間的關係,就好象Java中類與實例的關係。每個符合schemaXML文檔,都是這個Schema的一個實例;而Schema本身是一個模板,它規定了XML文檔應該是什麼樣的。
l          使用編譯工具生成相應的Java
    有了Schema文件以後,我們就可以利用JAXB工具包,讓它來替我們生成操縱符合這個Schema規定的所有XML實例文檔的所有Java源代碼。
%JAXB_HOME%/bin/xjc.bat demo.xsd -d src -p epri.jaxb
     其中 
u        %JAXB_HOME%是你安裝JAXB工具包的位置,通常在jwsdp工具包的子目錄下。
u        demo.xsd Schema的文件名,一般以xsd作爲文件名的後綴。
u        -d  的選項,是指定系統生成的Java源代碼所放置的目錄
u        -p  的選項,是指定系統生成的Java源代碼所在的Java Package的名稱。
u        還有更多的選項,請參考JAXB的相關文檔。
    如果運行成功的話,就會在你"-d"選項指定的目錄下產生一些java代碼。如果感興趣的話,大家可以查看這些代碼進行詳細的研究。如果你就想知道怎樣使用它們的話,那麼只需要簡單的瞭解就行了。
l          在應用程序中使用這些代碼
    下面我們分析一下如何在我們的應用程序中使用JAXB工具包替我們生成的代碼。在我們的實例中,主要有兩個Java源文件:JaxbDemoFrame.javaMyCanvas.java。當然,大家也可以從此處下載所有的源代碼。

    JaxbDemoFrame.java中大部分都是處理Frame中組件的代碼。包括畫布、菜單的初始化,以及各種事件的處理代碼。其中和JAXB有關的有JAXB初始化代碼:
 126      //init jaxb
 127       try {
 128           JAXBContext jc = JAXBContext.newInstance( "epri.jaxb" );
 129          
 130           ObjectFactory objFactory = new ObjectFactory();
 131           myContainer = objFactory.createShapeContainer();
                       ......
 134           m = jc.createMarshaller();
 135           u = jc.createUnmarshaller();
 136           m.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE );
 137         
 138          
 139       } catch( JAXBException je ) {
 140           je.printStackTrace();
 141       }
    JAXBContext 提供了一個入口,通過這個入口可以管理必要的XML/Java綁定信息。客戶端應用程序通過newInstance(contextPath)方法得到該類的新實例。contextPath 參數包含一個或多個Java包名,這些Java包就是了JAXB編譯器所產生的接口代碼所在的Java包。該參數值初始化JAXBContext 對象,使得它能夠管理JAXB編譯器產生的接口;而通過ObjectFactory,可以直接生成根節點java對象ShapeContainer;對於MarshallerUnmarshaller,這兩個對象主要是用於Java對象和XML文檔互相轉換的主要接口(Marshaller負責從Java對象到XML文檔的轉換,Unmarshaller負責從XML文檔到Java對象的轉換)。
    在打開文件的操作代碼中,也有對JAXB的操作:
150    private void openFile()  {
151        FileDialog open = new FileDialog(this,"open file..",FileDialog.LOAD);
152        open.setVisible(true);
153        filename = open.getDirectory()+open.getFile();
154      try {
155          myContainer  =(ShapeContainer)u.unmarshal( new FileInputStream(filename));
156          this.canvas.setShapeContainer(myContainer);
157          this.canvas.repaint();
158      } catch( JAXBException je ) {
159          je.printStackTrace();
160      } catch (FileNotFoundException fe) {
161          System.out.println("file not found!");
162      }
163    }

Unmarshaller.unmarshal() 的方法可以直接將一個XML文件裏的數據轉換成Java對象。此對象應該是整個java對象樹中最靠近根的對象(在本例中爲ShapeContainer)。其他的對象都可以通過這個根對象的其他方法進一步獲得,下面的章節會介紹到。
在文件保存的操作代碼中,也有對JAXB的操作:
182   private void saveAsFile() {
            ......
187         m.marshal(myContainer, new FileOutputStream(filename));
            ......
193    }
Marshaller.marshal()的方法是將一個根節點的Java對象,連同這個對象內部所包含的其他所有對象,都按照Schema的要求將對象中的內容輸出到XML文檔中去。
MyCanvas.java主要是畫布的繪圖操作,包含了鼠標事件的響應和繪製屏幕的方法中。在MyCanvas.java也有一些代碼使用了JAXB生成的class
53 public void mousePressed(MouseEvent e) {
        .......

65        java.util.List shapeList = myContainer.getShape();
66        try {
67             ShapeType newType = this.objectFactory.createShapeType();
68            newType.setShapecolor(this.currentColor);
69            newType.setShapename(this.currentgraph);
70            newType.setXposition(this.currentX);
71            newType.setYposition(this.currentY);
72            shapeList.add(newType);

73        } catch( JAXBException je ) {
74            je.printStackTrace();
75        }
76        repaint();
77    }

90  public void paint(Graphics g) {
91        if (myContainer !=null){
92            java.util.List shapeList = myContainer.getShape();
            ......  
           
95            for( Iterator iter = shapeList.iterator(); iter.hasNext(); ) {
96                ShapeType  myshape =  (ShapeType)iter.next();
97                String shapename = myshape.getShapename();
98                String shapeColor = myshape.getShapecolor();
99                int xposition = myshape.getXposition();
100               int yposition = myshape.getYposition();
101              
 if (shapeColor.equals("red"))  g.setColor(Color.red);
102               if (shapeColor.equals("green"))  g.setColor(Color.green);
103               if(shapename.equals("circle")) g.drawOval(xposition-25,yposition-25,50,50);
104                if(shapename.equals("rect")) g.drawRect(xposition-25,yposition-25,50,50);
105                this.setForeground(currentcolor);
106            }
107        }
108    }

在鼠標點擊事件的處理中和屏幕繪製的方法中有大量使用JAXB所生成的類的代碼(黑體字所表示的)。這些代碼比較清楚的表現了怎樣使用JAXB所生成的類。鼠標點擊事件的處理中,根據程序的邏輯,每次鼠標的點擊都應該根據鼠標當前的位置,畫筆的當前顏色和當前的形狀,來創建一個圖形,並將此圖形添加到整個對象樹中去;而在屏幕繪製的paint()方法中,應用程序遍歷整個對象樹,找到每個圖形的屬性,並將它們繪製在屏幕上。
 
JAXB所生成的代碼都有一定的規律(遵循JAXB標準),這些規律非常簡單易用。例如,在Schema中我們規定了,在ShapeContainer中允許有一個或多個shap節點,那麼在生成的ShapeContainer這個類中就getShape()方法來返回一個shape的集合。又比如,在Schema中我們規定了每個shape都有一些屬性(shapename, shapecolor, xposition, yposition)。那麼在相應的ShapeType的類中,我們就會看到getset的一些方法去設置和獲得這些屬性的值。
四. 使用XML和JAXB的優勢
l          簡單易用。
通過上面的例子,大家可以發現所有對XML文檔的操作都隱藏起來了,你的應用程序不用使用SAXDOM接口編程去操縱XML文檔,使程序量降低,錯誤率減少,有助於程序質量的提高。
l          維護性好。
當你的產品需要更新,或是數據模型需要改變時,只需要重新定義Schema,然後讓JAXB重新生成對XML文檔進行操縱的類,使得你的應用程序具有很好的維護性。
l          數據兼容性好。
當你進行版本更新的時候,往往要考慮兼容性問題。由於你使用了XML作爲數據交換的格式,只需要提供XSLT的模板就能將原有的數據格式自動轉換成新版本的格式。除此以外,你可以將你的XML表示的圖形數據文件任意轉換成其他標準的CAD數據格式,一切都在你的掌握之中。
l          有較好的性能。
因爲JAXB在運行時在大部分時間中都是直接操縱內存中的Java對象,只有在讀取和存儲文件的時候纔會與XML文件進行IO操作,實質上是爲XML文件做了一個緩存,因此運行時的性能很好。
五. 使用JAXB的限制
跟直接使用Java對象序列化相比,JAXB也有它的限制。在使用Java序列化的時候,Java對象和XML文檔是直接對應起來的。例如,在畫布上有一個java.awt.Button對象,那麼當你進行序列化的時候,java.awt.Button的所有數據被直接存儲到序列化文件中;在進行圖形恢復的時候,文件中的序列化數據直接就轉換成java.awt.Button對象了。然而在使用JAXB的時候,由於JAXB沒有將XML文檔轉換成指定的Java類的功能,XML和應用程序中間會多一層。例如,你不能要求JAXBXML文檔數據中直接生成java.awt.Button對象,JAXB在大多數環境中只能生成中間進行數據傳遞的對象。用java.awt.Button作爲例子,使用JAXB,你只能根據XML中的數據生成JAXB特有的MyButton對象,在再通過MyButton對象中的數據去初始化java.awt.Button對象。這種間接的數據傳遞所帶來的負面影響是使用JAXB的系統會產生大量傳遞數據的中間對象。雖然說,由於Java虛擬機自動回收內存的特點,大量中間對象的產生有可能在長時間運行的Server應用中降低系統的性能,但是這種數據傳遞的方法在許多系統設計中都是可行的。
六. 總結
本文通過一個簡單的實例程序,演示瞭如何運用JAXBXML設計開發你的應用系統,並且分析了JAXB的優勢和劣勢。現在JAXB越來越多的運用到各種應用程序當中,在越來越多的開放源碼的產品都能看到JAXB的影子。尤其是Web ServicesXML的應用的迅速發展,JAXB越來越受到廣大Java開發人員的重視。
七. 參考資料
2.         JAXB學習文檔 http://java.sun.com/webservices/docs.html
3.         XML Schema資料 http://www.w3.org/XML/Schema
4.         Java對象序列化文檔 http://java.sun.com/j2se/1.4.1/docs/guide/serialization/index.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章