解决Spring boot集成quartz时service注入失败为null的问题
一、报错信息
java
java.lang.NullPointerException: null
at farbun.server.scheduledTask.ScheduledTasks.execute(ScheduledTasks.java:41) ~[classes/:na]
二、代码
任务类源代码
java
package farbun.server.scheduledTask;
import farbun.server.service.CustomerSerice;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* @Author: 小新
* @Date: 2024/12/15 10:18
*/
@Slf4j
@Component
public class ScheduledTasks implements Job {
@Autowired
private CustomerSerice customerSerice; // 这里customerSerice为null
/**
* 触发任务
* 查询
* 添加
* @param context
* @throws JobExecutionException
*/
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 获取当前日期时间
LocalDateTime currentDateTime = LocalDateTime.now();
// 计算前一天的日期时间
LocalDateTime previousDateTime = currentDateTime.minusDays(1);
// 定义日期时间格式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 格式化前一天的日期时间为字符串
String previousDateTimeStr = previousDateTime.format(formatter);
log.info(previousDateTimeStr);
// 调用方法
log.info("customerSearcher方法开始执行了。。。。");
customerSerice.customerSearcher(previousDateTimeStr);
log.info("customerSearcher方法执行完了===================");
log.info("selectAndAdd方法开始执行了。。。。");
customerSerice.selectAndAdd();
log.info("selectAndAdd方法执行完了========================");
}
}
配置类原代码
java
package farbun.server.config;
import farbun.server.scheduledTask.ScheduledTasks;
import farbun.server.service.CustomerSerice;
import org.quartz.JobDetail;
import org.quartz.Trigger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
/**
* @Author: 小新
* @Date: 2023/8/15 10:28
*/
@Configuration
public class QuartzConfig {
/**
* 创建定时任务
* @return
*/
@Bean
public JobDetailFactoryBean jobDetailFactoryBean() {
JobDetailFactoryBean factory = new JobDetailFactoryBean();
factory.setJobClass(ScheduledTasks.class); // 指定作业类
factory.setDurability(true);
return factory;
}
/**
* 创建触发器
* @param jobDetail
* @return
*/
@Bean
public CronTriggerFactoryBean cronTriggerFactoryBean(JobDetail jobDetail) {
CronTriggerFactoryBean factory = new CronTriggerFactoryBean();
factory.setJobDetail(jobDetail);
factory.setCronExpression("0 0 0 * * ?"); // 每天凌晨0点触发
return factory;
}
/**
* 注册调度器
* @param trigger
* @return
*/
@Bean
public SchedulerFactoryBean schedulerFactoryBean(Trigger trigger) {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setTriggers(trigger);
return factory;
}
}
三、注入失败原因
- 出现这个问题的根本原因,还得从spring的IOC和AOP的基本原理开始讲起
- 在IOC中,是spring先把需要放置到IOC容器的对象放入,然后在通过IOC机制去请求获得该对象的时候,然后调用出来IOC容器中准备好的对象。
- 具体在springboot中的表现,如果你在一个类中增加了Component的注解,或者在一个Configure中增加了Bean的注解,IOC就会明白你想要把该对象放入到容器,然后在需要容器帮你实例化的地方加入Autoware,容器就会把该对象给注入。
- 需要注意的地方是,容器只能对容器注入的对象内部的属性注入,如果你通过自己的代码new了一个对象,这对象里面的Autoware的属性是不起作用的。
- 很好理解,因为你的对象不在容器的管理范围,容器就无法去注入。
- 而在quartz的job对象,是通过直接传入job类的class,由quartz框架去实例化的,而非通过spring框架去实例化的,自然就无法完成注解
java
JobDetailFactoryBean factory = new JobDetailFactoryBean();
factory.setJobClass(ScheduledTasks.class); // 把ScheduledTasks.class传入了JobDetailFactoryBean
factory.setDurability(true);
四、解决的思路1
- 在job中通过Autoware注解去实现,是不太可能了。
- 而JobDetail 可以通过jobDataMap的属性来传递对象,我们可以在需要spring注入的地方,把我们要注入的对象放到jobDataMap中去,然后在job中取出来使用,来绕道完成注解。
1、任务类修改
java
public void execute(JobExecutionContext context) throws JobExecutionException {
...
CustomerSerice customerSerice = (CustomerSerice) context.getJobDetail().getJobDataMap().get("customerSerice");
// 调用方法
log.info("customerSearcher方法开始执行了。。。。");
customerSerice.customerSearcher(previousDateTimeStr);
log.info("customerSearcher方法执行完了===================");
log.info("selectAndAdd方法开始执行了。。。。");
customerSerice.selectAndAdd();
log.info("selectAndAdd方法执行完了========================");
}
2、配置类修改
java
/**
* 创建触发器
* @param jobDetail
* @return
*/
@Autowired
private CustomerSerice customerSerice;
@Bean
public CronTriggerFactoryBean cronTriggerFactoryBean(JobDetail jobDetail) {
// 调度器
CronTriggerFactoryBean factory = new CronTriggerFactoryBean();
//在调用调度器的地方去实现注入
jobDetail.getJobDataMap().put("customerSerice",customerSerice);
//
factory.setJobDetail(jobDetail);
factory.setCronExpression("0 0 0 * * ?"); // 每天凌晨0点触发
return factory;
}
- 经过测试,我们已经能解决了在job中无法注入的问题。但是也有一些缺点,比如我们要再数据库中保存很多的任务,而每个任务所调用service都不一样。
- 我们就无法在我们的使用调度器的地方去实现找到需要注入的对象,然后放到jobDataMap中去。
五、 解决的思路2
- 我们把spring的容器的context注入,然后job中需要什么注入对象,就直接从context中去获得 ,这样就实现了通用性。
java
@Autowired
private ServletContext servletContext;
private void scheduleSumJob(Scheduler scheduler) throws SchedulerException
{
JobDetail jobDetail = JobBuilder.newJob(SumJob.class)
.withIdentity("sumJob","group1")
.build();
jobDetail.getJobDataMap().put("context",servletContext);
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/2 * * * * ?");
CronTrigger cronTrigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1","group1")
.withSchedule(scheduleBuilder).build();
scheduler.scheduleJob(jobDetail,cronTrigger);
}
java
public class SumJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException
{
ServletContext context = (ServletContext)jobExecutionContext.getJobDetail()
.getJobDataMap().get("context");
WebApplicationContext cxt = (WebApplicationContext) context.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
EmployeeService employeeService = cxt.getBean(EmployeeService.class);
employeeService.freshAreaEmployeeNum();
}
}