Creazione di un'annotazione personalizzata in Java

1. Introduzione

Le annotazioni Java sono un meccanismo per aggiungere informazioni sui metadati al nostro codice sorgente. Sono una parte potente di Java e sono stati aggiunti in JDK5. Le annotazioni offrono un'alternativa all'uso di descrittori XML e interfacce marker.

Sebbene possiamo collegarli a pacchetti, classi, interfacce, metodi e campi, le annotazioni da sole non hanno effetto sull'esecuzione di un programma.

In questo tutorial, ci concentreremo su come creare annotazioni personalizzate e su come elaborarle. Possiamo leggere di più sulle annotazioni nel nostro articolo sulle basi delle annotazioni.

2. Creazione di annotazioni personalizzate

Creeremo tre annotazioni personalizzate con l'obiettivo di serializzare un oggetto in una stringa JSON.

Useremo il primo a livello di classe, per indicare al compilatore che il nostro oggetto può essere serializzato. Successivamente, applicheremo il secondo ai campi che vogliamo includere nella stringa JSON.

Infine, useremo la terza annotazione a livello di metodo, per specificare il metodo che useremo per inizializzare il nostro oggetto.

2.1. Esempio di annotazione a livello di classe

Il primo passo verso la creazione di un'annotazione personalizzata è dichiararla utilizzando la parola chiave @interface :

public @interface JsonSerializable { }

Il passaggio successivo consiste nell'aggiungere meta-annotazioni per specificare l'ambito e l'obiettivo della nostra annotazione personalizzata:

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

Come possiamo vedere, la nostra prima annotazione ha visibilità in fase di esecuzione e possiamo applicarla ai tipi (classi) . Inoltre, non ha metodi e quindi funge da semplice indicatore per contrassegnare le classi che possono essere serializzate in JSON.

2.2. Esempio di annotazione a livello di campo

Allo stesso modo, creiamo la nostra seconda annotazione, per contrassegnare i campi che includeremo nel JSON generato:

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

L'annotazione dichiara un parametro String con il nome "chiave" e una stringa vuota come valore predefinito.

Quando si creano annotazioni personalizzate con metodi, dobbiamo essere consapevoli che questi metodi non devono avere parametri e non possono generare un'eccezione . Inoltre, i tipi restituiti sono limitati a primitive, String, Class, enumerazioni, annotazioni e array di questi tipi e il valore predefinito non può essere null .

2.3. Esempio di annotazione a livello di metodo

Immaginiamo che, prima di serializzare un oggetto in una stringa JSON, vogliamo eseguire un metodo per inizializzare un oggetto. Per questo motivo, creeremo un'annotazione per contrassegnare questo metodo:

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

Abbiamo dichiarato un'annotazione pubblica con visibilità in fase di esecuzione che possiamo applicare ai metodi delle nostre classi.

2.4. Applicazione di annotazioni

Vediamo ora come possiamo utilizzare le nostre annotazioni personalizzate. Ad esempio, immaginiamo di avere un oggetto di tipo Person che vogliamo serializzare in una stringa JSON. Questo tipo ha un metodo che rende maiuscola la prima lettera del nome e del cognome. Vorremo chiamare questo metodo prima di serializzare l'oggetto:

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

Utilizzando le nostre annotazioni personalizzate, stiamo indicando che possiamo serializzare un oggetto Person in una stringa JSON. Inoltre, l'output dovrebbe contenere solo i campi firstName , lastName e age di quell'oggetto. Inoltre, vogliamo che il metodo initNames () venga chiamato prima della serializzazione.

Impostando il parametro chiave dell'annotazione @JsonElement su "personAge", stiamo indicando che useremo questo nome come identificatore per il campo nell'output JSON.

Per motivi di dimostrazione, abbiamo reso privato initNames () , quindi non possiamo inizializzare il nostro oggetto chiamandolo manualmente e nemmeno i nostri costruttori lo stanno usando.

3. Elaborazione delle annotazioni

Finora abbiamo visto come creare annotazioni personalizzate e come usarle per decorare la classe Person . Ora vedremo come trarne vantaggio utilizzando l'API Reflection di Java.

Il primo passo sarà verificare se il nostro oggetto è nullo o meno, nonché se il suo tipo ha l' annotazione @JsonSerializable o meno:

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

Quindi, cerchiamo qualsiasi metodo con l'annotazione @Init e lo eseguiamo per inizializzare i campi del nostro oggetto:

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

Il richiamo del metodo . setAccessible ( true) ci permette di eseguire il metodo privato initNames () .

Dopo l'inizializzazione, iteriamo sui campi del nostro oggetto, recuperiamo la chiave e il valore degli elementi JSON e li inseriamo in una mappa. Quindi, creiamo la stringa JSON dalla mappa:

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

Ancora una volta, abbiamo usato field . setAccessible ( tru e ) perché la persona campi dell'oggetto sono private.

La nostra classe serializzatore JSON combina tutti i passaggi precedenti:

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

Infine, eseguiamo uno unit test per convalidare che il nostro oggetto è stato serializzato come definito dalle nostre annotazioni personalizzate:

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

In questo articolo abbiamo visto come creare diversi tipi di annotazioni personalizzate. Poi abbiamo discusso di come usarli per decorare i nostri oggetti. Infine, abbiamo esaminato come elaborarli utilizzando l'API Reflection di Java.

Come sempre, il codice completo è disponibile su GitHub.