Quarkus Topic
Part 1: Core CDI Annotations & Scopes
Quarkus uses a build-time optimized version of CDI (Contexts and Dependency Injection). The scopes determine the lifecycle of bean instances.
Basic Dependency Injection
java
// Service interface
public interface GreetingService {
String greet(String name);
}
// Implementation
@ApplicationScoped
public class EnglishGreetingService implements GreetingService {
public String greet(String name) {
return "Hello, " + name + "!";
}
}
// Client class using injection
@ApplicationScoped
public class GreetingResource {
@Inject
GreetingService greetingService;
public String welcome(String name) {
return greetingService.greet(name);
}
}
Bean Scopes
java
// ApplicationScoped - One instance per application
@ApplicationScoped
public class ApplicationConfig {
private final String appId = UUID.randomUUID().toString();
public String getAppId() {
return appId; // Same for entire application
}
}
// RequestScoped - New instance for each HTTP request
@RequestScoped
public class RequestContext {
private final String requestId = UUID.randomUUID().toString();
private String userAgent;
public String getRequestId() {
return requestId; // New for each request
}
// Getters/setters...
}
// Singleton - Similar to ApplicationScoped, but different semantics
@Singleton
public class SingletonService {
// Created eagerly at startup, guaranteed one instance
private int counter = 0;
public synchronized int increment() {
return ++counter;
}
}
// SessionScoped - For web sessions (requires quarkus-undertow)
// @SessionScoped
// public class UserSession { ... }
// Dependent - Default scope, new instance for each injection point
@Dependent
public class DependentService {
// New instance created every time it's injected
}
Scope Demonstration
java
@Path("/scopes")
@ApplicationScoped
public class ScopeDemoResource {
@Inject
ApplicationConfig appConfig;
@Inject
RequestContext requestContext;
@Inject
SingletonService singletonService;
@GET
@Path("/demo")
public String demonstrateScopes() {
return String.format(
"App ID: %s (same always)<br/>" +
"Request ID: %s (changes per request)<br/>" +
"Singleton Counter: %d (shared across app)<br/>",
appConfig.getAppId(),
requestContext.getRequestId(),
singletonService.increment()
);
}
}
Part 2: Advanced CDI Features
@Produces and @Disposes
Programmatic bean creation and cleanup.
java
@ApplicationScoped
public class DatabaseConnectionProducer {
private Map<String, Connection> connections = new ConcurrentHashMap<>();
// Produces a database connection
@Produces
@RequestScoped
@DatabaseConnection("main")
public Connection createConnection() throws SQLException {
Connection conn = DriverManager.getConnection(
"jdbc:postgresql://localhost:5432/mydb", "user", "pass");
connections.put(conn.toString(), conn);
System.out.println("Connection created: " + conn);
return conn;
}
// Disposes the connection when request ends
public void closeConnection(
@Disposes @DatabaseConnection("main") Connection connection) {
try {
System.out.println("Closing connection: " + connection);
connection.close();
connections.remove(connection.toString());
} catch (SQLException e) {
e.printStackTrace();
}
}
}
// Custom qualifier for database connections
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
public @interface DatabaseConnection {
String value();
}
Custom Qualifiers
Differentiate between implementations of the same type.
java
// Qualifiers
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
public @interface Email {}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
public @interface SMS {}
// Implementations
@ApplicationScoped
@Email
public class EmailNotificationService implements NotificationService {
public void send(String message) {
System.out.println("Sending email: " + message);
}
}
@ApplicationScoped
@SMS
public class SMSNotificationService implements NotificationService {
public void send(String message) {
System.out.println("Sending SMS: " + message);
}
}
// Usage with qualifiers
@ApplicationScoped
public class OrderService {
@Inject
@Email
NotificationService emailNotifications;
@Inject
@SMS
NotificationService smsNotifications;
public void processOrder(Order order) {
// Send different types of notifications
emailNotifications.send("Order confirmed: " + order.getId());
smsNotifications.send("Your order #" + order.getId() + " is confirmed");
}
}
Programmatic Lookup
java
@ApplicationScoped
public class NotificationRouter {
@Inject
Instance<NotificationService> notificationServices;
public void sendToAll(String message) {
// Get all implementations
for (NotificationService service : notificationServices) {
service.send(message);
}
}
@Inject
@Any
Instance<NotificationService> allNotifications;
}
Part 3: Interceptors
Interceptors allow cross-cutting concerns like logging, security, transactions.
1. Logging Interceptor
java
// Custom annotation to mark methods for logging
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Loggable {}
// The interceptor implementation
@Loggable
@Interceptor
public class LoggingInterceptor {
private static final Logger LOG = Logger.getLogger(LoggingInterceptor.class);
@AroundInvoke
public Object logMethod(InvocationContext context) throws Exception {
String methodName = context.getMethod().getName();
String className = context.getMethod().getDeclaringClass().getSimpleName();
long start = System.currentTimeMillis();
try {
LOG.infof("Entering: %s.%s()", className, methodName);
// Proceed with the original method call
Object result = context.proceed();
long duration = System.currentTimeMillis() - start;
LOG.infof("Exiting: %s.%s() [%d ms]", className, methodName, duration);
return result;
} catch (Exception e) {
LOG.errorf("Error in %s.%s(): %s", className, methodName, e.getMessage());
throw e;
}
}
}
2. Security/Role-Based Interceptor
java
// Security annotation
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RequiresRole {
String value();
}
// Security interceptor
@RequiresRole("")
@Interceptor
public class SecurityInterceptor {
@Inject
SecurityContext securityContext;
@AroundInvoke
public Object checkPermission(InvocationContext context) throws Exception {
RequiresRole annotation = context.getMethod().getAnnotation(RequiresRole.class);
String requiredRole = annotation.value();
// Simulate security check
if (!securityContext.hasRole(requiredRole)) {
throw new SecurityException("Access denied. Required role: " + requiredRole);
}
return context.proceed();
}
}
// Mock security context
@ApplicationScoped
public class SecurityContext {
private ThreadLocal<String> currentUserRole = new ThreadLocal<>();
public void setCurrentRole(String role) {
currentUserRole.set(role);
}
public boolean hasRole(String requiredRole) {
String userRole = currentUserRole.get();
return requiredRole.equals(userRole);
}
}
3. Retry Interceptor
java
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Retry {
int attempts() default 3;
long delay() default 1000;
}
@Retry
@Interceptor
public class RetryInterceptor {
@AroundInvoke
public Object retryOperation(InvocationContext context) throws Exception {
Retry retry = context.getMethod().getAnnotation(Retry.class);
int maxAttempts = retry.attempts();
long delay = retry.delay();
Exception lastException = null;
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return context.proceed();
} catch (Exception e) {
lastException = e;
System.out.printf("Attempt %d/%d failed: %s%n",
attempt, maxAttempts, e.getMessage());
if (attempt < maxAttempts) {
Thread.sleep(delay);
}
}
}
throw lastException;
}
}
Using Interceptors
java
@ApplicationScoped
public class OrderService {
@Inject
SecurityContext securityContext;
@Loggable
@Retry(attempts = 5, delay = 2000)
public void processOrder(Order order) {
System.out.println("Processing order: " + order.getId());
// Simulate occasional failure
if (Math.random() > 0.7) {
throw new RuntimeException("Temporary database failure");
}
}
@RequiresRole("ADMIN")
@Loggable
public void deleteOrder(Long orderId) {
System.out.println("Deleting order: " + orderId);
}
}
// REST Resource demonstrating interceptors
@Path("/orders")
@ApplicationScoped
public class OrderResource {
@Inject
OrderService orderService;
@Inject
SecurityContext securityContext;
@POST
@Loggable
public String createOrder() {
// This will be logged and retried on failure
Order order = new Order();
orderService.processOrder(order);
return "Order processed";
}
@DELETE
@Path("/{id}")
public String deleteOrder(@PathParam("id") Long id) {
// Set user role for security check
securityContext.setCurrentRole("ADMIN");
// This will check security and log
orderService.deleteOrder(id);
return "Order deleted";
}
}
Part 4: Advanced Bean Configuration
Conditional Beans
java
@ApplicationScoped
public class FeatureToggle {
@ConfigProperty(name = "features.advanced-mode", defaultValue = "false")
boolean advancedMode;
@Produces
@ApplicationScoped
public PaymentProcessor paymentProcessor() {
if (advancedMode) {
return new AdvancedPaymentProcessor();
} else {
return new BasicPaymentProcessor();
}
}
}
// Alternative using @IfBuildProperty
@IfBuildProperty(name = "features.advanced-mode", stringValue = "true")
@ApplicationScoped
public class AdvancedPaymentProcessor implements PaymentProcessor {
// Advanced implementation
}
@IfBuildProperty(name = "features.advanced-mode", stringValue = "false")
@ApplicationScoped
public class BasicPaymentProcessor implements PaymentProcessor {
// Basic implementation
}
Event System
java
// Custom events
public class OrderCreatedEvent {
private final Order order;
public OrderCreatedEvent(Order order) {
this.order = order;
}
public Order getOrder() { return order; }
}
public class OrderCancelledEvent {
private final Order order;
public OrderCancelledEvent(Order order) {
this.order = order;
}
public Order getOrder() { return order; }
}
// Event producers
@ApplicationScoped
public class OrderService {
@Inject
Event<OrderCreatedEvent> orderCreatedEvent;
@Inject
Event<OrderCancelledEvent> orderCancelledEvent;
@Transactional
public void createOrder(Order order) {
// Save order to database...
order.persist();
// Fire event
orderCreatedEvent.fire(new OrderCreatedEvent(order));
}
public void cancelOrder(Order order) {
// Update order status...
orderCancelledEvent.fire(new OrderCancelledEvent(order));
}
}
// Event observers
@ApplicationScoped
public class NotificationService {
public void onOrderCreated(@Observes OrderCreatedEvent event) {
System.out.println("Sending confirmation for order: " + event.getOrder().getId());
}
}
@ApplicationScoped
public class AnalyticsService {
public void onOrderCreated(@Observes OrderCreatedEvent event) {
System.out.println("Recording analytics for order: " + event.getOrder().getId());
}
public void onOrderCancelled(@Observes OrderCancelledEvent event) {
System.out.println("Recording cancellation for order: " + event.getOrder().getId());
}
}
Configuration
Add to application.properties:
properties
# Enable interceptors quarkus.log.level=INFO # For security interceptor demo features.advanced-mode=false # CDI configuration quarkus.arc.remove-unused-beans=false quarkus.arc.exclude-types=com.example.excluded.*
Key Takeaways
-
Scope Choice: Use
@RequestScopedfor request-specific data,@ApplicationScopedfor shared services -
Interceptors: Perfect for cross-cutting concerns without code duplication
-
Qualifiers: Essential when multiple implementations exist
-
Producers/Disposers: Great for resource management and conditional beans
-
Events: Enable loose coupling between components
-
Build-Time Optimization: Quarkus validates DI graph at build time, catching errors early
This powerful DI system combined with interceptors allows you to build clean, maintainable, and well-architected Quarkus applications.
Comments (0)
No comments yet. Be the first to comment!