Как реализуется Inversion of Control через Dependency Injection в Quarkus?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Inversion of Control и Dependency Injection в Quarkus
Inversion of Control (IoC) — это архитектурный паттерн, при котором управление потоком управления передаётся фреймворку. Dependency Injection (DI) — реализация IoC, когда зависимости объекта передаются извне, а не создаются самим объектом. Quarkus использует собственный DI контейнер на базе ArC (Arc is a CDI container).
1. Базовый DI в Quarkus
Quarkus использует стандарт Jakarta CDI (Contexts and Dependency Injection) с расширениями. Вот как это работает:
// 1. Bean-сервис
@ApplicationScoped
public class UserService {
public String getUsername(Long userId) {
return "User" + userId;
}
}
// 2. Инъекция зависимости
@Path("/users")
public class UserResource {
@Inject
UserService userService; // Quarkus сам создаст экземпляр!
@GET
@Path("/{id}")
public String getUser(@PathParam("id") Long id) {
return userService.getUsername(id);
}
}
// Quarkus автоматически:
// - Создаст singleton UserService
// - Инъектирует его в UserResource
// - Управляет жизненным циклом
2. Scopes (Области видимости)
Quarkus поддерживает различные scopes для управления жизненным циклом:
// @ApplicationScoped — один экземпляр на всё приложение
@ApplicationScoped
public class DatabaseConnection {
// Инициализируется один раз при старте
}
// @Singleton — синоним @ApplicationScoped
@Singleton
public class CacheManager { }
// @RequestScoped — новый экземпляр на каждый HTTP запрос
@RequestScoped
public class RequestContext {
public long createdAt = System.currentTimeMillis();
}
// @Dependent — новый экземпляр при каждой инъекции (по умолчанию)
@Dependent
public class RequestLogger {
public void log(String msg) { System.out.println(msg); }
}
3. Инъекция через конструктор (рекомендуется)
В современном Quarkus лучше использовать инъекцию через конструктор вместо @Inject на поле:
// Плохо: инъекция на поле
@RequestScoped
public class OrderService {
@Inject
OrderRepository repository; // Сложнее тестировать
}
// Хорошо: инъекция через конструктор
@RequestScoped
public class OrderService {
private final OrderRepository repository;
public OrderService(OrderRepository repository) {
this.repository = repository;
}
public Order getOrder(Long id) {
return repository.findById(id);
}
}
// Quarkus найдёт конструктор и инъектирует все зависимости
4. Qualifiers (Квалификаторы)
Если несколько реализаций одного интерфейса, используй qualifiers:
// Интерфейс
public interface PaymentService {
void processPayment(BigDecimal amount);
}
// Первая реализация
@ApplicationScoped
@Named("card")
public class CardPaymentService implements PaymentService {
@Override
public void processPayment(BigDecimal amount) {
System.out.println("Paying " + amount + " by card");
}
}
// Вторая реализация
@ApplicationScoped
@Named("crypto")
public class CryptoPaymentService implements PaymentService {
@Override
public void processPayment(BigDecimal amount) {
System.out.println("Paying " + amount + " by crypto");
}
}
// Использование
@Path("/payment")
public class PaymentResource {
private final PaymentService cardPayment;
private final PaymentService cryptoPayment;
public PaymentResource(
@Named("card") PaymentService cardPayment,
@Named("crypto") PaymentService cryptoPayment
) {
this.cardPayment = cardPayment;
this.cryptoPayment = cryptoPayment;
}
}
5. Factories (Фабрики для сложных объектов)
Для создания сложных объектов используй producer methods:
@ApplicationScoped
public class BeanProducers {
@Produces
@ApplicationScoped
public DatabaseConnection createDatabase() {
DatabaseConnection conn = new DatabaseConnection();
conn.connect("jdbc:postgresql://localhost:5432/mydb");
return conn;
}
@Produces
public RestClient createRestClient(DatabaseConnection db) {
return new RestClient(db.getConfig());
}
}
// Теперь DatabaseConnection инъектируется как обычный bean
@RequestScoped
public class DataService {
private final DatabaseConnection connection;
public DataService(DatabaseConnection connection) {
this.connection = connection;
}
}
6. Жизненный цикл beans
Quarkus вызывает специальные методы при создании/уничтожении beans:
@ApplicationScoped
public class ResourcePool {
private List<Connection> connections;
@PostConstruct
public void initialize() {
// Вызывается ПОСЛЕ создания bean'а
connections = new ArrayList<>();
for (int i = 0; i < 10; i++) {
connections.add(new Connection());
}
System.out.println("Pool initialized with 10 connections");
}
@PreDestroy
public void cleanup() {
// Вызывается ПЕРЕД уничтожением bean'а (при shutdown)
connections.forEach(Connection::close);
System.out.println("Pool cleaned up");
}
public Connection getConnection() {
return connections.get(0);
}
}
7. Тестирование с DI
Quarkus упрощает тестирование:
@QuarkusTest
class UserResourceTest {
@Inject
UserService userService;
@Test
void testGetUser() {
String username = userService.getUsername(1L);
assertEquals("User1", username);
}
}
// Или с mock'ом
@QuarkusTest
class OrderServiceTest {
@InjectMock
OrderRepository mockRepository;
@Inject
OrderService orderService;
@Test
void testGetOrder() {
when(mockRepository.findById(1L))
.thenReturn(new Order(1L, "Test Order"));
Order result = orderService.getOrder(1L);
assertNotNull(result);
}
}
8. Lazy loading в Quarkus
Можешь отложить инициализацию bean'а:
@ApplicationScoped
public class HeavyService {
public HeavyService() {
System.out.println("Initializing expensive resources...");
// Долгая инициализация
}
}
// Инъекция Lazy
@RequestScoped
public class MyResource {
private final Lazy<HeavyService> heavyService;
public MyResource(Lazy<HeavyService> heavyService) {
this.heavyService = heavyService;
}
@GET
public void doSomething() {
// heavyService инициализируется только здесь
heavyService.get().doWork();
}
}
Итоги
IoC/DI в Quarkus работает следующим образом:
- ArC контейнер управляет жизненным циклом beans
- @Inject/@ApplicationScoped — основные аннотации
- Scopes определяют, как долго живёт bean
- Qualifiers разрешают конфликты при нескольких реализациях
- Factories помогают с создание сложных объектов
- @PostConstruct/@PreDestroy для инициализации/очистки
- Тестирование упрощено встроенными инструментами
Ключевое преимущество: Quarkus компилирует DI граф во время build-time, а не runtime, что делает приложения быстрыми и потребляющими мало памяти — идеально для микросервисов и serverless.