• 解决org.quartz.SchedulerException: Job threw an unhandled exception.


    1. 复现错误


    今天在执行quartz定时任务时,报出如下错误:

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    org.quartz.SchedulerException: Job threw an unhandled exception.

    2. 分析错误


    org.quartz.SchedulerException: Job threw an unhandled exception.翻译成中文,即org.quartz.SchedulerException:作业抛出了一个未经处理的异常。

    这个未经处理的异常是什么?我们随着错误往下看,发现这个错误: No qualifying bean of type 'com.xxx.CollectionTaskServiceImpl' available

    我们继续看错误,错误发生在SpringApplicationContext.getBean的方法中。

    结合No qualifying bean of type 'com.xxx.CollectionTaskServiceImpl' available错误可知,SpringApplicationContext拿不到CollectionTaskServiceImpl这个类。

    如是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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    SpringApplicationContext实现了 ApplicationContextAware 接口,并由@Component注解。

    我们再去往下看,错误在CollectionTaskJob类的execute方法中,如下代码:

    @Slf4j
    @DisallowConcurrentExecution
    public class CollectionTaskJob implements Job {
    
        @Override
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
            CollectionTaskServiceImpl collectionTaskServiceImpl = SpringApplicationContext.getBean(CollectionTaskServiceImpl.class);
            //此处省略逻辑代码
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    我们再去看CollectionTaskServiceImpl类,如下代码所示:

    @Service
    public class CollectionTaskServiceImpl implements CollectionTaskService {
    	//此处省略逻辑代码
    }
    
    • 1
    • 2
    • 3
    • 4

    CollectionTaskServiceImpl实现了CollectionTaskService接口,并由@Service注解。

    按道理说,CollectionTaskServiceImpl类注入到spring容器中,通过SpringApplicationContext能够拿得到,但结果是拿不到的。

    但为什么拿不到呢?我们需要写个测试类,如下代码所示:

    @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;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    测试类Test实现了CommandLineRunnerApplicationContextAware接口,此时,我们运行代码:

    在这里插入图片描述

    你会清楚的看到,beansOfType的容器为0,确实没有拿到。

    我们将CollectionTaskServiceImpl修改为CollectionTaskService

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

    重新运行:

    在这里插入图片描述

    此时,拿到了CollectionTaskServiceImpl的对象,但注意红框处,它采用的是jdk aop 的动态代理。

    然后,我修改CollectionTaskServiceImpl类,不实现CollectionTaskService接口,如下代码所示:

    @Service
    public class CollectionTaskServiceImpl {
    	//此处省略逻辑代码
    }
    
    • 1
    • 2
    • 3
    • 4

    run方法依然是CollectionTaskServiceImpl,如下代码所示:

    @Override
      public void run(String... args) throws Exception {
        Map<String, CollectionTaskServiceImpl> beansOfType =
            applicationContext.getBeansOfType(CollectionTaskServiceImpl.class);
        System.out.println();
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    重新运行代码:

    在这里插入图片描述

    如此,也能拿到了CollectionTaskServiceImpl的对象,但注意红框处,它采用的是spring cglib的动态代理。

    分析到这里大体就明白了,可以有如下两种解决方法。

    3. 解决问题

    3.1 解决方法一


    修改CollectionTaskJob类的execute方法,在SpringApplicationContext.getBean方法中传入CollectionTaskService.class接口,如下代码所示:

    @Slf4j
    @DisallowConcurrentExecution
    public class CollectionTaskJob implements Job {
    
        @Override
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
            CollectionTaskServiceImpl collectionTaskServiceImpl = (CollectionTaskServiceImpl) SpringApplicationContext.getBean(CollectionTaskService.class);
            //此处省略逻辑代码
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.2 解决方法二


    修改CollectionTaskServiceImpl类,不实现CollectionTaskService即可。

    4. 分析spring中的jdk和cglib的动态代理

    4.1 动态代理对比


    JDK动态代理是实现了被代理对象所实现的接口,CGLib是继承了被代理对象。

    JDKCGLib都是在运行期生成字节码,JDK是直接写Class字节码。CGLib使用ASM框架Class字节码,Cglib代理实现更复杂,生成代理类的效率比JDK代理低。

    JDK调用代理方法,是通过反射机制调用,CGLib是通过FastClass机制直接调用方法,CGLib执行效率更高。

    4.2 原理区别


    java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。核心是实现InvocationHandler接口,使用invoke()方法进行面向切面的处理,调用相应的通知。

    cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

    核心是实现MethodInterceptor接口,使用intercept()方法进行面向切面的处理,调用相应的通知。

    1. 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
    2. 如果目标对象实现了接口,可以强制使用CGLIB实现AOP
    3. 如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换

    4.3 性能区别

    1. CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。

    2. jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6jdk7CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理。

    4.4 各自局限

    1. JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理。

    2. cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。

    类型机制回调方式适用场景效率
    JDK动态代理委托机制,代理类和目标类都实现了同样的接口,InvocationHandler持有目标类,代理类委托InvocationHandler去调用目标类的原始方法反射目标类是接口类效率瓶颈在反射调用稍慢
    CGLIB动态代理继承机制,代理类继承了目标类并重写了目标方法,通过回调函数MethodInterceptor调用父类方法执行原始逻辑通过FastClass方法索引调用非接口类、非final类,非final方法第一次调用因为要生成多个Class对象,比JDK方式慢。多次调用因为有方法索引比反射快,如果方法过多,switch case过多其效率还需测试

    4.5 静态代理和动态的本质区别

    1. 静态代理只能通过手动完成代理操作,如果被代理类增加新的方法,代理类需要同步新增,违背开闭原则。

    2. 动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则。

    3. 若动态代理要对目标类的增强逻辑扩展,结合策略模式,只需要新增策略类便可完成,无需修改代理类的代码。

  • 相关阅读:
    SQL动态分区、用户管理以及流程控制
    Linux环境基础开发工具使用
    配置管理新纪元:Eureka引领分布式服务配置潮流
    SQLAlchemy使用教程
    WCDMA 无线网络规划
    【从零开始的Java开发】2-10-1 XML入门:XML文档语法规则、DTD与XML Schema、Dom4j与XPath表达式
    计网学习笔记二 Link Layer Service
    国庆作业6
    Openssl X509 v3 AuthorityKeyIdentifier实验与逻辑分析
    音视频常见问题(七):首开慢
  • 原文地址:https://blog.csdn.net/lvoelife/article/details/134029039