quartz交流QQ群:77383408,喜欢的同行一起来探讨问题吧
最近在项目中需要用到quartz,开始使用的xml配置创建的job,一切ok。后来觉得每次添加任务都要写一大段xml,就将job放入了数据库,在spring启动时去启动数据库中保存的所有job。
其中遇到问题,无法注入spring管理的bean。本文是解决方案,研究了几天,终于找到原因了!
首先,本文实现使用的是内存型,没有持久化到数据库。
一、quartz.properties文件
#调度器名,无关紧要,名字任意定 org.quartz.scheduler.instanceName = XXScheduler org.quartz.scheduler.instanceId = AUTO #============================================================================ # Configure ThreadPool 配置数据库连接池 #============================================================================ org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 12 org.quartz.threadPool.threadPriority = 5 #============================================================================ # Configure JobStore 配置做业存储方式 #============================================================================ #相当于扫描频率,如果系统基于秒级,应培植成1000,quartz默认为分级(60000) org.quartz.jobStore.misfireThreshold = 1000 org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
二、创建任务Job
方法一:使用xml方式配置job
<!-- 自定义quartz的jobFactory,将spring管理bean放到jobFactory,这样才能在job类里面通过注解注入bean --> <bean id="jobFactory" class="com.cdrzt.pcs.timer.factory.MyJobFactory"></bean> <!-- 总管理类 如果将lazy-init='false'那么容器启动就会执行调度程序 --> <bean id="startQuertz" lazy-init="false" autowire="no" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="jobFactory" ref="jobFactory"></property> <property name="configLocation" value="classpath:quartz.properties" /> <!-- 管理trigger --> <property name="triggers"> <list> <ref bean="trigger_1"/> </list> </property> </bean> <!-- ********定时器1 ******** --> <!-- 定义jobDetail --> <bean id="detail_1" class="org.springframework.scheduling.quartz.JobDetailFactoryBean"> <property name="jobClass" value="com.xx.xx.xx.JobClassTest" /><!-- 这里指定job任务类 --> <property name="durability" value="true" /> <property name="group" value="group" /> <property name="name" value="name" /> </bean> <!-- 定义trigger --> <bean id="trigger_1" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> <property name="jobDetail" ref="detail_1"></property> <property name="cronExpression"> <value>0/1 * * * * ?</value><!-- 测试使用每一秒运行一次 --> </property> </bean>
好了,至此使用xml方式配置job就全部完毕了。
其中值得注意的是下面两句代码:自定义MyJobFactory,在job类里面才可以注入spring的service。
<bean id="jobFactory" class="com.cdrzt.pcs.timer.factory.MyJobFactory"></bean>
<property name="jobFactory" ref="jobFactory"></property>
public class MyJobFactory extends AdaptableJobFactory { // 这个对象Spring会帮我们自动注入进来,也属于Spring技术范畴. @Autowired private AutowireCapableBeanFactory capableBeanFactory; protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception { // 调用父类的方法 Object jobInstance = super.createJobInstance(bundle); // 进行注入,这属于Spring的技术,不清楚的可以查看Spring的API. capableBeanFactory.autowireBean(jobInstance); return jobInstance; } }
/** * Job任务类,实现Job接口,可以成功注入service */ public class JobClassTest implements Job { @Autowired private ProductBaseDao productBaseDao; @Override public void execute(JobExecutionContext context) throws JobExecutionException { ProductBase p = productBaseDao.load(ProductBase.class, 2); System.out.println("注入service成功!查询结果为:"+p); } }
至此,启动容器,job成功启动。
方式二:使用代码创建Job
表结构:本示例只需要一张表即可!
数据库有了记录,那么我们就需要在spring容器启动后,将所有记录获取出来,并通过代码创建每一个job。
<!-- 在Spring容器将所有的Bean都初始化完成之后的操作 --> <bean class="com.xx.xx.timer.InstantiationTracingBeanPostProcessor"/>
在applicationContext.xml配置文件最末加上这一句,即可在spring启动后,去执行指定类中的方法。
/** * 在Spring容器将所有的Bean都初始化完成之后的操作 */ public class InstantiationTracingBeanPostProcessor implements ApplicationListener<ContextRefreshedEvent> { @Autowired private TimerJobService timerJobService; @Autowired private MyJobFactory myJobFactory; @Override public void onApplicationEvent(ContextRefreshedEvent event) { // 避免onApplicationEvent方法被执行两次 if(event.getApplicationContext().getParent() == null){ try { // 获取Scheduler对象,并自定义jobFactory Scheduler scheduler = QuartzUtil.getInstance(); scheduler.setJobFactory(myJobFactory); // 查询所有正常状态的定时任务,并在容器启动后,启动任务 List<TimerJob> jobs = timerJobService.getNormalList(); for(TimerJob record : jobs){ Integer id = record.getId(); String name = record.getJobName()+"_"+id; String group = record.getJobGroup()+"_"+id; // 加载job类 Class<? extends Job> clazz = null; try { clazz = (Class<? extends Job>) Class.forName(record.getClassName()); } catch (ClassNotFoundException e) { e.printStackTrace(); } //生成jobDetail JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity(name, group).build(); //表达式调度构建器 CronScheduleBuilder cornSB= CronScheduleBuilder.cronSchedule(record.getCronExpression()); //生成触发器 CronTrigger trigger = (CronTrigger)TriggerBuilder.newTrigger().withIdentity(name, group).withSchedule(cornSB).build(); //添加job scheduler.scheduleJob(jobDetail, trigger); } //开始执行shceduler scheduler.start(); } catch (Exception e) { e.printStackTrace(); } } } }
其中创建Scheduler实例方法为:
public class QuartzUtil { private static SchedulerFactory ssf = new StdSchedulerFactory(); /** * 获取Scheduler实例,使用工厂模式获取 * @return */ public static Scheduler getInstance(){ Scheduler sched = null; try { sched = ssf.getScheduler(); } catch (SchedulerException e) { e.printStackTrace(); } return sched; } }
其中关键代码为:scheduler.setJobFactory(myJobFactory) ;同xml配置一样,需要指定自定义的JobFactory。
最开始,我是这样设置的:scheduler.setJobFactory(new MyJobFactory());
可是这样的结果就是,任务全部启动了,可是在job任务类注入不了bean。思考了很久,才突然发现, MyJobFactory是使用new关键字实例化出来的,在spring中,自己new出来的都不会交给spring的context去管理!!!
既然找到问题所在,解决就简单多了,只需要将MyJobFactory交给spring中去,再在上文通过注解注入即可!
在MyJobFactory.java类最上面添加@component注解,启动时spring去扫描组件会将该类注入。
@Component public class MyJobFactory extends AdaptableJobFactory {......}
至此,问题全部解决,两种创建job的方法,个人觉得第二种比较简单。需要新增任务时,只需要在数据库添加一条记录,再添加一个对应的job类即可。
有了这一张表,同样可以配置后台页面,实现对任务的控制,这里就不讲啦!
最后,欢迎喜欢quartz的人,加入QQ群:77383408
把你的问题说出来集思广益,避免大家重蹈覆辙。