W3docs

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 elements

Se 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 @Cached ao lado de um método não armazena nada em cache. Algo mais precisa procurar por @Cached e 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. @Override e @SuppressWarnings funcionam 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, via package-info.java.
  • TYPE_USEqualquer 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:

  1. O compilador. Annotations embutidas como @Override, @SafeVarargs e @FunctionalInterface são verificadas pelo próprio javac.
  2. 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 framework Micronaut funcionam dessa forma.
  3. Reflection em tempo de execução. Method.getAnnotations(), Class.getAnnotation(...) etc. retornam as instâncias de annotation de qualquer elemento com retenção RUNTIME. É 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.

java— editable, runs on the server

O que extrair da execução:

  • O loop no nível da classe encontrou @Deprecated em Greeter, mas nada mais — @Deprecated tem retenção RUNTIME. @Override e @SuppressWarnings são SOURCE, 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 @Deprecated em oldHello. Mesmo que toString tivesse sido declarado com @Override e cast com @SuppressWarnings("unchecked"), nenhuma das annotations chegou ao arquivo de classe. A informação existia no momento em que o javac executou — é assim que a verificação de sobrescrita aconteceu — e foi então descartada.
  • A verificação de retenção deixou isso claro: @Override e @SuppressWarnings carregam @Retention(SOURCE) em suas próprias declarações, enquanto @Deprecated carrega @Retention(RUNTIME). Retenção é uma propriedade do tipo de annotation, não de como ela é usada.
  • Ler @Deprecated em Greeter via cls.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), use SOURCE. Se um framework precisa ler a annotation enquanto o programa está em execução (DI, ORM, mapeamento JSON), use RUNTIME. 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".

Prática

Prática
Um colega escreve `@Cached` ao lado de um método e espera que a segunda chamada retorne um resultado em cache. O que ele entendeu errado sobre annotations?
Um colega escreve `@Cached` ao lado de um método e espera que a segunda chamada retorne um resultado em cache. O que ele entendeu errado sobre annotations?
Was this page helpful?