or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advice-interceptors.mdaspectj-integration.mdauto-proxy.mdcore-abstractions.mdindex.mdpointcuts.mdproxy-creation.mdtarget-sources.md

advice-interceptors.mddocs/

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

```