创建你自己的 Java 注解类

文章转至:http://www.oschina.net/translate/creating-your-own-java-annotations?cmp


If you’ve been programming in Java and using any one of the popular frameworks like Spring and Hibernate, you should be very familiar with using annotations. When working with an existing framework, its annotations typically suffice. But, have you ever found a need to create your own annotations?

Not too long ago, I found a reason to create my own annotations for a project that involved verifying common data stored in multiple databases.

The Scenario

The business had multiple databases that were storing the same information and had various means of keeping the data up to date. The business had planned a project to consolidate the data into a master database to alleviate some of the issues involved with having multiple sources of data.

译者信息

如果你已经在使用Java编程,并且也使用了任何像Spring和Hibernate这样的流行框架,那么你应该对注解的使用非常地熟悉。使用一个现有框架工作的时候,通常使用它的注解就够了。但是,你是不是也有时候有要创建属于你自己的注解的需求呢?

不久之前,我找到了一个自己创建一个注解的理由,那是一个涉及验证存储在多种数据库中的常用数据的项目。

场景描述

该业务有多种数据库都存储着相同的数据,它们有各自不同的保持数据更新的方法. 该业务曾计划把所有这些数据都整合到一个主数据库中,以减轻涉及到多种数据源所带来的问题的复杂性.

Before the project could begin however, the business needed to know how far out of sync the data was and make any necessary corrections to get in back in sync. The first step required creating a report that showed common data that belonged in multiple databases and validated the values, highlighting any records that didn’t match according to the reconciliation rules defined. Here’s a short summary of the requirements at the time:

  • Compare the data between multiple databases for a common piece of data, such as a customer, company, or catalog information.

  • By default the value found should match exactly across all of the databases based upon the type of value.

  • For certain fields we only want to display the value found and not perform any data comparison.

  • For certain fields we only want to compare the value found and perform data verification on the specific data sources specified.

  • For certain fields we may want to do some complicated data comparisons that may be based on the value of other fields within the record.

  • For certain fields we may want to format the data in a specific format, such as $000,000.00 for monetary amounts.

  • The report should be in MS Excel format, each row containing the field value from each source. Any row that doesn’t match according to the data verification rules should be highlighted in yellow.

译者信息

不过在项目开始之前,业务还需要知道数据距离可以同步还有多少差距,并做出任何必要的修正来使其可以进行同步. 第一步需要创建一个展示那些数据多种数据库的通用数据的报表,并对其值进行验证, 对那些不符合条件的记录进行高亮显示. 这里有一个对当时需求的简短摘要:

  • 比对多种数据库间公共部分的数据,诸如客户,公司或者目录信息.

  • 默认的值应该根据值的类型匹配所有的数据库.

  • 对于某些字段,我们只想展示其值,而不要进行任何数据比较.

  • 对于某些字段,我们只想要对比其值,并在指定的特定数据源上进行数据验证.

  • 对于某些字段,我们可能想要做一些复杂的数据比较,可能会基于记录内的其它字段.

  • 对于某些字段,我们可能想要用一种特定格式对数据进行格式化,比如钱币数量 使用 $000,000.00 .

  • 报表应该用MS Excel格式的,每一行都包含来自每个数据源的字段值. 任何不匹配数据验证规则的行都应该用黄色高亮显示.

Annotations

After going over the requirements and knocking around a few ideas, I decided to use annotations to drive the configuration for the data comparison and reporting process. We needed something that was somewhat simple, yet highly flexible and extensible. These annotations will be at the field level and I like the fact that the configuration won’t be hidden away in a file somewhere on the classpath. Instead you’ll be able to look at the annotation associated with a field to know exactly how it will be processed.

In the simplest terms, an annotation is nothing more than a marker, metadata that provides information but has no direct effect on the operation of the code itself. If you’ve been doing Java programming for a while now you should be pretty familiar with their use, but maybe you’ve never had a need to create your own. To do that you’ll need to create a new type that uses the Java type @interface that will contain the elements that specify the details of the metadata.

译者信息

注解

经过一阵子对需求和一些想法的推敲之后,我决定使用注解来驱动对于数据比对和报表处理的配置. 我们需要的东西得是简单,而高度灵活可扩展的. 这些注解将会是字段级别的,而我就喜欢配置不会被隐藏在classpath某个地方的文件中. 如此,你就能够直接查看同一个字段相关联的注解,以便知晓它具体是如何进行处理的.

在最简单的情况下,注解无非就是一个标记,就只是提供信息而不会对代码执行的操作本身有直接影响的元数据. 如果你一直在从事Java编程,那么现在你对它们的使用应该相当的熟悉了, 但是可能你从来没有过创建属于你自己的注解的需求. 为此,你需要创建一个带有Java类型@interface的新类型,它将包含能指定元数据详细信息的要素.

Here’s an example from the project:

@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 {};

}

This is the main annotation that will drive how the data comparison process will work. It contains the basic elements required to fulfill most of the requirements for comparing the data amongst the different data sources. The @ReconField should handle most of what we need except for the requirement of more complex data comparison, which we’ll go over a little bit later. Most of these elements are explained by the comments associated with each one in the code listing, however there are a couple of key annotations on our @ReconField that need to be pointed out.

  • @Target – This annotation allows you to specify which java elements your annotation should apply to. The possible target types are ANNOTATION_TYPE, CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER and TYPE. In our @ReconField annotation it is specific to the FIELD level.

  • @Retention – This allows you to specify when the annotation will be available. The possible values are CLASS, RUNTIME and SOURCE. Since we’ll be processing this annotation at RUNTIME, that’s what this needs to be set to.

译者信息

这里有一个来自这个项目的示例:

@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 {};

}

这是驱动数据比对过程如何运作的主要注解. 它包含的基本要素,可以满足不同数据源间数据进行比较的大部分需求. @ReconField 可以处理除更加复杂的比对之外,我们所期望的大多数需求, 而更加复杂的情况我们将会在稍后有所讨论. 这些要素的大多数在代码清单中一对一的注释中都有介绍, 而需要指出的是,在我们的@ReconField上有几个关键的注解.

  • @Target – 这个注解可以让你来指定你的注解应该被用在那个java元素上. 可能的目标类型是 ANNOTATION_TYPE, CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER 和 TYPE. 在我们的 @ReconField 注解中他被指定到了 FIELD 级别.

  • @Retention – 它可以让你指定注解在何时生效. 可能的值有 CLASS, RUNTIME 和 SOURCE. 因为我们将会在运行时 RUNTIME 处理这个注解, 所以那就是我们需要设置的值.

This data verification process will run one query for each database and then map the results to a common data bean that represents all of the fields for that particular type of business record. The annotations on each field of this mapped data bean tell the processor how to perform the data comparison for that particular field and its value found on each database. So let’s look at a few examples of how these annotations would be used for various data comparison configurations.

To verify that the value exists and matches exactly in each data source, you would only need to provide the field ID and the label that should be displayed for the field on the report.

@ReconField(id = CUSTOMER_ID, label = "Customer ID")
private String customerId;

To display the values found in each data source, but not do any data comparisons, you would need to specify the element compareSources and set its value to false.

@ReconField(id = NAME, label = "NAME", compareSources = false)
private String name;

译者信息

这一数据验证过程将会为每一个数据库运行一次查询,并且将结果映射到展示出针对特定业务记录类型所有字段的实体bean中. 映射数据实体的每一个字段上的注解会告诉处理器如何为特定字段及在每个数据库中找到的其值执行数据比对. 因此让我们来看几个示例来了解这些注解是如何被运用于不同的数据比对配置的.

为了验证现有的值并同每个数据源中的只精确匹配,你只需要提供一个字段ID以及将会展示在报表上字段的标记.

@ReconField(id = CUSTOMER_ID, label = "Customer ID")
private String customerId;

为了展示在每个数据源中找到的值,但不做任何数据比对,你可能需要制定 compareSources 元素,并将其值设置为false.

@ReconField(id = NAME, label = "NAME", compareSources = false)
private String name;

To verify the values found in specific data sources but not all of them, you would use the element sourcesToCompare. Using this would display all of the values found, but only perform any data comparisons on the data sources listed in the element. The handles the case in which some data is not stored in every data source. ReconSource is an enum that contains the data sources available for comparison.

@ReconField(id = PRIVATE_PLACEMENT_FLAG, label = "PRIVATE PLACEMENT FLAG", sourcesToCompare ={ ReconSource.LEGACY, ReconSource.PACE })
private String privatePlacementFlag;

Now that we’ve covered our basic requirements, we need to address the ability to run complex data comparisons that are specific to the field in question. To do that, we’ll create a second annotation that will drive the processing of custom rules.

@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;

}

译者信息

为了验证在指定数据源中找到的值,而不是全部,你可能会使用到 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;

}

Very similar to the previous annotation, the biggest difference in the @ReconCustomRule annotation is that we are specifying a class that will execute the data comparison when the recon process executes. You can only define the class that will be used, so your processor will have to instantiate and initialize any class that you specify. The class that is specified in this annotation will need to implement a custom rule interface, which will be used by the rule processor to execute the rule.

Now let’s take a look at a couple of examples of this annotation.

In this example, we’re using a custom rule that will check to see if the stock exchange is not the United States and skip the data comparison if that’s the case. To do this, the rule will need to check the exchange country field on the same record.

@ReconField(id = STREET_CUSIP, label = "STREET CUSIP", compareSources = false)
@ReconCustomRule(processor = SkipNonUSExchangeComparisonRule.class)
private String streetCusip;

译者信息

同之前的注解非常类似,最大的不同在于 @ReconCustomRule 注解中我们指定了一个类,它可以在重建处理执行时执行数据比对. 你可以只定义将会被用到的类,那样你的处理器就可以实例化并初始化你所指定的类. 在这个注解中指定的类将需要实现一个通用的规则接口,它将会被规则处理器用来执行规则.

现在让我们来看看使用这个注解 的例子.

在本例中,我们使用了一个自定义的规则,它将会检查股票交易所是不是 United States,如果是则跳过这条数据. 为此,这条规则将需要检查同一记录中的 exchange country 字段.

@ReconField(id = STREET_CUSIP, label = "STREET CUSIP", compareSources = false)
@ReconCustomRule(processor = SkipNonUSExchangeComparisonRule.class)
private String streetCusip;

Here’s an example where we are specifying a parameter for the custom rule, in this case it’s a tolerance amount. For this specific data comparison, the values being compared cannot be off by more than 1,000. By using a parameter to specify the tolerance amount, this allows us to use the same custom rule on multiple fields with different tolerance amounts. The only drawback is that these parameters are static and can’t be dynamic due to the nature of annotations.

@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;

As you can see, we’ve designed quite of bit of flexibility into a data verification report for multiple databases by just using a couple of fairly simple annotations. For this particular case, the annotations are driving the data comparison processing so we’re actually evaluating the annotations that we find on the mapped data bean and using those to direct the processing.

译者信息

这里的示例我们为自定义规则指定了一个参数,这里它是一个包容量. 对于这种特殊的数据比对,被比较的值不能偏离超过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;

如你所见,我们只使用了几个简单的注解,就设计出了一个具有相当程度灵活性的面向多数据库场景的数据验证报告功能. 在这个特殊情况下,注解驱动了数据的比对过程,因此我们实际上就是使用了注解在找到的映射数据实体上进行计算并直接使用它们进行处理.

Conclusion

There are numerous articles out there already about Java annotations, what they do, and the rules for using them. I wanted this article to focus more on an example of why you might want to consider using them and see the benefit directly.

Keep in mind that this is only the starting point, once you have decided on creating annotations you’ll still need to figure out how to process them to really take full advantage of them. In part two, I’ll show you how to process these annotations using Java reflection. Until then, here are a couple of good resources to learn more about Java annotations:

– Jonny Hackett, asktheteam@keyholesoftware.com

译者信息

结论

关于Java注解能做什么以及它们的使用规则,已经有了相当多的文章。 本文更多的是聚焦于一个实例,借以说明为什么你需要考虑使用注解,同时也能直观的看看有什么好处。

要记住这只是一个起步,一旦你决定要创建使用注解,你还要想清楚要怎样处理它们才能真正发挥他们的长处。在后面的文章中,我将向你们展示如何使用Java反射来处理注解。在那之前,有一些不错的关于Java注解的资料可以看看:

– Jonny Hackett, asktheteam@keyholesoftware.com


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值