Comparação de Desempenho entre Bibliotecas de Cópia de Propriedades em Java

No desenvolvimento de sistemas corporativos em Java, a cópia de atributos entre objetos de diferentes classes (como converter uma Entidade JPA para um DTO) é uma tarefa constante. Diversas bibliotecas facilitam esse trabalho, sendo as mais notáveis o Apache BeanUtils, o Spring BeanUtils e o Cglib BeanCopier. Embora tragam conveniência, a escolha da biblioteca impacta diretamente a performance da aplicação.

O uso do Apache BeanUtils é frequentemente desencorajado em guias de boas práticas de alta performance devido ao seu uso intensivo de introspecção e reflexão, o que resulta em uma execução consideravelmente mais lenta em comparação com as alternativas do Spring ou Cglib.

1. Configuração de Dependências

Para testar as diferentes abordagens, é necessário incluir as seguintes dependências no arquivo pom.xml do projeto Mavan:

<dependencies>
    <!-- Framework Spring para BeanUtils -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>5.3.10</version>
    </dependency>
    
    <!-- Apache Commons BeanUtils -->
    <dependency>
        <groupId>commons-beanutils</groupId>
        <artifactId>commons-beanutils</artifactId>
        <version>1.9.4</version>
    </dependency>
    
    <!-- Cglib para BeanCopier -->
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.3.0</version>
    </dependency>
</dependencies>

2. Definição das Classes de Modelo

Abaixo, definimos uma classe de origem com dados sensíveis e uma classe de destino simplificada para simular um cenário real de mapeamento.

public class EntradaDados {
    private Long id;
    private String nomeUsuario;
    private Integer nivelAcesso;
    private String senhaInterna;
    private String tokenSecreto;

    // Getters e Setters omitidos por brevidade
}

public class SaidaDados {
    private Long id;
    private String nomeUsuario;
    private Integer nivelAcesso;

    // Getters e Setters omitidos por brevidade
}

3. Imlpementação das Estratégias de Cópia

A classe a seguir encapsula os métodos para as três bibliotecas, incluindo uma estratégia de cache para o Cglib, que aumenta a eficiência ao reutilizar o objeto de cópia gerado.

import net.sf.cglib.beans.BeanCopier;
import org.apache.commons.beanutils.BeanUtils;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;

public class GerenciadorDeCopia {

    private static final Map<String, BeanCopier> CACHE_COPIER = new ConcurrentHashMap<>();

    /**
     * Utilizando Apache BeanUtils (Mais lento)
     */
    public static void copiarComApache(Object destino, Object origem) {
        try {
            BeanUtils.copyProperties(destino, origem);
        } catch (Exception e) {
            throw new RuntimeException("Erro na cópia Apache", e);
        }
    }

    /**
     * Utilizando Spring BeanUtils (Performance moderada)
     */
    public static void copiarComSpring(Object destino, Object origem) {
        org.springframework.beans.BeanUtils.copyProperties(origem, destino);
    }

    /**
     * Utilizando Cglib BeanCopier com Cache (Alta performance)
     */
    public static void copiarComCglib(Object destino, Object origem) {
        String chave = origem.getClass().getName() + "#" + destino.getClass().getName();
        BeanCopier copiador = CACHE_COPIER.computeIfAbsent(chave, k -> 
            BeanCopier.create(origem.getClass(), destino.getClass(), false)
        );
        copiador.copy(origem, destino, null);
    }
}

4. Demonstração e Verificação

Este exemplo demonstra a execução da cópia e valida se os campos correspondentes foram transferidos corretamente entre as instâncias.

public class Executora {
    public static void main(String[] args) {
        EntradaDados entrada = new EntradaDados();
        entrada.setId(500L);
        entrada.setNomeUsuario("DevMaster");
        entrada.setNivelAcesso(99);
        entrada.setSenhaInterna("123456");

        SaidaDados saida = new SaidaDados();

        // Executando a cópia via Cglib por ser a mais eficiente
        GerenciadorDeCopia.copiarComCglib(saida, entrada);

        System.out.println("Origem: " + entrada.getNomeUsuario() + ", ID: " + entrada.getId());
        System.out.println("Destino: " + saida.getNomeUsuario() + ", ID: " + saida.getId());
    }
}

Ao analisar o comportamento dessas ferramentas, observa-se que o Cglib se destaca por gerar bytecode em tempo de execução para realizar a atribuição de campos, evitando o overhaed de reflexão presente no Apache BeanUtils. O Spring BeanUtils oferece um meio-termo ideal para aplicações que já utilizam o ecossistema Spring e não possuem requisitos extremos de latência.

Tags: java BeanUtils CGLIB performance Software Architecture

Publicado em 8-17 18:30