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

target-sources.mddocs/

0

# Target Source Management

1

2

Different target source implementations for managing target object lifecycle, including singleton, prototype, pooled, thread-local, and hot-swappable target sources. Target sources provide flexible strategies for obtaining and managing the actual objects that AOP proxies delegate to, enabling advanced patterns like object pooling, lazy initialization, and runtime target replacement.

3

4

## Capabilities

5

6

### Core TargetSource Interface

7

8

The fundamental interface for all target source implementations, defining how proxies obtain target objects.

9

10

```java { .api }

11

public interface TargetSource extends TargetClassAware {

12

/**

13

* Return the type of targets returned by this {@link TargetSource}.

14

* <p>Can return {@code null}, although certain usages of a {@code TargetSource}

15

* might just work with a predetermined target class.

16

* @return the type of targets returned by this {@link TargetSource}

17

*/

18

@Override

19

Class<?> getTargetClass();

20

21

/**

22

* Will all calls to {@link #getTarget()} return the same object?

23

* <p>In that case, there will be no need to invoke {@link #releaseTarget(Object)},

24

* and the AOP framework can cache the return value of {@link #getTarget()}.

25

* @return {@code true} if the target is immutable

26

* @see #getTarget()

27

*/

28

boolean isStatic();

29

30

/**

31

* Return a target instance. Invoked immediately before the

32

* AOP framework calls the "target" of an AOP method invocation.

33

* @return the target object which contains the joinpoint,

34

* or {@code null} if there is no actual target instance

35

* @throws Exception if the target object can't be resolved

36

*/

37

Object getTarget() throws Exception;

38

39

/**

40

* Release the given target object obtained from the

41

* {@link #getTarget()} method, if any.

42

* @param target object obtained from a call to {@link #getTarget()}

43

* @throws Exception if the object can't be released

44

*/

45

void releaseTarget(Object target) throws Exception;

46

}

47

```

48

49

### Basic Target Source Implementations

50

51

Simple target source implementations for common scenarios.

52

53

```java { .api }

54

public class SingletonTargetSource implements TargetSource, Serializable {

55

/**

56

* Create a new SingletonTargetSource for the given target.

57

* @param target the target object

58

*/

59

public SingletonTargetSource(Object target);

60

61

/**

62

* Return the target object.

63

*/

64

public final Object getTarget();

65

66

/**

67

* Set the target object for this TargetSource.

68

* @param target the target object

69

*/

70

public void setTarget(Object target);

71

72

@Override

73

public Class<?> getTargetClass();

74

75

@Override

76

public boolean isStatic();

77

78

@Override

79

public Object getTarget() throws Exception;

80

81

@Override

82

public void releaseTarget(Object target);

83

}

84

85

public class EmptyTargetSource implements TargetSource, Serializable {

86

/** The canonical (Singleton) instance of this {@link EmptyTargetSource}. */

87

public static final EmptyTargetSource INSTANCE = new EmptyTargetSource(null, true);

88

89

/**

90

* Create a new instance of the {@link EmptyTargetSource} class.

91

* <p>This constructor is {@code private} to enforce the

92

* Singleton pattern / Flyweight pattern.

93

* @param targetClass the target class

94

* @param isStatic whether the target source is static

95

*/

96

private EmptyTargetSource(Class<?> targetClass, boolean isStatic);

97

98

/**

99

* Return an EmptyTargetSource for the given target Class.

100

* @param targetClass the target Class (may be {@code null})

101

* @return the corresponding EmptyTargetSource instance

102

*/

103

public static EmptyTargetSource forClass(Class<?> targetClass);

104

105

/**

106

* Return an EmptyTargetSource for the given target Class.

107

* @param targetClass the target Class (may be {@code null})

108

* @param isStatic whether the target source should be flagged as static

109

* @return the corresponding EmptyTargetSource instance

110

*/

111

public static EmptyTargetSource forClass(Class<?> targetClass, boolean isStatic);

112

113

@Override

114

public Class<?> getTargetClass();

115

116

@Override

117

public boolean isStatic();

118

119

@Override

120

public Object getTarget();

121

122

@Override

123

public void releaseTarget(Object target);

124

}

125

```

126

127

### Bean Factory-Based Target Sources

128

129

Target sources that obtain target objects from Spring BeanFactory instances.

130

131

```java { .api }

132

public abstract class AbstractBeanFactoryBasedTargetSource implements TargetSource, BeanFactoryAware, Serializable {

133

/** The owning BeanFactory. */

134

private BeanFactory beanFactory;

135

136

/** Name of the target bean we're proxying. */

137

private String targetBeanName;

138

139

/** Class of the target. */

140

private Class<?> targetClass;

141

142

/**

143

* Set the owning BeanFactory. We need to save a reference so that we can

144

* use the {@code getBean} method on every invocation.

145

*/

146

@Override

147

public final void setBeanFactory(BeanFactory beanFactory);

148

149

/**

150

* Return the owning BeanFactory.

151

*/

152

public final BeanFactory getBeanFactory();

153

154

/**

155

* Set the name of the target bean in the factory.

156

* <p>The target bean should not be a singleton, else the same instance will

157

* always be obtained from the factory, resulting in the same behavior as

158

* provided by {@link SingletonTargetSource}.

159

* @param targetBeanName name of the target bean in the BeanFactory

160

* that owns this interceptor

161

* @see SingletonTargetSource

162

*/

163

public void setTargetBeanName(String targetBeanName);

164

165

/**

166

* Return the name of the target bean in the factory.

167

*/

168

public String getTargetBeanName();

169

170

/**

171

* Set the target class explicitly, to avoid any kind of access to the

172

* target bean (for example, to avoid initialization of a FactoryBean instance).

173

* <p>Default is to detect the type automatically, through a {@code getType}

174

* call on the BeanFactory (or even a full {@code getBean} call as fallback).

175

*/

176

public void setTargetClass(Class<?> targetClass);

177

178

@Override

179

public Class<?> getTargetClass();

180

181

@Override

182

public boolean isStatic();

183

184

@Override

185

public void releaseTarget(Object target);

186

187

/**

188

* Copy configuration from the other AbstractBeanFactoryBasedTargetSource object.

189

* Subclasses should override this if they wish to expose it.

190

* @param other object to copy configuration from

191

*/

192

protected void copyFrom(AbstractBeanFactoryBasedTargetSource other);

193

}

194

195

public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource {

196

@Override

197

public Object getTarget() throws Exception;

198

}

199

200

public class LazyInitTargetSource extends AbstractBeanFactoryBasedTargetSource {

201

@Override

202

public Object getTarget() throws Exception;

203

}

204

205

public class PrototypeTargetSource extends AbstractBeanFactoryBasedTargetSource {

206

@Override

207

public Object getTarget() throws Exception;

208

209

/**

210

* Destroy the given independent instance.

211

* @param target the bean instance to destroy

212

* @see #getTarget()

213

*/

214

@Override

215

public void releaseTarget(Object target);

216

}

217

```

218

219

### Hot Swappable Target Source

220

221

Target source that allows runtime replacement of the target object.

222

223

```java { .api }

224

public class HotSwappableTargetSource implements TargetSource, Serializable {

225

/** The current target object. */

226

private Object target;

227

228

/**

229

* Create a new HotSwappableTargetSource with the given initial target object.

230

* @param initialTarget the initial target object

231

*/

232

public HotSwappableTargetSource(Object initialTarget);

233

234

@Override

235

public Class<?> getTargetClass();

236

237

@Override

238

public final boolean isStatic();

239

240

@Override

241

public Object getTarget();

242

243

@Override

244

public void releaseTarget(Object target);

245

246

/**

247

* Swap the target, returning the old target object.

248

* @param newTarget the new target object

249

* @return the previous target object

250

* @throws IllegalArgumentException if the new target is invalid

251

*/

252

public synchronized Object swap(Object newTarget) throws IllegalArgumentException;

253

}

254

```

255

256

### Thread-Local Target Source

257

258

Target source that maintains separate target instances per thread.

259

260

```java { .api }

261

public class ThreadLocalTargetSource extends AbstractPrototypeBasedTargetSource

262

implements ThreadLocalTargetSourceStats, DisposableBean {

263

264

/**

265

* ThreadLocal holding the target associated with the current

266

* thread. Unlike most ThreadLocals, which are static, this variable

267

* is meant to be per-thread per-instance of the ThreadLocalTargetSource class.

268

*/

269

private final ThreadLocal<Object> targetInThread = new NamedThreadLocal<>("Thread-local instance of bean '" + getTargetBeanName() + "'");

270

271

@Override

272

public Object getTarget() throws BeansException;

273

274

/**

275

* Dispose of targets if necessary; clear ThreadLocal.

276

* @see #destroyPrototypeInstance

277

*/

278

@Override

279

public void releaseTarget(Object target);

280

281

@Override

282

public void destroy();

283

284

/**

285

* Return the number of client invocations.

286

*/

287

@Override

288

public int getInvocationCount();

289

290

/**

291

* Return the number of hits that were satisfied by a thread-bound object.

292

*/

293

@Override

294

public int getHitCount();

295

296

/**

297

* Return the number of thread-bound objects created.

298

*/

299

@Override

300

public int getObjectCount();

301

302

/**

303

* Reset the statistics maintained by this object.

304

*/

305

public void resetStatistics();

306

}

307

308

public interface ThreadLocalTargetSourceStats {

309

/**

310

* Return the number of client invocations.

311

*/

312

int getInvocationCount();

313

314

/**

315

* Return the number of hits that were satisfied by a thread-bound object.

316

*/

317

int getHitCount();

318

319

/**

320

* Return the number of thread-bound objects created.

321

*/

322

int getObjectCount();

323

}

324

```

325

326

### Pooling Target Sources

327

328

Target sources that implement object pooling strategies.

329

330

```java { .api }

331

public interface PoolingConfig {

332

/**

333

* Return the maximum size of the pool.

334

*/

335

int getMaxSize();

336

337

/**

338

* Return the minimum size of the pool.

339

*/

340

int getMinSize();

341

342

/**

343

* Return the current number of active objects in the pool.

344

* @throws UnsupportedOperationException if not supported by the pool

345

*/

346

int getActiveCount() throws UnsupportedOperationException;

347

348

/**

349

* Return the current number of idle objects in the pool.

350

* @throws UnsupportedOperationException if not supported by the pool

351

*/

352

int getIdleCount() throws UnsupportedOperationException;

353

}

354

355

public abstract class AbstractPoolingTargetSource extends AbstractBeanFactoryBasedTargetSource implements PoolingConfig {

356

/** The maximum size of the pool. */

357

private int maxSize = -1;

358

359

/**

360

* Set the maximum size of the pool.

361

* Default is -1, indicating no size limit.

362

*/

363

public void setMaxSize(int maxSize);

364

365

@Override

366

public int getMaxSize();

367

368

/**

369

* Return the current number of active objects in the pool.

370

* @throws UnsupportedOperationException if not supported by the pool

371

*/

372

@Override

373

public final int getActiveCount() throws UnsupportedOperationException;

374

375

/**

376

* Return the current number of idle objects in the pool.

377

* @throws UnsupportedOperationException if not supported by the pool

378

*/

379

@Override

380

public final int getIdleCount() throws UnsupportedOperationException;

381

382

/**

383

* Subclasses must implement this to return the number of idle instances in the pool.

384

* @throws UnsupportedOperationException if not supported by the pool

385

*/

386

protected abstract int getIdleCountInternal() throws UnsupportedOperationException;

387

388

/**

389

* Subclasses must implement this to return the number of active instances in the pool.

390

* @throws UnsupportedOperationException if not supported by the pool

391

*/

392

protected abstract int getActiveCountInternal() throws UnsupportedOperationException;

393

}

394

395

public class CommonsPool2TargetSource extends AbstractPoolingTargetSource implements DisposableBean {

396

private ObjectPool<Object> pool;

397

398

private PooledObjectFactory<Object> pooledObjectFactory = new PooledObjectFactory<Object>() {

399

@Override

400

public PooledObject<Object> makeObject() throws Exception;

401

402

@Override

403

public void destroyObject(PooledObject<Object> p) throws Exception;

404

405

@Override

406

public boolean validateObject(PooledObject<Object> p);

407

408

@Override

409

public void activateObject(PooledObject<Object> p) throws Exception;

410

411

@Override

412

public void passivateObject(PooledObject<Object> p) throws Exception;

413

};

414

415

/**

416

* Create a CommonsPoolTargetSource with default settings.

417

* Default maximum size of the pool is 8.

418

* @see #setMaxSize

419

* @see GenericObjectPool#DEFAULT_MAX_TOTAL

420

*/

421

public CommonsPool2TargetSource();

422

423

/**

424

* Set the maximum number of idle objects in the pool.

425

* Default is 8.

426

* @see GenericObjectPool#setMaxIdle

427

*/

428

public void setMaxIdle(int maxIdle);

429

430

/**

431

* Return the maximum number of idle objects in the pool.

432

*/

433

public int getMaxIdle();

434

435

/**

436

* Set the minimum number of idle objects in the pool.

437

* Default is 0.

438

* @see GenericObjectPool#setMinIdle

439

*/

440

public void setMinIdle(int minIdle);

441

442

/**

443

* Return the minimum number of idle objects in the pool.

444

*/

445

@Override

446

public int getMinSize();

447

448

/**

449

* Set the maximum waiting time for the pool to return an object.

450

* Default is -1, meaning unlimited.

451

* @see GenericObjectPool#setMaxWaitMillis

452

*/

453

public void setMaxWait(long maxWait);

454

455

/**

456

* Return the maximum waiting time for the pool to return an object.

457

*/

458

public long getMaxWait();

459

460

/**

461

* Set the time between eviction runs that check for idle objects that can be removed.

462

* Default is -1, meaning no eviction thread will run.

463

* @see GenericObjectPool#setTimeBetweenEvictionRunsMillis

464

*/

465

public void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis);

466

467

/**

468

* Return the time between eviction runs.

469

*/

470

public long getTimeBetweenEvictionRunsMillis();

471

472

/**

473

* Set the minimum time that an idle object can sit in the pool before

474

* it becomes subject to eviction. Default is 30 minutes.

475

* @see GenericObjectPool#setMinEvictableIdleTimeMillis

476

*/

477

public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis);

478

479

/**

480

* Return the minimum evictable idle time.

481

*/

482

public long getMinEvictableIdleTimeMillis();

483

484

/**

485

* Set whether objects created for the pool will be validated before being returned

486

* from the {@code borrowObject()} method. Validation is performed by the

487

* {@code validateObject()} method of the factory associated with the pool.

488

* Default is {@code false}.

489

* @see GenericObjectPool#setTestOnBorrow

490

*/

491

public void setTestOnBorrow(boolean testOnBorrow);

492

493

/**

494

* Return whether objects are validated before being returned from the pool.

495

*/

496

public boolean isTestOnBorrow();

497

498

/**

499

* Set whether objects created for the pool will be validated before being returned

500

* to the pool. Validation is performed by the {@code validateObject()} method of

501

* the factory associated with the pool. Default is {@code false}.

502

* @see GenericObjectPool#setTestOnReturn

503

*/

504

public void setTestOnReturn(boolean testOnReturn);

505

506

/**

507

* Return whether objects are validated before being returned to the pool.

508

*/

509

public boolean isTestOnReturn();

510

511

/**

512

* Set whether objects sitting idle in the pool will be validated by the idle object

513

* evictor (if any - see {@link #setTimeBetweenEvictionRunsMillis}). Validation is

514

* performed by the {@code validateObject()} method of the factory associated with the pool.

515

* Default is {@code false}.

516

* @see GenericObjectPool#setTestWhileIdle

517

*/

518

public void setTestWhileIdle(boolean testWhileIdle);

519

520

/**

521

* Return whether objects are validated by the idle object evictor.

522

*/

523

public boolean isTestWhileIdle();

524

525

/**

526

* Sets the config for this pool.

527

* @param config the new pool configuration to use

528

* @see GenericObjectPool#setConfig

529

*/

530

public void setConfig(GenericObjectPoolConfig<Object> config);

531

532

/**

533

* Creates and holds an ObjectPool instance.

534

* @see #createObjectPool()

535

*/

536

protected final void createPool();

537

538

/**

539

* Subclasses can override this if they want to return a specific Commons pool.

540

* They should apply any configuration properties to the pool here.

541

* <p>Default is a GenericObjectPool instance with the given pool size.

542

* @return an empty Commons {@code ObjectPool}.

543

* @see GenericObjectPool

544

* @see #setMaxSize

545

*/

546

protected ObjectPool<Object> createObjectPool();

547

548

@Override

549

public Object getTarget() throws Exception;

550

551

@Override

552

public void releaseTarget(Object target) throws Exception;

553

554

@Override

555

protected int getActiveCountInternal();

556

557

@Override

558

protected int getIdleCountInternal();

559

560

/**

561

* Closes the underlying {@code ObjectPool} when destroying this object.

562

*/

563

@Override

564

public void destroy() throws Exception;

565

}

566

```

567

568

### Dynamic and Refreshable Target Sources

569

570

Target sources that can be refreshed or changed at runtime.

571

572

```java { .api }

573

public interface Refreshable {

574

/**

575

* Refresh the underlying target object.

576

*/

577

void refresh();

578

579

/**

580

* Return the number of actual refreshes since startup.

581

*/

582

long getRefreshCount();

583

584

/**

585

* Return the timestamp of the last refresh attempt (successful or not).

586

*/

587

Date getLastRefreshTime();

588

}

589

590

public abstract class AbstractRefreshableTargetSource extends AbstractBeanFactoryBasedTargetSource implements Refreshable {

591

592

protected Object cachedTarget;

593

594

private long refreshCount;

595

596

private Date lastRefreshCheck;

597

598

private long lastRefreshTime = -1;

599

600

/**

601

* Cache a target if it is meant to be cached.

602

*/

603

@Override

604

public Object getTarget();

605

606

/**

607

* No need to release cached target.

608

*/

609

@Override

610

public void releaseTarget(Object object);

611

612

@Override

613

public final synchronized void refresh();

614

615

/**

616

* Determine a refresh timestamp, indicating the last time

617

* that a refresh attempt was made.

618

* <p>This implementation returns the current system time when

619

* the refresh attempt is being made.

620

* <p>Subclasses can override this to return an appropriate timestamp

621

* based on configuration settings, metadata analysis, or other factors.

622

* @return the refresh timestamp to expose through {@link #getLastRefreshTime()}

623

* @see #getLastRefreshTime()

624

*/

625

protected long determineLastRefreshTime();

626

627

@Override

628

public long getRefreshCount();

629

630

@Override

631

public Date getLastRefreshTime();

632

633

/**

634

* Determine whether a refresh is required.

635

* Invoked for each refresh check, after successful retrieval of a cached instance.

636

* <p>The default implementation always returns {@code true}, triggering

637

* a refresh every time the refresh check delay has elapsed.

638

* To be overridden by subclasses with an appropriate check of the

639

* underlying target resource.

640

* @param cachedTarget the cached target object

641

* @param refreshCheckDelay ms that have elapsed since the last refresh check

642

* @return whether a refresh is required

643

*/

644

protected boolean requiresRefresh(Object cachedTarget, long refreshCheckDelay);

645

646

/**

647

* Obtain a fresh target object.

648

* <p>Only invoked when a refresh check returned {@code true}.

649

* @return the fresh target object

650

*/

651

protected abstract Object freshTarget();

652

}

653

654

public class BeanFactoryRefreshableTargetSource extends AbstractRefreshableTargetSource {

655

@Override

656

protected final Object freshTarget();

657

}

658

```

659

660

## Usage Examples

661

662

### Basic Target Source Usage

663

664

```java

665

// Singleton target source (most common)

666

Object target = new MyServiceImpl();

667

SingletonTargetSource targetSource = new SingletonTargetSource(target);

668

669

ProxyFactory factory = new ProxyFactory();

670

factory.setTargetSource(targetSource);

671

factory.addInterface(MyService.class);

672

factory.addAdvice(new LoggingInterceptor());

673

674

MyService proxy = (MyService) factory.getProxy();

675

676

// Hot swappable target source

677

HotSwappableTargetSource swappableSource = new HotSwappableTargetSource(new MyServiceImpl());

678

ProxyFactory swappableFactory = new ProxyFactory();

679

swappableFactory.setTargetSource(swappableSource);

680

swappableFactory.addInterface(MyService.class);

681

682

MyService swappableProxy = (MyService) swappableFactory.getProxy();

683

684

// Runtime target replacement

685

Object oldTarget = swappableSource.swap(new EnhancedServiceImpl());

686

// All subsequent calls to swappableProxy will use the new target

687

```

688

689

### Prototype Target Source Configuration

690

691

```java

692

@Configuration

693

public class PrototypeTargetSourceConfig {

694

695

@Bean

696

@Scope("prototype")

697

public ExpensiveService expensiveService() {

698

return new ExpensiveService();

699

}

700

701

@Bean

702

public PrototypeTargetSource expensiveServiceTargetSource() {

703

PrototypeTargetSource targetSource = new PrototypeTargetSource();

704

targetSource.setTargetBeanName("expensiveService");

705

targetSource.setBeanFactory(applicationContext);

706

return targetSource;

707

}

708

709

@Bean

710

public ProxyFactoryBean expensiveServiceProxy() {

711

ProxyFactoryBean proxyFactory = new ProxyFactoryBean();

712

proxyFactory.setTargetSource(expensiveServiceTargetSource());

713

proxyFactory.setInterfaces(ExpensiveService.class);

714

return proxyFactory;

715

}

716

}

717

```

718

719

### Thread-Local Target Source

720

721

```java

722

// Configuration for thread-local target source

723

@Configuration

724

public class ThreadLocalConfig {

725

726

@Bean

727

@Scope("prototype")

728

public StatefulService statefulService() {

729

return new StatefulService();

730

}

731

732

@Bean

733

public ThreadLocalTargetSource statefulServiceTargetSource() {

734

ThreadLocalTargetSource targetSource = new ThreadLocalTargetSource();

735

targetSource.setTargetBeanName("statefulService");

736

targetSource.setBeanFactory(applicationContext);

737

return targetSource;

738

}

739

}

740

741

// Usage - each thread gets its own instance

742

ThreadLocalTargetSource targetSource = new ThreadLocalTargetSource();

743

targetSource.setTargetBeanName("myStatefulBean");

744

targetSource.setBeanFactory(beanFactory);

745

746

ProxyFactory factory = new ProxyFactory();

747

factory.setTargetSource(targetSource);

748

MyStatefulService proxy = (MyStatefulService) factory.getProxy();

749

750

// Each thread will get its own instance

751

ExecutorService executor = Executors.newFixedThreadPool(5);

752

for (int i = 0; i < 10; i++) {

753

executor.submit(() -> {

754

proxy.doSomething(); // Each thread gets its own target instance

755

System.out.println("Thread: " + Thread.currentThread().getName() +

756

", Target: " + System.identityHashCode(proxy.getCurrentState()));

757

});

758

}

759

760

// Check statistics

761

System.out.println("Total invocations: " + targetSource.getInvocationCount());

762

System.out.println("Cache hits: " + targetSource.getHitCount());

763

System.out.println("Objects created: " + targetSource.getObjectCount());

764

```

765

766

### Object Pooling with Commons Pool

767

768

```java

769

@Configuration

770

public class PoolingConfig {

771

772

@Bean

773

@Scope("prototype")

774

public DatabaseConnection databaseConnection() {

775

return new DatabaseConnection();

776

}

777

778

@Bean

779

public CommonsPool2TargetSource databaseConnectionPool() {

780

CommonsPool2TargetSource targetSource = new CommonsPool2TargetSource();

781

targetSource.setTargetBeanName("databaseConnection");

782

targetSource.setBeanFactory(applicationContext);

783

784

// Pool configuration

785

targetSource.setMaxSize(20);

786

targetSource.setMaxIdle(10);

787

targetSource.setMinIdle(2);

788

targetSource.setMaxWait(5000); // 5 seconds max wait

789

targetSource.setTestOnBorrow(true);

790

targetSource.setTestOnReturn(true);

791

targetSource.setTimeBetweenEvictionRunsMillis(30000); // 30 seconds

792

targetSource.setMinEvictableIdleTimeMillis(60000); // 1 minute

793

794

return targetSource;

795

}

796

797

@Bean

798

public ProxyFactoryBean databaseConnectionProxy() {

799

ProxyFactoryBean proxyFactory = new ProxyFactoryBean();

800

proxyFactory.setTargetSource(databaseConnectionPool());

801

proxyFactory.setInterfaces(DatabaseConnection.class);

802

proxyFactory.addAdvice(new PoolMonitoringInterceptor());

803

return proxyFactory;

804

}

805

}

806

807

// Custom monitoring interceptor for pool statistics

808

public class PoolMonitoringInterceptor implements MethodInterceptor {

809

@Override

810

public Object invoke(MethodInvocation invocation) throws Throwable {

811

TargetSource targetSource =

812

((Advised) invocation.getThis()).getTargetSource();

813

814

if (targetSource instanceof PoolingConfig) {

815

PoolingConfig pool = (PoolingConfig) targetSource;

816

System.out.println("Pool stats - Active: " + pool.getActiveCount() +

817

", Idle: " + pool.getIdleCount() +

818

", Max: " + pool.getMaxSize());

819

}

820

821

return invocation.proceed();

822

}

823

}

824

```

825

826

### Custom Refreshable Target Source

827

828

```java

829

public class FileBasedRefreshableTargetSource extends AbstractRefreshableTargetSource {

830

private String configFilePath;

831

private long lastModified = -1;

832

833

public void setConfigFilePath(String configFilePath) {

834

this.configFilePath = configFilePath;

835

}

836

837

@Override

838

protected boolean requiresRefresh(Object cachedTarget, long refreshCheckDelay) {

839

File configFile = new File(configFilePath);

840

if (!configFile.exists()) {

841

return false;

842

}

843

844

long currentModified = configFile.lastModified();

845

if (currentModified != lastModified) {

846

lastModified = currentModified;

847

return true;

848

}

849

850

return false;

851

}

852

853

@Override

854

protected Object freshTarget() {

855

try {

856

// Read configuration and create new target

857

Properties config = new Properties();

858

config.load(new FileInputStream(configFilePath));

859

860

ConfigurableService service = new ConfigurableService();

861

service.configure(config);

862

863

return service;

864

} catch (IOException e) {

865

throw new RuntimeException("Failed to refresh target from config file", e);

866

}

867

}

868

}

869

870

// Usage

871

FileBasedRefreshableTargetSource refreshableSource = new FileBasedRefreshableTargetSource();

872

refreshableSource.setConfigFilePath("/path/to/config.properties");

873

refreshableSource.setTargetBeanName("configurableService");

874

refreshableSource.setBeanFactory(beanFactory);

875

876

ProxyFactory factory = new ProxyFactory();

877

factory.setTargetSource(refreshableSource);

878

factory.addInterface(ConfigurableService.class);

879

880

ConfigurableService proxy = (ConfigurableService) factory.getProxy();

881

882

// Service will automatically reload when config file changes

883

proxy.doWork(); // Uses current configuration

884

885

// Manually refresh if needed

886

refreshableSource.refresh();

887

888

// Check refresh statistics

889

System.out.println("Refresh count: " + refreshableSource.getRefreshCount());

890

System.out.println("Last refresh: " + refreshableSource.getLastRefreshTime());

891

```

892

893

### Complex Target Source with Failover

894

895

```java

896

public class FailoverTargetSource implements TargetSource {

897

private final List<TargetSource> targetSources;

898

private int currentIndex = 0;

899

private final CircuitBreaker circuitBreaker;

900

901

public FailoverTargetSource(List<TargetSource> targetSources) {

902

this.targetSources = targetSources;

903

this.circuitBreaker = new CircuitBreaker(5, Duration.ofMinutes(1));

904

}

905

906

@Override

907

public Class<?> getTargetClass() {

908

return targetSources.get(0).getTargetClass();

909

}

910

911

@Override

912

public boolean isStatic() {

913

return false;

914

}

915

916

@Override

917

public Object getTarget() throws Exception {

918

for (int i = 0; i < targetSources.size(); i++) {

919

int index = (currentIndex + i) % targetSources.size();

920

TargetSource targetSource = targetSources.get(index);

921

922

try {

923

if (circuitBreaker.canExecute(index)) {

924

Object target = targetSource.getTarget();

925

currentIndex = index; // Update to successful source

926

circuitBreaker.recordSuccess(index);

927

return target;

928

}

929

} catch (Exception e) {

930

circuitBreaker.recordFailure(index);

931

// Try next target source

932

}

933

}

934

935

throw new Exception("All target sources failed");

936

}

937

938

@Override

939

public void releaseTarget(Object target) throws Exception {

940

// Release to all sources that might own this target

941

for (TargetSource targetSource : targetSources) {

942

try {

943

targetSource.releaseTarget(target);

944

} catch (Exception e) {

945

// Ignore release failures

946

}

947

}

948

}

949

950

private static class CircuitBreaker {

951

private final Map<Integer, AtomicInteger> failureCounts = new ConcurrentHashMap<>();

952

private final Map<Integer, Long> lastFailureTime = new ConcurrentHashMap<>();

953

private final int threshold;

954

private final Duration timeout;

955

956

public CircuitBreaker(int threshold, Duration timeout) {

957

this.threshold = threshold;

958

this.timeout = timeout;

959

}

960

961

public boolean canExecute(int sourceIndex) {

962

AtomicInteger failures = failureCounts.getOrDefault(sourceIndex, new AtomicInteger(0));

963

Long lastFailure = lastFailureTime.get(sourceIndex);

964

965

if (failures.get() >= threshold && lastFailure != null) {

966

return System.currentTimeMillis() - lastFailure > timeout.toMillis();

967

}

968

969

return true;

970

}

971

972

public void recordSuccess(int sourceIndex) {

973

failureCounts.put(sourceIndex, new AtomicInteger(0));

974

lastFailureTime.remove(sourceIndex);

975

}

976

977

public void recordFailure(int sourceIndex) {

978

failureCounts.computeIfAbsent(sourceIndex, k -> new AtomicInteger(0)).incrementAndGet();

979

lastFailureTime.put(sourceIndex, System.currentTimeMillis());

980

}

981

}

982

}

983

```