Build time CDI dependency injection framework for Quarkus applications with conditional bean support and context management
—
Utilities for working with interceptor bindings and context data within interceptor implementations. The InterceptorBindings utility class provides access to Arc-specific interceptor metadata.
Utility class for accessing interceptor binding metadata from invocation context.
/**
* Utility class for accessing interceptor binding annotations and literals from invocation context.
* @see ArcInvocationContext#getInterceptorBindings()
*/
public final class InterceptorBindings {
/**
* Retrieves the set of interceptor binding annotations from the invocation context.
* @param invocationContext the interceptor invocation context
* @return set of interceptor binding annotations
*/
@SuppressWarnings("unchecked")
public static Set<Annotation> getInterceptorBindings(InvocationContext invocationContext);
/**
* This method is just a convenience for getting a hold of AbstractAnnotationLiteral.
* See the Javadoc of the class for an explanation of the reasons it might be used.
* @param invocationContext the interceptor invocation context
* @return set of interceptor binding literals
*/
@SuppressWarnings("unchecked")
public static Set<AbstractAnnotationLiteral> getInterceptorBindingLiterals(InvocationContext invocationContext);
}Usage Examples:
import jakarta.interceptor.AroundInvoke;
import jakarta.interceptor.Interceptor;
import jakarta.interceptor.InvocationContext;
import io.quarkus.arc.runtime.InterceptorBindings;
import java.lang.annotation.Annotation;
import java.util.Set;
// Custom interceptor binding
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@interface Audited {
String value() default "";
AuditLevel level() default AuditLevel.INFO;
}
enum AuditLevel {
DEBUG, INFO, WARN, ERROR
}
// Another interceptor binding
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@interface Secured {
String[] roles() default {};
}
// Interceptor that uses InterceptorBindings utility
@Audited
@Interceptor
@Priority(Interceptor.Priority.APPLICATION)
public class AuditInterceptor {
@AroundInvoke
public Object audit(InvocationContext context) throws Exception {
// Get all interceptor bindings
Set<Annotation> bindings = InterceptorBindings.getInterceptorBindings(context);
// Process each binding
for (Annotation binding : bindings) {
if (binding instanceof Audited) {
Audited auditedBinding = (Audited) binding;
performAudit(context, auditedBinding.value(), auditedBinding.level());
} else if (binding instanceof Secured) {
Secured securedBinding = (Secured) binding;
checkSecurity(context, securedBinding.roles());
}
}
long startTime = System.currentTimeMillis();
try {
Object result = context.proceed();
// Post-execution audit
long duration = System.currentTimeMillis() - startTime;
auditMethodExecution(context, duration, true);
return result;
} catch (Exception e) {
long duration = System.currentTimeMillis() - startTime;
auditMethodExecution(context, duration, false);
throw e;
}
}
private void performAudit(InvocationContext context, String auditValue, AuditLevel level) {
String methodName = context.getMethod().getName();
String className = context.getTarget().getClass().getSimpleName();
String message = String.format("Auditing %s.%s() - %s",
className, methodName, auditValue);
switch (level) {
case DEBUG:
System.out.println("DEBUG: " + message);
break;
case INFO:
System.out.println("INFO: " + message);
break;
case WARN:
System.out.println("WARN: " + message);
break;
case ERROR:
System.out.println("ERROR: " + message);
break;
}
}
private void checkSecurity(InvocationContext context, String[] requiredRoles) {
// Security check implementation
System.out.println("Security check for roles: " + String.join(", ", requiredRoles));
// In real implementation, would check current user's roles
}
private void auditMethodExecution(InvocationContext context, long duration, boolean success) {
String methodName = context.getMethod().getName();
String status = success ? "SUCCESS" : "FAILURE";
System.out.println(String.format("Method %s completed in %dms - %s",
methodName, duration, status));
}
}More sophisticated interceptor implementations using binding metadata.
import jakarta.interceptor.AroundInvoke;
import jakarta.interceptor.Interceptor;
import jakarta.interceptor.InvocationContext;
import io.quarkus.arc.runtime.InterceptorBindings;
import io.quarkus.arc.AbstractAnnotationLiteral;
import java.lang.annotation.Annotation;
import java.util.Set;
import java.util.stream.Collectors;
// Complex interceptor binding with multiple attributes
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@interface Monitored {
String name() default "";
MetricType[] metrics() default {MetricType.EXECUTION_TIME};
boolean includeParameters() default false;
boolean includeResult() default false;
String[] tags() default {};
}
enum MetricType {
EXECUTION_TIME, INVOCATION_COUNT, ERROR_RATE, PARAMETER_SIZE
}
// Performance monitoring interceptor
@Monitored
@Interceptor
@Priority(Interceptor.Priority.APPLICATION + 10)
public class MonitoringInterceptor {
@AroundInvoke
public Object monitor(InvocationContext context) throws Exception {
// Get interceptor binding literals for detailed analysis
Set<AbstractAnnotationLiteral> bindingLiterals =
InterceptorBindings.getInterceptorBindingLiterals(context);
// Find the Monitored binding
Monitored monitoredBinding = findMonitoredBinding(bindingLiterals);
if (monitoredBinding != null) {
return executeWithMonitoring(context, monitoredBinding);
} else {
// Fallback to default monitoring
return executeWithDefaultMonitoring(context);
}
}
private Monitored findMonitoredBinding(Set<AbstractAnnotationLiteral> literals) {
return literals.stream()
.filter(literal -> literal instanceof Monitored)
.map(literal -> (Monitored) literal)
.findFirst()
.orElse(null);
}
private Object executeWithMonitoring(InvocationContext context, Monitored binding) throws Exception {
String methodName = getMethodName(context);
String monitorName = binding.name().isEmpty() ? methodName : binding.name();
// Pre-execution monitoring
MetricCollector collector = new MetricCollector(monitorName);
if (binding.includeParameters()) {
collector.recordParameters(context.getParameters());
}
for (MetricType metricType : binding.metrics()) {
collector.startMetric(metricType);
}
long startTime = System.currentTimeMillis();
try {
Object result = context.proceed();
// Post-execution success monitoring
long duration = System.currentTimeMillis() - startTime;
collector.recordSuccess(duration);
if (binding.includeResult()) {
collector.recordResult(result);
}
// Record custom tags
for (String tag : binding.tags()) {
collector.addTag(tag);
}
return result;
} catch (Exception e) {
// Post-execution error monitoring
long duration = System.currentTimeMillis() - startTime;
collector.recordError(duration, e);
throw e;
} finally {
collector.flush();
}
}
private Object executeWithDefaultMonitoring(InvocationContext context) throws Exception {
String methodName = getMethodName(context);
long startTime = System.currentTimeMillis();
try {
Object result = context.proceed();
long duration = System.currentTimeMillis() - startTime;
System.out.println(String.format("Method %s executed successfully in %dms",
methodName, duration));
return result;
} catch (Exception e) {
long duration = System.currentTimeMillis() - startTime;
System.out.println(String.format("Method %s failed after %dms: %s",
methodName, duration, e.getMessage()));
throw e;
}
}
private String getMethodName(InvocationContext context) {
return context.getTarget().getClass().getSimpleName() + "." +
context.getMethod().getName();
}
}
// Metric collection utility
class MetricCollector {
private final String name;
private final Map<MetricType, Long> startTimes = new HashMap<>();
private final List<String> tags = new ArrayList<>();
public MetricCollector(String name) {
this.name = name;
}
public void startMetric(MetricType type) {
startTimes.put(type, System.currentTimeMillis());
}
public void recordParameters(Object[] parameters) {
System.out.println("Parameters for " + name + ": " +
Arrays.toString(parameters));
}
public void recordResult(Object result) {
System.out.println("Result for " + name + ": " + result);
}
public void recordSuccess(long duration) {
System.out.println("Success - " + name + " took " + duration + "ms");
}
public void recordError(long duration, Exception error) {
System.out.println("Error - " + name + " failed after " + duration + "ms: " +
error.getMessage());
}
public void addTag(String tag) {
tags.add(tag);
}
public void flush() {
if (!tags.isEmpty()) {
System.out.println("Tags for " + name + ": " + String.join(", ", tags));
}
}
}Interceptor that handles multiple different binding types using the InterceptorBindings utility.
import jakarta.interceptor.AroundInvoke;
import jakarta.interceptor.Interceptor;
import jakarta.interceptor.InvocationContext;
import io.quarkus.arc.runtime.InterceptorBindings;
import java.lang.annotation.Annotation;
import java.util.Set;
// Multiple interceptor bindings
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@interface Cached {
int ttlSeconds() default 300;
String keyPrefix() default "";
}
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@interface RateLimited {
int requestsPerMinute() default 60;
String identifier() default "";
}
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@interface Validated {
Class<?>[] groups() default {};
boolean failFast() default true;
}
// Multi-purpose interceptor
@Cached
@RateLimited
@Validated
@Interceptor
@Priority(Interceptor.Priority.APPLICATION + 20)
public class MultiPurposeInterceptor {
@AroundInvoke
public Object intercept(InvocationContext context) throws Exception {
Set<Annotation> bindings = InterceptorBindings.getInterceptorBindings(context);
// Process each type of binding
InterceptorChain chain = new InterceptorChain(context);
for (Annotation binding : bindings) {
if (binding instanceof Cached) {
chain.addHandler(new CacheHandler((Cached) binding));
} else if (binding instanceof RateLimited) {
chain.addHandler(new RateLimitHandler((RateLimited) binding));
} else if (binding instanceof Validated) {
chain.addHandler(new ValidationHandler((Validated) binding));
}
}
return chain.execute();
}
}
// Interceptor chain pattern
class InterceptorChain {
private final InvocationContext context;
private final List<InterceptorHandler> handlers = new ArrayList<>();
public InterceptorChain(InvocationContext context) {
this.context = context;
}
public void addHandler(InterceptorHandler handler) {
handlers.add(handler);
}
public Object execute() throws Exception {
return executeHandlers(0);
}
private Object executeHandlers(int index) throws Exception {
if (index >= handlers.size()) {
return context.proceed();
}
InterceptorHandler handler = handlers.get(index);
return handler.handle(context, () -> executeHandlers(index + 1));
}
}
// Handler interface
interface InterceptorHandler {
Object handle(InvocationContext context, HandlerChain next) throws Exception;
}
@FunctionalInterface
interface HandlerChain {
Object proceed() throws Exception;
}
// Specific handlers
class CacheHandler implements InterceptorHandler {
private final Cached cacheConfig;
public CacheHandler(Cached cacheConfig) {
this.cacheConfig = cacheConfig;
}
@Override
public Object handle(InvocationContext context, HandlerChain next) throws Exception {
String cacheKey = generateCacheKey(context);
// Check cache
Object cachedResult = getFromCache(cacheKey);
if (cachedResult != null) {
System.out.println("Cache hit for key: " + cacheKey);
return cachedResult;
}
// Execute and cache result
Object result = next.proceed();
putInCache(cacheKey, result, cacheConfig.ttlSeconds());
System.out.println("Cached result for key: " + cacheKey);
return result;
}
private String generateCacheKey(InvocationContext context) {
String methodName = context.getMethod().getName();
String params = Arrays.toString(context.getParameters());
String prefix = cacheConfig.keyPrefix().isEmpty() ? "" : cacheConfig.keyPrefix() + ":";
return prefix + methodName + ":" + params.hashCode();
}
private Object getFromCache(String key) {
// Simplified cache implementation
return null; // Cache miss
}
private void putInCache(String key, Object value, int ttlSeconds) {
// Simplified cache implementation
System.out.println("Storing in cache: " + key + " for " + ttlSeconds + " seconds");
}
}
class RateLimitHandler implements InterceptorHandler {
private final RateLimited rateLimitConfig;
public RateLimitHandler(RateLimited rateLimitConfig) {
this.rateLimitConfig = rateLimitConfig;
}
@Override
public Object handle(InvocationContext context, HandlerChain next) throws Exception {
String identifier = rateLimitConfig.identifier().isEmpty() ?
"default" : rateLimitConfig.identifier();
if (!checkRateLimit(identifier, rateLimitConfig.requestsPerMinute())) {
throw new RateLimitExceededException(
"Rate limit exceeded: " + rateLimitConfig.requestsPerMinute() + " requests per minute");
}
return next.proceed();
}
private boolean checkRateLimit(String identifier, int requestsPerMinute) {
// Simplified rate limiting implementation
System.out.println("Checking rate limit for: " + identifier +
" (" + requestsPerMinute + " requests/minute)");
return true; // Allow for demo
}
}
class ValidationHandler implements InterceptorHandler {
private final Validated validationConfig;
public ValidationHandler(Validated validationConfig) {
this.validationConfig = validationConfig;
}
@Override
public Object handle(InvocationContext context, HandlerChain next) throws Exception {
Object[] parameters = context.getParameters();
for (Object param : parameters) {
if (param != null) {
validateParameter(param, validationConfig.groups(), validationConfig.failFast());
}
}
return next.proceed();
}
private void validateParameter(Object param, Class<?>[] groups, boolean failFast) {
// Simplified validation implementation
System.out.println("Validating parameter: " + param.getClass().getSimpleName() +
" with groups: " + Arrays.toString(groups) + ", failFast: " + failFast);
}
}
// Custom exception
class RateLimitExceededException extends RuntimeException {
public RateLimitExceededException(String message) {
super(message);
}
}Examples of using the interceptor bindings in service classes.
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class BusinessService {
// Method with multiple interceptor bindings
@Audited(value = "user-creation", level = AuditLevel.INFO)
@Secured(roles = {"admin", "user-manager"})
@Monitored(name = "create-user",
metrics = {MetricType.EXECUTION_TIME, MetricType.INVOCATION_COUNT},
includeParameters = true,
tags = {"business", "user-management"})
public User createUser(String username, String email) {
// Business logic
return new User(username, email);
}
// Cached method
@Cached(ttlSeconds = 600, keyPrefix = "user-lookup")
@Monitored(metrics = {MetricType.EXECUTION_TIME})
public User findUser(String username) {
// Expensive lookup operation
return performUserLookup(username);
}
// Rate limited method
@RateLimited(requestsPerMinute = 10, identifier = "email-sender")
@Audited(value = "email-sent", level = AuditLevel.INFO)
public void sendEmail(String to, String subject, String body) {
// Email sending logic
performEmailSend(to, subject, body);
}
// Validated method
@Validated(groups = {ValidationGroup.Create.class}, failFast = true)
@Audited(value = "order-processing", level = AuditLevel.WARN)
public Order processOrder(OrderRequest request) {
// Order processing logic
return new Order(request);
}
// Multiple caching and monitoring
@Cached(ttlSeconds = 300, keyPrefix = "reports")
@Monitored(name = "generate-report",
includeParameters = true,
includeResult = false,
tags = {"reports", "business-intelligence"})
public ReportData generateReport(String reportType, Date fromDate, Date toDate) {
// Report generation logic
return new ReportData(reportType, fromDate, toDate);
}
// Helper methods (not intercepted)
private User performUserLookup(String username) {
// Database lookup
return new User(username, username + "@example.com");
}
private void performEmailSend(String to, String subject, String body) {
System.out.println("Sending email to: " + to + ", subject: " + subject);
}
}
// Supporting classes
class User {
private final String username;
private final String email;
public User(String username, String email) {
this.username = username;
this.email = email;
}
// getters...
}
class Order {
private final OrderRequest request;
public Order(OrderRequest request) {
this.request = request;
}
}
class OrderRequest {
// Order request fields
}
class ReportData {
private final String type;
private final Date fromDate;
private final Date toDate;
public ReportData(String type, Date fromDate, Date toDate) {
this.type = type;
this.fromDate = fromDate;
this.toDate = toDate;
}
}
interface ValidationGroup {
interface Create {}
interface Update {}
}@Interceptor
public class SafeInterceptor {
@AroundInvoke
public Object safeIntercept(InvocationContext context) throws Exception {
try {
Set<Annotation> bindings = InterceptorBindings.getInterceptorBindings(context);
if (bindings == null || bindings.isEmpty()) {
// No bindings found, proceed without special handling
return context.proceed();
}
// Process bindings safely
return processBindings(context, bindings);
} catch (Exception e) {
// Log error but don't prevent method execution
System.err.println("Interceptor error: " + e.getMessage());
return context.proceed();
}
}
private Object processBindings(InvocationContext context, Set<Annotation> bindings) throws Exception {
// Safe binding processing
for (Annotation binding : bindings) {
if (binding != null) {
processBinding(context, binding);
}
}
return context.proceed();
}
private void processBinding(InvocationContext context, Annotation binding) {
// Individual binding processing with error handling
try {
// Process specific binding
} catch (Exception e) {
System.err.println("Error processing binding " + binding.getClass().getSimpleName() +
": " + e.getMessage());
}
}
}@Interceptor
public class OptimizedInterceptor {
// Cache binding analysis results
private final Map<Method, Set<Annotation>> bindingCache = new ConcurrentHashMap<>();
@AroundInvoke
public Object optimizedIntercept(InvocationContext context) throws Exception {
Method method = context.getMethod();
// Use cached bindings if available
Set<Annotation> bindings = bindingCache.computeIfAbsent(method,
m -> InterceptorBindings.getInterceptorBindings(context));
if (bindings.isEmpty()) {
return context.proceed();
}
// Process with cached bindings
return processOptimized(context, bindings);
}
private Object processOptimized(InvocationContext context, Set<Annotation> bindings) throws Exception {
// Optimized processing logic
return context.proceed();
}
}Install with Tessl CLI
npx tessl i tessl/maven-io-quarkus--quarkus-arc