spring boot 集成quartz 2.0 实现前端动态配置(获取spring上下文)的两种方式,启动数据库中已开启定时任务

spring上下文我们可以直接在自定义job类中获取的,一般情况下集成我们获取spring注入类只会得到空指针异常,说此bean未注入,我们先看效果

第一种获取:

import com.len.util.SpringUtil;
import com.len.entity.SysUser;
import com.len.service.SysUserService;
import java.text.SimpleDateFormat;
import java.util.List;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;

/**
 * @author zhuxiaomeng
 * @date 2018/1/7.
 * @email [email protected]
 *
 * 定时
 */
public class JobDemo1 implements Job{

  @Autowired
  SysUserService sys;

  @Override
  public void execute(JobExecutionContext context) throws JobExecutionException {
    System.out.println("JobDemo1:启动任务=======================");
    run();
    System.out.println("JobDemo1:下次执行时间====="+
        new SimpleDateFormat("yyyy-MM-dd hh:mm:ss")
            .format(context.getNextFireTime())+"==============");
  }

  public void run(){
    ApplicationContext applicationContext=SpringUtil.getApplicationContext();
    List<SysUser> userList=sys.selectListByPage(new SysUser());
    System.out.println(userList.get(0).getUsername());;
    System.out.println("JobDemo1:执行完毕=======================");

  }
}

第二种获取

import com.len.entity.SysUser;
import com.len.service.SysUserService;
import com.len.service.impl.SysUserServiceImpl;
import com.len.util.SpringUtil;
import java.text.SimpleDateFormat;
import java.util.List;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.context.ApplicationContext;

/**
 * @author zhuxiaomeng
 * @date 2018/1/7.
 * @email [email protected]
 *
 * 定时测试类
 */
public class JobDemo2 implements Job{


  @Override
  public void execute(JobExecutionContext context) throws JobExecutionException {
    System.out.println("JobDemo2:启动任务=======================");
    run();
    System.out.println("JobDemo2:下次执行时间====="+
        new SimpleDateFormat("yyyy-MM-dd hh:mm:ss")
            .format(context.getNextFireTime())+"==============");
  }

  public void run(){
    ApplicationContext applicationContext=SpringUtil.getApplicationContext();
    SysUserService sys=SpringUtil.getBean(SysUserServiceImpl.class);
    List<SysUser> userList=sys.selectListByPage(new SysUser());
    System.out.println(userList.get(0).getUsername());;
    System.out.println("JobDemo2:执行完毕=======================");

  }
}

一般获取bean对象我们比较习惯使用第一种方式:@Autowired获取,第二种就是在方法中获取bean,前端展示下效果:

本教程所用版本:

quartz:2.3.0

spring :1.5.9 RELEASE

我们先从quartz2.x ,我相信本教程会帮助你的,quartz 1.x 和 2.x区别在于,需要new的对象换成了静态工厂实现。

quartz和spring boot集成(实际上要注入到spring中)

那么我们要做的就是要熟悉quartz、spring对quartz的支持类

首先熟悉下几个主要类 

SchedulerFactoryBean:这是spring提供的调度工厂bean,通过他我们可以创建和配置一个scheduler,管理其生命周期作为Spring上下文的一部分,并且暴露了Scheduler的bean引用依赖注入。我们可以用此方法获取到schduler。

Scheduler: quartz的主要API接口与作业调度器 我们所有的 JobDetail和Trigger都是通过此类来维护的,就是定时需要通过此类来实现。

Job and JobDetail:我们自定义任务类都要实现Job接口,其中方法execute()提供了定时的入口,JobDetail接口提供了工作类所需要的具体属性。

JobDetail jobDetail = JobBuilder.newJob(clazz).build();

通过 JobBuilder 创建一个 JobBuilder  并传入自定义的任务类clazz, build 返回一个 JobDetail  我们来看下其中具体操作:

public JobDetail build() {
    JobDetailImpl job = new JobDetailImpl();
    job.setJobClass(this.jobClass);
    job.setDescription(this.description);
    if(this.key == null) {
      this.key = new JobKey(Key.createUniqueName((String)null), (String)null);
    }

    job.setKey(this.key);
    job.setDurability(this.durability);
    job.setRequestsRecovery(this.shouldRecover);
    if(!this.jobDataMap.isEmpty()) {
      job.setJobDataMap(this.jobDataMap);
    }

    return job;
  }

JobDetail  接口实现类 JobDetailImpl set进JobBuilder 的各种信息,最终返回给对象 jobDetail 。

Trigger:任务触发器基础接口。

CronTrigger:继承Trigger 接口,来看下面代码:

CronTrigger trigger = TriggerBuilder.newTrigger()
          .withIdentity(triggerKey)
          .withSchedule(CronScheduleBuilder.cronSchedule(job.getCron())).build();

首先newTrigger() 创建了一个TriggerBuilder 然后传入触发器key 然后传入定义的cron表达式,最后build(),返回给对象trigger。

此时我们回头来看 Scheduler 接口 

scheduler.scheduleJob(jobDetail, trigger);

通过此方法就可以把我们定义的任务安排进执行的计划中。

scheduler.start();

然后启动任务如果你有兴趣可以看看start方法的具体实现,最终是操作的线程。

废话少说,开始集成:

spring为我们提供了一个class: AdaptableJobFactory 这个类实现了 JobFactory

其方法createJobInstance() 可以创建job实例 并返回;通过重写其方法createJobInstance();我们可以得到job实例,

通过spring 接口 AutowireCapableBeanFactory 的方法 autowireBean 来填充进spring bean中

看代码:

@Component
public class MyJobFactory extends AdaptableJobFactory{
      
    @Autowired
    private AutowireCapableBeanFactory capableBeanFactory;
  
    @Override  
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        Object job = super.createJobInstance(bundle);
        capableBeanFactory.autowireBean(job);
        return job;
    }
}

此方法来完成spring对quartz的支持

当然我们还要把SchedulerFactoryBean 注入到spring中:

@Configuration
public class MySchedulerListener {
      
    @Autowired  
    MyJobFactory myJobFactory;

      
    @Bean(name ="schedulerFactoryBean")
    public SchedulerFactoryBean mySchedulerFactory() {
        SchedulerFactoryBean bean = new SchedulerFactoryBean();
        bean.setJobFactory(myJobFactory);  
        return bean;  
    }  
  
}  

此时,自定义job已经支持spring上下文。

然后我们开始定义任务增删改job类:

import com.len.core.annotation.Log;
import com.len.core.annotation.Log.LOG_TYPE;
import com.len.entity.SysJob;
import java.util.Date;
import java.util.HashSet;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Service;

/**
 * @author zhuxiaomeng
 * @date 2018/1/5.
 * @email [email protected]
 *
 * 定时任务类 增删改 可参考api:http://www.quartz-scheduler.org/api/2.2.1/
 *
 * 任务名称 默认为 SysJob 类 id
 */
@Service
public class JobTask {

  private static final Logger log = LoggerFactory.getLogger(JobTask.class);

  @Autowired
  SchedulerFactoryBean schedulerFactoryBean;

  /**
   * true 存在 false 不存在
   * @param
   * @return
   */
  public boolean checkJob(SysJob job){
    Scheduler scheduler = schedulerFactoryBean.getScheduler();
    TriggerKey triggerKey = TriggerKey.triggerKey(job.getId(), Scheduler.DEFAULT_GROUP);
    try {
      if(scheduler.checkExists(triggerKey)){
        return true;
      }
    } catch (SchedulerException e) {
      e.printStackTrace();
    }
    return false;
  }

  /**
   * 开启
   */
  //@Log(desc = "开启定时任务")
  public boolean startJob(SysJob job) {
    Scheduler scheduler = schedulerFactoryBean.getScheduler();
    try {
      Class clazz = Class.forName(job.getClazzPath());
      JobDetail jobDetail = JobBuilder.newJob(clazz).build();
      // 触发器
      TriggerKey triggerKey = TriggerKey.triggerKey(job.getId(), Scheduler.DEFAULT_GROUP);
      CronTrigger trigger = TriggerBuilder.newTrigger()
          .withIdentity(triggerKey)
          .withSchedule(CronScheduleBuilder.cronSchedule(job.getCron())).build();
      scheduler.scheduleJob(jobDetail, trigger);
      // 启动
      if (!scheduler.isShutdown()) {
        scheduler.start();
        log.info("---任务[" + triggerKey.getName() + "]启动成功-------");
        return true;
      }else{
        log.info("---任务[" + triggerKey.getName() + "]已经运行,请勿再次启动-------");
      }
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
    return false;
  }

  /**
   * 更新
   */
  @Log(desc = "更新定时任务", type = LOG_TYPE.UPDATE)
  public boolean updateJob(SysJob job) {
    Scheduler scheduler = schedulerFactoryBean.getScheduler();
    String createTime = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");

    TriggerKey triggerKey = TriggerKey.triggerKey(job.getId(), Scheduler.DEFAULT_GROUP);
    try {
      if (scheduler.checkExists(triggerKey)) {
        return false;
      }

      JobKey jobKey = JobKey.jobKey(job.getId(), Scheduler.DEFAULT_GROUP);

      CronScheduleBuilder schedBuilder = CronScheduleBuilder.cronSchedule(job.getCron())
          .withMisfireHandlingInstructionDoNothing();
      CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey)
          .withDescription(createTime).withSchedule(schedBuilder).build();
      Class clazz = null;
      JobDetail jobDetail = scheduler.getJobDetail(jobKey);
      HashSet<Trigger> triggerSet = new HashSet<>();
      triggerSet.add(trigger);
      scheduler.scheduleJob(jobDetail, triggerSet, true);
      log.info("---任务["+triggerKey.getName()+"]更新成功-------");
      return true;
    } catch (SchedulerException e) {
      e.printStackTrace();
    }
    return false;
  }

  /**
   * 删除
   */
  @Log(desc = "删除定时任务", type = LOG_TYPE.DEL)
  public boolean remove(SysJob job) {
    Scheduler scheduler = schedulerFactoryBean.getScheduler();
    TriggerKey triggerKey = TriggerKey.triggerKey(job.getId(), Scheduler.DEFAULT_GROUP);
    try {
      if (checkJob(job)) {
        scheduler.pauseTrigger(triggerKey);
        scheduler.unscheduleJob(triggerKey);
        scheduler.deleteJob(JobKey.jobKey(job.getId(), Scheduler.DEFAULT_GROUP));
        log.info("---任务[" + triggerKey.getName() + "]删除成功-------");
        return true;
      }
    } catch (SchedulerException e) {
      e.printStackTrace();
    }
    return false;
  }
}

开头已经有定义job了,

我们需要自定义启动job并且储存到数据库,需要一个实体类:

public class SysJob implements Serializable {
    private String id;

    private String jobName;

    private String cron;

    private Boolean status;

    private String clazzPath;

    private String jobDesc;

    private String createBy;

    private Date createDate;

    private String updateBy;

    private Date updateDate;

    private static final long serialVersionUID = 1L;
}

第二种方式就是通过实现 ApplicationContextAware 来获取上下文,并进行封装:

public class ApplicationContextUtil implements ApplicationContextAware {

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

  public static ApplicationContext getContext(){
    return applicationContext;
  }

  public static Object getBean(String arg){
    return applicationContext.getBean(arg);
  }
}

如果你不知道bean名称,可以在spring bootmain方法启动后获取:

public static void main(String[] args) {
    ApplicationContext applicationContext=SpringApplication.run(Application.class,args);
    String[] names = applicationContext.getBeanDefinitionNames();
    Arrays.asList(names).forEach(name -> System.out.println(name));//1.8 forEach循环
  }

就可以把所有bean name 打印出

那么当spring boot 启动时候 我们需要从数据库中获取到相应的启动项,可以利用spring提供监听器:ApplicationListener  容器初始化后调用 只提供一个方法 onApplicationEvent方法 我们利用此方法开启一个线程然后执行开启数据库中标示为启动的任务类,这样的好处是 无需等待 开启新线程去执行:

@Component
public class MyApplicationListener  implements ApplicationListener<ContextRefreshedEvent> {

  Logger logger= LoggerFactory.getLogger(MyApplicationListener.class);



  @Override
  public void onApplicationEvent(ContextRefreshedEvent event) {
    logger.info("-------------bean初始化完毕-------------");
    /**
     * 通过线程开启数据库中已经开启的定时任务 灵感来自spring
     * spring boot继续执行 mythread开辟线程,延迟后执行
     */
    DataSourceJobThread myThread= (DataSourceJobThread) event.getApplicationContext().getBean(
        "dataSourceJobThread");
    myThread.start();
  }

}
/**
 * @author zhuxiaomeng
 * @date 2018/1/6.
 * @email [email protected]
 *
 * 启动数据库中已经设定为 启动状态(status:true)的任务 项目启动时init
 */
@Configuration
public class DataSourceJobThread extends Thread {

  private static final Logger log = LoggerFactory.getLogger(DataSourceJobThread.class);
  @Autowired
  RoleService roleService;

  @Autowired
  JobService jobService;

  @Override
  public void run() {
    try {
      Thread.sleep(1000);
      log.info("---------线程启动---------");
      JobTask jobTask = SpringUtil.getBean("jobTask");
      SysJob job = new SysJob();
      job.setStatus(true);
      List<SysJob> jobList = jobService.selectListByPage(job);
      //开启任务
      jobList.forEach(jobs -> {
        log.info("---任务["+jobs.getId()+"]系统 init--开始启动---------");
        jobTask.startJob(jobs);
          }
      );
      if(jobList.size()==0){
        log.info("---数据库暂无启动的任务---------");
      }else
      System.out.println("---任务启动完毕---------");
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

完毕,不足之处,还请留言说明,本教程提供了一些service这里没有给出;

不过本人已经在开源框架lenos中实现可配置化定时任务,如果你感兴趣,或者需要帮助,可以下载学习:

地址:https://gitee.com/bweird/lenosp 

lenos是一款快速开发脚手架,不仅有定时任务,还有权限管理,日志监控等其他技术,如果你喜欢 别忘记点个star,谢谢。如果有疑问,可以在lenos下加群询问。

猜你喜欢

转载自my.oschina.net/u/3312115/blog/1603703