Processamento de Anotações em Tempo de Execução em Java
Uma anotação, por si só, não executa nenhuma ação. Ela apenas marca elementos do código (como classes, métodos ou campos) com metadados. Para que esses metadados tenham efeito, precisamos processá-los — geralmente usando reflexão ou ferramentas de processamento em tempo de compilação.
Aqui vamos focar no processamento em tempo de execução, feito com a API de reflexão (Reflection).
Interface AnnotatedElement
O pacote java.lang.reflect define a interface AnnotatedElement, que é implementada por várias classes da API de reflexão, como:
ClassFieldMethodConstructorParameterPackage
Isso significa que podemos inspecionar anotações em qualquer desses elementos.
Principais métodos:
T getAnnotation(Class<T> annotationClass)
T getDeclaredAnnotation(Class<T> annotationClass)
T[] getAnnotationsByType(Class<T> annotationClass)
T[] getDeclaredAnnotationsByType(Class<T> annotationClass)
Annotation[] getAnnotations()
Annotation[] getDeclaredAnnotations()
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)Esses métodos permitem verificar, obter e listar as anotações aplicadas a um elemento.
Exemplo simples: lendo uma anotação de classe
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME) // Disponível em tempo de execução
@Target(ElementType.TYPE) // Pode ser usada em classes e interfaces
@interface MyAnnotation {
String value();
}
// Aplicação da anotação
@MyAnnotation(value = "Thing")
class Thing {
String name;
}
public class Program {
public static void main(String[] args) throws Exception {
var obj = new Thing();
var cl = obj.getClass();
// Obtém a anotação do tipo especificado
MyAnnotation annot = cl.getAnnotation(MyAnnotation.class);
System.out.println(annot); // Saída: @MyAnnotation(value=Thing)
if (annot != null) {
System.out.println(annot.value()); // Saída: Thing
}
}
}O que acontece aqui:
getClass()obtém o objetoClassassociado aThing.getAnnotation(MyAnnotation.class)retorna um proxy que representa a anotação aplicada.- Chamando
annot.value(), acessamos o valor definido na anotação. - Caso a anotação não esteja presente, o método retorna
null.
Obtendo várias anotações
Se um elemento possuir múltiplas anotações, podemos listá-las com getAnnotations():
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@interface Description {
String data();
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@interface Name {
String value();
}
@Name(value = "Thing")
@Description(data = "Represents some object with a name")
class Thing { }
public class Program {
public static void main(String[] args) {
var cl = Thing.class;
// Retorna todas as anotações aplicadas
Annotation[] annotations = cl.getAnnotations();
for (var annot : annotations) {
System.out.println(annot);
System.out.println(annot.annotationType());
}
}
}Saída:
@Name(value=Thing) interface Name @Description(data=Represents some object with a name) interface Description
O método annotationType() retorna o tipo da anotação — útil para identificar ou processar diferentes tipos de metadados dinamicamente.
Exemplo prático: validação com anotações
Vamos criar um pequeno sistema de validação que usa anotações para definir regras de intervalo de valores numéricos.
1. Criando a anotação
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Range {
int min();
int max();
}Essa anotação indica que um campo deve ter valor dentro de um intervalo definido.
2. Aplicando a anotação
class Person {
String name;
@Range(min = 1, max = 110)
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
String getName() { return name; }
int getAge() { return age; }
}Aqui, age deve estar entre 1 e 110.
3. Criando o validador com reflexão
import java.lang.reflect.Field;
class RangeValidator {
public static boolean validate(Object obj) throws Exception {
var cl = obj.getClass();
for (Field field : cl.getDeclaredFields()) {
// Verifica se o campo tem a anotação @Range e é do tipo int
if (field.isAnnotationPresent(Range.class) && field.getType() == int.class) {
Range range = field.getAnnotation(Range.class);
field.setAccessible(true); // permite acessar campos privados
int value = (int) field.get(obj);
if (value < range.min() || value > range.max()) {
return false; // fora do intervalo
}
}
}
return true; // todos os campos válidos
}
}4. Testando o validador
public class Program {
public static void main(String[] args) throws Exception {
Person tom = new Person("Tom", 41);
validatePerson(tom);
Person bob = new Person("Bob", 141);
validatePerson(bob);
}
static void validatePerson(Person p) throws Exception {
if (RangeValidator.validate(p))
System.out.printf("Person %s is valid%n", p.getName());
else
System.out.printf("Person %s is invalid!!!%n", p.getName());
}
}Saída:
Person Tom is valid Person Bob is invalid!!!
O que acontece nos bastidores
- O método
validate()obtém a classe do objeto comobj.getClass(). - Em seguida, percorre todos os campos declarados.
- Para cada campo, verifica se possui a anotação
@RangecomisAnnotationPresent(). - Se tiver, a anotação é obtida via
getAnnotation(). - O valor do campo é lido com
field.get(obj). - Por fim, o método compara o valor com os limites definidos na anotação (
minemax).
Esse padrão é amplamente usado em frameworks reais — por exemplo:
- O Spring faz isso para injeção de dependências.
- O Hibernate Validator usa anotações como
@NotNull,@Size,@Min,@Max.
Resumo
| Conceito | Método/Classe | Descrição |
|---|---|---|
| Inspeção de anotações | AnnotatedElement | Interface base para classes, campos e métodos |
| Leitura de anotação | getAnnotation() | Retorna uma instância de uma anotação específica |
| Listagem de todas | getAnnotations() | Retorna todas as anotações aplicadas |
| Verificação de presença | isAnnotationPresent() | Verifica se um elemento tem uma anotação |
| Exemplo prático | RangeValidator | Demonstra uso de reflexão para validação de dados |
Conclusão
O processamento de anotações em tempo de execução é uma técnica poderosa que permite criar código declarativo e extensível. Com ela, é possível construir frameworks que “leem” as intenções do programador diretamente das anotações — um dos pilares de tecnologias modernas como Spring, Jakarta EE, JUnit, e Lombok.