No ecossistema Spring, uma dependência circular ocorre quando dois ou mais componentes (Beans) dependem um do outro de forma direta ou indireta, criando um ciclo fechado. Por exemplo, o componente A necessita do componente B para ser instanciado, enquanto o componente B exige o componente A. Esse cenário impede que o Spring complete o ciclo de vida de criação dos objetos, resultando em falhas durante a inicialização do contexto da aplicação.
O Problema das Dependências Cíclicas
Desde o Spring Boot 2.6, a detecção de referências circulares vem desativada por padrão (spring.main.allow-circular-references=false). Isso ocorre porque esse padrão de design geralmente indica falhas de arquitetura, como:
- Acoplamento excessivo: Classes que conhecem detalhes demais umas das outras.
- Dificuldade de menutenção: Alterações em um componente impactam o outro de forma imprevisível.
- Inconsistência na inicialização: O estado dos objetos pode não estar totalmente pronto quando um método é chamado.
Veja um exemplo clássico de acoplamento mútuo:
// Classe de Processamento que depende do Notificador
@Component
public class ProcessadorPedido {
@Autowired
private ServicoNotificacao notificacao;
}
// Classe de Notificação que depende do Processador
@Component
public class ServicoNotificacao {
@Autowired
private ProcessadorPedido processador;
}
Soluções Recomendadas
1. Refatoração e Extração de Lógica
A abordagem mais saudável é quebrar o ciclo movendo a lógica compartilhada para um terceiro componente. Se o ProcessadorPedido e o ServicoNotificacao precisam de uma funcionalidade comum, essa funcionalidade deve residir em um novo Bean.
// Nova classe para centralizar a lógica comum
@Component
public class UtilitarioPedidos {
public void validarDados() {
/* lógica compartilhada */
}
}
@Component
public class ProcessadorPedido {
@Autowired
private UtilitarioPedidos utilitario;
}
@Component
public class ServicoNotificacao {
@Autowired
private UtilitarioPedidos utilitario;
}
2. Uso da anotação @Lazy
Se a refatoração imediata não for viável, a anotação @Lazy é a solução técnica mais comum. Ela instrui o Spring a injetar um Proxy no lugar da instância real, adiando a criação do Bean dependente até que ele seja efetivamente utilizado.
@Component
public class ServicoNotificacao {
private final ProcessadorPedido processador;
// O Spring criará um proxy do ProcessadorPedido para quebrar o ciclo na inicialização
public ServicoNotificacao(@Lazy ProcessadorPedido processador) {
this.processador = processador;
}
}
3. Injeção via Setter (Método de Modificação)
Embora a injeção via construtor seja a prática recomendada para garantir a imutabilidade, o Spring consegue resolver dependências circulares mais facilmente através de Setters, pois o objeto é instenciado antes que suas propriedades sejam preenchidas.
@Component
public class ServicoNotificacao {
private ProcessadorPedido processador;
@Autowired
public void setProcessador(ProcessadorPedido processador) {
this.processador = processador;
}
}
4. Habilitação via Configuração (Não Recomendado)
Como última alternativa, é possível reverter o comportamento padrão do Spring Boot para permitir referências circulares globalmente através do arquivo application.properties:
spring.main.allow-circular-references=true
Aviso: Esta prática deve ser evitada em novos projetos, pois apenas mascara um problema de design que pode causar bugs complexos de depurar no futuro.
Diretrizes de Design
Para evitar esses problemas, priorize o Princípio da Responasbilidade Única (SRP). Se dois serviços dependem mutuamente, é provável que eles façam parte da mesma abstração ou que a comunicação entre eles deva ser feita de forma assíncrona (usando eventos do Spring, por exemplo), o que naturalmente desacopla o ciclo de vida dos componentes.