如果你已經(jīng)在使用Java編程,并且也使用了任何像Spring和Hibernate這樣的流行框架,那么你應(yīng)該對注解的使用非常地熟悉。使用一個現(xiàn)有框架工作的時候,通常使用它的注解就夠了。但是,你是不是也有時候有要創(chuàng)建屬于你自己的注解的需求呢?
不久之前,我找到了一個自己創(chuàng)建一個注解的理由,那是一個涉及驗(yàn)證存儲在多種數(shù)據(jù)庫中的常用數(shù)據(jù)的項(xiàng)目。
場景描述
該業(yè)務(wù)有多種數(shù)據(jù)庫都存儲著相同的數(shù)據(jù),它們有各自不同的保持?jǐn)?shù)據(jù)更新的方法. 該業(yè)務(wù)曾計(jì)劃把所有這些數(shù)據(jù)都整合到一個主數(shù)據(jù)庫中,以減輕涉及到多種數(shù)據(jù)源所帶來的問題的復(fù)雜性.
不過在項(xiàng)目開始之前,業(yè)務(wù)還需要知道數(shù)據(jù)距離可以同步還有多少差距,并做出任何必要的修正來使其可以進(jìn)行同步. 第一步需要創(chuàng)建一個展示那些數(shù)據(jù)多種數(shù)據(jù)庫的通用數(shù)據(jù)的報表,并對其值進(jìn)行驗(yàn)證, 對那些不符合條件的記錄進(jìn)行高亮顯示. 這里有一個對當(dāng)時需求的簡短摘要:
- 比對多種數(shù)據(jù)庫間公共部分的數(shù)據(jù),諸如客戶,公司或者目錄信息.
- 默認(rèn)的值應(yīng)該根據(jù)值的類型匹配所有的數(shù)據(jù)庫.
- 對于某些字段,我們只想展示其值,而不要進(jìn)行任何數(shù)據(jù)比較.
- 對于某些字段,我們只想要對比其值,并在指定的特定數(shù)據(jù)源上進(jìn)行數(shù)據(jù)驗(yàn)證.
- 對于某些字段,我們可能想要做一些復(fù)雜的數(shù)據(jù)比較,可能會基于記錄內(nèi)的其它字段.
- 對于某些字段,我們可能想要用一種特定格式對數(shù)據(jù)進(jìn)行格式化,比如錢幣數(shù)量 使用 $000,000.00 .
- 報表應(yīng)該用MS Excel格式的,每一行都包含來自每個數(shù)據(jù)源的字段值. 任何不匹配數(shù)據(jù)驗(yàn)證規(guī)則的行都應(yīng)該用黃色高亮顯示.
注解
經(jīng)過一陣子對需求和一些想法的推敲之后,我決定使用注解來驅(qū)動對于數(shù)據(jù)比對和報表處理的配置. 我們需要的東西得是簡單,而高度靈活可擴(kuò)展的. 這些注解將會是字段級別的,而我就喜歡配置不會被隱藏在classpath某個地方的文件中. 如此,你就能夠直接查看同一個字段相關(guān)聯(lián)的注解,以便知曉它具體是如何進(jìn)行處理的.
在最簡單的情況下,注解無非就是一個標(biāo)記,就只是提供信息而不會對代碼執(zhí)行的操作本身有直接影響的元數(shù)據(jù). 如果你一直在從事Java編程,那么現(xiàn)在你對它們的使用應(yīng)該相當(dāng)?shù)氖煜ち? 但是可能你從來沒有過創(chuàng)建屬于你自己的注解的需求. 為此,你需要創(chuàng)建一個帶有Java類型@interface的新類型,它將包含能指定元數(shù)據(jù)詳細(xì)信息的要素.
這里有一個來自這個項(xiàng)目的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
@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 {}; } |
這是驅(qū)動數(shù)據(jù)比對過程如何運(yùn)作的主要注解. 它包含的基本要素,可以滿足不同數(shù)據(jù)源間數(shù)據(jù)進(jìn)行比較的大部分需求. @ReconField 可以處理除更加復(fù)雜的比對之外,我們所期望的大多數(shù)需求, 而更加復(fù)雜的情況我們將會在稍后有所討論. 這些要素的大多數(shù)在代碼清單中一對一的注釋中都有介紹, 而需要指出的是,在我們的@ReconField上有幾個關(guān)鍵的注解.
@Target – 這個注解可以讓你來指定你的注解應(yīng)該被用在那個java元素上. 可能的目標(biāo)類型是 ANNOTATION_TYPE, CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER 和 TYPE. 在我們的 @ReconField 注解中他被指定到了 FIELD 級別.
@Retention – 它可以讓你指定注解在何時生效. 可能的值有 CLASS, RUNTIME 和 SOURCE. 因?yàn)槲覀儗谶\(yùn)行時 RUNTIME 處理這個注解, 所以那就是我們需要設(shè)置的值.
這一數(shù)據(jù)驗(yàn)證過程將會為每一個數(shù)據(jù)庫運(yùn)行一次查詢,并且將結(jié)果映射到展示出針對特定業(yè)務(wù)記錄類型所有字段的實(shí)體bean中. 映射數(shù)據(jù)實(shí)體的每一個字段上的注解會告訴處理器如何為特定字段及在每個數(shù)據(jù)庫中找到的其值執(zhí)行數(shù)據(jù)比對. 因此讓我們來看幾個示例來了解這些注解是如何被運(yùn)用于不同的數(shù)據(jù)比對配置的.
為了驗(yàn)證現(xiàn)有的值并同每個數(shù)據(jù)源中的只精確匹配,你只需要提供一個字段ID以及將會展示在報表上字段的標(biāo)記.
1
2
|
@ReconField (id = CUSTOMER_ID, label = "Customer ID" ) private String customerId; |
為了展示在每個數(shù)據(jù)源中找到的值,但不做任何數(shù)據(jù)比對,你可能需要制定 compareSources 元素,并將其值設(shè)置為false.
1
2
|
@ReconField (id = NAME, label = "NAME" , compareSources = false ) private String name; |
為了驗(yàn)證在指定數(shù)據(jù)源中找到的值,而不是全部,你可能會使用到 elementsourcesToCompare. 使用這個東西會展示所有找到的值,但是只對在元素中列出的數(shù)據(jù)源中找到的值進(jìn)行比對. 這樣就能處理有些不是在每一個數(shù)據(jù)源中都會存儲的數(shù)據(jù)場景了. ReconSource 是一個包含了可以用來進(jìn)行比對的數(shù)據(jù)源的枚舉類型.
1
2
|
@ReconField (id = PRIVATE_PLACEMENT_FLAG, label = "PRIVATE PLACEMENT FLAG" , sourcesToCompare ={ ReconSource.LEGACY, ReconSource.PACE }) private String privatePlacementFlag; |
現(xiàn)在我們已經(jīng)滿足了我們的基本需求,我們需要解決實(shí)現(xiàn)指定字段來進(jìn)行復(fù)雜數(shù)據(jù)比對能力的問題. 為此,我們將創(chuàng)建第二個注解,來驅(qū)動定制規(guī)則處理.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@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 ; } |
同之前的注解非常類似,最大的不同在于 @ReconCustomRule 注解中我們指定了一個類,它可以在重建處理執(zhí)行時執(zhí)行數(shù)據(jù)比對. 你可以只定義將會被用到的類,那樣你的處理器就可以實(shí)例化并初始化你所指定的類. 在這個注解中指定的類將需要實(shí)現(xiàn)一個通用的規(guī)則接口,它將會被規(guī)則處理器用來執(zhí)行規(guī)則.
現(xiàn)在讓我們來看看使用這個注解 的例子.
在本例中,我們使用了一個自定義的規(guī)則,它將會檢查股票交易所是不是 United States,如果是則跳過這條數(shù)據(jù). 為此,這條規(guī)則將需要檢查同一記錄中的 exchange country 字段.
1
2
3
|
@ReconField (id = STREET_CUSIP, label = "STREET CUSIP" , compareSources = false ) @ReconCustomRule (processor = SkipNonUSExchangeComparisonRule. class ) private String streetCusip; |
這里的示例我們?yōu)樽远x規(guī)則指定了一個參數(shù),這里它是一個包容量. 對于這種特殊的數(shù)據(jù)比對,被比較的值不能偏離超過1000.通過使用指定了包容量的參數(shù),我們就可以使用不同的包容量將同一套自定義規(guī)則運(yùn)用到多個字段上. 唯一的缺點(diǎn)就是,由于注解的性質(zhì),這些參數(shù)都只能是靜態(tài)的,所以不能動態(tài)的修改.
1
2
3
4
|
@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; |
如你所見,我們只使用了幾個簡單的注解,就設(shè)計(jì)出了一個具有相當(dāng)程度靈活性的面向多數(shù)據(jù)庫場景的數(shù)據(jù)驗(yàn)證報告功能. 在這個特殊情況下,注解驅(qū)動了數(shù)據(jù)的比對過程,因此我們實(shí)際上就是使用了注解在找到的映射數(shù)據(jù)實(shí)體上進(jìn)行計(jì)算并直接使用它們進(jìn)行處理.