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.classe 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 anythingOs 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— empackage-info.java.TYPE_USE(Java 8+) — qualquer uso de um tipo, incluindo casts ((@NonNull String) o), cláusulasextends, 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+) — emmodule-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 inheritanceRessalvas:
- Ela percorre apenas a cadeia de superclasses — interfaces não propagam anotações mesmo com
@Inherited.class Foo implements Auditable { }não fazFoocarregar um@Auditableda 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
valuedo 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@Documentedtambém — e igualmente para@Inherited. O compilador rejeita uma incompatibilidade comcontaining 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) quantogetAnnotationsByType(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.
O que aprender com a execução:
@Modulefoi declarado na superclasseReportingBase, mas a reflection o encontrou emWeeklyReportsporque 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:
WithInterfaceimplementaAnnotatedInterface, que tem@Module, masgetAnnotation(Module.class)retornounull.@Inheritedpercorre apenasextends, nuncaimplements. Isso confunde muitos iniciantes; se você precisar de visibilidade de anotações entre interfaces, terá que percorrer a árvore de tipos você mesmo. runWeeklycarregava 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ê usargetAnnotationsByType.- O caso de
@Scheduleúnico emrunDailyfoi simétrico:getAnnotation(Schedule.class)funcionou porque não havia container, egetAnnotationsByTyperetornou um array de tamanho 1. Qualquer forma é válida quando você conhece a multiplicidade. - As linhas de "caso repetido via
getAnnotation" expuseram a armadilha. EmrunWeekly,getAnnotation(Schedule.class)retornounull— a anotação real no arquivo de classe é um container@Schedulessintetizado, não umSchedule. Acessar o container viagetAnnotation(Schedules.class)funciona. A regra: para qualquer anotação@Repeatable, sempre usegetAnnotationsByTypepara 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:
- Quem precisa lê-la? →
@Retention. - Onde ela pode aparecer? →
@Target. - Os usuários devem vê-la no Javadoc? →
@Documentedou não. - Subclasses devem herdá-la? →
@Inheritedpara marcadores no nível de classe como@Auditable. Omita para anotações no nível de método. - Deve ser aplicada mais de uma vez? →
@Repeatablese 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.