Injeção de Dependência em Java: na prática
Injeção de Dependência (DI) — é um dos princípios-chave do desenvolvimento moderno em Java, que está na base de frameworks como Spring e Jakarta EE. Neste artigo, vamos analisar o que é injeção de dependências, por que ela é necessária e, o mais importante, como aplicá-la na prática com exemplos de código.
O que é Injeção de Dependência e por que é importante?
Injeção de Dependência é um padrão de projeto no qual um objeto recebe suas dependências de fora, em vez de criá-las por conta própria. Isso implementa o princípio Inversion of Control (IoC) — inversão de controle. Em vez de a classe gerenciar suas próprias dependências, o gerenciamento é transferido para um container ou fábrica externa.
Imagine que você está escrevendo um aplicativo para enviar notificações. Sem DI, seu código pode ser assim:
public class NotificationService { private EmailSender emailSender = new EmailSender();
public void send(String message) { emailSender.send(message); }}O problema é óbvio: NotificationService está rigidamente acoplado a EmailSender. Se você quiser adicionar SMS ou notificações Push, terá que reescrever toda a classe. Com Injeção de Dependência, esse problema é resolvido:
public class NotificationService { private final MessageSender messageSender;
// A dependência é passada através do construtor public NotificationService(MessageSender messageSender) { this.messageSender = messageSender; }
public void send(String message) { messageSender.send(message); }}Agora a classe depende da abstração MessageSender, e não de uma implementação concreta. Isso torna o código flexível, testável e escalável.
Formas de injeção de dependências em Java
Existem três maneiras principais de implementar DI em Java. Vamos analisar cada uma delas com exemplos.
1. Injeção via construtor (Constructor Injection)
Esta é a forma mais preferível. As dependências são passadas através dos parâmetros do construtor e geralmente são marcadas como final.
public class UserService { private final UserRepository userRepository; private final EmailService emailService;
public UserService(UserRepository userRepository, EmailService emailService) { this.userRepository = userRepository; this.emailService = emailService; }
public void registerUser(User user) { userRepository.save(user); emailService.sendWelcomeEmail(user.getEmail()); }}Vantagens:
- Dependências obrigatórias são explicitamente visíveis
- O objeto está sempre em um estado válido
- Excelente para objetos imutáveis (immutable)
- Fácil de testar — basta passar objetos mock
2. Injeção via setters (Setter Injection)
As dependências são definidas através de métodos setter após a criação do objeto.
public class OrderService { private PaymentGateway paymentGateway;
public void setPaymentGateway(PaymentGateway paymentGateway) { this.paymentGateway = paymentGateway; }
public void processOrder(Order order) { paymentGateway.charge(order.getTotal()); }}Quando usar: Para dependências opcionais ou quando as dependências podem mudar durante a vida do objeto. No entanto, essa abordagem torna o objeto mutável e menos previsível.
3. Injeção via campo (Field Injection)
Mais frequentemente usada com frameworks através de anotações, como @Autowired no Spring.
public class ReportService { @Autowired private DataSource dataSource;
public Report generateReport() { return dataSource