Guia de Anotações Personalizadas em Java

Introdução às Anotações Personalizadas

Para dominar anotações em Java, é essencial aprender a definir e utilizar suas próprias anotações. Antes de criar anotações customizadas, é necessário compreender os meta-annotations fornecidos pela linguagem e a sintaxe para defini-los.

Meta-Anotações

Meta-anotações são responsáveis por anotar outras anotações. Java 5.0 define quatro tipos padrão de meta-anotações que fornecem informações sobre outros tipos de annotation. Esses tipos e as classes correspondentes podem ser encontrados no pacote java.lang.annotation. Vamos analisar cada meta-anotação e seus parâmetros:

@Target

O @Target especifica o escopo dos objetos que podem ser anotados: pacotes, tipos (classes, interfaces, enumerações, tipos de annotation), membros de tipo (métodos, construtores, variáveis de membro, valores de enumeração), parâmetros de método e variáveis locais (como variáveis de loop ou parâmetros catch). O uso do target na declaração de um tipo annotation deixa mais claro qual é o seu alvo.

Função: Descrever o escopo de uso de uma anotação

Valores possíveis (ElementType):

  • CONSTRUCTOR: para descrição de construtores
  • FIELD: para descrição de campos
  • LOCAL_VARIABLE: para descrição de variáveis locais
  • METHOD: para descrição de métodos
  • PACKAGE: para descrição de pacotes
  • PARAMETER: para descrição de parâmetros
  • TYPE: para descrição de classes, interfaces (incluindo tipos de annotation) ou declarações de enum

Exemplo de uso:

@Target(ElementType.TYPE)
public @interface Tabela {
    /**
     * Nome da tabela no banco de dados, padrão é o nome da classe
     * @return
     */
    public String nomeTabela() default "nomeClasse";
}

@Target(ElementType.FIELD)
public @interface NaoColunaBD {

}

A anotação Tabela pode ser usada em classes, interfaces (incluindo tipos de annotation) ou declarações de enum, enquanto a anotação NaoColunaBD só pode ser aplicada a variáveis de membro de classes.

@Retention

O @Retention define o tempo de retenção de uma Annotation: algumas anotações aparecem apenas no código fonte e são descartadas pelo compilador; outras são compiladas no arquivo class; as anotações compiladas no arquivo class podem ser ignoradas pela JVM, enquanto outras são lidas quando a classe é carregada (sem afetar a execução da classe, já que Annotation e classe são separados em uso). Este meta-Annotation pode ser usado para limitar o "ciclo de vida" da Annotation.

Função: Indica em que nível as informações da anotação devem ser mantidas

Valores possíveis (RetentionPolicy):

  • SOURCE: válido apenas no arquivo fonte (retenção no fonte)
  • CLASS: válido no arquivo class (retenção no bytecode)
  • RUNTIME: válido em tempo de execução (retenção em runtime)

O tipo meta-Annotation Retention tem um único membro value, cujo valor vem do tipo enumerado java.lang.annotation.RetentionPolicy. Exemplo:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Coluna {
    public String nome() default "nomeCampo";
    public String nomeSetFuncao() default "setCampo";
    public String nomeGetFuncao() default "getCampo"; 
    public boolean valorPadraoBD() default false;
}

A anotação Coluna tem um valor de RetentionPolicy igual a RUNTIME, permitindo que processadores de anotação, através de reflexão, obtenham os valores dos atributos da anotação para realizar lógicas de tempo de execução.

@Documented

O @Documented indica que outros tipos de annotation devem ser tratados como API pública dos membros do programa anotado, podendo ser documentados por freramentas como javadoc. Documented é uma anotação de marcação, sem membros.

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Coluna {
    public String nome() default "nomeCampo";
    public String nomeSetFuncao() default "setCampo";
    public String nomeGetFuncao() default "getCampo"; 
    public boolean valorPadraoBD() default false;
}

@Inherited

O @Inherited é uma anotação de marcação que indica que um tipo anotado é herdável. Se uma annotation usando @Inherited for aplicada a uma classe, essa annotation também será aplicada às subclasses da classe.

É importante notar que annotation do tipo @Inherited é herdada pelas subclasses da classe anotada. Classes não herdam annotations das interfaces que implementam, e métodos não herdam annotations dos métodos que sobrescrevem.

Quando uma annotation com @Inherited tem RetentionPolicy.RUNTIME, a API de reflexão aumenta essa herança. Ao usar java.lang.reflect para consultar uma annotation do tipo @Inherited, o código de reflexão verifica a classe e suas superclasses até encontrar a annotation desejada ou chegar ao topo da hierarquia de herança.

Exemplo de código:

@Inherited
public @interface Saudacao {
    public enum CorFonte{ AZUL,VERMELHO,VERDE};
    String nome();
    CorFonte corFonte() default CorFonte.VERDE;
}

Definindo Anotações Personalizadas

Ao usar @interface para definir uma anotação personalizada, a mesma automaticamente herda a interface java.lang.annotation.Annotation, com detalhes tratados pelo compilador. Ao definir uma anotação, não é possível herdar outras anotações ou interfaces. O @interface é usado para declarar uma anotação, onde cada método na realidade declara um parâmetro de configuração. O nome do método é o nome do parâmetro e o tipo de retorno é o tipo do parâmetro (tipos de retorno podem ser apenas tipos primitivos, Class, String, enum). É possível usar default para declarar valores padrão para os parâmetros.

Formato de definição:

public @interface NomeAnotacao {corpo}

Tipos de dados suportados para parâmetros de anotação:

  • Todos os tipos de dados primitivos (int, float, boolean, byte, double, char, long, short)
  • Tipos String
  • Tipos Class
  • Tipos enum
  • Tipos Annotation
  • Arrays dos tipos acima

Regras para parâmetros em Annotation:

  1. Apenas modificadores de acesso public ou default podem ser usados. Por exemplo: String valor(); aqui o método é definido como default.
  2. Parâmetros de membro só podem usar os oito tipos de dados primitivos (byte, short, char, int, long, float, double, boolean) e tipos como String, Enum, Class, annotations e arrays desses tipos.
  3. Se houver apenas um parâmetro de membro, é recomendável nomeá-lo como "value", seguido de parênteses. Por exemplo, a anotação FrutoNome abaixo tem apenas um parâmetro de membro.

Exemplo Simples de Anotação Personalizada e Uso

package anotacoes;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Anotação para nome de fruta
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NomeFruta {
    String value() default "";
}
package anotacoes;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Anotação para cor de fruta
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CorFruta {
    /**
     * Enumeração de cores
     */
    public enum Cor{ AZUL,VERMELHO,VERDE};
    
    /**
     * Atributo de cor
     * @return
     */
    Cor corFruta() default Cor.VERDE;

}
package anotacoes;

import anotacoes.CorFruta.Cor;

public class Maçã {
    
    @NomeFruta("Maçã")
    private String nomeMaçã;
    
    @CorFruta(corFruta=Cor.VERMELHO)
    private String corMaçá;
    
    
    
    public void setCorMaçá(String corMaçá) {
        this.corMaçá = corMaçá;
    }
    public String getCorMaçá() {
        return corMaçá;
    }
    
    
    public void setNomeMaçã(String nomeMaçá) {
        this.nomeMaçá = nomeMaçá;
    }
    public String getNomeMaçá() {
        return nomeMaçá;
    }
    
    public void exibirNome(){
        System.out.println("O nome da fruta é: Maçã");
    }
}

Valores Padrão para Elementos de Anotação

Elementos de anotação devem ter valores definidos, seja especificando um valor padrão na definição da anotação ou ao usar a anotação. Elementos de anotação de tipos não primitivos não podem ser nulos. Portanto, usar uma string vazia ou 0 como valor padrão é uma prática comum. Essa restrição torna difícil para processadores representar o estado de existência ou ausência de um elemento, pois em cada declaração de anotação, todos os elementos existem e têm valores correspondentes. Para contornar essa limitação, definioms valores especiais, como string vazia ou números negativos, para indicar a ausência de um elemento. Isso se tornou uma convenção ao definir anotações. Exemplo:

package anotacoes;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Anotação para fornecedor de fruta
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FornecedorFruta {
    /**
     * ID do fornecedor
     * @return
     */
    public int id() default -1;
    
    /**
     * Nome do fornecedor
     * @return
     */
    public String nome() default "";
    
    /**
     * Endereço do fornecedor
     * @return
     */
    public String endereco() default "";
}

Após definir anotações e aplicá-las às classes e atributos relevantes, o próximo passo é implementar o processamento dessas informações. Sem um mecanismo de processamento correspondente, as anotações não têm valor prático. A chave para fazer as anotações realmente funcionarem está nos métodos de processamento de anotação, que abordaremos em nosso próximo estudo sobre obtenção e processamento de informações de anotação!

Tags: java annotations meta-annotations custom-annotations reflection

Publicado em 6-3 19:05 por Thomas