W3docs

Meta-Anotações Java

Anotações que anotam outras anotações em Java — @Retention, @Target, @Documented, @Inherited, @Repeatable.

Uma meta-anotação é uma anotação que você coloca em uma declaração de anotação. Elas configuram como essa anotação se comporta: quanto tempo ela sobrevive, onde é permitido aparecer, se subclasses a herdam, se você pode aplicá-la mais de uma vez. Existem cinco delas em java.lang.annotation, e todo tipo de anotação que você escrever usará pelo menos as duas primeiras.

As cinco:

  • @Retention — controla se a anotação é mantida em arquivos .class e em tempo de execução.
  • @Target — restringe os tipos de elementos do programa que a anotação pode decorar.
  • @Documented — faz a anotação aparecer no Javadoc gerado.
  • @Inherited — faz subclasses herdarem anotações no nível de classe de sua superclasse.
  • @Repeatable — permite que a mesma anotação seja aplicada mais de uma vez ao mesmo elemento.

@Retention

@Retention escolhe um dos três valores de RetentionPolicy:

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.SOURCE)        // stripped by javac; never in bytecode
@interface SuppressEvenMoreWarnings { }

@Retention(RetentionPolicy.CLASS)         // in bytecode; not loaded by the VM (the default)
@interface BytecodeOnly { }

@Retention(RetentionPolicy.RUNTIME)       // in bytecode and accessible via reflection
@interface MyRuntimeMarker { }

A escolha certa depende do consumidor:

  • O compilador é o consumidor (verificações de override, supressão de avisos, lint) → SOURCE.
  • Uma ferramenta de processamento de bytecode, otimizador JIT ou analisador pós-compilação é o consumidor → CLASS.
  • Um framework lê a anotação em tempo de execução via reflection (DI, ORM, binding JSON) → RUNTIME.

RUNTIME é o mais permissivo — também é o mais custoso, porque cada anotação que sobrevive adiciona bytes ao seu arquivo de classe e um pouco de overhead de reflection no momento da consulta.

@Target

@Target restringe onde a anotação pode ser colocada. Seu valor é um array de constantes ElementType:

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
@interface Audited { }                                   // only on methods or constructors

@Target(ElementType.TYPE)
@interface Entity { }                                    // only on classes/interfaces/enums

@Target({})
@interface CannotBeApplied { }                           // exists only as a type — can't be used to annotate anything

Os valores de ElementType que você encontrará:

  • TYPE — classe, interface, enum, anotação.
  • METHOD, CONSTRUCTOR, FIELD, PARAMETER, LOCAL_VARIABLE.
  • ANNOTATION_TYPE — para meta-anotações como as deste capítulo.
  • PACKAGE — em package-info.java.
  • TYPE_USE (Java 8+) — qualquer uso de um tipo, incluindo casts ((@NonNull String) o), cláusulas extends, argumentos genéricos. Usado por verificadores de nulidade como o Checker Framework.
  • TYPE_PARAMETER (Java 8+) — apenas em declarações <T extends ...>.
  • MODULE (Java 9+) — em module-info.java.
  • RECORD_COMPONENT (Java 16+) — nos parâmetros do cabeçalho de um record.

Se você omitir @Target completamente, a anotação pode ir em quase qualquer lugar — útil para marcadores muito gerais, restritivo para todo o resto. Quase sempre defina um @Target explícito.

@Documented

Por padrão, as anotações não são exibidas no Javadoc dos elementos que decoram. @Documented faz uma anotação ser incluída:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface ApiNote {
  String value();
}

Se um método carrega @ApiNote("rate-limited to 5 rps"), o Javadoc mostrará essa anotação nos docs renderizados. Sem @Documented, a anotação existe em tempo de execução, mas é invisível na documentação gerada. Adicione @Documented a tudo que você espera que os usuários da sua biblioteca vejam.

@Inherited

@Inherited se aplica apenas a anotações direcionadas a TYPE (classes). Significa: se uma classe é anotada, suas subclasses também são tratadas como anotadas. O método getAnnotation(...) da reflection percorrerá a cadeia de superclasses e a encontrará.

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Auditable { }

@Auditable
class Account { }

class SavingsAccount extends Account { }                 // also "auditable" via inheritance

Ressalvas:

  • Ela percorre apenas a cadeia de superclasses — interfaces não propagam anotações mesmo com @Inherited. class Foo implements Auditable { } não faz Foo carregar um @Auditable da interface.
  • Afeta apenas como a reflection relata anotações em classes. Métodos, campos e parâmetros nunca herdam anotações de membros sobrescritos.

A maioria dos frameworks agora prefere anotações explícitas e repetidas à herança porque as regras são mais simples. Use @Inherited apenas quando "qualquer coisa que estenda uma classe marcadora também está marcada" é genuinamente o que você quer.

@Repeatable

Antes do Java 8, não era possível aplicar a mesma anotação duas vezes ao mesmo elemento. @Repeatable remove essa restrição, mas a mecânica requer cuidado. Você declara uma anotação container que contém um array dos valores repetidos, e aponta @Repeatable para o container:

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Schedules { Schedule[] value(); }            // the container

@Repeatable(Schedules.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Schedule { String cron(); }                  // the repeated annotation

class Reports {
  @Schedule(cron = "0 9 * * MON")
  @Schedule(cron = "0 9 * * FRI")
  public void weekly() { /* ... */ }
}

Regras:

  • O elemento value do container deve ser um array do tipo de anotação repetida.
  • O container e a anotação repetida devem ter a mesma retenção e pelo menos os mesmos targets.
  • Se a anotação repetida é @Documented, o container deve ser @Documented também — e igualmente para @Inherited. O compilador rejeita uma incompatibilidade com containing annotation interface ... is not @Documented. Mantenha as meta-anotações em sincronia.
  • Em tempo de execução, o compilador agrupa usos repetidos em uma única anotação container. A reflection tem tanto getAnnotation(Schedule.class) (retorna o único elemento do container quando há dois) quanto getAnnotationsByType(Schedule.class) (retorna o array diretamente). Use o segundo em anotações @Repeatable.

Um exemplo prático: usando todas as cinco meta-anotações em uma anotação real

O exemplo constrói um pequeno sistema de anotações de ponta a ponta: um @Schedule com RUNTIME, apenas em métodos, documentado e repetível; um marcador @Module herdado por subclasses. O método main então os lê via reflection.

java— editable, runs on the server

O que aprender com a execução:

  • @Module foi declarado na superclasse ReportingBase, mas a reflection o encontrou em WeeklyReports porque a anotação carrega @Inherited. A consulta percorre a hierarquia de classes até encontrar a anotação ou ficar sem superclasses.
  • O caso da interface mostrou o limite da herança: WithInterface implementa AnnotatedInterface, que tem @Module, mas getAnnotation(Module.class) retornou null. @Inherited percorre apenas extends, nunca implements. Isso confunde muitos iniciantes; se você precisar de visibilidade de anotações entre interfaces, terá que percorrer a árvore de tipos você mesmo.
  • runWeekly carregava duas anotações @Schedule. getAnnotationsByType(Schedule.class) retornou um array de tamanho 2 — a forma correta de ler anotações repetidas. O container @Schedules é invisível para o código do usuário se você usar getAnnotationsByType.
  • O caso de @Schedule único em runDaily foi simétrico: getAnnotation(Schedule.class) funcionou porque não havia container, e getAnnotationsByType retornou um array de tamanho 1. Qualquer forma é válida quando você conhece a multiplicidade.
  • As linhas de "caso repetido via getAnnotation" expuseram a armadilha. Em runWeekly, getAnnotation(Schedule.class) retornou null — a anotação real no arquivo de classe é um container @Schedules sintetizado, não um Schedule. Acessar o container via getAnnotation(Schedules.class) funciona. A regra: para qualquer anotação @Repeatable, sempre use getAnnotationsByType para que os dois casos (uma ocorrência vs. várias) pareçam idênticos.

Escolhendo seu conjunto

Quando você escreve uma nova anotação, decida todas as cinco de uma vez:

  1. Quem precisa lê-la? → @Retention.
  2. Onde ela pode aparecer? → @Target.
  3. Os usuários devem vê-la no Javadoc? → @Documented ou não.
  4. Subclasses devem herdá-la? → @Inherited para marcadores no nível de classe como @Auditable. Omita para anotações no nível de método.
  5. Deve ser aplicada mais de uma vez? → @Repeatable se e somente se você genuinamente precisar de multiplicidade.

O esqueleto padrão para uma anotação de método visível em tempo de execução:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
@interface MyAnnotation { /* elements */ }

O próximo capítulo — Anotações Personalizadas — usa exatamente esse padrão para criar uma do zero e consumi-la com reflection. Para as anotações que o JDK fornece, veja Anotações Integradas; para a API de reflection usada acima, veja Lendo Anotações com Reflection.

Prática

Prática
Você declara `@Tagged` com `@Inherited` e `@Target(ElementType.TYPE)`. `interface Marker {}` é anotada com `@Tagged`, e `class Concrete implements Marker {}`. O que `Concrete.class.getAnnotation(Tagged.class)` retorna?
Você declara `@Tagged` com `@Inherited` e `@Target(ElementType.TYPE)`. `interface Marker {}` é anotada com `@Tagged`, e `class Concrete implements Marker {}`. O que `Concrete.class.getAnnotation(Tagged.class)` retorna?
Was this page helpful?