Annotations Runtime
- #Java
As anotações em tempo de execução (runtime annotations) são metadados que permanecem disponíveis durante a execução da aplicação e podem ser inspecionados dinamicamente por meio de reflexão (Reflection API).
Conceito
- Uma annotation é uma forma declarativa de adicionar informações extras a classes, métodos, atributos, parâmetros, construtores ou até mesmo outras annotations, sem alterar diretamente a lógica do programa.
- Quando uma annotation possui política de retenção RUNTIME, a JVM mantém esses metadados carregados na memória e permite consultá-los enquanto o programa está em execução.
Ciclo de vida de uma annotation
O Java define 3 níveis principais de retenção:
- Source: a anotação existe apenas no código-fonte
- Não é armazenada no bytecode compilado (.class).
- É descartada durante a compilação.
- Utilizada principalmente por ferramentas de análise de código ou geração automática de código.
- Runtime: a anotação permanece disponível em execução
- A JVM a carrega em memória.
- Reflection pode acessá-la dinamicamente.
- Frameworks modernos dependem fortemente desse comportamento.
- Class: a anotação permanece no bytecode compilado
- É armazenada no arquivo .class.
- A JVM não a expõe em runtime.
- Reflection não consegue acessá-la.
- É útil para ferramentas de pós-processamento ou manipulação de bytecode.
Por que existem as runtime annotations?
- Permitem construir sistemas desacoplados e configuráveis baseados em metadados declarativos.
- Em vez de escrever lógica rígida, o sistema analisa as annotations e decide comportamentos dinamicamente.
- Isso possibilita:
- Injeção de dependências
- Validações automáticas
- Mapeamento objeto-relacional
- Serialização
- Segurança declarativa
- Programação orientada a aspectos
- Descoberta automática de componentes
- Configuração baseada em convenções.
Relação com Reflection
- As runtime annotations dependem diretamente de reflection.
- Reflection permite:
- Inspecionar classes carregadas
- Analisar métodos e atributos
- Detectar annotations presentes
- Obter valores definidos dentro das annotations
- Executar lógica condicional baseada em metadados
- Sem reflection, uma runtime annotation seria apenas uma informação armazenada sem utilidade dinâmica.
Arquitetura típica baseada em annotations
- Em sistemas Enterprise modernos, as annotations normalmente fazem parte de um pipeline:
- O desenvolvedor declara metadados
- O framework inspeciona as classes na inicialização
- Um modelo interno é construído
- Comportamentos automáticos são registrados
- O runtime executa lógica baseada nessas definições
Modelo declarativo
- O programador descreve intenções ou regras e o framework interpreta o comportamento.
- Exemplo conceitual:
- Esse método requer autenticação
- Este atributo não pode ser nulo
- Esta classe representa uma entidade persistente
Metaprogramação
- As runtime annotations são uma forma de metaprogramação.
- A aplicação pode:
- Analisar sua própria estrutura
- Modificar comportamentos dinamicamente
- Construir proxies
- Interceptar execuções
- Gerar configurações internas
- Registrar componentes automaticamente
Custos e limitações
- Overhead de reflection, ler annotations implica inspeção dinâmica.
- Pode afetar:
- Tempo de inicialização
- Consumo de memória
- Performance em escaneamentos massivos
Por isso, muitos frameworks fazem cache dos metadados.
Acoplamento implícito
- A lógica pode se tornar menos visível.
- O comportamento deixa de estar explicitamente codificado, passando a ficar distribuído entre:
- Annotations
- Reflection
- Contêineres
- Proxies
- Interceptadores
Isso pode dificultar o debugging.
Tipos de processamento de annotations
- Runtime Processing: são processadas durante a execução por meio de reflection.
- Compile-time Processing: são processadas durante a compilação utilizando annotation processors.
- Isso permite:
- Geração de código
- Validações estáticas
- Otimizações
- Eliminação de reflection
Frameworks modernos buscam migrar parte do processamento para compile-time a fim de melhorar a performance.
Target em annotations
- Define onde uma annotation pode ser aplicada.
- É uma restrição semântica e estrutural que indica ao compilador quais são os elementos válidos sobre os quais uma annotation pode ser utilizada.
- Sem target, uma annotation pode ser aplicada praticamente sobre qualquer elemento da linguagem.
Objetivo principal
- Evitar usos indevidos ou incoerentes de metadados.
- Uma annotation projetada para métodos não deveria ser aplicada sobre classes.
- O target formaliza essas regras.
Funcionamento conceitual
- Quando o compilador encontra uma annotation:
- Verifica sua definição
- Analisa os targets permitidos
- Valida se o elemento anotado é compatível
- Gera erro caso o uso seja indevido
É uma validação estrutural da linguagem.
Elementos que podem ser target
- O Java define diferentes tipos de elementos programáticos que podem ser ser anotados.

Relação entre Target e design de APIs
- Target também atua como mecanismo de design arquitetural.
- Define:
- Intenção
- Alcance
- Contexto semântico
- Contrato de uso
Uma annotation bem projetada deve possuir targets precisos. Targets muito amplos normalmente indicam um design pobre ou ambíguo.
Meta-annotations
- Target pertence ao grupo das meta-annotations, ou seja, é uma annotation aplicada sobre outra annotation para definir seu comportamento.
- As principais meta-annotations da linguagem são:

Design conceitual importante
- Annotations não são comportamento, e sim metadados.
- Target ajuda a delimitar exatamente sobre qual tipo de elemento esses metadados fazem sentido.
- Ou seja:
- Define contexto válido
- Reduz ambiguidades
- Melhora a legibilidade
- Fortalece contratos semânticos
- Evita erros arquitetônicos
Ejemplo
Passaremos para um exemplo de implementação de annotation runtime com target field. O mesmo refere-se à criação de um usuário com validação prévia de campos vazios e quantidade de caracteres.
Ele suporta tanto validação direta (dados primitivos) quanto por reflection (objetos), incluindo ambos os métodos nos validadores, porém, para este exemplo específico, optaremos pela validação direta.
O projeto completo pode ser obtido no repositório do GitHub.
https://github.com/Design-System-ET/annotations_runtime.git
Aqui faremos foco em:
- Obrigatório
- Validador
- Usuario
- Main
Porém, o exemplo completo está disponível no repositório.
@Retention(RetentionPolicy.RUNTIME) // Disponível em tempo de execução via Reflection
@Target(ElementType.FIELD) // Só pode ser aplicada a campos da classe
public @interface Obligatorio {
}
Annotation que indica que um campo é obrigatório.
- Não pode estar vazio nem ser null.
- Uso através da annotation @Obrigatorio
- Disponibilidade: RUNTIME (acessível via reflection)
- Objetivo: FIELD (aplicável apenas a campos da classe)
public class Validador {
public static void validar(String nombreCampo, String valor) {
if (valor == null || valor.trim().isEmpty()) {
throw new RuntimeException(
"El campo '" + nombreCampo + "' es obligatorio"
);
}
}
}
Clase que valida:
- Valida um valor direto (String), verificando se não é null nem vazio.
- É utilizado para validar dados inseridos pelo usuário antes da criação do objeto.
public class Usuario {
@Obligatorio
@TamanoMinimo(valor = 3)
private final String id;
@Obligatorio
@TamanoMinimo(valor = 5)
private final String name;
public Usuario(String id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Usuario{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
'}';
}
}
Modelo de dados
- Representa os atributos/propriedades da entidade. As annotations permitem adicionar metadados e comportamentos adicionais, como validações e regras de negócio, de forma desacoplada da implementação principal da classe.
public class Main {
static void main(String[] args) throws Exception {
Scanner scanner = new Scanner(System.in);
String id;
while (true) {
System.out.print("ID: ");
id = scanner.nextLine();
try {
Validador.validar("id", id);
TamanoMinimoValidator.validar("id", id, 3);
break;
} catch (RuntimeException e) {
System.out.println("Error: " + e.getMessage());
}
}
String nombre;
while (true) {
System.out.print("Nombre: ");
nombre = scanner.nextLine();
try {
Validador.validar("nombre", nombre);
TamanoMinimoValidator.validar("nombre", nombre, 5);
break;
} catch (RuntimeException e) {
System.out.println("Error: " + e.getMessage());
}
}
Usuario usuario = new Usuario(id, nombre);
System.out.println(usuario.toString());
}
}
Classe principal de execução da aplicação.
Responsável por:
- Solicitar dados ao usuário através do console (Scanner).
- Validar manualmente cada campo utilizando validadores externos.
- Controlar erros de validação por meio de exceções.
- Garantir que apenas objetos Usuario sejam criados com dados válidos.
- Instanciar o modelo Usuario e exibir seu conteúdo.
- Implementa um fluxo de validação iterativo até obter valores corretos para cada atributo.
Espero que seja útil.



