Quartz集成Spring时出现未处理NullPointerException问题排查
嘿,我一眼就看出问题所在了——你的getDataServiceQuartz之所以为null,是因为Quartz默认会自己创建SampleJob的实例,完全不经过Spring的Bean生命周期管理,所以你用@Autowired或者构造器注入的依赖根本没法被Spring自动装配,调用的时候自然就空指针了!
下面给你几种可行的修复方案,按推荐程度排序:
Spring专门提供了Quartz的集成支持,能让Quartz直接使用Spring容器里的Bean,完全不用自己折腾实例化的问题。你需要做这些修改:
1. 调整你的Job类
不用再实现Quartz的Job接口了,改成普通的Spring Bean就行,把业务逻辑放在一个普通方法里:
@Profile("quartz") @Component public class SampleJob { private final GetDataServiceQuartz getDataServiceQuartz; // 用构造器注入(这是Spring推荐的方式,从根源避免空指针) public SampleJob(GetDataServiceQuartz getDataServiceQuartz) { this.getDataServiceQuartz = getDataServiceQuartz; } // 把原来的execute逻辑移到这个普通方法里 public void runJob() { this.getDataServiceQuartz.storeData(); } }
2. 重构Quartz配置类
使用Spring提供的JobDetailFactoryBean、SimpleTriggerFactoryBean和SchedulerFactoryBean来整合两者:
@Configuration @Profile("quartz") public class QuartzConfig { @Autowired private SampleJob sampleJob; // 定义Job详情,指定要调用的Bean和方法 @Bean public MethodInvokingJobDetailFactoryBean sampleJobDetail() { MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean(); jobDetail.setTargetObject(sampleJob); jobDetail.setTargetMethod("runJob"); jobDetail.setConcurrent(false); // 避免同一Job并发执行 return jobDetail; } // 定义触发器,设置执行间隔 @Bean public SimpleTriggerFactoryBean sampleJobTrigger(JobDetail sampleJobDetail) { SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean(); trigger.setJobDetail(sampleJobDetail); trigger.setRepeatInterval(5000); // 每5秒执行一次 trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY); // 无限重复 return trigger; } // 配置Scheduler,让Spring管理Quartz调度器 @Bean public SchedulerFactoryBean schedulerFactory(SimpleTrigger sampleJobTrigger) { SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean(); schedulerFactory.setTriggers(sampleJobTrigger); return schedulerFactory; } }
这样配置后,Quartz会直接调用Spring管理的SampleJob实例的runJob方法,依赖注入自然就生效了,完全不用管实例化的问题。
如果你不想改动太多现有代码,可以通过静态持有Spring上下文的方式,在Job里手动获取Bean:
1. 先写一个Spring上下文持有类
@Component public class SpringContextHolder implements ApplicationContextAware { private static ApplicationContext context; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { context = applicationContext; } // 提供静态方法获取Bean public static <T> T getBean(Class<T> beanClass) { return context.getBean(beanClass); } }
2. 修改SampleJob的execute方法
@Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { // 手动从Spring上下文获取Bean GetDataServiceQuartz dataService = SpringContextHolder.getBean(GetDataServiceQuartz.class); dataService.storeData(); }
这种方式能快速解决问题,但不够优雅——它引入了对Spring上下文的静态依赖,不利于单元测试和代码维护,所以只建议作为临时方案。
如果你想保留SampleJob实现QuartzJob接口的结构,可以自定义一个JobFactory,让Quartz用Spring的BeanFactory来创建Job实例:
1. 自定义JobFactory
@Component public class SpringQuartzJobFactory extends AdaptableJobFactory { @Autowired private AutowireCapableBeanFactory beanFactory; @Override protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception { // 先让Quartz创建实例,再交给Spring完成依赖注入 Object jobInstance = super.createJobInstance(bundle); beanFactory.autowireBean(jobInstance); return jobInstance; } }
2. 修改你的调度启动代码
@Autowired private SpringQuartzJobFactory springQuartzJobFactory; public void startQuartzScheduling () { try { Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); // 给Quartz设置自定义的JobFactory,让Spring负责注入依赖 scheduler.setJobFactory(springQuartzJobFactory); scheduler.start(); JobDetail job = JobBuilder.newJob(SampleJob.class) .withIdentity("dummyJobName", "group1").build(); Trigger trigger = TriggerBuilder .newTrigger() .withIdentity("dummyTriggerName", "group1") .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(5).repeatForever()) .build(); scheduler.scheduleJob(job, trigger); } catch (SchedulerException e){ log.info("Quartz调度启动异常: {}", e.getMessage()); } }
这种方式既保留了原有Job的结构,又让Spring完成依赖注入,也算一种不错的折中方案。
最推荐的是方案一,完全利用Spring的Quartz集成能力,让Job成为普通的Spring Bean,既避免了和Quartz的强耦合,又能完美支持依赖注入,代码也更简洁易维护。
内容的提问来源于stack exchange,提问作者Maciaz

