Injeção de Dependência Simples em Java
O JToolbox Dependency Injector (DI) é um container leve de Inversao de Controle (IoC) desenvolvido para simplificar o gerenciamento de componentes (services) e suas dependências em aplicações Java.
Vantagens Principais do Uso
O objetivo do DI é transferir a responsabilidade de criar e conectar objetos do seu código para o container.
Redução de Acoplamento (Loose Coupling): Seus objetos consomem dependências através de interfaces ou construtores, sem usar o operador
new. Isso permite que você mude a implementação de um serviço (ex:CorreiosparaPatrusTransportes) sem alterar o código que o consome (Freight).Testabilidade: A injeção por construtor ou por campo permite que você substitua facilmente as dependências reais por objetos Mock durante testes unitários, isolando o componente em teste.
Gerenciamento de Ciclo de Vida: O DI gerencia quando e como um objeto é criado, garantindo a criação única (
SINGLETON) ou a criação sob demanda (PROTOTYPE).
Anotações Chave e Seus Usos
O sistema é baseado em quatro anotações simples que definem o comportamento dos componentes.
1. @Service (Definição do Componente)
Marca uma classe para ser gerenciada pelo container DI.
| Parâmetro | Descrição | Exemplo |
|---|---|---|
value (String) | Nome único para qualificar o serviço (usado pelo @Qualifier). | @Service(value = "prod") |
scope (ScopeType) | Define o ciclo de vida da instância. | @Service(scope = ScopeType.PROTOTYPE) |
Exemplo de Scope:
SINGLETON(Padrão): O objeto é criado uma única vez e reutilizado em todas as injeções. Ideal para serviços sem estado (stateless) comoLoggers,FactorieseCalculators.PROTOTYPE: Uma nova instância é criada toda vez que é injetada ou solicitada. Ideal para objetos com estado específico que precisam ser isolados (ex: uma transação de banco de dados ou umTarefacom estado).
2. @Qualifier (Resolução de Ambiguidade)
Usado para especificar qual implementação de uma interface deve ser injetada quando houver mais de uma. O value deve corresponder ao value da anotação @Service da implementação desejada.
@Service
public class Freight {
private final FreightInterface freightInterface;
// Injeta a implementação anotada com @Service(value = "correios")
public Freight(@Qualifier("correios") FreightInterface freightInterface) {
this.freightInterface = freightInterface;
}
3. @Inject (Injeção por Campo)
Marca um campo para que o container injete o serviço correspondente após a criação do objeto (depois da execução do construtor).
public class MyService {
@Inject
private Logger logger; // O logger será injetado após a construção.
}
4. @PostConstruct (Hook de Inicialização)
Marca um método (que deve ser parameterless) para ser executado depois que o objeto foi construído e todas as suas dependências (@Inject e construtor) foram resolvidas.
@Service
public class DatabaseConnector {
// construtor injeta a Configuração
@PostConstruct
public void initializeConnection() {
// Abrir a conexão, pois todas as dependências estão prontas.
}
}
Outro exemplo simples:
Object Task
package com.github.rickmvi.app.task;
import com.github.rickmvi.jtoolbox.container.ScopeType;
import com.github.rickmvi.jtoolbox.container.annotations.Service;
import lombok.Setter;
import static com.github.rickmvi.jtoolbox.console.IO.*;
@Service(value = "task", scope = ScopeType.PROTOTYPE)
public class Task {
@Setter
private String description;
@Setter
private boolean conclude;
public void display() {
if (!conclude) {
format("Task: {0} - Status: Pending$n", description);
return;
}
format("Task: {0} - Status: Completed$n", description);
}
}
Class Main
package com.github.rickmvi.app;
import com.github.rickmvi.app.task.Task;
import com.github.rickmvi.jtoolbox.collections.Dynamic;
import com.github.rickmvi.jtoolbox.container.Dependency;
public class Main {
public static void main(String[] args) {
Dependency injector = Dependency.inject("com.github.rickmvi.app.task").get();
Task t1 = injector.get(Task.class);
t1.setDescription("To study Java");
t1.setConclude(false);
Task t2 = injector.get(Task.class);
t2.setDescription("Do exercises");
t2.setConclude(true);
Dynamic<Task> list = Dynamic.of(t1, t2);
list.forEachDo(Task::display);
}
}