Création d'une annotation personnalisée en Java

1. Introduction

Les annotations Java sont un mécanisme pour ajouter des informations de métadonnées à notre code source. Ils sont une partie puissante de Java et ont été ajoutés dans JDK5. Les annotations offrent une alternative à l'utilisation de descripteurs XML et d'interfaces de marqueurs.

Bien que nous puissions les attacher à des packages, classes, interfaces, méthodes et champs, les annotations en elles-mêmes n'ont aucun effet sur l'exécution d'un programme.

Dans ce didacticiel, nous allons nous concentrer sur la création d'annotations personnalisées et sur leur traitement. Nous pouvons en savoir plus sur les annotations dans notre article sur les bases des annotations.

2. Création d'annotations personnalisées

Nous allons créer trois annotations personnalisées dans le but de sérialiser un objet dans une chaîne JSON.

Nous utiliserons le premier au niveau de la classe, pour indiquer au compilateur que notre objet peut être sérialisé. Ensuite, nous appliquerons le second aux champs que nous voulons inclure dans la chaîne JSON.

Enfin, nous utiliserons la troisième annotation au niveau de la méthode, pour spécifier la méthode que nous utiliserons pour initialiser notre objet.

2.1. Exemple d'annotation au niveau de la classe

La première étape pour créer une annotation personnalisée consiste à la déclarer à l'aide du mot clé @interface :

public @interface JsonSerializable { }

L'étape suivante consiste à ajouter des méta-annotations pour spécifier la portée et la cible de notre annotation personnalisée:

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

Comme nous pouvons le voir, notre première annotation a une visibilité d'exécution, et nous pouvons l'appliquer aux types (classes) . De plus, il n'a pas de méthode, et sert donc de simple marqueur pour marquer les classes qui peuvent être sérialisées en JSON.

2.2. Exemple d'annotation au niveau du champ

De la même manière, nous créons notre deuxième annotation, pour marquer les champs que nous allons inclure dans le JSON généré:

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

L'annotation déclare un paramètre String avec le nom «clé» et une chaîne vide comme valeur par défaut.

Lors de la création d'annotations personnalisées avec des méthodes, nous devons être conscients que ces méthodes ne doivent avoir aucun paramètre et ne peuvent pas lever d'exception . En outre, les types de retour sont limités aux primitives, String, Class, énumérations, annotations et tableaux de ces types, et la valeur par défaut ne peut pas être null .

2.3. Exemple d'annotation au niveau de la méthode

Imaginons qu'avant de sérialiser un objet en une chaîne JSON, nous souhaitons exécuter une méthode pour initialiser un objet. Pour cette raison, nous allons créer une annotation pour marquer cette méthode:

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

Nous avons déclaré une annotation publique avec une visibilité d'exécution que nous pouvons appliquer aux méthodes de nos classes.

2.4. Application d'annotations

Voyons maintenant comment nous pouvons utiliser nos annotations personnalisées. Par exemple, imaginons que nous ayons un objet de type Person que nous voulons sérialiser dans une chaîne JSON. Ce type a une méthode qui met en majuscule la première lettre du prénom et du nom. Nous voudrons appeler cette méthode avant de sérialiser l'objet:

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

En utilisant nos annotations personnalisées, nous indiquons que nous pouvons sérialiser un objet Person en une chaîne JSON. En outre, la sortie doit contenir uniquement les champs firstName , lastName et age de cet objet. De plus, nous voulons que la méthode initNames () soit appelée avant la sérialisation.

En définissant le paramètre clé de l' annotation @JsonElement sur «personAge», nous indiquons que nous utiliserons ce nom comme identifiant du champ dans la sortie JSON.

Pour des raisons de démonstration, nous avons rendu initNames () privé, nous ne pouvons donc pas initialiser notre objet en l'appelant manuellement, et nos constructeurs ne l'utilisent pas non plus.

3. Traitement des annotations

Jusqu'à présent, nous avons vu comment créer des annotations personnalisées et comment les utiliser pour décorer la classe Person . Nous allons maintenant voir comment en tirer parti en utilisant l'API Reflection de Java.

The first step will be to check whether our object is null or not, as well as whether its type has the @JsonSerializable annotation or not:

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"); } }

Then, we look for any method with @Init annotation, and we execute it to initialize our object's fields:

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); } } }

The call of method.setAccessible(true) allows us to execute the private initNames() method.

After the initialization, we iterate over our object's fields, retrieve the key and value of JSON elements, and put them in a map. Then, we create the JSON string from the map:

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 + "}"; }

Encore une fois, nous avons utilisé le champ . setAccessible ( tru e ) car les champs de l'objet Person sont privés.

Notre classe de sérialiseur JSON combine toutes les étapes ci-dessus:

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()); } } }

Enfin, nous exécutons un test unitaire pour valider que notre objet a été sérialisé comme défini par nos annotations personnalisées:

@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. Conclusion

Dans cet article, nous avons vu comment créer différents types d'annotations personnalisées. Ensuite, nous avons discuté de la façon de les utiliser pour décorer nos objets. Enfin, nous avons examiné comment les traiter à l'aide de l'API Reflection de Java.

Comme toujours, le code complet est disponible à l'adresse over sur GitHub.