W3docs

Java Reflection: Lendo Anotações

Leia metadados de anotações em tempo de execução em Java com reflection — getAnnotation, getAnnotations.

Os capítulos anteriores abordaram a declaração de anotações (veja Anotações Personalizadas e Meta-Anotações); este capítulo trata de lê-las de volta em tempo de execução por meio de reflection. Uma anotação com @Retention(RUNTIME) torna-se consultável na Class, Method, Field, Constructor e Parameter à qual está vinculada. Ler anotações é como o JUnit encontra @Test, como o Spring encontra @Autowired e como os frameworks de validação encontram @NotNull. Este capítulo reúne a API de leitura completa em um só lugar, incluindo os detalhes de @Inherited e @Repeatable.

Esta página aborda os quatro métodos de leitura de AnnotatedElement, por que a retenção é um pré-requisito obrigatório, como @Inherited e @Repeatable alteram o que você recebe, como ler anotações de parâmetros e um exemplo funcional de scanner de validação que você pode executar.

Os quatro métodos de leitura

Todo elemento anotável (Class, Method, Field, Constructor, Parameter) implementa AnnotatedElement, que define os mesmos quatro métodos em todos os lugares:

AnnotatedElement el = SomeClass.class;   // or a Method, Field, etc.

el.isAnnotationPresent(Audited.class);   // boolean — quick check
el.getAnnotation(Audited.class);         // the annotation instance, or null
el.getAnnotations();                     // ALL annotations (declared + inherited)
el.getDeclaredAnnotations();             // only those declared directly here

Como a API é uniforme, o código para ler uma anotação de um método é idêntico ao de ler uma de uma classe — você apenas mantém um AnnotatedElement diferente. Os valores de anotação recuperados são proxies gerados pela JVM; chamar a.value() é uma chamada de método real que retorna o valor do elemento definido em tempo de compilação.

Retenção é um pré-requisito

Vale repetir porque é o bug número um: somente anotações com retenção RUNTIME são visíveis para reflection.

@Retention(RetentionPolicy.RUNTIME)   // <-- required for reflection
@interface Audited { String value(); }

A retenção padrão é CLASS, que mantém a anotação no arquivo .class, mas a descarta antes do tempo de execução. A retenção SOURCE a descarta ainda mais cedo. Se getAnnotation retornar null em uma anotação que você pode ver claramente no código-fonte, o @Retention(RUNTIME) ausente é quase sempre o motivo.

getAnnotations vs getDeclaredAnnotations e @Inherited

A diferença entre esses dois é @Inherited. Por padrão, as anotações não são herdadas por subclasses. Mas se um tipo de anotação é meta-anotado com @Inherited, então uma subclasse herda uma anotação no nível de classe de sua superclasse:

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Component { }

@Component class Base { }
class Derived extends Base { }     // Derived has no @Component in source

Derived.class.getAnnotation(Component.class)          // → present! (inherited)
Derived.class.getDeclaredAnnotation(Component.class)  // → null (not declared here)

Portanto, getAnnotations() inclui anotações herdadas; getDeclaredAnnotations() informa apenas o que está fisicamente escrito naquele elemento. Dois limites importantes: @Inherited funciona somente para anotações de classe (não métodos ou campos), e somente ao longo da cadeia de superclasse (não interfaces).

Anotações repetíveis

Desde o Java 8, uma anotação marcada com @Repeatable pode aparecer várias vezes em um elemento. Por baixo dos panos, o compilador agrupa as repetições em uma anotação contêiner, então o simples getAnnotation não as verá — você usa getAnnotationsByType, que descompacta o contêiner de forma transparente:

@Repeatable(Roles.class)
@Retention(RetentionPolicy.RUNTIME)
@interface Role { String value(); }
@Retention(RetentionPolicy.RUNTIME)
@interface Roles { Role[] value(); }     // the container

@Role("admin") @Role("user") class Account { }

Account.class.getAnnotationsByType(Role.class);   // → [Role(admin), Role(user)]
Account.class.getAnnotation(Role.class);          // → null! (it's wrapped in Roles)

Use getAnnotationsByType(Role.class) para anotações repetíveis; ele retorna um array e lida com os casos de ocorrência única e múltipla.

Lendo anotações de parâmetros e outros alvos

Os parâmetros obtêm suas próprias anotações por meio do Method.getParameterAnnotations() bidimensional (um array por parâmetro), ou pela API mais clara de Parameter:

for (Parameter p : method.getParameters()) {
  if (p.isAnnotationPresent(NotNull.class)) { /* validate */ }
}

Os mesmos métodos de AnnotatedElement funcionam em Field, Constructor, Package e até mesmo nas próprias anotações (para ler meta-anotações como @Retention). Para saber como obter os identificadores de Method, Field e Constructor em primeiro lugar, veja Refletindo Métodos, Campos e Construtores.

Nota

getParameterAnnotations() retorna um array [parameterIndex][annotation] — um array interno por parâmetro, mesmo para parâmetros sem anotações (esses arrays internos ficam simplesmente vazios). O loop baseado em Parameter acima geralmente é mais claro.

Um exemplo funcional: um mini scanner de validação

O programa declara anotações de tempo de execução, marca casos @Inherited e @Repeatable, e então um scanner genérico percorre as anotações de uma classe, as anotações de seus métodos e as anotações dos parâmetros de seus métodos — o esqueleto de um framework de validação ou roteamento.

java— editable, runs on the server

O que aprender com a execução:

  • getAnnotation(Service.class) retornou um proxy ativo cujo value() devolveu "users" — o valor escrito no código-fonte. Ler uma anotação é apenas chamar seus métodos de elemento; o framework reage a esses valores (aqui, tratando "users" como prefixo de rota). A anotação carrega dados, o scanner fornece comportamento.
  • AdminController reportou @Service presente mas não declarado: isAnnotationPresent retornou true (herdado de UserController) enquanto getDeclaredAnnotation retornou null. Essa diferença é inteiramente devida à meta-anotação @Inherited, e funciona apenas porque @Service tem como alvo uma classe — métodos e campos nunca herdam anotações desta forma.
  • list.getAnnotation(Role.class) retornou null mesmo com duas anotações @Role claramente visíveis no código-fonte. Anotações repetíveis são envolvidas em um contêiner Roles pelo compilador, então o getter de valor único as perde; getAnnotationsByType(Role.class) descompactou o contêiner e retornou ambos os papéis. Sempre use getAnnotationsByType para anotações repetíveis.
  • As anotações de parâmetros eram acessíveis por parâmetro: o parâmetro tenant reportou @NotNull presente e page não. Essa granularidade por parâmetro é o que os frameworks de validação de bean e de vinculação de requisições usam para validar ou injetar argumentos individuais.
  • getDeclaredAnnotations() em list contou duas anotações — @Endpoint e o contêiner sintético Roles — confirmando que os dois @Roles foram colapsados em um contêiner no nível do arquivo de classe. Qualquer anotação sem @Retention(RUNTIME) não teria aparecido nessa contagem.

Prática

Prática
Um framework marca métodos com uma anotação repetível '@Role' (um método pode ter várias). Em um método anotado '@Role('admin') @Role('editor')', chamar 'method.getAnnotation(Role.class)' retorna null. Por quê, e o que deve ser chamado em vez disso?
Um framework marca métodos com uma anotação repetível '@Role' (um método pode ter várias). Em um método anotado '@Role('admin') @Role('editor')', chamar 'method.getAnnotation(Role.class)' retorna null. Por quê, e o que deve ser chamado em vez disso?
Was this page helpful?