Ise annotatsioonide loomine
Sissejuhatus
Sisseehitatud annotatsioonid on kasulikud, kuid annotatsioonide tegelik jõud seisneb selles, et neid on võimalik ka ise luua.
Kohandatud annotatsioon on märgis, mille mõtled ise välja ning mida loetakse seejärel käitusajal selleks, et kood saaks sellele reageerida.
Raamistikud defineerivad pidevalt oma annotatsioone, mõndade nendega oleme juba tutvunud (JUniti @Test näiteks).
Selles peatükis tutvume annotatsioonide loomisega, järgmises peatükis tutvume, kuidas neid lugeda ja nende põhjal käitumust programmile juurde lisada.
Annotatsiooni loomine
Annotatsioone deklareeritakse @interface märksõnaga:
public @interface Audited {
}
Vaatamata märksõnale ei ole tegemist tavapärases mõttes liidesega. Seda ei saa teostada, sellele saab ainult elemente külge lisada:
@Audited
public void deleteBook(int id) {
...
}
Iseenesest ei tee @Audited veel midagi.
Selleks, et sellega midagi teha peame esmalt kompilaatorile ütlema kahte asja:
- Millistele elementidele seda saab lisada
- Kui kaua see peab pärast lähtekoodi kompileerimist eksisteerima
@Target ehk millistele elementidele saab annotatsiooni lisada
@Target piirab, millist tüüpi elementidele võib annotatsiooni juurde lisada.
Ilma selleta võib annotatsiooni lisada peaaegu kõikjale, mille tulemusena kompilaator ei anna veateadet olukordades, kus seda kasutatakse valedes kohtades.
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
public @interface Audited {
}
Sellega sai @Audited piiratud ainult meetoditel kasutamiseks.
Selle lisamine klassile või väljale põhjustaks kompileerimisvea.
ElementType sisaldab endas võimalike sihtkohti, millele annotatsioone saab lisada, nendeks on:
| Väärtus | Kus annotatsioon võib esineda |
|---|---|
TYPE | Klassid, liidesed, enumid |
FIELD | Väljad |
METHOD | Meetodid |
PARAMETER | Meetodi parameetrid |
CONSTRUCTOR | Konstruktorid |
LOCAL_VARIABLE | Kohalikud muutujad |
@Target ei ole piiratud ainult ühe sihiga - neid saab kombineerida järgnevalt:
@Target({ ElementType.METHOD, ElementType.FIELD })
public @interface Audited {
}
Lisaks meetoditele võib nüüd @Audited annotatsiooni lisada ka väljadele.
@Retention ehk annotatsiooni eluiga
@Retention määrab annotatsiooni eluea.
Võimalik on valida kolme valiku vahel, millest igaüks käitub väga erinevalt:
| Reegel | Eluiga | Kasutusjuht |
|---|---|---|
SOURCE | Eemaldatakse koodist kompileerimise ajal | Tööriistad, mis loevad lähtekoodi |
CLASS (vaikimisi) | Salvestatakse .class failidesse, kuid ei laeta programmi käitusajal | Baitkoodi analüsaatorid |
RUNTIME | Salvestatakse .class failidesse ja on reflektsiooni kaudu nähtav | Raamistikud nagu JUnit, Spring |
Kui eesmärk on annotatsioone kasutada loogika lisamiseks, peab valik olema RUNTIME.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Audited {
}
Ilma @Retention(RetentionPolicy.RUNTIME)-ita ei ilmu annotatsioon välja läbi getAnnotations() meetodi käitusajal.
Annotatsiooni parameetrid
Annotatsioonid võivad defineerida parameetreid, mida saab kasutada koodis info edastamiseks.
Need näevad @interface sees välja nagu tavalised meetodid:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Audited {
String reason();
String level() default "info";
}
Kasutuskohas kirjutatakse parameetrid sulgudesse:
@Audited(reason = "GDPR", level = "critical")
public void deleteUser(int id) {
...
}
Parameetritel võivad olla vaikeväärtused, kuid need ei ole kohustuslikud. Vaikeväärtustega parameetreid ei pea annotatsiooni kasutamisel eraldi välja kirjutama, ilma nendeta peab.
value parameeter
Kui annotatsioonil on ainult üks parameeter ning selle nimi on täpselt value, siis võib parameetri nime kasutuskohas ära jätta:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface JsonField {
String value();
}
@JsonField("book_title")
private String title;
See on võrdväärne @JsonField(value = "book_title")-le.
Lubatud tüübid parameetritel
Annotatsiooni parameetrid ei ole suvalised Java väärtused. Lubatud tüübid on:
- Primitiivtüübid (
int,long,boolean, ...) StringClass<?>(kasutatakse sageli teistele tüüpidele viitamiseks, näiteksexpected = IllegalArgumentException.class)- Enum-tüübid
- Teised annotatsioonitüübid
- Massiivid ükskõik millisest eelnevalt loetletud tüübist
Tavalist objektiviidet, List-i ega Map-i kasutada ei saa.
Sellel on kindel põhjus: annotatsioonid peavad olema klassifailis esitatavad staatilise metaandmestikuna.
Massiivid parameetrina
Massiiviparameetrid võtavad looksulgude sees komadega eraldatud väärtuste loendi:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Tag {
String[] names();
}
@Tag(names = {"slow", "integration"})
public void loadFromDatabase() { ... }
Kui massiivis on ainult üks element, võib looksulud ära jätta: @Tag(names = "slow").
Class parameeter
Class<?> parameeter on loomulik viis teisele tüübi viitamiseks:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Handles {
Class<? extends Throwable> exception();
}
@Handles(exception = IOException.class)
public void onIoFailure(IOException e) { ... }
Käitusajal saab seda annotatsiooni lugev raamistik kätte tegeliku Class<?> objekti ning saab seda kasutada - näiteks koos isAssignableFrom meetodiga - otsustamaks, kuidas meetodit käsitleda.
Praktiline näide
Pannes kõik eelnevalt õpitu kokku, saame tulemuseks midagi sellist:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MaxLength {
int value();
String message() default "Value is too long";
}
Ning seda saab kasutada järgnevalt:
public class BookForm {
@MaxLength(value = 200, message = "Title cannot exceed 200 characters")
private String title;
@MaxLength(100)
private String author;
}
Järgmine samm oleks seda annotatsiooni lugeda ja sellele loogikat juurde lisada, kasutades reflektsiooni - teema, mida käsitleme järgmises peatükis.