Introdução às Annotations Java
O que são annotations Java, como adicionam metadados ao código e onde são processadas.
Uma annotation é um marcador que você adiciona a um elemento do código-fonte Java — uma classe, método, campo, parâmetro, uso de tipo — que acrescenta metadados. A annotation em si não faz nada. É um rótulo que algum outro trecho de código (o compilador, um framework, uma IDE, uma ferramenta de build) lê depois e reage. @Override em um método diz ao compilador "estou sobrescrevendo um método da superclasse; por favor, avise se não estiver." @Test em um método diz ao JUnit "este é um teste." @Entity em uma classe diz ao JPA "mapeie isso para uma tabela do banco de dados." Em cada caso, a annotation apenas carrega informação; o comportamento reside no processador que a lê.
Você já viu annotations ao longo deste livro — @Override em métodos sobrescritos, @FunctionalInterface em interfaces de método único, @SuppressWarnings para silenciar o compilador. Esta parte do livro trata do sistema por trás disso: o que é uma annotation, quando ela está disponível (somente em código-fonte, no arquivo de classe ou em tempo de execução), como escrever a sua própria e como os processadores as consomem.
A estrutura de uma annotation
Sintaticamente, uma annotation é o símbolo @ seguido do nome da annotation, aplicado imediatamente antes do elemento que ela anota:
@Override
public String toString() { ... }
@Deprecated
public void oldApi() { ... }
@SuppressWarnings("unchecked")
List<String> list = (List<String>) raw;Algumas annotations recebem elementos (sua versão de campos). Os valores dos elementos vão entre parênteses:
@SuppressWarnings("unchecked") // one element, value "unchecked"
@SuppressWarnings({"unchecked", "rawtypes"}) // array of strings
@RequestMapping(path = "/users", method = GET) // two named elementsSe uma annotation declara um único elemento chamado value, você pode omitir o nome — @SuppressWarnings("unchecked") é forma abreviada de @SuppressWarnings(value = "unchecked").
O que annotations não são
Três negativas que evitam os mal-entendidos mais comuns:
- Annotations não executam código. Escrever
@Cachedao lado de um método não armazena nada em cache. Algo mais precisa procurar por@Cachede adicionar o comportamento de cache. A annotation é uma flag, não uma função. - Annotations não são comentários. Comentários desaparecem em tempo de compilação; annotations são construções de linguagem de primeira classe. Elas participam do sistema de tipos, podem ser obrigadas a permanecer no arquivo de classe e podem ser lidas em tempo de execução via reflection.
- Annotations não substituem código claro. Uma longa pilha de annotations no topo de uma classe é densidade de informação, nem sempre clareza. Frameworks que dependem muito de annotations (Spring, JPA, JAX-RS) pagam pela conveniência com uma curva de aprendizado e custo em tempo de execução.
Quando a annotation existe
Toda annotation tem uma política de retenção que determina por quanto tempo os metadados sobrevivem:
SOURCE— o compilador a lê e depois a descarta.@Overridee@SuppressWarningsfuncionam assim; o bytecode não contém nenhum registro delas.CLASS— a annotation é escrita no arquivo.class, mas não é carregada pela JVM em tempo de execução. Este é o padrão. Ferramentas que inspecionam bytecode (analisadores estáticos, pós-processadores) podem lê-la.RUNTIME— a annotation é mantida até o final; reflection pode consultar qualquer classe, método ou campo pelas suas annotations em tempo de execução. Frameworks como Spring e Jackson dependem disso.
Você verá a meta-annotation @Retention(...) que define essa política no capítulo Meta-annotations. O resumo: escolha a retenção com base em quem precisa ler a annotation — o compilador, uma ferramenta de build ou código em tempo de execução.
Onde a annotation pode ir
Cada annotation também tem um alvo — o conjunto de elementos do programa que ela pode legalmente anotar. Os alvos comuns:
TYPE— classes, interfaces, enums.METHOD— métodos.FIELD— campos.PARAMETER— parâmetros de método.CONSTRUCTOR— construtores.LOCAL_VARIABLE— declarações de variáveis locais.ANNOTATION_TYPE— outras declarações de annotation (meta-annotations).PACKAGE— pacotes, viapackage-info.java.TYPE_USE— qualquer uso de um tipo, incluindo parâmetros genéricos e casts (Java 8+).
Se você colocar uma annotation em algum lugar que o seu @Target não permite, o compilador recusa. Tentar usar @Override em uma declaração de classe é um erro de compilação porque @Override tem alvo METHOD.
Quem lê annotations
Três lugares consomem dados de annotation:
- O compilador. Annotations embutidas como
@Override,@SafeVarargse@FunctionalInterfacesão verificadas pelo própriojavac. - Processadores de annotation. Ferramentas plugáveis em tempo de compilação que são executadas durante o
javac. Podem ler annotations nas fontes em compilação e gerar novos arquivos-fonte em resposta. Lombok, Dagger, o metamodelo estático do Hibernate e o frameworkMicronautfuncionam dessa forma. - Reflection em tempo de execução.
Method.getAnnotations(),Class.getAnnotation(...)etc. retornam as instâncias de annotation de qualquer elemento com retençãoRUNTIME. É assim que Spring decide o que injetar, como JUnit encontra seus testes e como Jackson mapeia JSON.
Os dois primeiros não precisam de suporte da máquina virtual além do que o javac fornece. O terceiro precisa que a annotation esteja gravada no arquivo de classe e carregada pelo runtime.
Um exemplo prático: inspecionando annotations na sua própria classe
O objetivo deste exemplo é mostrar que @Override, @Deprecated e @SuppressWarnings parecem idênticas no código-fonte, mas se comportam de forma diferente após a compilação da classe. O programa declara uma classe com várias annotations e então pergunta à reflection o que ela pode realmente ver.
O que extrair da execução:
- O loop no nível da classe encontrou
@DeprecatedemGreeter, mas nada mais —@Deprecatedtem retençãoRUNTIME.@Overridee@SuppressWarningssãoSOURCE, então o compilador as descartou antes que o arquivo de classe fosse escrito e a reflection não consegue recuperá-las. - O loop por método só imprimiu
@DeprecatedemoldHello. Mesmo quetoStringtivesse sido declarado com@Overrideecastcom@SuppressWarnings("unchecked"), nenhuma das annotations chegou ao arquivo de classe. A informação existia no momento em que ojavacexecutou — é assim que a verificação de sobrescrita aconteceu — e foi então descartada. - A verificação de retenção deixou isso claro:
@Overridee@SuppressWarningscarregam@Retention(SOURCE)em suas próprias declarações, enquanto@Deprecatedcarrega@Retention(RUNTIME). Retenção é uma propriedade do tipo de annotation, não de como ela é usada. - Ler
@DeprecatedemGreeterviacls.getAnnotation(Deprecated.class)retornou um proxy cujos métodos de elemento (since(),forRemoval()) retornaram os valores escritos no código-fonte. Essa é a interface em tempo de execução para metadados de annotation: uma instância cujos elementos são métodos acessores. - A lição para escolher a retenção: se o único consumidor é o
javac(verificações de sobrescrita, supressão de avisos), useSOURCE. Se um framework precisa ler a annotation enquanto o programa está em execução (DI, ORM, mapeamento JSON), useRUNTIME. O capítulo sobre meta-annotations explica como você declara isso para seus próprios tipos de annotation.
O que vem a seguir nesta parte
Os capítulos restantes desta parte abrangem:
- As annotations embutidas mais comuns em
java.lang—@Override,@Deprecated,@SuppressWarnings,@SafeVarargs,@FunctionalInterface. - As meta-annotations que configuram as suas próprias —
@Retention,@Target,@Documented,@Inherited,@Repeatable. - Como escrever tipos de annotation personalizados e lê-los via reflection.
- A API de processamento de annotation em tempo de compilação que frameworks usam para gerar código.
O caminho vai de "quais annotations a linguagem fornece" a "quais annotations você pode construir sobre elas".