تزریق وابستگی در کوارکوس با CDI: ArC، Scopes، Qualifiers و Build-Time Injection
تزریق وابستگی (DI) زیربنای توسعه برنامه کاربردی جاوا سازمانی مدرن است. برای سالها، توسعهدهندگان جاوا برای جدا کردن چرخههای عمر مؤلفه، مدیریت وابستگیهای سرویس و تزریق ویژگیهای پیکربندی، به Spring IoC (Inversion of Control) یا کانتینرهای استاندارد Jakarta EE CDI (Contexts and Dependency Injection) مانند Hibernate Weld یا OpenWebBeans متکی بودند.
با این حال، زمانی که برنامههای کاربردی سازمانی به سمت میکروسرویسهای بومی ابری، کانتینریسازی با Docker و Kubernetes و مدلهای اجرای بدون سرور (AWS Lambda، Knative) حرکت کردند، کانتینرهای تزریق وابستگی سنتی یک نقص مهم را نشان دادند: بازتاب زمان اجرا سنگین و سربار حافظه بالا.
Quarkus و موتور سبک وزن CDI آن، ArC را وارد کنید. Quarkus که از ابتدا برای کامپایل بومی Kubernetes و GraalVM ساخته شده است، تزریق وابستگی را با تغییر بازتاب، اسکن مسیر کلاس و سیمکشی گراف bean از زمان اجرای برنامه به زمان ساخت، دوباره تعریف میکند.
در این راهنمای غواصی عمیق، تزریق وابستگی در کوارکوس با CDI را بررسی خواهیم کرد، که مکانیک درونی محفظه ArC، حوزههای CDI، واجد شرایط سفارشی، تولیدکنندگان، رهگیرها، ناظران رویداد محور، جستجوهای برنامهای و بهترین شیوهها برای نوشتن برنامههای کاربردی ابری با کارایی بالا را پوشش میدهد.
1. درک ArC: Quarkus Build-Time CDI Engine
ظروف استاندارد جاکارتا EE CDI به طور کامل در هنگام راه اندازی برنامه کار می کنند. هنگامی که یک برنامه قدیمی بوت می شود، کانتینر CDI هر JAR را در مسیر کلاس اسکن می کند، ابرداده ها را تجزیه می کند، حاشیه نویسی ها را با استفاده از بازتاب جاوا (java.lang.reflect) بررسی می کند، پراکسی های پویا در حافظه تولید می کند، و نمودار وابستگی را می سازد. این بازتاب پویا RAM قابل توجهی (Metaspace) مصرف می کند و زمان راه اندازی سرد را افزایش می دهد.
Quarkus این تنگنا را با ArC (یک پیاده سازی CDI در زمان ساخت بر اساس مشخصات Jakarta CDI 4.0) حل می کند.
چگونه ArC با CDI استاندارد متفاوت است:
- افزایش زمان ساخت: در طول
mvn packageیا./gradlew build، ArC همه دانه های برنامه را اسکن می کند، نقاط تزریق را تایید می کند و نمودار وابستگی را از قبل می سازد. - تولید بایت کد پیش از زمان (AOT): ArC بایت کد مستقیم جاوا را برای نمونه سازی، پراکسی های bean و رهگیرها تولید می کند و بازتاب را در زمان اجرا حذف می کند.
- ** هرس لوبیا استفاده نشده (حذف کد مرده) **: هر دانه CDI که هرگز به صورت دینامیکی تزریق یا ارجاع داده نمی شود، در طول کامپایل شناسایی می شود و به طور خودکار از باینری نهایی حذف می شود و ردپای حافظه را کاهش می دهد.
- بهینه سازی تصویر بومی GraalVM: از آنجایی که سیم کشی bean در زمان ساخت حل می شود، برنامه های Quarkus به طور یکپارچه در فایل های اجرایی بومی با نکات پیکربندی بازتاب صفر کامپایل می شوند.
2. CDI Bean Scopes & Lifecycle Management
در CDI، یک Scope چرخه حیات و نمایان بودن یک نمونه bean را دیکته می کند. Quarkus ArC از حوزههای استاندارد CDI جاکارتا با بهینهسازیهای زمان ساخت پشتیبانی میکند.
حوزه های CDI اولیه در کوارکوس:
| حاشیه نویسی دامنه | چرخه زندگی و رفتار | ایجاد پروکسی | بهترین حالت استفاده |
|---|---|---|---|
@ApplicationScoped |
تک نمونه در هر برنامه مقداردهی اولیه تنبل در اولین فراخوانی حالت در همه رشته ها به اشتراک گذاشته شده است. | پروکسی مشتری ایجاد شد | خدمات بدون تابعیت، مخازن، تنظیمات. |
@RequestScoped |
نمونه به یک درخواست HTTP یا فراخوانی رویداد محدود شده است. با تکمیل درخواست از بین می رود. | پروکسی مشتری ایجاد شد | جلسات کاربر، درخواست داده های زمینه، نشانه های امنیتی. |
@وابسته |
محدوده پیشفرض اگر مشخص نشده باشد. نمونه مستقیماً با طول عمر لوبیا تزریقی مرتبط است. | بدون پروکسی (نمونه مستقیم) | کلاس های ابزار کمکی، دارندگان حالت زودگذر. |
@Singleton |
تک نمونه در هر برنامه با اشتیاق یا با تنبلی شروع می شود. بسته بندی پروکسی مشتری وجود ندارد. | بدون پروکسی (نمونه مستقیم) | تکتونهای با کارایی بالا که باید از سربار پروکسی مشتری اجتناب شود. |
| «@غیرقابل حذف». | حاشیه نویسی نشانگر که به ArC دستور می دهد در طول بهینه سازی زمان ساخت، دانه را حذف نکند. | بستگی به دامنه دارد | اهداف جستجوی پویا یا لوبیاهای ارجاع شده به بازتاب. |
مثال کد: اعلام Scoped Beans
package com.ghaznix.services;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.RequestScoped;
import java.util.UUID;
@ApplicationScoped
public class OrderProcessingService {
public String processOrder(String orderId) {
return "Order " + orderId + " processed successfully.";
}
}
@RequestScoped
public class RequestContextHolder {
private final String requestId = UUID.randomUUID().toString();
public String getRequestId() {
return requestId;
}
}
3. الگوهای تزریق وابستگی در کوارکوس
Quarkus از چندین سبک تزریق با استفاده از حاشیه نویسی استاندارد jakarta.inject.Inject پشتیبانی می کند.
الف. تزریق سازنده (بهترین روش توصیه شده)
تزریق سازنده تغییر ناپذیری را تضمین می کند (فیلدهای final)، تست واحد را بدون نیاز به کانتینرهای CDI ساده می کند و وابستگی ها را واضح می کند.
package com.ghaznix.resources;
import com.ghaznix.services.OrderProcessingService;
import jakarta.inject.Inject;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
@Path("/orders")
public class OrderResource {
private final OrderProcessingService orderService;
// In Quarkus, if a class has a single constructor, @Inject is OPTIONAL!
@Inject
public OrderResource(OrderProcessingService orderService) {
this.orderService = orderService;
}
@POST
@Path("/{id}")
public String checkout(@PathParam("id") String id) {
return orderService.processOrder(id);
}
}
نکته حرفه ای: در Quarkus ArC، اگر یک bean فقط یک سازنده داشته باشد، می توانید حاشیه نویسی
@Injectرا به طور کامل حذف کنید. ArC به طور خودکار آن را به عنوان سازنده تزریق تشخیص می دهد.
ب. تزریق میدانی
تزریق فیلد مختصر است اما زمینه ها را تغییرپذیر می کند و آزمایش واحد مستقل را پیچیده می کند.
@ApplicationScoped
public class NotificationService {
@Inject
EmailClient emailClient; // Field injection
}
4. واجد شرایط و ابهام زدایی از چند پیاده سازی
وقتی چندین bean یک رابط را پیاده سازی می کنند، CDI یک استثنا وابستگی مبهم در زمان ساخت ایجاد می کند. Quarkus از Qualifiers برای حل و فصل ایمن اجرای bean هدف استفاده می کند.
تعریف واجد شرایط سفارشی
package com.ghaznix.payment;
import jakarta.inject.Qualifier;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Qualifier
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface PaymentType {
Mode value();
enum Mode {
CREDIT_CARD,
PAYPAL,
CRYPTO
}
}
اجرای لوبیاهای واجد شرایط
public interface PaymentProcessor {
void process(double amount);
}
@ApplicationScoped
@PaymentType(PaymentType.Mode.CREDIT_CARD)
public class CreditCardProcessor implements PaymentProcessor {
@Override
public void process(double amount) {
System.out.println("Processing Credit Card payment: $" + amount);
}
}
@ApplicationScoped
@PaymentType(PaymentType.Mode.PAYPAL)
public class PayPalProcessor implements PaymentProcessor {
@Override
public void process(double amount) {
System.out.println("Processing PayPal payment: $" + amount);
}
}
تزریق حبوبات واجد شرایط
@ApplicationScoped
public class CheckoutService {
private final PaymentProcessor creditCardProcessor;
private final PaymentProcessor payPalProcessor;
@Inject
public CheckoutService(
@PaymentType(PaymentType.Mode.CREDIT_CARD) PaymentProcessor creditCardProcessor,
@PaymentType(PaymentType.Mode.PAYPAL) PaymentProcessor payPalProcessor) {
this.creditCardProcessor = creditCardProcessor;
this.payPalProcessor = payPalProcessor;
}
}
5. تولیدکنندگان و دفع کنندگان: تزریق اشیاء شخص ثالث
گاهی اوقات لازم است کلاس هایی را از کتابخانه های شخص ثالث (به عنوان مثال، کلاینت های AWS SDK، اتصالات Redis، دانه های پیکربندی سفارشی) که با @ApplicationScoped حاشیه نویسی نشده اند، تزریق کنید. کوارکوس با استفاده از روشهای تولیدکننده و دفترکن این کار را انجام میدهد.
package com.ghaznix.config;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Disposes;
import jakarta.enterprise.inject.Produces;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
@ApplicationScoped
public class DatabaseConnectionProducer {
@Produces
@ApplicationScoped
public Connection createDatabaseConnection() throws SQLException {
return DriverManager.getConnection("jdbc:postgresql://localhost:5432/mydb", "user", "pass");
}
// Disposer method called automatically when bean is destroyed
public void closeConnection(@Disposes Connection connection) throws SQLException {
if (connection != null && !connection.isClosed()) {
connection.close();
}
}
}
6. CDI Interceptors: Aspect Oriented Programming (AOP)
رهگیرها اجازه میدهند که نگرانیهای بینبخشی (ثبتبرداری، مسیرهای حسابرسی، معیارها، محدود کردن نرخ) به روشهای لوبیا بدون ایجاد آلاینده منطق تجاری متصل شوند.
مرحله 1: تعریف Interceptor Binding
package com.ghaznix.interceptors;
import jakarta.interceptor.InterceptorBinding;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@InterceptorBinding
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface LogExecutionTime {
}
مرحله 2: منطق Interceptor را پیاده سازی کنید
package com.ghaznix.interceptors;
import jakarta.annotation.Priority;
import jakarta.interceptor.AroundInvoke;
import jakarta.interceptor.Interceptor;
import jakarta.interceptor.InvocationContext;
@LogExecutionTime
@Interceptor
@Priority(Interceptor.Priority.APPLICATION)
public class LogExecutionTimeInterceptor {
@AroundInvoke
public Object logInvocation(InvocationContext context) throws Exception {
long startTime = System.currentTimeMillis();
try {
return context.proceed();
} finally {
long duration = System.currentTimeMillis() - startTime;
System.out.printf("[AUDIT] Method %s executed in %d ms%n",
context.getMethod().getName(), duration);
}
}
}
مرحله 3: حبوبات هدف را حاشیه نویسی کنید
@ApplicationScoped
public class AnalyticsService {
@LogExecutionTime
public void computeAggregates() {
// Business computation
}
}
از آنجایی که کوارکوس در زمان ساخت، پراکسی های رهگیر استاتیک تولید می کند، سربار فراخوانی روش در مقایسه با پراکسی های زمان اجرا CGLIB پویا در Spring Boot تقریباً صفر است.
7. معماری رویداد محور با رویدادهای CDI
CDI انتشار رویدادهای همزمان و ناهمزمان داخلی را برای ارتباطات درون فرآیند جدا شده فراهم می کند.
انتشار و مشاهده رویدادها
// 1. Define Event Payload
public record OrderCreatedEvent(String orderId, double totalAmount) {}
// 2. Publish Event
@ApplicationScoped
public class OrderService {
@Inject
Event<OrderCreatedEvent> orderEventPublisher;
public void createOrder(String orderId, double amount) {
// Create order logic...
orderEventPublisher.fire(new OrderCreatedEvent(orderId, amount));
}
}
// 3. Observe Event Synchronously or Asynchronously
@ApplicationScoped
public class InventoryObserver {
public void onOrderCreated(@Observes OrderCreatedEvent event) {
System.out.println("Updating inventory for order: " + event.orderId());
}
public void onOrderCreatedAsync(@ObservesAsync OrderCreatedEvent event) {
System.out.println("Async audit log for order: " + event.orderId());
}
}
رویدادهای چرخه حیات برنامه
Quarkus رویدادهای راه اندازی و خاموش شدن را برای اجرای کد اولیه هنگام راه اندازی یا خاتمه برنامه نمایش می دهد:
@ApplicationScoped
public class ApplicationLifecycleBean {
void onStart(@Observes StartupEvent ev) {
System.out.println("Quarkus Application starting up...");
}
void onStop(@Observes ShutdownEvent ev) {
System.out.println("Quarkus Application shutting down...");
}
}
8. جستجوی برنامهای Bean با Instance<T> & Arc.container()
هنگامی که پیادهسازیهای هدف باید در زمان اجرا به صورت پویا انتخاب شوند (یا در نمونههای غیر CDI قدیمی)، از Instance<T> یا Quarkus Arc.container() استفاده کنید.
@ApplicationScoped
public class DynamicPluginRegistry {
@Inject
Instance<PaymentProcessor> processors;
public void executeAll(double amount) {
for (PaymentProcessor processor : processors) {
processor.process(amount);
}
}
}
9. مقایسه معماری: Quarkus ArC در مقابل Spring IoC در مقابل Weld
| ویژگی | کوارکوس آر سی | کانتینر IoC فنری | جوش استاندارد جاکارتا |
|---|---|---|---|
| قطعیت وابستگی | زمان ساخت (AOT) | بازتاب زمان اجرا | بازتاب زمان اجرا |
| سربار رم متاسپیس | بسیار کم (~0MB برای ابرداده) | بالا (حافظه پنهان کلاس پروکسی پویا) | متوسط تا زیاد |
| زمان راه اندازی | میلی ثانیه (<0.05 ثانیه) | ثانیه (2.5 - 8 ثانیه) | ثانیه (1.5 - 4 ثانیه) |
| پشتیبانی بومی GraalVM | بومی خارج از جعبه | به نکات بومی بهار نیاز دارد | پیکربندی پیچیده |
| هرس لوبیا استفاده نشده | هرس خودکار زمان ساخت | در Classpath نگهداری می شود | در Classpath نگهداری می شود |
| تزریق تک سازنده | ضمنی (بدون نیاز به حاشیه نویسی) | پشتیبانی می شود | به «@Inject» نیاز دارد |
10. تست واحد CDI Beans با @QuarkusTest و @InjectMock
Quarkus آزمایش دانه های CDI را با JUnit 5 و RestAssured یکپارچه می کند. از @InjectMock برای جایگزینی dependency bean با mockito mock به طور خودکار در طول آزمایش استفاده کنید.
package com.ghaznix;
import com.ghaznix.services.OrderProcessingService;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
@QuarkusTest
public class OrderServiceTest {
@Inject
OrderService orderService;
@InjectMock
OrderProcessingService orderProcessingService;
@Test
public void testOrderCheckout() {
Mockito.when(orderProcessingService.processOrder("ORD-101"))
.thenReturn("Mocked Success");
String result = orderService.checkout("ORD-101");
Assertions.assertEquals("Mocked Success", result);
}
}
11. نتیجه گیری
تزریق وابستگی در کوارکوس با CDI (ArC) نشان دهنده یک تغییر بزرگ در طراحی جاوا سازمانی است. Quarkus با انتقال اسکن ابرداده bean، تولید پروکسی و وضوح نمودار تزریقی به زمان کامپایل، سرعت راه اندازی مافوق صوت، ردپای حافظه زیر اتمی و پشتیبانی یکپارچه باینری بومی GraalVM را ارائه می دهد.
چه در حال مهاجرت از Spring Boot باشید یا در حال ساخت میکروسرویسهای بومی ابری سبز فیلد در Kubernetes، تسلط بر Quarkus CDI شما را به عملکرد و بهرهوری توسعهدهنده مورد نیاز برای معماری نرمافزار مدرن مجهز میکند.
Tags
Empower Your Digital Presence & Workflows
Explore top-tier tools built by Ghaznix to streamline your links, surveys, and brand growth.