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 hereComo 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.
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.
O que aprender com a execução:
getAnnotation(Service.class)retornou um proxy ativo cujovalue()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.AdminControllerreportou@Servicepresente mas não declarado:isAnnotationPresentretornoutrue(herdado deUserController) enquantogetDeclaredAnnotationretornounull. Essa diferença é inteiramente devida à meta-anotação@Inherited, e funciona apenas porque@Servicetem como alvo uma classe — métodos e campos nunca herdam anotações desta forma.list.getAnnotation(Role.class)retornounullmesmo com duas anotações@Roleclaramente visíveis no código-fonte. Anotações repetíveis são envolvidas em um contêinerRolespelo compilador, então o getter de valor único as perde;getAnnotationsByType(Role.class)descompactou o contêiner e retornou ambos os papéis. Sempre usegetAnnotationsByTypepara anotações repetíveis.- As anotações de parâmetros eram acessíveis por parâmetro: o parâmetro
tenantreportou@NotNullpresente epagenã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()emlistcontou duas anotações —@Endpointe o contêiner sintéticoRoles— 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.