הזרקת תלות ב-Quarkus עם CDI: ArC, Scopes, Qualifiers והזרקת זמן בנייה
הזרקת תלות (DI) היא הבסיס של פיתוח יישומי Java ארגוניים מודרניים. במשך שנים, מפתחי Java הסתמכו על Spring IoC (היפוך בקרה) או מיכלים סטנדרטיים של Jakarta EE CDI (Contexts and Dependency Injection) כמו Hibernate Weld או OpenWebBeans כדי לנתק מחזורי חיים של רכיבים, לנהל תלות בשירות ולהחדיר מאפייני תצורה.
עם זאת, כאשר יישומים ארגוניים עברו לשירותי מיקרו מקוריים בענן, קונטיינריזציה עם Docker וKubernetes ומודלים של הפעלה ללא שרת (AWS Lambda, Knative), מיכלי הזרקת תלות מסורתיים חשפו פגם קריטי: השתקפות זמן ריצה כבדה ותקורה גבוהה של זיכרון.
כנסו ל-Quarkus ולמנוע ה-CDI קל המשקל שלו, ArC. נבנה מהיסוד עבור קומפילציה מקורית של Kubernetes ו-GraalVM, Quarkus מגדיר מחדש את הזרקת התלות על ידי הסבת השתקפות, סריקת נתיב כיתה וחיווט גרפי שעועית מזמן ריצה של אפליקציה לזמן בנייה.
במדריך הצלילה המעמיק הזה, נחקור את הזרקת תלות ב-Quarkus עם CDI, המכסה את המכניקה הפנימית של ArC מיכל, היקפי CDI, מוקדים מותאמים אישית, מפיקים, מיירטים, צופים מונעי אירועים, חיפושים פרוגרמטיים ושיטות עבודה מומלצות לכתיבת יישומי Java בעלי ביצועים גבוהים בענן.
1. הבנת ArC: Quarkus Build-Time CDI Engine
מיכלי ג’קרטה EE CDI סטנדרטיים פועלים לחלוטין בעת הפעלת האפליקציה. כאשר יישום מדור קודם מאתחל, מיכל ה-CDI סורק כל JAR בנתיב הכיתה, מנתח מטא נתונים, בודק הערות באמצעות Java Reflection (java.lang.reflect), יוצר פרוקסי דינמיים בזיכרון ובונה את גרף התלות. השתקפות דינמית זו צורכת זיכרון RAM משמעותי (Metaspace) ומגדילה את זמן האתחול בהתחלה קרה.
Quarkus פותר את צוואר הבקבוק הזה עם ArC (יישום CDI בזמן בנייה המבוסס על מפרטי Jakarta CDI 4.0).
איך ArC שונה מ-CDI רגיל:
- הגדלה בזמן בנייה: במהלך
mvn packageאו./gradlew build, ArC סורקת את כל שעועית היישום, מאמתת נקודות הזרקה ובונה מראש את גרף התלות. - יצירת Bytecode Ahead-Of-Time (AOT): ArC מייצרת קוד Java Byte ישיר לאינסטציה, פרוקסי שעועית ויירטים מראש, ומבטלת השתקפות בזמן ריצה.
- גיזום שעועית שאינה בשימוש (חיסול קוד מת): כל שעועית CDI שלעולם לא מוזרקת או שמתייחסת אליה באופן דינמי מזוהה במהלך ההידור ונשלפת אוטומטית מהבינארי הסופי, ומפחיתה את טביעת הרגל של הזיכרון.
- GraalVM Native Image Optimization: מכיוון שחיווט שעועית נפתר בזמן הבנייה, יישומי Quarkus מבצעים קומפלט חלק לקובצי הפעלה מקוריים עם רמזים לתצורת אפס השתקפות.
2. היקפי שעועית CDI וניהול מחזור חיים
ב-CDI, Scope מכתיב את מחזור החיים והנראות של מופע שעועית. Quarkus ArC תומך בהיקפי ג’קרטה CDI סטנדרטיים עם אופטימיזציות בזמן הבנייה.
סקופי CDI ראשיים ב-Quarkus:
| ביאור היקף | מחזור חיים והתנהגות | יצירת פרוקסי | מקרה השימוש הטוב ביותר |
|---|---|---|---|
@ApplicationScoped |
מופע בודד לכל אפליקציה. אתחול עצלן בהזמנה ראשונה. מצב משותף בכל השרשורים. | Proxy הלקוח נוצר | שירותים חסרי מדינה, מאגרים, תצורות. |
@RequestScoped |
מופע קשור לבקשת HTTP בודדת או הפעלת אירוע. מושמד כאשר הבקשה מסתיימת. | Proxy הלקוח נוצר | הפעלות משתמש, נתוני הקשר של בקשה, אסימוני אבטחה. |
@תלוי |
היקף ברירת המחדל אם לא צוין. מופע קשור ישירות לכל החיים של השעועית המזריקה. | ללא פרוקסי (מופע ישיר) | שיעורי שירות עוזרים, בעלי מדינה ארעית. |
@סינגלטון |
מופע בודד לכל אפליקציה. הופיע בשקיקה או בעצלתיים. אין מעטפת פרוקסי לקוח. | ללא פרוקסי (מופע ישיר) | יחידות עם ביצועים גבוהים שבהם יש להימנע מתקורה של פרוקסי לקוח. |
@לא ניתן להסרה |
הערת סמן המורה ל-ArC לא להסיר את השעועית במהלך אופטימיזציה של זמן הבנייה. | תלוי בהיקף | יעדי חיפוש דינמיים או שעועית בהתייחסות לשתקפות. |
דוגמה לקוד: הצהרת שעועית בהיקף
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
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, אם לשעועית יש רק בנאי אחד, אתה יכול להשמיט לחלוטין את ההערה
@Inject. ArC מזהה אותו אוטומטית בתור בנאי ההזרקה.
ב. הזרקת שדה
הזרקת שדה היא תמציתית אך הופכת שדות לניתנים לשינוי ומסבכת בדיקות יחידות עצמאיות.
@ApplicationScoped
public class NotificationService {
@Inject
EmailClient emailClient; // Field injection
}
4. מוקדמות וביטול יישום מרובים
כאשר מספר שעועית מיישם את אותו ממשק, CDI זורק חריג תלות מעורפל בזמן הבנייה. Quarkus משתמש ב-Qualifiers כדי לפתור יישומי שעועית יעד בצורה בטוחה.
הגדרת מוקדמות מותאמות אישית
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. Quarkus מטפל בזה באמצעות שיטות מפיק ומפטרים.
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: תכנות מונחה-היבט (AOP)
מיירטים מאפשרים להצמיד דאגות צולבות (רישום, מסלולי ביקורת, מדדים, הגבלת קצב) לשיטות שעועית מבלי לזהם את ההיגיון העסקי.
שלב 1: הגדר את כריכת המיירט
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: הטמעת לוגיקה מיירט
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
}
}
מכיוון ש-Quarkus מייצר פרוקסי מיירט סטטי בזמן הבנייה, תקורה של הפעלת השיטה היא כמעט אפס בהשוואה ל-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. חיפוש שעועית פרוגרמטי עם 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
| תכונה | Quarkus ArC | מיכל IoC אביב | ריתוך ג’קרטה סטנדרטי |
|---|---|---|---|
| רזולוציית תלות | זמן בנייה (AOT) | רפלקציית זמן ריצה | רפלקציית זמן ריצה |
| Metaspace RAM תקורה | נמוך במיוחד (~0MB למטא נתונים) | גבוה (מטמון דינמי של מחלקת Proxy) | בינוני עד גבוה |
| זמן הפעלה | אלפיות שניות (<0.05 שניות) | שניות (2.5 שניות - 8 שניות) | שניות (1.5 שניות - 4 שניות) |
| תמיכה מקורית של GraalVM | מקורי מחוץ לקופסה | דורש רמזים מקוריים באביב | תצורה מורכבת |
| גיזום שעועית לא בשימוש | גיזום אוטומטי בזמן בנייה | נשמר ב-Classpath | נשמר ב-Classpath |
| הזרקת קונסטרוקטור יחיד | מרומז (אין צורך בהערה) | נתמך | דורש @Inject |
10. בדיקת יחידות CDI שעועית עם @QuarkusTest ו@InjectMock
Quarkus הופך את בדיקת שעועית CDI ללא חלק עם JUnit 5 ו-RestAssured. השתמש ב-@InjectMock כדי להחליף שעועית תלות ב-Mockito מוקסים באופן אוטומטי במהלך הבדיקה.
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. מסקנה
הזרקת תלות ב-Quarkus עם CDI (ArC) מסמנת שינוי מונומנטלי בעיצוב Java ארגוני. על ידי העברת סריקת מטא-נתונים של שעועית, יצירת פרוקסי ורזולוציית גרפי הזרקה לזמן הידור, Quarkus מספקת מהירויות הפעלה על-פרסונליות, טביעות זיכרון תת-אטומיות ותמיכה בינארית חלקה של GraalVM.
בין אם אתה עובר מ-Spring Boot או בונה מיקרו-שירותים מקוריים בענן ב-Greenfield ב-Kubernetes, שליטה ב-Quarkus CDI מציידת אותך בביצועים ובפרודוקטיביות המפתחים הנדרשים לארכיטקטורת תוכנה מודרנית.
Tags
Empower Your Digital Presence & Workflows
Explore top-tier tools built by Ghaznix to streamline your links, surveys, and brand growth.