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.