Article directory
- 1. Reproducing the error
- 2. Analysis errors
- 3. Solve the problem
-
- 3.1 Solution 1
- 3.2 Solution 2
- 4. Analyze the dynamic proxy of jdk and cglib in spring
-
- 4.1 Dynamic proxy comparison
- 4.2 Differences in principles
- 4.3 Performance differences
- 4.4 Respective limitations
- 4.5 The essential difference between static agents and dynamic agents
1. Reproduction error
Today, when executing the quartz
scheduled task, the following error was reported:
org.quartz.SchedulerException: Job threw an unhandled exception. at org.quartz.core.JobRunShell.run(JobRunShell.java:213) at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.xxx.CollectionTaskServiceImpl' available at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351) at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342) at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1127) at com.xxx.SpringApplicationContext.getBean(SpringApplicationContext.java:19) at com.xxx.quartz.CollectionTaskJob.execute(CollectionTaskJob.java:27) at org.quartz.core.JobRunShell.run(JobRunShell.java:202) ... 1 common frames omitted
org.quartz.SchedulerException: Job threw an unhandled exception.
2. Analysis error
Translate org.quartz.SchedulerException: Job threw an unhandled exception.
into Chinese, that is, org.quartz.SchedulerException: Job threw an unhandled exception.
What is this unhandled exception? We followed the error and found this error: No qualifying bean of type 'com.xxx.CollectionTaskServiceImpl' available
.
Let’s continue to look at the error. The error occurs in the method of SpringApplicationContext.getBean
.
Combined with the No qualifying bean of type 'com.xxx.CollectionTaskServiceImpl' available
error, it can be seen that SpringApplicationContext
cannot get the CollectionTaskServiceImpl
class.
For example, the source code of SpringApplicationContext
:
@Component public class SpringApplicationContext implements ApplicationContextAware {<!-- --> private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {<!-- --> SpringApplicationContext.applicationContext = applicationContext; } public static <T> T getBean(Class<T> requiredType){<!-- --> return applicationContext.getBean(requiredType); } }
SpringApplicationContext
implements the ApplicationContextAware
interface and is annotated by @Component
.
Let’s look further down. The error is in the execute
method of the CollectionTaskJob
class, as shown in the following code:
@Slf4j @DisallowConcurrentExecution public class CollectionTaskJob implements Job {<!-- --> @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {<!-- --> CollectionTaskServiceImpl collectionTaskServiceImpl = SpringApplicationContext.getBean(CollectionTaskServiceImpl.class); //Omit logic code here } }
Let’s look at the CollectionTaskServiceImpl
class again, as shown in the following code:
@Service public class CollectionTaskServiceImpl implements CollectionTaskService {<!-- --> //Omit logic code here }
CollectionTaskServiceImpl
implements the CollectionTaskService
interface and is annotated by @Service
.
Logically speaking, the CollectionTaskServiceImpl
class is injected into the spring
container and can be obtained through SpringApplicationContext
, but the result cannot be obtained.
But why can’t I get it? We need to write a test class, as shown in the following code:
@Component public class Test implements CommandLineRunner, ApplicationContextAware {<!-- --> private ApplicationContext applicationContext; @Override public void run(String... args) throws Exception {<!-- --> Map<String, CollectionTaskServiceImpl> beansOfType = applicationContext.getBeansOfType(CollectionTaskServiceImpl.class); System.out.println(); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {<!-- --> this.applicationContext = applicationContext; } }
The test class Test
implements the CommandLineRunner
and ApplicationContextAware
interfaces. At this time, we run the code:
You will clearly see that the container of beansOfType
is 0
, and it is indeed not obtained.
We modify CollectionTaskServiceImpl
to CollectionTaskService
:
@Override public void run(String... args) throws Exception {<!-- --> Map<String, CollectionTaskService> beansOfType = applicationContext.getBeansOfType(CollectionTaskService.class); System.out.println(); }
Rerun:
At this time, the object of CollectionTaskServiceImpl
is obtained, but pay attention to the red box. It uses the dynamic proxy of jdk aop
.
Then, I modified the CollectionTaskServiceImpl
class and did not implement the CollectionTaskService
interface, as shown in the following code:
@Service public class CollectionTaskServiceImpl {<!-- --> //Omit logic code here }
The run
method is still CollectionTaskServiceImpl
, as shown in the following code:
@Override public void run(String... args) throws Exception {<!-- --> Map<String, CollectionTaskServiceImpl> beansOfType = applicationContext.getBeansOfType(CollectionTaskServiceImpl.class); System.out.println(); }
Rerun the code:
In this way, you can also get the object of CollectionTaskServiceImpl
, but pay attention to the red box. It uses the dynamic proxy of spring cglib
.
After analyzing this point, we can roughly understand that there are two solutions as follows.
3. Solve the problem
3.1 Solution 1
Modify the execute
method of the CollectionTaskJob
class and pass in the CollectionTaskService.class
interface in the SpringApplicationContext.getBean
method, as shown in the following code Shown:
@Slf4j @DisallowConcurrentExecution public class CollectionTaskJob implements Job {<!-- --> @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {<!-- --> CollectionTaskServiceImpl collectionTaskServiceImpl = (CollectionTaskServiceImpl) SpringApplicationContext.getBean(CollectionTaskService.class); //Omit logic code here } }
3.2 Solution 2
Modify the CollectionTaskServiceImpl
class so that it does not implement CollectionTaskService
.
4. Analyze the dynamic proxy of jdk and cglib in spring
4.1 Dynamic proxy comparison
JDK
Dynamic proxy implements the interface implemented by the proxy object, and CGLib
inherits the proxy object.
JDK
and CGLib
both generate bytecode during runtime, and JDK
directly writes Class
bytecode. CGLib
uses the ASM
framework Class
bytecode. Cglib
proxy implementation is more complex and the efficiency of generating proxy classes is less than code>JDKAgent is low.
JDK
calls the proxy method through the reflection mechanism. CGLib
calls the method directly through the FastClass
mechanism. CGLib
executes higher efficiency.
4.2 Principle difference
java
Dynamic proxy uses the reflection mechanism to generate an anonymous class that implements the proxy interface, and calls InvokeHandler
to process it before calling specific methods. The core is to implement the InvocationHandler
interface, use the invoke()
method to perform aspect-oriented processing, and call corresponding notifications.
The cglib
dynamic proxy uses the asm
open source package to load the class
file of the proxy object class and generate subclasses by modifying its bytecode. to handle.
The core is to implement the MethodInterceptor
interface, use the intercept()
method to perform aspect-oriented processing, and call corresponding notifications.
- If the target object implements the interface, the dynamic proxy implementation of
JDK
will be used by default to implementAOP
- If the target object implements the interface, you can force the use of
CGLIB
to implementAOP
- If the target object does not implement the interface, the
CGLIB
library must be used.spring
will automatically useJDK
dynamic proxy andCGLIB
Convert between
4.3 Performance differences
-
The bottom layer of
CGLib
adopts theASM
bytecode generation framework and uses bytecode technology to generate proxy classes. Beforejdk6
, it was better than usingJava
Reflection efficiency should be high. The only thing to note is thatCGLib
cannot proxy methods declared asfinal
, because the principle ofCGLib
is to dynamically generate subclasses of the proxy class . -
After
jdk6, jdk7, jdk8
gradually optimized theJDK
dynamic proxy, when the number of calls was small, theJDK
proxy efficiency was higher thanCGLIB
proxy efficiency, only when a large number of calls are made,jdk6
andjdk7
are slightly less efficient thanCGLIB
proxy, but By the time ofjdk8
, thejdk
proxy is more efficient than theCGLIB
proxy.
4.4 Respective limitations
-
JDK
‘s dynamic proxy mechanism can only proxy classes that implement interfaces, and classes that cannot implement interfaces cannot implementJDK
‘s dynamic proxy. -
cglib
implements proxies for classes. Its principle is to generate a subclass for the specified target class and overwrite its methods to achieve enhancement. However, because inheritance is used,cannot be Classes modified with final
are proxied.
Type | Mechanism | Callback method | Applicable scenarios | Efficiency |
JDK dynamic proxy | Delegation mechanism, the proxy class and the target class implement the same interface, the InvocationHandler holds the target class, and the proxy class entrusts the InvocationHandler to call the target class Original method | Reflection | The target class is an interface class | The efficiency bottleneck is that the reflection call is slightly slower |
CGLIB dynamic proxy | Inheritance mechanism, the proxy class inherits the target class and rewrites the target method, and calls the parent class method through the callback function MethodInterceptor to execute the original logic | Called through the FastClass method index | Non-interface class, non-final class, non-final method | The first call requires generating multiple Class objects, which is slower than the JDK method. Multiple calls are faster than reflection because there are method indexes. If there are too many methods and too many switch cases, the efficiency needs to be tested |
4.5 The essential difference between static proxy and dynamic proxy
-
Static proxies can only complete proxy operations manually. If the proxied class adds new methods, the proxy class needs to be added simultaneously, which violates the opening and closing principle.
-
Dynamic proxy uses the method of dynamically generating code at runtime, cancels the expansion restrictions on the proxy class, and follows the opening and closing principle.
-
If the dynamic agent wants to enhance the logical extension of the target class, combined with the strategy mode, it can be completed by adding a new strategy class without modifying the code of the agent class.