image

Acesse bootcamps ilimitados e +650 cursos pra sempre

70
%OFF
Article image
Claudio Silverira
Claudio Silverira15/05/2026 23:42
Compartilhe

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.

image

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:

image

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.

Compartilhe
Recomendados para você
Bootcamp NTT DATA: Backend Java com Spring AI
Globant  - Java & Spring Boot AI Developer
Almaviva Solutions - Back-end com Java & QA
Comentários (0)