解决Spring boot集成quartz时service注入失败为null的问题 一、报错信息 二、代码 三、注入失败原因 四、解决的思路1 五、 解决的思路2
一、报错信息
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 ;
@Slf4j
@Component
public class ScheduledTasks implements Job { @Autowired private CustomerSerice customerSerice; @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 ;
@Configuration
public class QuartzConfig { @Bean public JobDetailFactoryBean jobDetailFactoryBean ( ) { JobDetailFactoryBean factory = new JobDetailFactoryBean ( ) ; factory. setJobClass ( ScheduledTasks . class ) ; factory. setDurability ( true ) ; return factory; } @Bean public CronTriggerFactoryBean cronTriggerFactoryBean ( JobDetail jobDetail) { CronTriggerFactoryBean factory = new CronTriggerFactoryBean ( ) ; factory. setJobDetail ( jobDetail) ; factory. setCronExpression ( "0 0 0 * * ?" ) ; return factory; } @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 ) ;
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">
@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 * * ?" ) ; 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 ( ) ; }
}