Menu

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

  1. Scope Choice: Use @RequestScoped for request-specific data, @ApplicationScoped for shared services

  2. Interceptors: Perfect for cross-cutting concerns without code duplication

  3. Qualifiers: Essential when multiple implementations exist

  4. Producers/Disposers: Great for resource management and conditional beans

  5. Events: Enable loose coupling between components

  6. 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!

LecturreAI Assistant

Hello! I'm LecturreAI

What would you like to learn today?