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, Class, перечислениями, аннотациями и массивами этих типов, а значение по умолчанию не может быть null .
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
( true)
позволяет нам выполнить закрытый метод initNames()
.
После инициализации мы перебираем поля нашего объекта, извлекаем ключ и значение элементов JSON и помещаем их в карту. Затем мы создаем строку JSON из карты:
private String getJsonString(Object object) throws Exception {
Class<?> clazz = object.getClass();
Map<String, String> 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
)
, потому что поля объекта 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");
ObjectToJsonConverter serializer = new ObjectToJsonConverter();
String jsonString = serializer.convertToJson(person);
assertEquals(
"{\"personAge\":\"34\",\"firstName\":\"Soufiane\",\"lastName\":\"Cheouati\"}",
jsonString);
}
4. Вывод
В этой статье мы узнали, как создавать различные типы пользовательских аннотаций. Затем мы обсудили, как использовать их для украшения наших объектов. Наконец, мы рассмотрели, как их обрабатывать с помощью Java Reflection API.
Как всегда, полный код доступен на GitHub .