Geração de Logs de Alterações em Atributos de Entidades usando Reflexão em Java

Em determinados cenários de desenvolvimento, é necessário registrar alterações realizadas em entidades para fins de auditoria. A técnica descrita a seguir utiliza reflexão para comparar os atributos de dois objetos (o estado anteriro e o novo estado) e gerar um registro das mudanças.

1. Definição da Anotação de Marcação

Criamos uma anotação customizada para indicar quais campos da entidade devem ser incluídos na comparação. Apenas campos anotados serão avaliados.

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;

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CampoAuditavel {
    String descricao() default "";
}

2. Estrutura do Registro de Alteração

Esta classe repersenta uma única alteração detectada em um campo, contendo a descrição do campo, o valor entigo e o novo valor.

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class RegistroAlteracao {
    @CampoAuditavel(descricao = "Nome do Campo")
    private String nomeCampo;
    @CampoAuditavel(descricao = "Valor Anterior")
    private String valorAntigo;
    @CampoAuditavel(descricao = "Novo Valor")
    private String valorNovo;
}

3. Classe Utilitária de Comparação

O utilitário abaixo realiza a comparação objeto a objeto. A lógica itera sobre os campos declarados, verifica a presença da anotação, acessa os valores via reflexão (usando PropertyDescriptor) e adiciona uma entrada na lista de alterações sempre que uma diferença for detectada.

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class ComparadorEntidades {

    public static List<RegistroAlteracao> compararObjetos(Object entidadeAntiga, Object entidadeNova) {
        List<RegistroAlteracao> historico = new ArrayList<>();
        Class<?> classe = entidadeAntiga.getClass();

        for (Field campo : classe.getDeclaredFields()) {
            CampoAuditavel anotacao = campo.getAnnotation(CampoAuditavel.class);
            if (anotacao == null) {
                continue;
            }

            try {
                PropertyDescriptor descritor = new PropertyDescriptor(campo.getName(), classe);
                Object valorAntigo = descritor.getReadMethod().invoke(entidadeAntiga);
                Object valorNovo = descritor.getReadMethod().invoke(entidadeNova);

                // Compara os valores, tratando corretamente nulos
                if (!Objects.equals(valorAntigo, valorNovo)) {
                    // Converte para String, usando "" se for nulo
                    String strAntigo = valorAntigo == null ? "" : valorAntigo.toString();
                    String strNovo = valorNovo == null ? "" : valorNovo.toString();

                    RegistroAlteracao registro = RegistroAlteracao.builder()
                            .nomeCampo(anotacao.descricao())
                            .valorAntigo(strAntigo)
                            .valorNovo(strNovo)
                            .build();
                    historico.add(registro);
                }
            } catch (Exception e) {
                // Em um projeto real, logar a exceção
                throw new RuntimeException("Falha ao comparar entidades", e);
            }
        }
        return historico;
    }
}

Tags: java Reflexão Lombok slf4j Logs de Auditoria

Publicado em 6-20 20:51