Thoughts on spring jdk and cglib dynamic proxy caused by Job threw an unhandled exception and No qualifying bean of type ‘x’ available

1. Reproducing the error


When executing the quartz scheduled task today, 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

for and dynamic proxies caused by Job threw an unhandled exception and No qualifying bean of type 'x' available think. spring jdkcglib

2. Analysis errors


Although the error message is Job threw an 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 SpringApplicationContext.getBean.

Combined withNo qualifying bean of type 'com.xxx.CollectionTaskServiceImpl' available errors, it can be seen that SpringApplicationContext cannot get CollectionTaskServiceImpl this class.

How are youSpringApplicationContextSource:

@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);
    }
}

SpringApplicationContextAccomplished ApplicationContextAware Close, 并由@ComponentCommentary.

Let’s look down. The error is in the method of the CollectionTaskJob class, as shown in the following code: execute

@Slf4j
@DisallowConcurrentExecution
public class CollectionTaskJob implements Job {
    
    

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
    
    
        CollectionTaskServiceImpl collectionTaskServiceImpl = SpringApplicationContext.getBean(CollectionTaskServiceImpl.class);
        //此处省略逻辑代码
   }
}

Let’s look at theCollectionTaskServiceImpl class again, as shown in the following code:

@Service
public class CollectionTaskServiceImpl implements CollectionTaskService {
    
    
	//此处省略逻辑代码
}

CollectionTaskServiceImplAccomplishedCollectionTaskServiceClose, 并由@ServiceCommentary.

Logically speaking, the CollectionTaskServiceImpl class is injected into the spring container and can be obtained through SpringApplicationContext, but the result is Unreachable.

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 classTest implements the CommandLineRunner and ApplicationContextAware interfaces. At this time, we run the code:

Insert image description here

You will clearly see that the container of beansOfType is 0, and it is indeed not obtained.

My general CollectionTaskServiceImplRepairs CollectionTaskService

 @Override
  public void run(String... args) throws Exception {
    
    
    Map<String, CollectionTaskService> beansOfType =
        applicationContext.getBeansOfType(CollectionTaskService.class);
    System.out.println();
  }

Rerun:

Insert image description here

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 theCollectionTaskServiceImpl class and did not implement theCollectionTaskService interface, as shown in the following code:

@Service
public class CollectionTaskServiceImpl {
    
    
	//此处省略逻辑代码
}

Andrun method is stillCollectionTaskServiceImpl, 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:

Insert image description here

In this way, you can also get the object of CollectionTaskServiceImpl, but pay attention to the red box, which 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 method of the CollectionTaskJob class and pass in method >Interface, as shown in the following code:executeSpringApplicationContext.getBeanCollectionTaskService.class

@Slf4j
@DisallowConcurrentExecution
public class CollectionTaskJob implements Job {
    
    

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
    
    
        CollectionTaskServiceImpl collectionTaskServiceImpl = (CollectionTaskServiceImpl) SpringApplicationContext.getBean(CollectionTaskService.class);
        //此处省略逻辑代码
   }
}

3.2 Solution 2


RepairCollectionTaskServiceImplclass, unrealisticCollectionTaskServiceAvailable now.

4. Analyze the dynamic proxy of jdk and cglib in spring

4.1 Dynamic proxy comparison


JDKA dynamic proxy implements the interface implemented by the proxy object, and CGLib inherits the proxy object.

JDK and CGLib both generate bytecode at runtime, and JDK writes Class bytecode directly. CGLibUsing ASM framework Class bytecode, Cglib the proxy implementation is more complex and generates proxy classes is less efficient than the JDK agent.

JDK calls the proxy method through the reflection mechanism. CGLib calls the method directly through the FastClass mechanism. CGLib executes higher efficiency.

4.2 Differences in principles


javaDynamic proxy uses the reflection mechanism to generate an anonymous class that implements the proxy interface, and callsInvokeHandler before calling the specific method for processing. The core is to implement the InvocationHandler interface, use the invoke() method to perform aspect-oriented processing, and call the corresponding notifications.

Andcglib the dynamic proxy uses the asm open source package to load the class file of the proxy object class through Modify its bytecode to generate subclasses to handle it.

The core of is to implement the MethodInterceptor interface, use the intercept() method for aspect-oriented processing, and call the corresponding notifications.

  1. If the target object implements the interface, the dynamic proxy implementation of JDK will be used by defaultAOP
  2. If the target object implements the interface, it can be forced to useCGLIBimplementationAOP
  3. If the target object does not implement the interface, the CGLIB library must be used, spring will automatically use the JDK dynamic proxy and CGLIBConvert between

4.3 Performance differences

  1. CGLibThe bottom layer adoptsASM bytecode generation framework, using bytecode technology to generate proxy classes, which is better than using jdk6 =3>The reflection efficiency should be high. The only thing to note is that cannot proxy a method declared as , because the principle is to dynamically generate the proxy class subcategory. JavaCGLibfinalCGLib

  2. After jdk6、jdk7、jdk8 gradually optimizing the JDK dynamic proxy, when the number of calls is small, the JDK proxy Efficiency is higher than CGLIB proxy efficiency, only when a large number of calls are made, jdk6 and jdk7 are better than CGLIBThe agent efficiency is a little lower, but by the time jdk8, the jdk agent efficiency is higher than the CGLIB agent.

4.4 Respective limitations

  1. JDKThe dynamic proxy mechanism of can only proxy classes that implement the interface, and classes that cannot implement the interface cannot implement the dynamic proxy of JDK.

  2. cglib implements proxy 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, it cannot be used for finalThe modified class is proxied.

type mechanism callback method Applicable scene efficiency
JDK dynamic proxy Delegation mechanism, both the proxy class and the target class implement the same interface. InvocationHandler holds the target class, and the proxy class entrusts InvocationHandler to call the original method of the target class. reflection The target class is the 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 via 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. Calling multiple times is 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 agents and dynamic agents

  1. 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.

  2. 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.

  3. 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.

Guess you like

Origin blog.csdn.net/lvoelife/article/details/134029959