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
```