0
# Interceptor Integration
1
2
Utilities for working with interceptor bindings and context data within interceptor implementations. The InterceptorBindings utility class provides access to Arc-specific interceptor metadata.
3
4
## Capabilities
5
6
### InterceptorBindings Utility Class
7
8
Utility class for accessing interceptor binding metadata from invocation context.
9
10
```java { .api }
11
/**
12
* Utility class for accessing interceptor binding annotations and literals from invocation context.
13
* @see ArcInvocationContext#getInterceptorBindings()
14
*/
15
public final class InterceptorBindings {
16
/**
17
* Retrieves the set of interceptor binding annotations from the invocation context.
18
* @param invocationContext the interceptor invocation context
19
* @return set of interceptor binding annotations
20
*/
21
@SuppressWarnings("unchecked")
22
public static Set<Annotation> getInterceptorBindings(InvocationContext invocationContext);
23
24
/**
25
* This method is just a convenience for getting a hold of AbstractAnnotationLiteral.
26
* See the Javadoc of the class for an explanation of the reasons it might be used.
27
* @param invocationContext the interceptor invocation context
28
* @return set of interceptor binding literals
29
*/
30
@SuppressWarnings("unchecked")
31
public static Set<AbstractAnnotationLiteral> getInterceptorBindingLiterals(InvocationContext invocationContext);
32
}
33
```
34
35
**Usage Examples:**
36
37
```java
38
import jakarta.interceptor.AroundInvoke;
39
import jakarta.interceptor.Interceptor;
40
import jakarta.interceptor.InvocationContext;
41
import io.quarkus.arc.runtime.InterceptorBindings;
42
import java.lang.annotation.Annotation;
43
import java.util.Set;
44
45
// Custom interceptor binding
46
@InterceptorBinding
47
@Retention(RetentionPolicy.RUNTIME)
48
@Target({ElementType.TYPE, ElementType.METHOD})
49
@interface Audited {
50
String value() default "";
51
AuditLevel level() default AuditLevel.INFO;
52
}
53
54
enum AuditLevel {
55
DEBUG, INFO, WARN, ERROR
56
}
57
58
// Another interceptor binding
59
@InterceptorBinding
60
@Retention(RetentionPolicy.RUNTIME)
61
@Target({ElementType.TYPE, ElementType.METHOD})
62
@interface Secured {
63
String[] roles() default {};
64
}
65
66
// Interceptor that uses InterceptorBindings utility
67
@Audited
68
@Interceptor
69
@Priority(Interceptor.Priority.APPLICATION)
70
public class AuditInterceptor {
71
72
@AroundInvoke
73
public Object audit(InvocationContext context) throws Exception {
74
// Get all interceptor bindings
75
Set<Annotation> bindings = InterceptorBindings.getInterceptorBindings(context);
76
77
// Process each binding
78
for (Annotation binding : bindings) {
79
if (binding instanceof Audited) {
80
Audited auditedBinding = (Audited) binding;
81
performAudit(context, auditedBinding.value(), auditedBinding.level());
82
} else if (binding instanceof Secured) {
83
Secured securedBinding = (Secured) binding;
84
checkSecurity(context, securedBinding.roles());
85
}
86
}
87
88
long startTime = System.currentTimeMillis();
89
90
try {
91
Object result = context.proceed();
92
93
// Post-execution audit
94
long duration = System.currentTimeMillis() - startTime;
95
auditMethodExecution(context, duration, true);
96
97
return result;
98
} catch (Exception e) {
99
long duration = System.currentTimeMillis() - startTime;
100
auditMethodExecution(context, duration, false);
101
throw e;
102
}
103
}
104
105
private void performAudit(InvocationContext context, String auditValue, AuditLevel level) {
106
String methodName = context.getMethod().getName();
107
String className = context.getTarget().getClass().getSimpleName();
108
109
String message = String.format("Auditing %s.%s() - %s",
110
className, methodName, auditValue);
111
112
switch (level) {
113
case DEBUG:
114
System.out.println("DEBUG: " + message);
115
break;
116
case INFO:
117
System.out.println("INFO: " + message);
118
break;
119
case WARN:
120
System.out.println("WARN: " + message);
121
break;
122
case ERROR:
123
System.out.println("ERROR: " + message);
124
break;
125
}
126
}
127
128
private void checkSecurity(InvocationContext context, String[] requiredRoles) {
129
// Security check implementation
130
System.out.println("Security check for roles: " + String.join(", ", requiredRoles));
131
// In real implementation, would check current user's roles
132
}
133
134
private void auditMethodExecution(InvocationContext context, long duration, boolean success) {
135
String methodName = context.getMethod().getName();
136
String status = success ? "SUCCESS" : "FAILURE";
137
System.out.println(String.format("Method %s completed in %dms - %s",
138
methodName, duration, status));
139
}
140
}
141
```
142
143
### Advanced Interceptor Binding Analysis
144
145
More sophisticated interceptor implementations using binding metadata.
146
147
```java
148
import jakarta.interceptor.AroundInvoke;
149
import jakarta.interceptor.Interceptor;
150
import jakarta.interceptor.InvocationContext;
151
import io.quarkus.arc.runtime.InterceptorBindings;
152
import io.quarkus.arc.AbstractAnnotationLiteral;
153
import java.lang.annotation.Annotation;
154
import java.util.Set;
155
import java.util.stream.Collectors;
156
157
// Complex interceptor binding with multiple attributes
158
@InterceptorBinding
159
@Retention(RetentionPolicy.RUNTIME)
160
@Target({ElementType.TYPE, ElementType.METHOD})
161
@interface Monitored {
162
String name() default "";
163
MetricType[] metrics() default {MetricType.EXECUTION_TIME};
164
boolean includeParameters() default false;
165
boolean includeResult() default false;
166
String[] tags() default {};
167
}
168
169
enum MetricType {
170
EXECUTION_TIME, INVOCATION_COUNT, ERROR_RATE, PARAMETER_SIZE
171
}
172
173
// Performance monitoring interceptor
174
@Monitored
175
@Interceptor
176
@Priority(Interceptor.Priority.APPLICATION + 10)
177
public class MonitoringInterceptor {
178
179
@AroundInvoke
180
public Object monitor(InvocationContext context) throws Exception {
181
// Get interceptor binding literals for detailed analysis
182
Set<AbstractAnnotationLiteral> bindingLiterals =
183
InterceptorBindings.getInterceptorBindingLiterals(context);
184
185
// Find the Monitored binding
186
Monitored monitoredBinding = findMonitoredBinding(bindingLiterals);
187
188
if (monitoredBinding != null) {
189
return executeWithMonitoring(context, monitoredBinding);
190
} else {
191
// Fallback to default monitoring
192
return executeWithDefaultMonitoring(context);
193
}
194
}
195
196
private Monitored findMonitoredBinding(Set<AbstractAnnotationLiteral> literals) {
197
return literals.stream()
198
.filter(literal -> literal instanceof Monitored)
199
.map(literal -> (Monitored) literal)
200
.findFirst()
201
.orElse(null);
202
}
203
204
private Object executeWithMonitoring(InvocationContext context, Monitored binding) throws Exception {
205
String methodName = getMethodName(context);
206
String monitorName = binding.name().isEmpty() ? methodName : binding.name();
207
208
// Pre-execution monitoring
209
MetricCollector collector = new MetricCollector(monitorName);
210
211
if (binding.includeParameters()) {
212
collector.recordParameters(context.getParameters());
213
}
214
215
for (MetricType metricType : binding.metrics()) {
216
collector.startMetric(metricType);
217
}
218
219
long startTime = System.currentTimeMillis();
220
221
try {
222
Object result = context.proceed();
223
224
// Post-execution success monitoring
225
long duration = System.currentTimeMillis() - startTime;
226
collector.recordSuccess(duration);
227
228
if (binding.includeResult()) {
229
collector.recordResult(result);
230
}
231
232
// Record custom tags
233
for (String tag : binding.tags()) {
234
collector.addTag(tag);
235
}
236
237
return result;
238
} catch (Exception e) {
239
// Post-execution error monitoring
240
long duration = System.currentTimeMillis() - startTime;
241
collector.recordError(duration, e);
242
throw e;
243
} finally {
244
collector.flush();
245
}
246
}
247
248
private Object executeWithDefaultMonitoring(InvocationContext context) throws Exception {
249
String methodName = getMethodName(context);
250
long startTime = System.currentTimeMillis();
251
252
try {
253
Object result = context.proceed();
254
long duration = System.currentTimeMillis() - startTime;
255
System.out.println(String.format("Method %s executed successfully in %dms",
256
methodName, duration));
257
return result;
258
} catch (Exception e) {
259
long duration = System.currentTimeMillis() - startTime;
260
System.out.println(String.format("Method %s failed after %dms: %s",
261
methodName, duration, e.getMessage()));
262
throw e;
263
}
264
}
265
266
private String getMethodName(InvocationContext context) {
267
return context.getTarget().getClass().getSimpleName() + "." +
268
context.getMethod().getName();
269
}
270
}
271
272
// Metric collection utility
273
class MetricCollector {
274
private final String name;
275
private final Map<MetricType, Long> startTimes = new HashMap<>();
276
private final List<String> tags = new ArrayList<>();
277
278
public MetricCollector(String name) {
279
this.name = name;
280
}
281
282
public void startMetric(MetricType type) {
283
startTimes.put(type, System.currentTimeMillis());
284
}
285
286
public void recordParameters(Object[] parameters) {
287
System.out.println("Parameters for " + name + ": " +
288
Arrays.toString(parameters));
289
}
290
291
public void recordResult(Object result) {
292
System.out.println("Result for " + name + ": " + result);
293
}
294
295
public void recordSuccess(long duration) {
296
System.out.println("Success - " + name + " took " + duration + "ms");
297
}
298
299
public void recordError(long duration, Exception error) {
300
System.out.println("Error - " + name + " failed after " + duration + "ms: " +
301
error.getMessage());
302
}
303
304
public void addTag(String tag) {
305
tags.add(tag);
306
}
307
308
public void flush() {
309
if (!tags.isEmpty()) {
310
System.out.println("Tags for " + name + ": " + String.join(", ", tags));
311
}
312
}
313
}
314
```
315
316
### Multi-Binding Interceptor
317
318
Interceptor that handles multiple different binding types using the InterceptorBindings utility.
319
320
```java
321
import jakarta.interceptor.AroundInvoke;
322
import jakarta.interceptor.Interceptor;
323
import jakarta.interceptor.InvocationContext;
324
import io.quarkus.arc.runtime.InterceptorBindings;
325
import java.lang.annotation.Annotation;
326
import java.util.Set;
327
328
// Multiple interceptor bindings
329
@InterceptorBinding
330
@Retention(RetentionPolicy.RUNTIME)
331
@Target({ElementType.TYPE, ElementType.METHOD})
332
@interface Cached {
333
int ttlSeconds() default 300;
334
String keyPrefix() default "";
335
}
336
337
@InterceptorBinding
338
@Retention(RetentionPolicy.RUNTIME)
339
@Target({ElementType.TYPE, ElementType.METHOD})
340
@interface RateLimited {
341
int requestsPerMinute() default 60;
342
String identifier() default "";
343
}
344
345
@InterceptorBinding
346
@Retention(RetentionPolicy.RUNTIME)
347
@Target({ElementType.TYPE, ElementType.METHOD})
348
@interface Validated {
349
Class<?>[] groups() default {};
350
boolean failFast() default true;
351
}
352
353
// Multi-purpose interceptor
354
@Cached
355
@RateLimited
356
@Validated
357
@Interceptor
358
@Priority(Interceptor.Priority.APPLICATION + 20)
359
public class MultiPurposeInterceptor {
360
361
@AroundInvoke
362
public Object intercept(InvocationContext context) throws Exception {
363
Set<Annotation> bindings = InterceptorBindings.getInterceptorBindings(context);
364
365
// Process each type of binding
366
InterceptorChain chain = new InterceptorChain(context);
367
368
for (Annotation binding : bindings) {
369
if (binding instanceof Cached) {
370
chain.addHandler(new CacheHandler((Cached) binding));
371
} else if (binding instanceof RateLimited) {
372
chain.addHandler(new RateLimitHandler((RateLimited) binding));
373
} else if (binding instanceof Validated) {
374
chain.addHandler(new ValidationHandler((Validated) binding));
375
}
376
}
377
378
return chain.execute();
379
}
380
}
381
382
// Interceptor chain pattern
383
class InterceptorChain {
384
private final InvocationContext context;
385
private final List<InterceptorHandler> handlers = new ArrayList<>();
386
387
public InterceptorChain(InvocationContext context) {
388
this.context = context;
389
}
390
391
public void addHandler(InterceptorHandler handler) {
392
handlers.add(handler);
393
}
394
395
public Object execute() throws Exception {
396
return executeHandlers(0);
397
}
398
399
private Object executeHandlers(int index) throws Exception {
400
if (index >= handlers.size()) {
401
return context.proceed();
402
}
403
404
InterceptorHandler handler = handlers.get(index);
405
return handler.handle(context, () -> executeHandlers(index + 1));
406
}
407
}
408
409
// Handler interface
410
interface InterceptorHandler {
411
Object handle(InvocationContext context, HandlerChain next) throws Exception;
412
}
413
414
@FunctionalInterface
415
interface HandlerChain {
416
Object proceed() throws Exception;
417
}
418
419
// Specific handlers
420
class CacheHandler implements InterceptorHandler {
421
private final Cached cacheConfig;
422
423
public CacheHandler(Cached cacheConfig) {
424
this.cacheConfig = cacheConfig;
425
}
426
427
@Override
428
public Object handle(InvocationContext context, HandlerChain next) throws Exception {
429
String cacheKey = generateCacheKey(context);
430
431
// Check cache
432
Object cachedResult = getFromCache(cacheKey);
433
if (cachedResult != null) {
434
System.out.println("Cache hit for key: " + cacheKey);
435
return cachedResult;
436
}
437
438
// Execute and cache result
439
Object result = next.proceed();
440
putInCache(cacheKey, result, cacheConfig.ttlSeconds());
441
System.out.println("Cached result for key: " + cacheKey);
442
443
return result;
444
}
445
446
private String generateCacheKey(InvocationContext context) {
447
String methodName = context.getMethod().getName();
448
String params = Arrays.toString(context.getParameters());
449
String prefix = cacheConfig.keyPrefix().isEmpty() ? "" : cacheConfig.keyPrefix() + ":";
450
return prefix + methodName + ":" + params.hashCode();
451
}
452
453
private Object getFromCache(String key) {
454
// Simplified cache implementation
455
return null; // Cache miss
456
}
457
458
private void putInCache(String key, Object value, int ttlSeconds) {
459
// Simplified cache implementation
460
System.out.println("Storing in cache: " + key + " for " + ttlSeconds + " seconds");
461
}
462
}
463
464
class RateLimitHandler implements InterceptorHandler {
465
private final RateLimited rateLimitConfig;
466
467
public RateLimitHandler(RateLimited rateLimitConfig) {
468
this.rateLimitConfig = rateLimitConfig;
469
}
470
471
@Override
472
public Object handle(InvocationContext context, HandlerChain next) throws Exception {
473
String identifier = rateLimitConfig.identifier().isEmpty() ?
474
"default" : rateLimitConfig.identifier();
475
476
if (!checkRateLimit(identifier, rateLimitConfig.requestsPerMinute())) {
477
throw new RateLimitExceededException(
478
"Rate limit exceeded: " + rateLimitConfig.requestsPerMinute() + " requests per minute");
479
}
480
481
return next.proceed();
482
}
483
484
private boolean checkRateLimit(String identifier, int requestsPerMinute) {
485
// Simplified rate limiting implementation
486
System.out.println("Checking rate limit for: " + identifier +
487
" (" + requestsPerMinute + " requests/minute)");
488
return true; // Allow for demo
489
}
490
}
491
492
class ValidationHandler implements InterceptorHandler {
493
private final Validated validationConfig;
494
495
public ValidationHandler(Validated validationConfig) {
496
this.validationConfig = validationConfig;
497
}
498
499
@Override
500
public Object handle(InvocationContext context, HandlerChain next) throws Exception {
501
Object[] parameters = context.getParameters();
502
503
for (Object param : parameters) {
504
if (param != null) {
505
validateParameter(param, validationConfig.groups(), validationConfig.failFast());
506
}
507
}
508
509
return next.proceed();
510
}
511
512
private void validateParameter(Object param, Class<?>[] groups, boolean failFast) {
513
// Simplified validation implementation
514
System.out.println("Validating parameter: " + param.getClass().getSimpleName() +
515
" with groups: " + Arrays.toString(groups) + ", failFast: " + failFast);
516
}
517
}
518
519
// Custom exception
520
class RateLimitExceededException extends RuntimeException {
521
public RateLimitExceededException(String message) {
522
super(message);
523
}
524
}
525
```
526
527
### Usage Examples
528
529
Examples of using the interceptor bindings in service classes.
530
531
```java
532
import jakarta.enterprise.context.ApplicationScoped;
533
534
@ApplicationScoped
535
public class BusinessService {
536
537
// Method with multiple interceptor bindings
538
@Audited(value = "user-creation", level = AuditLevel.INFO)
539
@Secured(roles = {"admin", "user-manager"})
540
@Monitored(name = "create-user",
541
metrics = {MetricType.EXECUTION_TIME, MetricType.INVOCATION_COUNT},
542
includeParameters = true,
543
tags = {"business", "user-management"})
544
public User createUser(String username, String email) {
545
// Business logic
546
return new User(username, email);
547
}
548
549
// Cached method
550
@Cached(ttlSeconds = 600, keyPrefix = "user-lookup")
551
@Monitored(metrics = {MetricType.EXECUTION_TIME})
552
public User findUser(String username) {
553
// Expensive lookup operation
554
return performUserLookup(username);
555
}
556
557
// Rate limited method
558
@RateLimited(requestsPerMinute = 10, identifier = "email-sender")
559
@Audited(value = "email-sent", level = AuditLevel.INFO)
560
public void sendEmail(String to, String subject, String body) {
561
// Email sending logic
562
performEmailSend(to, subject, body);
563
}
564
565
// Validated method
566
@Validated(groups = {ValidationGroup.Create.class}, failFast = true)
567
@Audited(value = "order-processing", level = AuditLevel.WARN)
568
public Order processOrder(OrderRequest request) {
569
// Order processing logic
570
return new Order(request);
571
}
572
573
// Multiple caching and monitoring
574
@Cached(ttlSeconds = 300, keyPrefix = "reports")
575
@Monitored(name = "generate-report",
576
includeParameters = true,
577
includeResult = false,
578
tags = {"reports", "business-intelligence"})
579
public ReportData generateReport(String reportType, Date fromDate, Date toDate) {
580
// Report generation logic
581
return new ReportData(reportType, fromDate, toDate);
582
}
583
584
// Helper methods (not intercepted)
585
private User performUserLookup(String username) {
586
// Database lookup
587
return new User(username, username + "@example.com");
588
}
589
590
private void performEmailSend(String to, String subject, String body) {
591
System.out.println("Sending email to: " + to + ", subject: " + subject);
592
}
593
}
594
595
// Supporting classes
596
class User {
597
private final String username;
598
private final String email;
599
600
public User(String username, String email) {
601
this.username = username;
602
this.email = email;
603
}
604
605
// getters...
606
}
607
608
class Order {
609
private final OrderRequest request;
610
611
public Order(OrderRequest request) {
612
this.request = request;
613
}
614
}
615
616
class OrderRequest {
617
// Order request fields
618
}
619
620
class ReportData {
621
private final String type;
622
private final Date fromDate;
623
private final Date toDate;
624
625
public ReportData(String type, Date fromDate, Date toDate) {
626
this.type = type;
627
this.fromDate = fromDate;
628
this.toDate = toDate;
629
}
630
}
631
632
interface ValidationGroup {
633
interface Create {}
634
interface Update {}
635
}
636
```
637
638
## Best Practices
639
640
### Defensive Programming
641
642
```java
643
@Interceptor
644
public class SafeInterceptor {
645
646
@AroundInvoke
647
public Object safeIntercept(InvocationContext context) throws Exception {
648
try {
649
Set<Annotation> bindings = InterceptorBindings.getInterceptorBindings(context);
650
651
if (bindings == null || bindings.isEmpty()) {
652
// No bindings found, proceed without special handling
653
return context.proceed();
654
}
655
656
// Process bindings safely
657
return processBindings(context, bindings);
658
659
} catch (Exception e) {
660
// Log error but don't prevent method execution
661
System.err.println("Interceptor error: " + e.getMessage());
662
return context.proceed();
663
}
664
}
665
666
private Object processBindings(InvocationContext context, Set<Annotation> bindings) throws Exception {
667
// Safe binding processing
668
for (Annotation binding : bindings) {
669
if (binding != null) {
670
processBinding(context, binding);
671
}
672
}
673
return context.proceed();
674
}
675
676
private void processBinding(InvocationContext context, Annotation binding) {
677
// Individual binding processing with error handling
678
try {
679
// Process specific binding
680
} catch (Exception e) {
681
System.err.println("Error processing binding " + binding.getClass().getSimpleName() +
682
": " + e.getMessage());
683
}
684
}
685
}
686
```
687
688
### Performance Considerations
689
690
```java
691
@Interceptor
692
public class OptimizedInterceptor {
693
694
// Cache binding analysis results
695
private final Map<Method, Set<Annotation>> bindingCache = new ConcurrentHashMap<>();
696
697
@AroundInvoke
698
public Object optimizedIntercept(InvocationContext context) throws Exception {
699
Method method = context.getMethod();
700
701
// Use cached bindings if available
702
Set<Annotation> bindings = bindingCache.computeIfAbsent(method,
703
m -> InterceptorBindings.getInterceptorBindings(context));
704
705
if (bindings.isEmpty()) {
706
return context.proceed();
707
}
708
709
// Process with cached bindings
710
return processOptimized(context, bindings);
711
}
712
713
private Object processOptimized(InvocationContext context, Set<Annotation> bindings) throws Exception {
714
// Optimized processing logic
715
return context.proceed();
716
}
717
}
718
```