Inicialização de Beans e Proxies CGLIB na Injeção de Dependência do Spring

A injeção de dependência (IoC) no Spring permite que o contêiner gerencie o ciclo de vida dos objetos. No entanto, a ordem de inicialização e os mecanismos de proxy subjacentes podem causar comportamentos inesperados se não forem bem compreendidos.

Um cenário comum envolve a necessidade de inicializar dados ou executar lógicas de configuração durante a instanciação de um bean. Considere a seguinte implementação, onde se tenta utilizar dependências injetadas via campo diretamente no construtor:

@Component
public class OrderStatusProcessor {
    
    @Autowired
    private CatalogService catalogService;
    
    @Autowired
    private OrderService orderService;
    
    private List<String> statusCache;

    public OrderStatusProcessor() {
        // Tentativa de usar as dependências injetadas
        this.statusCache = catalogService.fetchAllStatuses();
        orderService.initializeCache(statusCache);
    }
}

Ao executar esta classe, uma NullPointerException é lançada. Isso ocorre porque a injeção de campo (via @Autowired em variáveis de instância) é realizada pelo Spring após a execução do construtor padrão. No momento em que o construtor é invocado, as referências catalogService e orderService ainda são nulas.

Injeção via Construtor

A solução imediata para esse problema é alterar a estratégia de injeção, movendo a anotação @Autowired para o construtor. Isso garante que as dependências sejam resolvidas e passadas como argumentos antes que o corpo do construtor seja executado:

@Component
public class OrderStatusProcessor {
    
    private final CatalogService catalogService;
    private final OrderService orderService;
    private List<String> statusCache;

    @Autowired
    public OrderStatusProcessor(CatalogService catalogService, OrderService orderService) {
        this.catalogService = catalogService;
        this.orderService = orderService;
        
        // As dependências agora estão disponíveis
        this.statusCache = catalogService.fetchAllStatuses();
        orderService.initializeCache(statusCache);
    }
}

Embora essa abordagem resolva o problema do ponteiro nulo, ela pode introduzir um novo erro durante a inicialização do contexto do Spring, resultando em falhas na criação do bean. O contêiner pode reclamar da ausência de um construtor padrão (sem argumentos).

A Necessidade do Construtor Padrão e Proxies CGLIB

Para entender por que o Spring exige um construtor sem argumentos mesmo quando um construtor parametrizado está anotado com @Autowired, é necessário analisar como o framework lida com a criação de proxies para recursos como transações declarativas (@Transactional) ou aspectos (@Aspect).

O Spring utiliza principalmente duas estratégias para gerar proxies:

  • JDK Dynamic Proxies: Baseado em interfaces. O Spring gera uma classe proxy que implementa as mesmas interfaces do bean alvo. A invocação de métodos é delegada através de um InvocationHandler. Como o proxy não estende a classe original, o construtor da classe alvo não é um problema para a instanciação do proxy.
  • CGLIB Proxies: Baseado em herança. O Spring gera uma subclasse dinâmica que estende a classe do bean original. Essa subclasse intercepta as chamadas de método para aplicar comportamentos adicionais e depois delega a execução para a superclasse.

Quando a classe OrderStatusProcessor não implementa interfaces, o Spring recorre ao CGLIB. A biblioteca CGLIB cria uma subclasse em tempo de execução. Em Java, ao instanciar uma subclasse, o construtor da superclasse deve ser invocado. Se a superclasse possui apenas um construtor com parâmetros, a subclasse gerada pelo CGLIB não conseguirá ser instanciada corretamente pelo mecanismo padrão, pois não há um construtor sem argumentos para ser chamado implicitamente via super().

Portanto, para que o proxy CGLIB seja criado com sucesso, a classe original deve manter um construtor padrão vazio. O Spring utilizará o construtor sem argumentos para instanciar a subclasse proxy e, em seguida, aplicará a injeção de dependência ou utilizará mecanismos como Objenesis para contornar a inicialização completa, dependendo da versão e configuração.

A estrutura correta e robusta para este cenário é:

@Component
public class OrderStatusProcessor {
    
    private CatalogService catalogService;
    private OrderService orderService;
    private List<String> statusCache;

    // Necessário para a instanciação da subclasse proxy CGLIB
    public OrderStatusProcessor() {
    }

    @Autowired
    public OrderStatusProcessor(CatalogService catalogService, OrderService orderService) {
        this.catalogService = catalogService;
        this.orderService = orderService;
        
        this.statusCache = catalogService.fetchAllStatuses();
        orderService.initializeCache(this.statusCache);
    }
}

Nesta configuração, o construtor vazio satisfaz os requisitos de herança do proxy CGLIB, enquento o construtor parametrizado com @Autowired garante que as dependências sejam corretamente resolvidas e a lógica de inicialização seja executdaa no momento apropriado do ciclo de vida do bean.

Tags: Spring IoC dependency injection CGLIB Java Proxies

Publicado em 7-16 17:13