O que realmente é um modelo de anotações
Modelo de anotações é a estrutura que define como metadados são declarados, lidos e tratados em tempo de execução dentro de um sistema ou linguagem. Em Java, por exemplo, isso significa definir uma interface com a sigla @interface, escolher entre os três níveis de validação -- SOURCE, CLASS ou RUNTIME -- e decidir se a anotação será hereditária ou não. Na prática, isso determina se uma ferramenta como o Spring pode injetar comportamento automaticamente ou se você precisa escrever código manual para interpretar o que foi anotado. A maioria dos desenvolvedores aprende a sintaxe básica e para por aí. O problema é que a diferença entre um modelo bem desenhado e um modelo razoável aparece só quando o projeto cresce. Anotações mal posicionadas geram problemas que não aparecem no teste unitário mas quebram a aplicação em produção durante a inicialização do container.
modelo de anotações
Para construir um modelo de anotações funcional, você precisa primeiro responder a uma pergunta simples: esse dado precisa estar disponível em tempo de compilação ou em tempo de execução. Se for apenas para documentação ou para o compilador gerar algo estaticamente, o nível SOURCE basta. Se for para reflection, o nível RUNTIME é obrigatório. Eu já perdi tempoando por horas porque uma anotação foi declarada como RetentionPolicy.CLASS quando deveria ser RUNTIME. O código compila perfeitamente. A aplicação simplesmente não lê a anotação e segue em frente sem erro algum. A estrutura mínima de uma anotação em Java é:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@Documented
public @interface MinhaAnotacao {
String valor() default "";
int prioridade() default 0;
}
Cada atributo dentro da anotação precisa ter um tipo válido -- String, int, enum, classe, ou arrays desses tipos. Você não pode colocar coleções arbitrárias como lista direta. Se precisar de múltiplos valores, declare o atributo como array: String[] categorias() default {}.
👉 Clique no botão abaixo para saber mais sobre o assunto!
O processamento disso exige um segundo componente: o leitor. Pode ser um processor de annotations via AnnotationProcessor no compile-time ou reflection no runtime. O processor é mais rápido e funciona em build time. A reflection é mais flexível mas tem custo de performance. Use processor se puder. Use reflection apenas quando o framework exigir, como no Spring ou JPA. Um detalhe que quase ninguém menciona: se sua anotação precisa ser aplicada a elementos genéricos como tipos parametrizados, você também precisa usar @Target com ElementType.TYPE_USE. Sem isso, anotações como @NonNull em variáveis genéricas simplesmente não são reconhecidas pelo compilador. Isso é diferente do TYPE normal, que se aplica à declaração da classe em si, não aos tipos usados dentro dela.
Eu trabalhei num sistema onde precisávamos marcar campos de entidade com regras de validação customizada e mapeamento para uma API externa. Criamos um modelo de anotações com três camadas: uma para validação, uma para transformação e uma para exposição. O erro comum seria misturar tudo numa única anotação com dezenas de atributos. Funcionou até o projeto dobrar de tamanho, quando começou a ficar intratável de manter. A solução foi separar em anotações compostas usando @Repeatable para campos que precisavam de múltiplas regras e criar um parser unificado que lia todas as anotações em sequência. Outro ponto prático: evite colocar lógica dentro da anotação. Anotações não executam nada por si mesmas. Se você tentar simular comportamento com default values complexos ou métodos padrão que chamam funções pesadas, vai ter problemas de performance e comportamento imprevisível. Mantenha a anotação como dados puros e deixe o processador fazer o trabalho pesado.
Para quem está começando, o caminho mais direto é usar o que o ecossistema já oferece. Se estiver no Spring, @Autowired, @Service, @Component já implementam o padrão corretamente. Entenda como cada um funciona antes de criar o seu próprio modelo. Isso evita reinventar soluções que já foram testadas em escala. Se precisar criar algo customizado, comece pequeno -- uma anotação com um único atributo, um processador simples, e vá expandindo conforme a necessidade aparecer. A tentação de antecipar requisitos futuros leva a modelos excessivamente complexos que ninguém consegue manter depois de seis meses. Se quiser um exemplo completo pronto para uso, a estrutura base pode ser adaptada rapidamente para diferentes cenários. O importante é que o modelo seja coerente com o ciclo de vida do dado que ele representa e com a ferramenta que vai consumi-lo.