Статьи

Создание ваших собственных аннотаций Java

Если вы программировали на Java и использовали любую из популярных платформ, таких как Spring и Hibernate, вы должны быть хорошо знакомы с использованием аннотаций. При работе с существующей платформой обычно достаточно аннотаций. Но вам когда-нибудь приходилось создавать свои собственные аннотации?

Не так давно я нашел причину для создания собственных аннотаций для проекта, который включал проверку общих данных, хранящихся в нескольких базах данных.

Сценарий

У компании было несколько баз данных, в которых хранилась одна и та же информация, и имелись различные способы обновления данных. Бизнес планировал проект по консолидации данных в основную базу данных, чтобы облегчить некоторые проблемы, связанные с наличием нескольких источников данных.

Однако, прежде чем проект мог начаться, бизнес должен был знать, насколько далеко не синхронизированы данные, и внести все необходимые исправления, чтобы восстановить синхронизацию. На первом этапе потребовалось создать отчет, который показал бы общие данные, принадлежащие нескольким базам данных, и проверил значения, выделив все записи, которые не совпадали в соответствии с определенными правилами сверки. Вот краткое изложение требований на тот момент:

  • Сравните данные между несколькими базами данных для общего фрагмента данных, такого как информация о клиенте, компании или каталоге.
  • По умолчанию найденное значение должно точно совпадать для всех баз данных в зависимости от типа значения.
  • Для определенных полей мы хотим отображать только найденное значение, а не сравнивать данные.
  • Для определенных полей мы только хотим сравнить найденное значение и выполнить проверку данных на определенных указанных источниках данных.
  • Для определенных полей мы можем захотеть сделать несколько сложных сравнений данных, которые могут основываться на значении других полей в записи.
  • Для определенных полей мы можем захотеть отформатировать данные в определенном формате, например, 000 000,00 долларов США для денежных сумм.
  • Отчет должен быть в формате MS Excel, каждая строка содержит значение поля из каждого источника. Любая строка, которая не соответствует правилам проверки данных, должна быть выделена желтым цветом.

Аннотации

Изучив требования и обдумав несколько идей, я решил использовать аннотации для управления конфигурацией процесса сравнения данных и составления отчетов. Нам нужно было что-то простое, но очень гибкое и расширяемое. Эти аннотации будут на уровне поля, и мне нравится тот факт, что конфигурация не будет скрыта в файле где-то на пути к классам. Вместо этого вы сможете просмотреть аннотацию, связанную с полем, чтобы точно знать, как оно будет обрабатываться.

Проще говоря, аннотация — это не что иное, как маркер, метаданные, которые предоставляют информацию, но не имеют прямого влияния на работу самого кода. Если вы некоторое время занимались программированием на Java, вы должны быть хорошо знакомы с их использованием, но, возможно, вам никогда не приходилось создавать свои собственные. Для этого вам нужно создать новый тип, который использует тип Java @interface, который будет содержать элементы, которые определяют детали метаданных.

Вот пример из проекта:

01
02
03
04
05
06
07
08
09
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 {};
 
}

Это основная аннотация, которая определяет, как будет работать процесс сравнения данных. Он содержит основные элементы, необходимые для выполнения большинства требований для сравнения данных между различными источниками данных. @ReconField должен обрабатывать большую часть того, что нам нужно, за исключением требования более сложного сравнения данных, о котором мы поговорим чуть позже. Большинство этих элементов объясняются комментариями, связанными с каждым в листинге кода, однако есть несколько ключевых аннотаций в нашем @ReconField, на которые необходимо указать.

  • @Target — эта аннотация позволяет вам указать, к каким java-элементам должна применяться ваша аннотация. Возможные типы целей: ANNOTATION_TYPE, CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER и TYPE. В нашей аннотации @ReconField это относится к уровню поля.
  • @Retention — позволяет указать, когда аннотация будет доступна. Возможные значения: CLASS, RUNTIME и SOURCE. Так как мы будем обрабатывать эту аннотацию в RUNTIME, это то, что нужно установить.

Этот процесс проверки данных запускает один запрос для каждой базы данных, а затем сопоставляет результаты с общим компонентом данных, который представляет все поля для данного конкретного типа бизнес-записи. Аннотации в каждом поле этого bean-компонента отображенных данных указывают процессору, как выполнить сравнение данных для этого конкретного поля и его значения, найденного в каждой базе данных. Итак, давайте рассмотрим несколько примеров того, как эти аннотации будут использоваться для различных конфигураций сравнения данных.

Чтобы убедиться, что значение существует и точно соответствует каждому источнику данных, вам нужно всего лишь указать идентификатор поля и метку, которая должна отображаться для поля в отчете.

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

Чтобы отобразить значения, найденные в каждом источнике данных, но не проводить никаких сравнений данных, вам необходимо указать элемент compareSources и установить для него значение false.

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

Чтобы проверить значения, найденные в определенных источниках данных, но не во всех, вы должны использовать элемент sourcesToCompare . Использование этого отобразит все найденные значения, но выполнит любые сравнения данных только с источниками данных, перечисленными в элементе. Обрабатывает случай, когда некоторые данные хранятся не в каждом источнике данных. ReconSource — это перечисление, которое содержит источники данных, доступные для сравнения.

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

Теперь, когда мы рассмотрели наши основные требования, нам необходимо рассмотреть возможность проведения сложных сравнений данных, специфичных для рассматриваемой области. Для этого мы создадим вторую аннотацию, которая будет управлять обработкой пользовательских правил.

01
02
03
04
05
06
07
08
09
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 состоит в том, что мы указываем класс, который будет выполнять сравнение данных при выполнении процесса восстановления. Вы можете определить только класс, который будет использоваться, поэтому ваш процессор должен будет создать экземпляр и инициализировать любой класс, который вы укажете. Класс, указанный в этой аннотации, должен реализовать пользовательский интерфейс правил, который будет использоваться обработчиком правил для выполнения правила.

Теперь давайте посмотрим на пару примеров этой аннотации.

В этом примере мы используем пользовательское правило, которое проверит, не является ли фондовая биржа США, и пропустит сравнение данных, если это так. Для этого правилу необходимо проверить поле страны обмена в той же записи.

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

Вот пример, где мы указываем параметр для пользовательского правила, в данном случае это величина допуска. Для этого конкретного сравнения данных сравниваемые значения не могут быть отклонены более чем на 1000. Использование параметра для указания величины допуска позволяет нам использовать одно и то же пользовательское правило для нескольких полей с разными значениями допуска. Единственным недостатком является то, что эти параметры являются статическими и не могут быть динамическими из-за природы аннотаций.

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;

Как вы можете видеть, мы внесли немного гибкости в отчет о проверке данных для нескольких баз данных, просто используя несколько довольно простых аннотаций. В этом конкретном случае аннотации управляют обработкой сравнения данных, поэтому мы на самом деле оцениваем аннотации, которые мы находим в компоненте отображенных данных, и используем их для управления обработкой.

Вывод

Уже есть множество статей о аннотациях Java, о том, что они делают, и правилах их использования. Я хотел, чтобы в этой статье было больше внимания на примере того, почему вы можете рассмотреть возможность их использования и увидеть преимущества непосредственно.

Имейте в виду, что это только отправная точка, после того, как вы решили создать аннотации, вам все равно нужно будет выяснить, как их обрабатывать, чтобы в полной мере воспользоваться ими. Во второй части я покажу вам, как обрабатывать эти аннотации с помощью отражения Java. А пока вот несколько полезных ресурсов, чтобы узнать больше о аннотациях Java:

Ссылка: Создание собственных аннотаций Java от нашего партнера JCG Джонни Хакетта в блоге