創建你自己的 Java 注解類
如果你已經在使用Java編程,并且也使用了任何像Spring和Hibernate這樣的流行框架,那么你應該對注解的使用非常地熟悉。使用一個現有框架工作的時候,通常使用它的注解就夠了。但是,你是不是也有時候有要創建屬于你自己的注解的需求呢?
不久之前,我找到了一個自己創建一個注解的理由,那是一個涉及驗證存儲在多種數據庫中的常用數據的項目。
場景描述
該業務有多種數據庫都存儲著相同的數據,它們有各自不同的保持數據更新的方法. 該業務曾計劃把所有這些數據都整合到一個主數據庫中,以減輕涉及到多種數據源所帶來的問題的復雜性.
不過在項目開始之前,業務還需要知道數據距離可以同步還有多少差距,并做出任何必要的修正來使其可以進行同步. 第一步需要創建一個展示那些數據多種數據庫的通用數據的報表,并對其值進行驗證, 對那些不符合條件的記錄進行高亮顯示. 這里有一個對當時需求的簡短摘要:
-
比對多種數據庫間公共部分的數據,諸如客戶,公司或者目錄信息.
</li> -
默認的值應該根據值的類型匹配所有的數據庫.
</li> -
對于某些字段,我們只想展示其值,而不要進行任何數據比較.
</li> -
對于某些字段,我們只想要對比其值,并在指定的特定數據源上進行數據驗證.
</li> -
對于某些字段,我們可能想要做一些復雜的數據比較,可能會基于記錄內的其它字段.
</li> -
對于某些字段,我們可能想要用一種特定格式對數據進行格式化,比如錢幣數量 使用 $000,000.00 .
</li> -
報表應該用MS Excel格式的,每一行都包含來自每個數據源的字段值. 任何不匹配數據驗證規則的行都應該用黃色高亮顯示.
</li> </ul>注解
經過一陣子對需求和一些想法的推敲之后,我決定使用注解來驅動對于數據比對和報表處理的配置. 我們需要的東西得是簡單,而高度靈活可擴展的. 這些注解將會是字段級別的,而我就喜歡配置不會被隱藏在classpath某個地方的文件中. 如此,你就能夠直接查看同一個字段相關聯的注解,以便知曉它具體是如何進行處理的.
在最簡單的情況下,注解無非就是一個標記,就只是提供信息而不會對代碼執行的操作本身有直接影響的元數據. 如果你一直在從事Java編程,那么現在你對它們的使用應該相當的熟悉了, 但是可能你從來沒有過創建屬于你自己的注解的需求. 為此,你需要創建一個帶有Java類型@interface的新類型,它將包含能指定元數據詳細信息的要素.
這里有一個來自這個項目的示例:
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface ReconField {
/* Value indicates whether or not the values from the specified sources should be compared or will be used to display values or reference within a rule. @return The value if sources should be compared, defaults to true. */ boolean compareSources() default true;
/* Value indicates the format that should be used to display the value in the report. @return The format specified, defaulting to native. */ ReconDisplayFormat displayFormat() default ReconDisplayFormat.NATIVE;
/* Value indicates the ID value of the field used for matching source values up to the field. @return The ID of the field. */ String id();
/* Value indicates the label that should be displayed in the report for the field. @return The label value specified, defaults to an empty string. */ String label() default "";
/* Value that indicates the sources that should be compared for differences. @return The list of sources for comparison. */ ReconSource[] sourcesToCompare() default {};
}</pre>
這是驅動數據比對過程如何運作的主要注解. 它包含的基本要素,可以滿足不同數據源間數據進行比較的大部分需求. @ReconField 可以處理除更加復雜的比對之外,我們所期望的大多數需求, 而更加復雜的情況我們將會在稍后有所討論. 這些要素的大多數在代碼清單中一對一的注釋中都有介紹, 而需要指出的是,在我們的@ReconField上有幾個關鍵的注解.
-
@Target – 這個注解可以讓你來指定你的注解應該被用在那個java元素上. 可能的目標類型是 ANNOTATION_TYPE, CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER 和 TYPE. 在我們的 @ReconField 注解中他被指定到了 FIELD 級別.
-
@Retention – 它可以讓你指定注解在何時生效. 可能的值有 CLASS, RUNTIME 和 SOURCE. 因為我們將會在運行時 RUNTIME 處理這個注解, 所以那就是我們需要設置的值.
</ul>
這一數據驗證過程將會為每一個數據庫運行一次查詢,并且將結果映射到展示出針對特定業務記錄類型所有字段的實體bean中. 映射數據實體的每一個字段上的注解會告訴處理器如何為特定字段及在每個數據庫中找到的其值執行數據比對. 因此讓我們來看幾個示例來了解這些注解是如何被運用于不同的數據比對配置的.
為了驗證現有的值并同每個數據源中的只精確匹配,你只需要提供一個字段ID以及將會展示在報表上字段的標記.
@ReconField(id = CUSTOMER_ID, label = "Customer ID") private String customerId;
為了展示在每個數據源中找到的值,但不做任何數據比對,你可能需要制定 compareSources 元素,并將其值設置為false.
@ReconField(id = NAME, label = "NAME", compareSources = false) private String name;
為了驗證在指定數據源中找到的值,而不是全部,你可能會使用到 elementsourcesToCompare. 使用這個東西會展示所有找到的值,但是只對在元素中列出的數據源中找到的值進行比對. 這樣就能處理有些不是在每一個數據源中都會存儲的數據場景了. ReconSource 是一個包含了可以用來進行比對的數據源的枚舉類型.
@ReconField(id = PRIVATE_PLACEMENT_FLAG, label = "PRIVATE PLACEMENT FLAG", sourcesToCompare ={ ReconSource.LEGACY, ReconSource.PACE }) private String privatePlacementFlag;
現在我們已經滿足了我們的基本需求,我們需要解決實現指定字段來進行復雜數據比對能力的問題. 為此,我們將創建第二個注解,來驅動定制規則處理.
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface ReconCustomRule {
/* Value indicates the parameters used to instantiate a custom rule processor, the default value is no parameters. @return The String[] of parameters to instantiate a custom rule processor. */ String[] params() default {};
/* Value indicates the class of the custom rule processor to be used in comparing the values from each source. @return The class of the custom rule processor. */ Class<?> processor() default DefaultReconRule.class;
}</pre>
同之前的注解非常類似,最大的不同在于 @ReconCustomRule 注解中我們指定了一個類,它可以在重建處理執行時執行數據比對. 你可以只定義將會被用到的類,那樣你的處理器就可以實例化并初始化你所指定的類. 在這個注解中指定的類將需要實現一個通用的規則接口,它將會被規則處理器用來執行規則.
現在讓我們來看看使用這個注解 的例子.
在本例中,我們使用了一個自定義的規則,它將會檢查股票交易所是不是 United States,如果是則跳過這條數據. 為此,這條規則將需要檢查同一記錄中的 exchange country 字段.
@ReconField(id = STREET_CUSIP, label = "STREET CUSIP", compareSources = false) @ReconCustomRule(processor = SkipNonUSExchangeComparisonRule.class) private String streetCusip;
這里的示例我們為自定義規則指定了一個參數,這里它是一個包容量. 對于這種特殊的數據比對,被比較的值不能偏離超過1000.通過使用指定了包容量的參數,我們就可以使用不同的包容量將同一套自定義規則運用到多個字段上. 唯一的缺點就是,由于注解的性質,這些參數都只能是靜態的,所以不能動態的修改.
@ReconField(id = USD_MKT_CAP, label = "MARKET CAP USD", displayFormat = ReconDisplayFormat.NUMERIC_WHOLE, sourcesToCompare = { ReconSource.LEGACY, ReconSource.PACE, ReconSource.BOB_PRCM }) @ReconCustomRule(processor = ToleranceAmountRule.class, params = { "10000" }) private BigDecimal usdMktCap;
如你所見,我們只使用了幾個簡單的注解,就設計出了一個具有相當程度靈活性的面向多數據庫場景的數據驗證報告功能. 在這個特殊情況下,注解驅動了數據的比對過程,因此我們實際上就是使用了注解在找到的映射數據實體上進行計算并直接使用它們進行處理.
結論
關于Java注解能做什么以及它們的使用規則,已經有了相當多的文章。 本文更多的是聚焦于一個實例,借以說明為什么你需要考慮使用注解,同時也能直觀的看看有什么好處。
要記住這只是一個起步,一旦你決定要創建使用注解,你還要想清楚要怎樣處理它們才能真正發揮他們的長處。在后面的文章中,我將向你們展示如何使用Java反射來處理注解。在那之前,有一些不錯的關于Java注解的資料可以看看:
-
Java注解教程 - http://docs.oracle.com/javase/tutorial/java/annotations/
</li> - </li>
-
注解是如何工作的 - http://java.dzone.com/articles/how-annotations-work-java
</li> </ul>– Jonny Hackett, asktheteam@keyholesoftware.com
-