Создание пользовательской аннотации в Java

1. Введение

Аннотации Java - это механизм для добавления информации метаданных в наш исходный код. Они являются мощной частью Java и были добавлены в JDK5. Аннотации предлагают альтернативу использованию дескрипторов XML и интерфейсов маркеров.

Хотя мы можем прикреплять их к пакетам, классам, интерфейсам, методам и полям, аннотации сами по себе не влияют на выполнение программы.

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

2. Создание пользовательских аннотаций

Мы собираемся создать три пользовательских аннотации с целью сериализации объекта в строку JSON.

Мы будем использовать первый на уровне класса, чтобы указать компилятору, что наш объект может быть сериализован. Затем мы применим второй к полям, которые мы хотим включить в строку JSON.

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

2.1. Пример аннотации уровня класса

Первый шаг к созданию пользовательской аннотации - объявить ее с помощью ключевого слова @interface :

public @interface JsonSerializable { }

Следующим шагом будет добавление метааннотаций, чтобы указать область действия и цель нашей пользовательской аннотации:

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.Type) public @interface JsonSerializable { }

Как мы видим, наша первая аннотация имеет видимость во время выполнения, и мы можем применять ее к типам (классам) . Более того, у него нет методов, и он служит простым маркером для обозначения классов, которые можно сериализовать в JSON.

2.2. Пример аннотации на уровне поля

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

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface JsonElement { public String key() default ""; }

В аннотации объявляется один параметр String с именем «ключ» и пустая строка в качестве значения по умолчанию.

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

2.3. Пример аннотации уровня метода

Представим, что перед сериализацией объекта в строку JSON мы хотим выполнить некоторый метод для инициализации объекта. По этой причине мы собираемся создать аннотацию, чтобы отметить этот метод:

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Init { }

Мы объявили общедоступную аннотацию с видимостью во время выполнения, которую мы можем применить к методам наших классов.

2.4. Применение аннотаций

Теперь давайте посмотрим, как мы можем использовать наши пользовательские аннотации. Например, представим, что у нас есть объект типа Person, который мы хотим сериализовать в строку JSON. У этого типа есть метод, который использует первую букву имени и фамилии с заглавной буквы. Мы хотим вызвать этот метод перед сериализацией объекта:

@JsonSerializable public class Person { @JsonElement private String firstName; @JsonElement private String lastName; @JsonElement(key = "personAge") private String age; private String address; @Init private void initNames() { this.firstName = this.firstName.substring(0, 1).toUpperCase() + this.firstName.substring(1); this.lastName = this.lastName.substring(0, 1).toUpperCase() + this.lastName.substring(1); } // Standard getters and setters }

Используя наши пользовательские аннотации, мы указываем, что можем сериализовать объект Person в строку JSON. Кроме того, вывод должен содержать только поля firstName , lastName и age этого объекта. Более того, мы хотим, чтобы метод initNames () вызывался перед сериализацией.

Установив для ключевого параметра аннотации @JsonElement значение «personAge», мы указываем, что будем использовать это имя в качестве идентификатора поля в выходных данных JSON.

Ради демонстрации мы сделали initNames () закрытым, поэтому мы не можем инициализировать наш объект, вызывая его вручную, и наши конструкторы также не используют его.

3. Обработка аннотаций

До сих пор мы видели, как создавать собственные аннотации и как использовать их для украшения класса Person . Теперь мы посмотрим, как воспользоваться ими, используя Java Reflection API.

Первым шагом будет проверка, является ли наш объект нулевым или нет, а также имеет ли его тип аннотацию @JsonSerializable или нет:

private void checkIfSerializable(Object object) { if (Objects.isNull(object)) { throw new JsonSerializationException("The object to serialize is null"); } Class clazz = object.getClass(); if (!clazz.isAnnotationPresent(JsonSerializable.class)) { throw new JsonSerializationException("The class " + clazz.getSimpleName() + " is not annotated with JsonSerializable"); } }

Затем мы ищем любой метод с аннотацией @Init и выполняем его для инициализации полей нашего объекта:

private void initializeObject(Object object) throws Exception { Class clazz = object.getClass(); for (Method method : clazz.getDeclaredMethods()) { if (method.isAnnotationPresent(Init.class)) { method.setAccessible(true); method.invoke(object); } } }

Вызов метода . setAccessible ( истина) позволяет выполнять частные initNames () метод .

После инициализации мы перебираем поля нашего объекта, извлекаем ключ и значение элементов JSON и помещаем их на карту. Затем мы создаем строку JSON из карты:

private String getJsonString(Object object) throws Exception { Class clazz = object.getClass(); Map jsonElementsMap = new HashMap(); for (Field field : clazz.getDeclaredFields()) { field.setAccessible(true); if (field.isAnnotationPresent(JsonElement.class)) { jsonElementsMap.put(getKey(field), (String) field.get(object)); } } String jsonString = jsonElementsMap.entrySet() .stream() .map(entry -> "\"" + entry.getKey() + "\":\"" + entry.getValue() + "\"") .collect(Collectors.joining(",")); return "{" + jsonString + "}"; }

Опять же, мы использовали поле . setAccessible ( true e ), потому что поля объекта Person являются закрытыми.

Наш класс сериализатора JSON объединяет все вышеперечисленные шаги:

public class ObjectToJsonConverter { public String convertToJson(Object object) throws JsonSerializationException { try { checkIfSerializable(object); initializeObject(object); return getJsonString(object); } catch (Exception e) { throw new JsonSerializationException(e.getMessage()); } } }

Наконец, мы запускаем модульный тест, чтобы убедиться, что наш объект был сериализован в соответствии с нашими пользовательскими аннотациями:

@Test public void givenObjectSerializedThenTrueReturned() throws JsonSerializationException { Person person = new Person("soufiane", "cheouati", "34"); JsonSerializer serializer = new JsonSerializer(); String jsonString = serializer.serialize(person); assertEquals( "{\"personAge\":\"34\",\"firstName\":\"Soufiane\",\"lastName\":\"Cheouati\"}", jsonString); }

4. Вывод

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

Как всегда, полный код доступен на GitHub.