Part2 — Problem and Solution when using Async(proxyTargetClass) in Spring Boot
Hello everyone
When you use asynchronous in spring boot project, sometimes you will face some problems. I told in this article before.
In this article, you get below error when using asynchronous in spring boot app.
could not be injected as a 'com.xxx.service.xxxService'
because it is a JDK dynamic proxy that implements:
Action:
Consider injecting the bean as one of its interfaces
or forcing the use of CGLib-based proxies
by setting proxyTargetClass=true on @EnableAsync
If you search on Google, developers said: “ you fix it with below configuration”
@EnableAsync(proxyTargetClass=true)
OK.
What is this error?
Why add proxyTargetClass definitation in our configuration class?
First of all, you have to understand Spring Proxy Mechanism with JDK Proxy and CGLIB Proxy
Every Spring bean (heretofore referred to as just a “bean”) can be considered a managed component.You define this beans in XML or annotation based ( @Component,@Service,@Repository annotation). Spring scan this beans via @ComponentScan and that classes are instantiated and managed as a singleton bean by the Spring framework. (It’s technically possible to use different scoping mechanisms to avoid singletons but ignore that topic in this article). After that, Spring injects references into beans via @Autowired (@Resource or @Inject annotation ) Now I said as a general concept in Spring (In Spring 3, not need to use @Autowired annotation. You can use references to injection in construction of class)
However, Spring doesn’t actually provide a literal reference to the original bean when it’s injected. Instead of that, Spring provides this reference as a wrapped proxy object.
Spring make it 2 ways :
- JDK Dynamic Proxy (default)
By default, Spring use JDK Dynamic Proxy libraries to create a new instance of the injected bean’s interface which will act as a delegate to that bean.
- CGLIB Proxy
If CGLIB is available on the classpath and there is no interface to implement a proxy with,CGLIB will be used to make a new object that extends the target bean’s class and acts as a delegate to that original bean.
By default in Spring,
If bean has an interface (bean implement interface), Spring uses JDK Dynamic Proxy, otherwise uses CGLIB.
For example (JDK Proxy) :
@Service
public class ServiceA implements ServiceAInterface{
// methods
}
@Service
public class ServiceB {
@Autowired
private ServiceAInterface serviceA; //interface injection
}
For example (CGLIB Proxy) :
@Service
public class ServiceA {
// methods
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA; //original bean injection
}
OK.
Problem scenario:
Let’s use asynchronous in our project. Let’s code configuration class.
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name="taskAsyncExecutor")
public TaskExecutor taskWorkExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setThreadNamePrefix("Async-");
threadPoolTaskExecutor.setCorePoolSize(5);
threadPoolTaskExecutor.setMaxPoolSize(10);
threadPoolTaskExecutor.setKeepAliveSeconds(60);
threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
threadPoolTaskExecutor.setQueueCapacity(1000);
threadPoolTaskExecutor.initialize();
return threadPoolTaskExecutor;
}
}
Our classes and interface :
public interface ServiceAInterface {
CompletableFuture<Void> methodA();
}
//....
@Service
public class ServiceA implements ServiceAInterface{
@Override
@Async("taskAsyncExecutor")
public CompletableFuture<Void> methodA() {
//process
return CompletableFuture.completedFuture(null);
}
@Async("taskAsyncExecutor")
public CompletableFuture<Void> methodX()
{
//process
return CompletableFuture.completedFuture(null);
}
}
//....
@Service
public class ServiceB {
private final ServiceA serviceA;
@Autowired
public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
public void methodB()
{
serviceA.methodX();
}
}
If we run project ;
"The bean 'serviceA' could not be injected because it is a JDK dynamic proxy"
get error.
Problems :
Spring AOP uses either JDK dynamic proxies or CGLIB to create the proxy for a given target object. @Transactional, @Async tell Spring that it needs to create proxy of ServiceA at runtime, as default Spring uses JDK dynamic proxies.
But the problem is at runtime there is not bean of type ServiceA.
There is only a bean of type ServiceAInterface.
Solution :
So you have to inject ServiceAInterface or tell Spring to use CGLIB proxy via proxyTargetClass = true
Fix it :
@Configuration
@EnableAsync(proxyTargetClass = true)
public class AsyncConfig {
}
Note : In general, if possible, inject bean by interface, not by class.
I hope, it was useful for you.
Thanx to reading