问题描述

项目中采用了Quartz来执行调度任务,但在 Quartz自动调用 Job任务时,发现在JobBean中使用@Autowired进行依赖注入会产生null指针错误
经查阅资料得知,大致原因为Job由Quartz实例化创建,因而在Job中使用依赖注入时无法找到Spring的Bean
开发所使用的版本号

SpringBoot:2.1.5.RELEASE
quartz:2.3.0

解决方案

经过我的研究,无论是好坏的方法共有下面几种,都已经过测试。【这里我推荐第一种,更加符合Spring的要求】

一、使用SpringBeanJobFactory,将Job对象与Spring对象关联

先创建一个Factory,该方法继承SpringBeanJobFactory并实现ApplicationContextAware
通过autowireBean方法将Quartz实例化后的Job添加到Spring的Bean中

public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements
        ApplicationContextAware {
    private transient AutowireCapableBeanFactory beanFactory;

    @Override
    public void setApplicationContext(final ApplicationContext context) {
        beanFactory = context.getAutowireCapableBeanFactory();
    }
	
    @Override
    protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
        final Object job = super.createJobInstance(bundle);
        beanFactory.autowireBean(job);
        return job;
    }
}

然后,将其附加到ScheduleConfig

 	@Bean
    public SchedulerFactoryBean schedulerFactoryBean() {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
        jobFactory.setApplicationContext(SpringContextUtils.applicationContext);
        factory.setJobFactory(jobFactory);
        
        ...........
    }

其中SpringContextUtils为自定义的Spring Context 工具类

@Component
public class SpringContextUtils implements ApplicationContextAware {
    public static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContextUtils.applicationContext = applicationContext;
    }
}

推荐优先使用该方式


二、在Servlet中调用SpringBeanAutowiringSupport

使用当前方法需要Spring-Web版本大于 2.5.1
将其作为Job的第一行即可

public class BatchJob extends Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
        
        ...........
    }
}

该方式虽可行,但不利于多个Job的情况


三、直接使用工具类获取Bean

改造第一种方法中的工具类如下,增加获取bean的方法

@Component
public class SpringContextUtils implements ApplicationContextAware {
    public static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContextUtils.applicationContext = applicationContext;
    }

    /**
     * 获取Bean
     * @param name bean名称
     */
    public static Object getBean(String name) {
        return applicationContext.getBean(name);
    }
}

之后在需要使用Bean的地方直接使用工具类获取。例如

public class BatchJob extends Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
        // 需要获取的Bean
        JobService jobService = (JobService) SpringContextUtils.getBean("jobService");
        ...........
    }
}

当前方式简单但与第二个方法大同小异,都不利于多种任务的情况

总结

严格来说还有其他的方式,比如继承QuartzJobBean,但实际验证后并无作用,因此目前就列举以上三种,各有优缺点,可根据需求自行选择。
另外作者才疏学浅,若有疏漏或不对的地方敬请谅解!
转载请注明出处!!!

参考

stackoverflow
SpringBeanAutowiringSupport介绍

Q.E.D.

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议

You got to put the past behind you before you can move on.