0
# Advice Types and Interceptors
1
2
Implementation of different advice types (before, after, around, throws) and method interceptors for cross-cutting concerns like performance monitoring, debugging, concurrency control, and asynchronous execution. These components provide the actual behavior that gets executed at join points identified by pointcuts.
3
4
## Capabilities
5
6
### AOP Alliance Method Interceptors
7
8
The foundational interceptor interface from AOP Alliance that all Spring interceptors implement or extend.
9
10
```java { .api }
11
public interface MethodInterceptor extends Interceptor {
12
/**
13
* Implement this method to perform extra treatments before and
14
* after the invocation. Polite implementations would certainly
15
* like to invoke {@link Joinpoint#proceed()}.
16
* @param invocation the method invocation joinpoint
17
* @return the result of the call to {@link Joinpoint#proceed()};
18
* might be intercepted by the interceptor
19
* @throws Throwable if the interceptors or the target object
20
* throws an exception
21
*/
22
Object invoke(MethodInvocation invocation) throws Throwable;
23
}
24
25
public interface MethodInvocation extends Invocation {
26
/**
27
* Get the method being invoked.
28
* <p>This method is a friendly implementation of the
29
* {@link Joinpoint#getStaticPart()} method (same result).
30
* @return the method being invoked
31
*/
32
Method getMethod();
33
}
34
```
35
36
### Performance and Debugging Interceptors
37
38
Interceptors for monitoring method execution performance and debugging method calls.
39
40
```java { .api }
41
public class PerformanceMonitorInterceptor extends AbstractMonitoringInterceptor {
42
/**
43
* Create a new PerformanceMonitorInterceptor with a static logger.
44
*/
45
public PerformanceMonitorInterceptor();
46
47
/**
48
* Create a new PerformanceMonitorInterceptor with a dynamic or static logger,
49
* according to the given flag.
50
* @param useDynamicLogger whether to use a dynamic logger or a static logger
51
* @see #setUseDynamicLogger
52
*/
53
public PerformanceMonitorInterceptor(boolean useDynamicLogger);
54
55
@Override
56
protected Object invokeUnderTrace(MethodInvocation invocation, Log logger) throws Throwable;
57
58
/**
59
* Return a description for the given method invocation.
60
* @param invocation the invocation to describe
61
* @return the description to use
62
*/
63
protected String getDescriptionString(MethodInvocation invocation);
64
}
65
66
public class SimpleTraceInterceptor extends AbstractTraceInterceptor {
67
/**
68
* Create a new SimpleTraceInterceptor with a static logger.
69
*/
70
public SimpleTraceInterceptor();
71
72
/**
73
* Create a new SimpleTraceInterceptor with dynamic or static logger,
74
* according to the given flag.
75
* @param useDynamicLogger whether to use a dynamic logger or a static logger
76
* @see #setUseDynamicLogger
77
*/
78
public SimpleTraceInterceptor(boolean useDynamicLogger);
79
80
@Override
81
protected Object invokeUnderTrace(MethodInvocation invocation, Log logger) throws Throwable;
82
83
/**
84
* Return a description for the given method invocation.
85
* @param invocation the invocation to describe
86
* @return the description
87
*/
88
protected String getDescriptionString(MethodInvocation invocation);
89
}
90
91
public class CustomizableTraceInterceptor extends AbstractTraceInterceptor {
92
/**
93
* Set the template to use for method entry log messages.
94
* This template can contain any of the following placeholders:
95
* <ul>
96
* <li>{@code $[methodName]} - replaced with the name of the method being invoked</li>
97
* <li>{@code $[targetClassName]} - replaced with the name of the class that is
98
* the target of the invocation</li>
99
* <li>{@code $[targetClassShortName]} - replaced with the short name of the class
100
* that is the target of the invocation</li>
101
* <li>{@code $[returnValue]} - replaced with the value returned by the invocation</li>
102
* <li>{@code $[argumentTypes]} - replaced with a comma-separated list of the
103
* argument types for the method</li>
104
* <li>{@code $[arguments]} - replaced with a comma-separated list of the
105
* String representation of the method arguments</li>
106
* <li>{@code $[exception]} - replaced with the {@code String} representation
107
* of any {@link Throwable} raised during the invocation</li>
108
* <li>{@code $[invocationTime]} - replaced with the time, in milliseconds,
109
* taken by the method invocation</li>
110
* </ul>
111
* @param enterMessage the template to use for method entry log messages
112
*/
113
public void setEnterMessage(String enterMessage);
114
115
/**
116
* Set the template to use for method exit log messages.
117
* @param exitMessage the template to use for method exit log messages
118
* @see #setEnterMessage
119
*/
120
public void setExitMessage(String exitMessage);
121
122
/**
123
* Set the template to use for method exception log messages.
124
* @param exceptionMessage the template to use for method exception log messages
125
* @see #setEnterMessage
126
*/
127
public void setExceptionMessage(String exceptionMessage);
128
129
@Override
130
protected Object invokeUnderTrace(MethodInvocation invocation, Log logger) throws Throwable;
131
132
/**
133
* Replace the placeholders in the given message template with the
134
* supplied values, or values derived from those supplied.
135
* @param message the message template containing the placeholders to be replaced
136
* @param methodInvocation the {@code MethodInvocation} being logged.
137
* Used to derive values for all placeholders except {@code $[exception]}
138
* and {@code $[invocationTime]}.
139
* @param ex the {@code Throwable} returned by the method invocation. Used to
140
* derive the {@code $[exception]} placeholder. Can be {@code null}.
141
* @param invocationTime the value to write in place of the
142
* {@code $[invocationTime]} placeholder
143
* @return the formatted output to write to the log
144
*/
145
protected String replacePlaceholders(String message, MethodInvocation methodInvocation,
146
Object returnValue, Throwable ex, long invocationTime);
147
}
148
149
public class DebugInterceptor extends SimpleTraceInterceptor {
150
/**
151
* Create a new DebugInterceptor with a static logger.
152
*/
153
public DebugInterceptor();
154
155
/**
156
* Create a new DebugInterceptor with dynamic or static logger,
157
* according to the given flag.
158
* @param useDynamicLogger whether to use a dynamic logger or a static logger
159
* @see #setUseDynamicLogger
160
*/
161
public DebugInterceptor(boolean useDynamicLogger);
162
163
@Override
164
public Object invoke(MethodInvocation invocation) throws Throwable;
165
166
/**
167
* Return the number of times this interceptor has been invoked.
168
* @return the invocation count
169
*/
170
public long getCount();
171
172
/**
173
* Reset the invocation count to zero.
174
*/
175
public synchronized void resetCount();
176
}
177
```
178
179
### Base Monitoring Interceptor
180
181
Abstract base class for interceptors that monitor method execution.
182
183
```java { .api }
184
public abstract class AbstractMonitoringInterceptor implements MethodInterceptor, Serializable {
185
/**
186
* Set the name of the logger to use. The name will be passed to the
187
* underlying logger implementation through Commons Logging, getting
188
* interpreted as log category according to the logger's configuration.
189
* <p>This can be specified to not log into the category of a class
190
* (whether this interceptor's class or the class getting called)
191
* but rather into a specific named category.
192
* <p><b>NOTE:</b> Specify either this property or "useDynamicLogger", not both.
193
* @param loggerName the name of the logger
194
* @see #setUseDynamicLogger
195
*/
196
public void setLoggerName(String loggerName);
197
198
/**
199
* Set whether to use a dynamic logger or a static logger.
200
* Default is a static logger for this trace interceptor.
201
* <p>Used to determine which {@code Log} instance should be used to write
202
* log messages for a particular method invocation: a dynamic one for the
203
* {@code Class} getting called, or a static one for the {@code Class}
204
* of the trace interceptor.
205
* <p><b>NOTE:</b> Specify either this property or "loggerName", not both.
206
* @param useDynamicLogger {@code true} if the logger should be dynamic;
207
* {@code false} for a static logger
208
* @see #setLoggerName
209
*/
210
public void setUseDynamicLogger(boolean useDynamicLogger);
211
212
/**
213
* Return whether to use a dynamic logger or a static logger.
214
*/
215
public boolean isUseDynamicLogger();
216
217
/**
218
* Set whether to log at TRACE level. Default is DEBUG level.
219
* @param logAtTrace {@code true} to log at TRACE;
220
* {@code false} to log at DEBUG
221
*/
222
public void setLogAtTrace(boolean logAtTrace);
223
224
/**
225
* Return whether to log at TRACE level.
226
*/
227
public boolean isLogAtTrace();
228
229
/**
230
* Determine the appropriate {@code Log} instance to use for the given
231
* {@code MethodInvocation}. If the {@code useDynamicLogger} flag is set,
232
* the {@code Log} instance will be for the target class of the
233
* {@code MethodInvocation}, otherwise the {@code Log} will be the
234
* default static logger.
235
* @param invocation the {@code MethodInvocation} being traced
236
* @return the {@code Log} instance to use
237
* @see #setUseDynamicLogger
238
*/
239
protected Log getLoggerForInvocation(MethodInvocation invocation);
240
241
/**
242
* Determine whether the interceptor should kick in, that is,
243
* whether the {@code invokeUnderTrace} method should be called.
244
* <p>Default behavior is to check whether the given {@code Log}
245
* instance is enabled. Subclasses can override this to apply the
246
* interceptor in other cases as well.
247
* @param invocation the {@code MethodInvocation} being traced
248
* @param logger the {@code Log} instance to use
249
* @return {@code true} if the {@code invokeUnderTrace} method
250
* should be called; {@code false} otherwise
251
*/
252
protected boolean isInterceptorEnabled(MethodInvocation invocation, Log logger);
253
254
/**
255
* Subclasses must override this method to perform any tracing around the
256
* supplied {@code MethodInvocation}. Subclasses are responsible for
257
* ensuring that the {@code MethodInvocation} actually executes by
258
* calling {@code MethodInvocation.proceed()}.
259
* <p>By default, the passed-in {@code Log} instance will have log level
260
* "trace" enabled. Subclasses do not have to check for this again, unless
261
* they overwrite the {@code isInterceptorEnabled} method to modify
262
* the default behavior, and may delegate to {@code writeToLog} for actual
263
* messages to be written.
264
* @param invocation the method invocation to proceed with
265
* @param logger the {@code Log} to write trace messages to
266
* @return the result of the call to {@code MethodInvocation.proceed()}
267
* @throws Throwable if the interceptor or the target object throws an exception
268
* @see #writeToLog(Log, String)
269
* @see #writeToLog(Log, String, Throwable)
270
*/
271
protected abstract Object invokeUnderTrace(MethodInvocation invocation, Log logger) throws Throwable;
272
273
/**
274
* Write the supplied trace message to the supplied {@code Log} instance.
275
* <p>To be called by {@code invokeUnderTrace} for enter/exit outcomes.
276
* @param logger the {@code Log} instance to write to
277
* @param message the message to write
278
* @see #invokeUnderTrace
279
*/
280
protected void writeToLog(Log logger, String message);
281
282
/**
283
* Write the supplied trace message and {@link Throwable} to the
284
* supplied {@code Log} instance.
285
* <p>To be called by {@code invokeUnderTrace} for exceptional outcomes.
286
* @param logger the {@code Log} instance to write to
287
* @param message the message to write
288
* @param ex the exception that was thrown
289
* @see #invokeUnderTrace
290
*/
291
protected void writeToLog(Log logger, String message, Throwable ex);
292
}
293
```
294
295
### Concurrency Control Interceptors
296
297
Interceptors for managing concurrent access to methods.
298
299
```java { .api }
300
public class ConcurrencyThrottleInterceptor extends ConcurrencyThrottleSupport
301
implements MethodInterceptor, Serializable {
302
303
/**
304
* Create a new ConcurrencyThrottleInterceptor.
305
* Default concurrency limit is 1 (no concurrency).
306
*/
307
public ConcurrencyThrottleInterceptor();
308
309
@Override
310
public Object invoke(MethodInvocation methodInvocation) throws Throwable;
311
}
312
313
public abstract class ConcurrencyThrottleSupport implements Serializable {
314
/** Transient to optimize serialization. */
315
private transient Object monitor = new Object();
316
317
private int concurrencyLimit = 1;
318
319
private int concurrencyCount = 0;
320
321
/**
322
* Set the maximum number of concurrent access attempts that are allowed.
323
* -1 indicates no concurrency limit at all.
324
* <p>In principle, this limit can be changed at runtime,
325
* although it is generally designed as a config time setting.
326
* NOTE: Do not switch between -1 and any concrete limit at runtime,
327
* as this will lead to inconsistent concurrency counts.
328
*/
329
public void setConcurrencyLimit(int concurrencyLimit);
330
331
/**
332
* Return the maximum number of concurrent access attempts allowed.
333
*/
334
public int getConcurrencyLimit();
335
336
/**
337
* Return whether this throttle is currently active.
338
* @return {@code true} if the concurrency limit is active;
339
* {@code false} if the concurrency limit is not active
340
*/
341
public boolean isThrottleActive();
342
343
/**
344
* Return the current number of concurrent access attempts.
345
*/
346
public int getConcurrencyCount();
347
348
/**
349
* To be invoked before the main execution logic of concrete subclasses.
350
* <p>This implementation applies the concurrency throttle.
351
* @throws IllegalStateException if the concurrency limit has been reached
352
*/
353
protected void beforeAccess();
354
355
/**
356
* To be invoked after the main execution logic of concrete subclasses.
357
* @see #beforeAccess()
358
*/
359
protected void afterAccess();
360
}
361
```
362
363
### Asynchronous Execution Interceptors
364
365
Interceptors for executing methods asynchronously.
366
367
```java { .api }
368
public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport
369
implements MethodInterceptor, Ordered {
370
371
/**
372
* Create a new instance with a default {@link AsyncUncaughtExceptionHandler}.
373
* @param defaultExecutor the {@code Executor} (typically a Spring {@code AsyncTaskExecutor}
374
* or {@link java.util.concurrent.ExecutorService}) to delegate to,
375
* unless a more specific executor has been requested via a qualifier on the async method,
376
* in which case the executor will be looked up at invocation time against the enclosing bean factory
377
* @see AsyncExecutionAspectSupport#getExecutorQualifier
378
* @see AsyncExecutionAspectSupport#setBeanFactory
379
* @since 4.2.6
380
*/
381
public AsyncExecutionInterceptor(Executor defaultExecutor);
382
383
/**
384
* Create a new {@code AsyncExecutionInterceptor}.
385
* @param defaultExecutor the {@code Executor} (typically a Spring {@code AsyncTaskExecutor}
386
* or {@link java.util.concurrent.ExecutorService}) to delegate to,
387
* unless a more specific executor has been requested via a qualifier on the async method,
388
* in which case the executor will be looked up at invocation time against the enclosing bean factory
389
* @param exceptionHandler the {@link AsyncUncaughtExceptionHandler} to use
390
*/
391
public AsyncExecutionInterceptor(Executor defaultExecutor, AsyncUncaughtExceptionHandler exceptionHandler);
392
393
@Override
394
public Object invoke(final MethodInvocation invocation) throws Throwable;
395
396
/**
397
* Return the order value of this object.
398
*/
399
@Override
400
public int getOrder();
401
402
/**
403
* Set the order value of this object.
404
*/
405
public void setOrder(int order);
406
}
407
408
public interface AsyncUncaughtExceptionHandler {
409
/**
410
* Handle the given uncaught exception thrown from an asynchronous method.
411
* @param ex the exception thrown from the asynchronous method
412
* @param method the asynchronous method
413
* @param params the parameters used to invoke the method
414
*/
415
void handleUncaughtException(Throwable ex, Method method, Object... params);
416
}
417
418
public class SimpleAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
419
/**
420
* A default {@link AsyncUncaughtExceptionHandler} that simply logs the exception.
421
*/
422
@Override
423
public void handleUncaughtException(Throwable ex, Method method, Object... params);
424
}
425
```
426
427
### Adapter Classes for Advice Types
428
429
Adapters that convert Spring advice types to AOP Alliance method interceptors.
430
431
```java { .api }
432
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {
433
/**
434
* Create a new MethodBeforeAdviceInterceptor for the given advice.
435
* @param advice the MethodBeforeAdvice to wrap
436
*/
437
public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice);
438
439
@Override
440
public Object invoke(MethodInvocation mi) throws Throwable;
441
}
442
443
public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice, Serializable {
444
/**
445
* Create a new AfterReturningAdviceInterceptor for the given advice.
446
* @param advice the AfterReturningAdvice to wrap
447
*/
448
public AfterReturningAdviceInterceptor(AfterReturningAdvice advice);
449
450
@Override
451
public Object invoke(MethodInvocation mi) throws Throwable;
452
}
453
454
public class ThrowsAdviceInterceptor implements MethodInterceptor, AfterAdvice {
455
/**
456
* Create a new ThrowsAdviceInterceptor for the given ThrowsAdvice.
457
* @param throwsAdvice the ThrowsAdvice to wrap
458
*/
459
public ThrowsAdviceInterceptor(Object throwsAdvice);
460
461
/**
462
* Return the throws advice.
463
* @return the throws advice
464
*/
465
public Object getThrowsAdvice();
466
467
/**
468
* Return the number of applicable handler methods.
469
*/
470
public int getHandlerMethodCount();
471
472
@Override
473
public Object invoke(MethodInvocation mi) throws Throwable;
474
}
475
```
476
477
### Introduction Interceptors
478
479
Interceptors for implementing introductions (mixins).
480
481
```java { .api }
482
public class DelegatingIntroductionInterceptor extends IntroductionInfoSupport
483
implements IntroductionInterceptor {
484
485
/**
486
* Construct a new DelegatingIntroductionInterceptor, providing
487
* a delegate that implements the interfaces to be introduced.
488
* @param delegate the delegate that implements the introduced interfaces
489
*/
490
public DelegatingIntroductionInterceptor(Object delegate);
491
492
/**
493
* Construct a new DelegatingIntroductionInterceptor.
494
* The delegate will be the subclass, which must implement
495
* additional interfaces.
496
*/
497
protected DelegatingIntroductionInterceptor();
498
499
/**
500
* Both invoke the delegate, and check that it implements the
501
* interface indicated by the method name. If not, throw an exception.
502
*/
503
@Override
504
public Object invoke(MethodInvocation mi) throws Throwable;
505
506
/**
507
* Is this method on an introduced interface?
508
* @param mi the method invocation
509
* @return whether the invoked method is on an introduced interface
510
*/
511
protected final boolean isMethodOnIntroducedInterface(MethodInvocation mi);
512
}
513
514
public class DelegatePerTargetObjectIntroductionInterceptor extends IntroductionInfoSupport
515
implements IntroductionInterceptor {
516
517
/**
518
* Create a new DelegatePerTargetObjectIntroductionInterceptor,
519
* providing a delegate class that implements additional interfaces.
520
* @param delegateClass class that implements the introduced interfaces
521
*/
522
public DelegatePerTargetObjectIntroductionInterceptor(Class<?> delegateClass, Class<?>... introducedInterfaces);
523
524
@Override
525
public Object invoke(MethodInvocation mi) throws Throwable;
526
527
/**
528
* Create a new delegate for this introduction interceptor.
529
* The default implementation simply calls the no-arg constructor.
530
* <p>Subclasses can override to return any object that implements the interfaces.
531
* For example, the delegate can be returned from a factory, or
532
* it can be a mock object.
533
* @return the delegate instance
534
*/
535
protected Object createDelegate();
536
}
537
```
538
539
## Usage Examples
540
541
### Performance Monitoring
542
543
```java
544
// Simple performance monitoring
545
PerformanceMonitorInterceptor performanceInterceptor =
546
new PerformanceMonitorInterceptor();
547
performanceInterceptor.setUseDynamicLogger(true);
548
549
// Custom performance interceptor
550
public class CustomPerformanceInterceptor implements MethodInterceptor {
551
private final Map<String, Long> executionTimes = new ConcurrentHashMap<>();
552
553
@Override
554
public Object invoke(MethodInvocation invocation) throws Throwable {
555
String methodKey = invocation.getMethod().getDeclaringClass().getSimpleName() +
556
"." + invocation.getMethod().getName();
557
558
long start = System.nanoTime();
559
try {
560
Object result = invocation.proceed();
561
long executionTime = System.nanoTime() - start;
562
executionTimes.put(methodKey, executionTime / 1_000_000); // Convert to milliseconds
563
564
if (executionTime > 1_000_000_000) { // Log if > 1 second
565
System.out.println("SLOW METHOD: " + methodKey + " took " +
566
(executionTime / 1_000_000) + "ms");
567
}
568
569
return result;
570
} catch (Exception e) {
571
System.out.println("EXCEPTION in " + methodKey + ": " + e.getMessage());
572
throw e;
573
}
574
}
575
576
public Map<String, Long> getExecutionTimes() {
577
return new HashMap<>(executionTimes);
578
}
579
}
580
```
581
582
### Custom Trace Interceptor
583
584
```java
585
// Customizable trace interceptor with placeholders
586
CustomizableTraceInterceptor traceInterceptor = new CustomizableTraceInterceptor();
587
traceInterceptor.setUseDynamicLogger(true);
588
traceInterceptor.setEnterMessage(
589
"Entering method '$[methodName]' on class '$[targetClassShortName]' with arguments: $[arguments]"
590
);
591
traceInterceptor.setExitMessage(
592
"Exiting method '$[methodName]' with return value: '$[returnValue]' (took $[invocationTime]ms)"
593
);
594
traceInterceptor.setExceptionMessage(
595
"Exception in method '$[methodName]': $[exception]"
596
);
597
```
598
599
### Concurrency Control
600
601
```java
602
// Limit concurrent access to sensitive methods
603
ConcurrencyThrottleInterceptor throttleInterceptor = new ConcurrencyThrottleInterceptor();
604
throttleInterceptor.setConcurrencyLimit(3); // Allow max 3 concurrent executions
605
606
// Custom concurrency interceptor with queueing
607
public class QueueingConcurrencyInterceptor implements MethodInterceptor {
608
private final Semaphore semaphore;
609
private final long timeoutMs;
610
611
public QueueingConcurrencyInterceptor(int maxConcurrent, long timeoutMs) {
612
this.semaphore = new Semaphore(maxConcurrent);
613
this.timeoutMs = timeoutMs;
614
}
615
616
@Override
617
public Object invoke(MethodInvocation invocation) throws Throwable {
618
if (!semaphore.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
619
throw new ConcurrencyThrottleException(
620
"Could not acquire permit within " + timeoutMs + "ms for method: " +
621
invocation.getMethod().getName()
622
);
623
}
624
625
try {
626
return invocation.proceed();
627
} finally {
628
semaphore.release();
629
}
630
}
631
}
632
```
633
634
### Asynchronous Execution
635
636
```java
637
// Configure async execution
638
@Configuration
639
@EnableAsync
640
public class AsyncConfig {
641
642
@Bean
643
public Executor taskExecutor() {
644
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
645
executor.setCorePoolSize(5);
646
executor.setMaxPoolSize(10);
647
executor.setQueueCapacity(100);
648
executor.setThreadNamePrefix("async-");
649
executor.initialize();
650
return executor;
651
}
652
653
@Bean
654
public AsyncUncaughtExceptionHandler asyncUncaughtExceptionHandler() {
655
return new SimpleAsyncUncaughtExceptionHandler();
656
}
657
658
@Bean
659
public AsyncExecutionInterceptor asyncExecutionInterceptor() {
660
return new AsyncExecutionInterceptor(taskExecutor(), asyncUncaughtExceptionHandler());
661
}
662
}
663
664
// Custom async exception handler
665
public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
666
private static final Logger logger = LoggerFactory.getLogger(CustomAsyncExceptionHandler.class);
667
668
@Override
669
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
670
logger.error("Async method '{}' threw an exception with parameters: {}",
671
method.getName(), Arrays.toString(params), ex);
672
673
// Could also send notifications, write to database, etc.
674
if (ex instanceof RuntimeException) {
675
// Handle specific exception types
676
handleRuntimeException((RuntimeException) ex, method, params);
677
}
678
}
679
680
private void handleRuntimeException(RuntimeException ex, Method method, Object... params) {
681
// Custom handling for runtime exceptions
682
logger.warn("Runtime exception in async method {}: {}", method.getName(), ex.getMessage());
683
}
684
}
685
```
686
687
### Introduction/Mixin Support
688
689
```java
690
// Interface to be introduced
691
public interface Timestamped {
692
Date getLastModified();
693
void touch();
694
}
695
696
// Implementation of the introduced interface
697
public class TimestampedImpl implements Timestamped {
698
private Date lastModified = new Date();
699
700
@Override
701
public Date getLastModified() {
702
return lastModified;
703
}
704
705
@Override
706
public void touch() {
707
lastModified = new Date();
708
}
709
}
710
711
// Using introduction interceptor
712
DelegatingIntroductionInterceptor introductionInterceptor =
713
new DelegatingIntroductionInterceptor(new TimestampedImpl());
714
715
// Create proxy with introduction
716
ProxyFactory factory = new ProxyFactory(targetObject);
717
factory.addAdvice(introductionInterceptor);
718
factory.addInterface(Timestamped.class);
719
720
Object proxy = factory.getProxy();
721
722
// Now the proxy implements both original interfaces and Timestamped
723
if (proxy instanceof Timestamped) {
724
((Timestamped) proxy).touch();
725
System.out.println("Last modified: " + ((Timestamped) proxy).getLastModified());
726
}
727
```
728
729
### Complex Interceptor Chain
730
731
```java
732
public class AuditInterceptor implements MethodInterceptor {
733
private final AuditService auditService;
734
735
public AuditInterceptor(AuditService auditService) {
736
this.auditService = auditService;
737
}
738
739
@Override
740
public Object invoke(MethodInvocation invocation) throws Throwable {
741
String user = getCurrentUser();
742
String methodName = invocation.getMethod().getName();
743
String className = invocation.getThis().getClass().getSimpleName();
744
745
AuditEntry entry = new AuditEntry(user, className, methodName, System.currentTimeMillis());
746
747
try {
748
Object result = invocation.proceed();
749
entry.setSuccess(true);
750
entry.setResult(result != null ? result.toString() : "null");
751
return result;
752
} catch (Exception e) {
753
entry.setSuccess(false);
754
entry.setError(e.getMessage());
755
throw e;
756
} finally {
757
entry.setEndTime(System.currentTimeMillis());
758
auditService.saveAuditEntry(entry);
759
}
760
}
761
762
private String getCurrentUser() {
763
// Get current user from security context
764
return "current-user";
765
}
766
}
767
768
// Combine multiple interceptors
769
ProxyFactory factory = new ProxyFactory(targetObject);
770
factory.addAdvice(new SecurityInterceptor()); // Security checks first
771
factory.addAdvice(new AuditInterceptor(auditService)); // Then audit
772
factory.addAdvice(new PerformanceMonitorInterceptor()); // Then performance monitoring
773
factory.addAdvice(new CacheInterceptor()); // Finally caching
774
775
Object proxy = factory.getProxy();
776
```