Classworking 工具箱: 注釋與配置文件

發表于:2007-05-24來源:作者:點擊數: 標簽:Classworking配置文件工具箱注釋
注釋允許您將元數據指定為源代碼的一部分。使用這個特性,可以將工具指令嵌入代碼,而不是創建單獨的配置文件(需要與源代碼同時進行維護)。但是,根據 Java 咨詢顧問 Dennis Sosnoski 的解釋,配置文件仍然有它們的用處,尤其是對于那些橫切應用程序源代碼
注釋允許您將元數據指定為源代碼的一部分。使用這個特性,可以將工具指令嵌入代碼,而不是創建單獨的配置文件(需要與源代碼同時進行維護)。但是,根據 Java™ 咨詢顧問 Dennis Sosnoski 的解釋,配置文件仍然有它們的用處,尤其是對于那些橫切應用程序源代碼結構的類似方面的函數。

注釋是 J2SE 5.0 的一個主要的新特性,其設計目標是允許將關于代碼的元數據定義為代碼的一部分。使元數據與它引用的實際代碼實現內聯,從而提供引用的本地性,使所有事物都匯集在一個地方。在對應的代碼發生變化時,這種方式可以更容易地維護元數據。但是,注釋也有一些弱點。

除了將元數據與代碼內聯之外,還可以用其他方法定義元數據?;?ldquo;配置文件方式就是使用獨立的非 Java 文件表達某種形式的元數據”這一認識,我針對這期專欄,在“配置文件”這個通用標題下將這些替代方法收集在一起。這種方式產生了潛在的維護問題,因為元數據必須引用物理上分離的代碼。如果代碼發生變化,那么就可能需要修改元數據,并且采用配置文件的方式表示元數據,您必須記得要找出單獨的配置文件并修改它。

在這一期中,我將討論我認為的這兩種方式的利弊,并研究在將 Java 標準進程和許多開源項目轉換成始終采用基于注釋的方式表示元數據時,對開發人員有意義的東西。

歷史背景

在開發 Java 平臺之前,配置文件已經應用了很長一段時間,但是通常只是用來提供運行時參數,控制應用程序的執行。J2EE 平臺和相關組件標準的開發使得另外一種配置文件得以廣泛應用,這類文件包含框架用來處理應用程序代碼的信息。最早的一個示例是用于 Java servlet 部署配置的 web.xml 文件。

Java 平臺支持動態的類裝入和通過反射在運行時訪問類元數據,提供了許多不同的利用配置文件,在運行時控制程序裝入的方式。開發人員很快就利用上了這種方式提供的靈活性。但是隨著框架配置文件的泛濫,開發人員發現生成和維護這些文件太復雜。對 Java 源代碼進行修改可能要求對配置文件也要進行修改,但是在源代碼中沒有任何跡象表現出這種需求 —— 相反,開發人員需要清楚源代碼和配置文件之間的連接,并記得保持它們同步。

進入 XDoclet

流行的 XDoclet 工具的開發目的是幫助解決管理文件之間的這個連接問題。XDoclet 不以獨立文件的方式維護配置信息,而是允許使用特殊的 Javadoc 標簽在 Java 源代碼中嵌入配置信息。使用 XDoclet,可以將配置信息與源代碼保存在一起,這樣需要跟蹤的事情就都在一個地方了。

XDoclet 實際上不只是擅長生成配置文件。它最初的設計目標是用于 EJB,除了實際的配置文件之外,對于每個 bean,EJB 還需要多個樣板文件。XDoclet 可以根據基本的 bean 源代碼(受特殊的 Javadoc 標簽控制)替 EJB 生成這些樣板文件。隨著 XDoclet 已經擴展成可以處理許多 EJB 之外的其他應用,這個代碼生成功能也得到了擴展。在需要樣板代碼的地方,它是極為有用的工具,可以幫助源代碼樹消除那些對應用程序的操作毫無用處的混亂。

XDoclet 也有一些限制。因為它使用簡單的轉換將 Javadoc 樣式的注釋轉換到配置文件,所以無法檢測配置在許多方面的準確性。過去,我曾經由于拼寫錯誤或者指定值的逗號不匹配,在使用 XDoclet 時有過不幸遭遇。這類錯誤會帶入生成的代碼或配置文件,從而導致難以回溯問題的根源。

用注釋來營救?

受 XDoclet 的成功的部分影響,JSR-175 在 2002 年成形了 ,它提供了一個標準的機制,以任意屬性信息的形式,將元數據與具體的 Java 類、接口、方法和字段關聯起來。雖然在 JSR 草案中列出了與 XDoclet 類似的定制 Javadoc 標簽,并且這可以作為一種可能的實現,但是 JSR-175 元數據支持的最終形式所采用的方式完全不同。該支持對 Java 語言定義注釋的方式進行了擴展,在任何 Java 組件聲明的前面,都可以添加一組類似 JavaBean 的名稱-值對來作為修飾符。

注釋消除了 XDoclet 方式的許多限制。通過使用類型化的值并允許將特定注釋限制在只能用于某種類型的 Java 組件上,注釋支持的驗證級別比 XDoclet 提供的驗證級別更高。因為注釋集成在 Java 語言的定義中,所以處理它們更容易一些(只要正在使用 JDK 5.0 即可 —— 關于在早期的 JVM 中使用注釋的一些指示,請參閱 上月的專欄)。從使用的角度來說,注釋更加靈活,注釋擁有一些選項,這些選項可以指定編譯器的類文件輸出中是否包含注釋信息,是否允許應用程序在運行時使用這些信息。

從 JSR-175 出現開始,人們就對將元數據定義成其他 JSR 的組成部分很感興趣。在作出在 J2SE 5.0 中包含注釋的決定之后,注釋的使用就成為許多計劃在 J2SE 5.0 發布之前完成的新 JSR 的一部分。我們先從了解目前這些提交公眾評論的 JSR 開始,然后,在下一節中,我將使用其中一個示例。





權衡

注釋代表對于將配置信息嵌入 Java 源代碼中的 XDoclet 規則的極大改進。這種方式的主要優勢是:每件事都在一個位置上,配置信息直接與 Java 組件關聯。由于這個原因,源代碼的許多重構類型對于注釋來說都是透明的,因為注釋總是應用在它們附著的組件上,即使在組件移動或改名時也是如此。對于要求創建新注釋或修改注釋的其他重構來說,每件事都處于相同的位置,從而確保開發人員能夠看到注釋,并增加開發人員記得進行必要的修改的可能性。

在注釋的優勢之外,我看到了它們過度使用的兩個主要不足。第一個不足是:源代碼可能充斥著各種各樣與實際程序邏輯沒有關系的注釋,防礙了代碼的可讀性。第二個不足是:雖然注釋對于與某個組件相關的元數據來說很理想,但是對于跨組件應用程序的元數據,它并不是非常合適。

而在另一方面 ,對于應用程序中跨越不同組件的關系網絡,配置文件可以提供一個有組織的視圖。因為它們獨立于實際的源代碼,所以不會防礙 Java 源代碼的可讀性。配置文件的主要不足就是前面所提到的:它們是獨立的工件,需要與應用程序的源代碼并行維護,而在兩者之間沒有明顯的聯系。

注釋誤用

作為我認為的注釋誤用的一個示例,清單 1 給出了 JAX-RPC 2.0 的早期訪問版本提供的注釋示例的一段摘錄(這段代碼是在早期訪問發行版使用的 Java Research License 的發行許可之下出現在這里的)。聲明: 雖然我是 JAX-RPC 2.0 專家組的成員,但這里所表述的觀點,僅屬于我個人的意見,并不代表整個專家組。


清單 1. JAX-RPC 2.0 注釋示例

            /*
            * Copyright (c) 2005 Sun Microsystems, Inc.
            * All rights reserved.
            */
            package annotations.server;
            import java.rmi.Remote;
            import java.rmi.RemoteException;
            import javax.jws.WebService;
            import javax.jws.WebMethod;
            import javax.jws.soap.SOAPBinding;
            import javax.jws.WebResult;
            import javax.jws.WebParam;
            @WebService(targetNamespace = "http://duke.org", name="AddNumbers")
            @SOAPBinding(style=SOAPBinding.Style.RPC, use=SOAPBinding.Use.LITERAL)
            public interface AddNumbersIF extends Remote {
            @WebMethod(operationName="add", soapAction="urn:addNumbers")
            @WebResult(name="return")
            public int addNumbers(
            @WebParam(name="num1")int number1,
            @WebParam(name="num2")int number2) throws RemoteException, AddNumbersException;
            }
            /*
            * Copyright (c) 2005 Sun Microsystems, Inc.
            * All rights reserved.
            */
            package annotations.server;
            import javax.jws.WebService;
            @WebService(endpointInterface="annotations.server.AddNumbersIF")
            public class AddNumbersImpl {
            /**
            * @param number1
            * @param number2
            * @return The sum
            * @throws AddNumbersException
            *             if any of the numbers to be added is negative.
            */
            public int addNumbers(int number1, int number2) throws AddNumbersException {
            if (number1 < 0 || number2 < 0) {
            throw new AddNumbersException("Negative number can't be added!",
            "Numbers: " + number1 + ", " + number2);
            }
            return number1 + number2;
            }
            }
            

清單 1 的代碼包含一個 Web 服務接口定義和對應的實現類。雖然接口定義是用 Java 代碼表示的(或者是這樣的代碼 —— 我在最近的一次講座中介紹這個示例時,有位聽眾指出,如果我不是像這樣指出的話,他可能認不出這是 Java 代碼),它實際上是用注釋編碼的配置文件。

清單 1 中使用的注釋是否確實提供了比獨立配置文件更多的好處呢?清單 2 給出了一個配置文件,提供了與清單 1 相同的信息(使用 Apache Axis 的內部配置格式)。對我來說,配置文件的形式看起來更整齊一些,因此也就比示例中顯示的一堆注釋更容易維護。如果確實想把實現類作為 Web 服務綁定到它的使用上(清單 1 實現類中的 @WebService 注釋的一些明顯之處),那么可以仍然將注釋只用作此目的(引用配置文件,而不是引用接口)。否則,根本不需要在實現類中包含任何特殊信息。


清單 2. 與清單 1 的注釋等價的配置文件

            <deployment xmlns="http://xml.apache.org/axis/wsdd/"
            xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
            <service name="AddNumbers" provider="java:RPC" style="rpc" use="literal">
            <namespace>http://duke.org</namespace>
            <parameter name="className" value="annotations.server.AddNumbersImpl"/>
            <parameter name="allowedMethods" value="add"/>
            <operation name="add" qname="tns:addNumbers" returnQName="return">
            <parameter name="num1"/>
            <parameter name="num2"/>
            </operation>
            </service>
            </deployment>
            

注釋的敗筆之處

在最后一節,我給出一個示例,我認為它是注釋使用不當的示例,在這個示例中,獨立的配置文件可以工作得很好,并且更容易理解。作為配置文件比注釋工作得好的示例,我將轉用 JiBX 數據綁定框架 —— 這是我自己喜歡的類處理框架。

JiBX 使用字節碼增強向編譯后的類添加方法,以便在類的實例與 XML 之間進行轉換。Java 類和 XML 之間的關系由綁定定義決定,綁定定義是一種 XML 配置文件的工。這個配置由 JiBX 框架的綁定編譯器組件處理,它實現了編譯后的類的實際字節碼增強。

JiBX 與其他數據綁定框架的不同之處(不談速度 —— 它一般要比其他框架快幾倍)是 Java 類與 XML 之間關系的靈活性。大多數數據綁定框架假設 Java 類的結構與 XML 的結構進行匹配,這樣,包含屬性或子元素的元素就與類似 JavaBean 的類對應,而單一屬性或單一子元素則對應著類似 JavaBean 的類的簡單屬性。JiBX 消除了這個假設,采用的綁定定義支持 Java 類與 XML 表示之間的結構性差異。JiBX 甚至還支持對相同的類進行多重綁定,實際的綁定將用于運行時選定的某個特殊 XML 文檔。

為了說明問題,我將提供一個 JiBX 靈活性的簡單示例。清單 3 顯示了一對數據類,清單 4 和清單 5 則分別給出了這兩個類的綁定定義和對應的 XML 文檔。兩個示例 XML 文檔表示的都是相同的數據,多數開發人員會發現,雖然這兩個文檔的結構明顯不同,但很容易將這兩個文檔與清單 3 的 Java 類聯系在一起。多數開發人員還會發現,可以很容易地理解對應的綁定定義是如何與每個 XML 文檔的結構關聯的。


清單 3. XML 綁定的數據類

            package simple;
            public class Customer
            {
            private Name name;
            private String street1;
            private String street2;
            private String city;
            private String state;
            private String zip;
            private String phone;
            ...
            }
            package simple;
            public class Name
            {
            private String firstName;
            private String lastName;
            ...
            }
            


清單 4. 第一個綁定和 XML 文檔

            <binding name="binding1">
            <mapping name="customer" class="simple.Customer">
            <structure field="name">
            <value name="first-name" field="firstName"/>
            <value name="last-name" field="lastName"/>
            </structure>
            <value name="street" field="street1"/>
            <value name="city" field="city"/>
            <value name="state" field="state"/>
            <value name="zip" field="zip"/>
            <value name="phone" field="phone"/>
            </mapping>
            </binding>
            <customer>
            <first-name>John</first-name>
            <last-name>Smith</last-name>
            <street>12345 Happy Lane</street>
            <city>Plunk</city>
            <state>WA</state>
            <zip>98059</zip>
            <phone>888.555.1234</phone>
            </customer>
            


清單 5. 第二個綁定和 XML 文檔

            <binding name="binding2">
            <mapping name="customer" class="simple.Customer">
            <structure name="name" field="name">
            <value name="first-name" field="firstName"/>
            <value name="last-name" field="lastName"/>
            </structure>
            <structure name="address">
            <value name="street" field="street1"/>
            <value name="city" field="city"/>
            <value style="attribute" name="state" field="state"/>
            <value style="attribute" name="zip" field="zip"/>
            </structure>
            <value style="attribute" name="phone" field="phone"/>
            </mapping>
            </binding>
            <customer phone="888.555.1234">
            <name>
            <first-name>John</first-name>
            <last-name>Smith</last-name>
            <name>
            <address state="WA" zip="98059">
            <street>12345 Happy Lane</street>
            <city>Plunk</city>
            </address>
            </customer>
            

現在考慮一下,如果使用注釋來定義清單 4 和清單 5 的綁定,將涉及哪些內容。清單 6 給出了這類注釋的一個版本。在這里,我假設了一些表示能在綁定定義中使用的不同選項的注釋,其中大多數注釋都有進行單一綁定(使用簡單值)和多重綁定(使用一組值)的變體。即使對于這個簡單示例,結果也非?;靵y,至少對我來說,閱讀這樣的 Java 代碼(比起未加注釋的版本)或者了解綁定定義(比起清單 4 和清單 5 的定義)似乎更難一些。


清單 6. 為了綁定而進行注釋的 Java 類

            package simple;
            // Need the JiBXAddedWrappers annotation here because there's no other place to
            //  put it. The ugly format of the annotation is necessary because you can't
            //  repeat an annotation, and can only have array values of base types.
            @JiBXMapping(names={"binding1", "binding2"})
            @JiBXAddedWrappers(bindings={"binding2"}, names={"address"}, field-sets={"street1,city.state,zip"})
            public class Customer
            {
            @JiBXStructure(bindings={"binding1", "binding2"}, names={"", "name"}) private Name name;
            @JiBXValue(name="street") private String street1;
            private String street2;
            @JiBXValue private String city;
            @JiBXValue(bindings={"binding1", "binding2"}, styles={element, attribute})
            private String state;
            @JiBXValue(bindings={"binding1", "binding2"}, styles={element, attribute})
            private String zip;
            @JiBXValue(bindings={"binding1", "binding2"}, styles={element, attribute})
            private String phone;
            ...
            }
            package simple;
            public class Name
            {
            @JiBXValue(name="first-name") private String firstName;
            @JiBXValue(name="last-name") private String lastName;
            ...
            }
            

在我開始開發 JiBX 時,我最初計劃提供 XDoclet 支持,為那些不想維護獨立綁定文件的用戶提供一個方便的備選解決方案。但是,因為試圖表現 JiBX 綁定的全面靈活性,這很快使事物變得一團糟。XDoclet 標簽承受了與注釋相同的限制,所以 XDoclet 版本的綁定看起來與清單 6 的注釋版本很相似。所以我決定最好還是強制使用綁定定義文件,而不是慫恿用戶陷入用標簽定義綁定元數據的歧路。

最佳實踐

在這一點上,我希望已經說服了您:注釋并不總是比配置文件好。這就留下了一個問題,也就是說,要判斷在具體的使用情況下,哪種方式工作得更好。對我來說,看起來答案與需要表示的配置信息類型有關。

對于總要與特定 Java 組件(類、方法或字段)連接的配置信息,用注釋表示是一個好的選擇。當配置是代碼的核心目標時,注釋工作得特別好。因為注釋方面的限制,所以當每個組件只有一個配置時,注釋也是最適合的。如果需要處理多重配置,特別是配置取決于包含注釋的 Java 類之外的東西時,注釋帶來的問題可能要比它們解決的問題還要多。最后,如果不重新編譯 Java 源代碼,就不能修改注釋,所以要求在運行時配置的內容不能使用注釋。

早期專欄 中,我提供了一個使用注釋自動構建 toString() 方法的示例。雖然這只是一個微不足道的示例,但是我認為它確實表現了注釋的正確用法:注釋適用于某個直接涉及代碼的目標(toString() 方法),不要求多重配置(因為每個類永遠只能有一個方法實例),只在類單元內部工作,并且不需要在運行時進行改變。

而另一方面,當以協作的方式使用不同的 Java 組件時,配置文件工作得最好。最后一節的 JiBX 綁定定義就是這個事實的一個最好例子:用注釋將配置嵌入代碼中會使代理和配置都難以被人們所理解。我將數據綁定函數看作是將要應用到程序程序的方面(從 AOP 的角度),這甚至超出了這個混淆問題的范圍。AOP 的立場來看,最好是把方面信息(在該例是綁定定義)放在代碼之外。





結束語

在這期專欄中,我介紹了我認為是用基于注釋的方法表示配置信息的弱點的地方。除了這些弱點,我認為注釋是對 Java 開發人員工具包非常有用的一個補充。另一方面,我還認為它會被會濫用,而目前正在開發的一些 Java 標準可能正朝著這個方向發展。

對于我自己的 JiBX 項目,我傾向于依然采用配置文件,因為我認為這些對表示 JiBX 綁定更合適一些。即使如此,我對在 JiBX 2.0 中添加對 JAXB 2.0 注釋的支持也很感興趣(希望直接采用注釋,并把它們轉換成 JiBX 綁定定義文件)。通過這種方法,真想采用注釋的用戶可以使用標準的表示方式,而想得到 JiBX 綁定的全面靈活性的用戶則可以轉而采用綁定定義文件。

下個月,我將略微深入地研究 JiBX。在撰寫這一期的文章時,我已經完成 JiBX 1.0 的生產發行版本,并計劃立刻開始著手對 2.0 版本進行有計劃的改進。對于我從 JiBX 1.0 的開發過程中學到的關于類處理是什么和不是什么的知識,現在是做一總結的好時候,這就是我在下個月的專欄中將要做的工作。請回過頭來看看在 JiBX 字節碼生成內核幕后所做的工作。

原文轉自:http://www.anti-gravitydesign.com

評論列表(網友評論僅供網友表達個人看法,并不表明本站同意其觀點或證實其描述)
国产97人人超碰caoprom_尤物国产在线一区手机播放_精品国产一区二区三_色天使久久综合给合久久97