Master Spring Boot TargetSource: Custom, HotSwappable, Pooled & More
This article explains Spring Boot's TargetSource mechanism, covering the default SingletonTargetSource and demonstrating how to implement custom, hot‑swappable, pooled, prototype, and thread‑local TargetSources with full code examples and execution results, helping developers manage bean instances dynamically in AOP proxies.
TargetSource is a core interface in Spring AOP that provides the "target object" for a join point. Each method invocation on an AOP proxy obtains a target instance via a TargetSource.
By default Spring AOP uses SingletonTargetSource , which returns the same instance every time.
1. Custom TargetSource
Enable AOP proxying:
<code>// Enable AOP proxying
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {}
@Aspect
public class LogAspect {
// TODO
}</code>Define a custom TargetSource:
<code>@Configuration
public class AppConfig {
@Bean
public AnnotationAwareAspectJAutoProxyCreator annotationAwareAspectJAutoProxyCreator(BeanFactory beanFactory) {
AnnotationAwareAspectJAutoProxyCreator proxyCreator = new AnnotationAwareAspectJAutoProxyCreator();
proxyCreator.setCustomTargetSourceCreators(new TargetSourceCreator() {
@Override
public TargetSource getTargetSource(Class<?> beanClass, String beanName) {
return new TargetSource() {
public Class<?> getTargetClass() {
return beanClass; // target type
}
public Object getTarget() throws Exception {
// instantiate using default constructor
return beanClass.getConstructors()[0].newInstance();
}
};
}
});
return proxyCreator;
}
}</code>Test with a simple service:
<code>@Component
public class PersonService {
public PersonService() {
System.out.println("init...");
}
public void save() {
System.out.println("save method invoke..." + this);
}
}
// Test
@Resource
private PersonService ps;
@Test
public void testSave() {
this.ps.save();
this.ps.save();
this.ps.save();
}</code>Result (each call uses a different instance):
2. HotSwappableTargetSource
Allows swapping the target object at runtime while keeping the proxy reference.
<code>@Configuration
public class AppConfig {
@Bean
public HotSwappableTargetSource hotSwappableTargetSource() {
return new HotSwappableTargetSource(new PersonService());
}
@Bean
public PersonService personService(HotSwappableTargetSource targetSource) {
ProxyFactory factory = new ProxyFactory();
factory.setTargetSource(targetSource);
return (PersonService) factory.getProxy();
}
}</code>Test:
<code>@Resource
private HotSwappableTargetSource ts;
@Resource
private PersonService ps;
@Test
public void testSave() {
this.ps.save();
// swap to a new instance
this.ts.swap(new PersonService());
this.ps.save();
}</code>Result image:
3. Pooled TargetSource (CommonsPool2TargetSource)
Requires the common-pool2 library.
<code>@Component
@Scope("prototype")
public class PersonService {
public PersonService() {
System.out.println("init...");
}
public void save() {
System.out.println("save method invoke..." + this);
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) {}
}
}
@Configuration
public class AppConfig {
@Bean
CommonsPool2TargetSource poolTargetSource() {
CommonsPool2TargetSource targetSource = new CommonsPool2TargetSource();
targetSource.setTargetBeanName("personService");
targetSource.setMaxSize(2);
return targetSource;
}
@Bean
@Primary
public PersonService ppp(CommonsPool2TargetSource targetSource) {
ProxyFactory factory = new ProxyFactory();
factory.setTargetSource(targetSource);
return (PersonService) factory.getProxy();
}
}</code>Test:
<code>@Resource
private PersonService ps;
@Test
public void testSave() {
for (int i = 0; i < 5; i++) {
new Thread(() -> { ps.save(); }).start();
}
}</code>Result image (pool size limited to 2):
4. PrototypeTargetSource
<code>@Bean
public PrototypeTargetSource prototypeTargetSource() {
PrototypeTargetSource targetSource = new PrototypeTargetSource();
targetSource.setTargetBeanName("personService");
return targetSource;
}</code>Test:
<code>@Resource
private PersonService ps;
@Test
public void testSave() {
ps.save();
ps.save();
ps.save();
}</code>Result image (different instance each call):
5. ThreadLocalTargetSource
<code>@Bean
public ThreadLocalTargetSource prototypeTargetSource() {
ThreadLocalTargetSource targetSource = new ThreadLocalTargetSource();
targetSource.setTargetBeanName("personService");
return targetSource;
}</code>Test:
<code>@Resource
private PersonService ps;
@Test
public void testSave() {
for (int i = 0; i < 5; i++) {
new Thread(() -> { ps.save(); }).start();
}
}</code>Result image (each thread gets its own instance):
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.