Java-项目中定时任务实现过程

438 阅读4分钟

需求

实现消息定时推送策略的增删改查,并根据策略内容发送邮件。

实现过程

  其实从一开始打算使用java自带timer来实现定时,但是马上就遇到了问题,不管是用schedule方法还是scheduleAtFixedRate都会出现一旦设置的时间已经过去,这两种方法都会先执行一次。特别是scheduleAtFixedRate方法,它会先计算出设置的已经过去的时间相对与现在的时间一个差值,然后这个差值包含多少个时间间隔,它就会执行多少次。这显然与需求不相符。

  如果当前时间为2021-02-19 10:52:00那么下面的代码就会打印5+1次。

  public static void main(String[] args) throws ParseException {
        Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2021-02-19 10:00:00");
        new Timer().scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println("哈哈");
            }
        },date,1000*60*10);
    }

  而且需求中不是单一的定时策略,而且存在对定时任务的增删改查,如果继续使用timer可能需要引入线程池来解决,这样就变得复杂了。这时候quartz就登场了。

Quartz

  Quartz是一个开源项目,完全由Java开发,可以用来执行定时任务,类似于java.util.Timer。但是相较于Timer, Quartz增加了很多功能,但是相比于timer我觉得最重要的就是对每一个定时任务可以很方便的进行管理。

  下面的图是我对于Quartz的一个大概的理解。

  在使用时建议创建QuartzUtil类方便管理。

public class Quartz {


    @Autowired
    private  Scheduler scheduler;


    public void addJob(String jobName, String jobGroup, String triggerName, String triggerGroup, String strDate, String fre, PushStrategyPO pushStrategyPO) throws InterruptedException{
        try {
            DateFormat bf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date date=null;
            try {
                date = bf.parse(strDate);
            } catch (ParseException e) {
                e.printStackTrace();
            }
            if (null == date || StringUtils.isEmpty(fre)) {
                return;
            }

            String cronExpress="";

            if (fre.equals("d")){
                cronExpress = getEveryDayCron(date);
            }
            if (fre.equals("w")){
                cronExpress = getEveryWeekCron(date);
            }

//            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
            //创建任务器:定义任务细节
            JobDetail jobDetail=null;
            if(pushStrategyPO.getType()==0){
                  jobDetail = JobBuilder.newJob(CustomerEmailJob.class).withIdentity(jobName, jobGroup).build();
            }
            if(pushStrategyPO.getType()==1){
                jobDetail = JobBuilder.newJob(ContractEmailJob.class).withIdentity(jobName, jobGroup).build();
            }

            JobDataMap jobDataMap = jobDetail.getJobDataMap();
            jobDataMap.put("fre",fre);
            jobDataMap.put("pushStrategyPO",pushStrategyPO);
            CronTrigger trigger = TriggerBuilder
                    .newTrigger()
                    .withIdentity(triggerName, triggerGroup)    //创建一个标识符
                    //每秒钟触发一次任务
                    .withSchedule(CronScheduleBuilder.cronSchedule(cronExpress))
                    .build();
            //将任务和触发器注册到调度器中
            scheduler.scheduleJob(jobDetail, trigger);
            if (!scheduler.isShutdown()) {
                scheduler.start();
            }
        } catch (SchedulerException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    private  String formatDateByPattern(Date date,String dateFormat){
        SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
        String formatTimeStr = null;
        if (date != null) {
            formatTimeStr = sdf.format(date);
        }
        return formatTimeStr;
    }
    /***
     * convert Date to cron ,eg.  "0 07 10 15 1 ? 2016"
     * @param date  : 时间点
     * @return
     */
    public   String getEveryDayCron(Date  date){
        String dateFormat="ss mm HH * * ?";
        return formatDateByPattern(date, dateFormat);
    }

    /**
     *默认周一发
     * @param date
     * @return
     */
    public   String getEveryWeekCron(Date  date){
        String dateFormat="ss mm HH ? * 2";
        return formatDateByPattern(date, dateFormat);
    }

    public  void modifyJobTime(String jobName, String jobGroupName, String triggerName, String triggerGroupName,
                               String cron) {
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            if (trigger == null) {
                return;
            }
            String oldTime = trigger.getCronExpression();
            if (!oldTime.equalsIgnoreCase(cron)) {
                // 触发器
                TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
                // 触发器名,触发器组
                triggerBuilder.withIdentity(triggerName, triggerGroupName);
                triggerBuilder.startNow();
                // 触发器时间设定
                triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));
                // 创建Trigger对象
                trigger = (CronTrigger) triggerBuilder.build();
                // 方式一 :修改一个任务的触发时间
                scheduler.rescheduleJob(triggerKey, trigger);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 功能: 移除一个任务
     *
     * @param jobName
     * @param jobGroupName
     * @param triggerName
     * @param triggerGroupName
     */
    public  void removeJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName) {
        try {

            TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
            // 停止触发器
            scheduler.pauseTrigger(triggerKey);
            // 移除触发器
            scheduler.unscheduleJob(triggerKey);
            // 删除任务
            scheduler.deleteJob(JobKey.jobKey(jobName, jobGroupName));

            System.out.println("removeJob:"+JobKey.jobKey(jobName));

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     *
     * 功能:启动所有定时任务
     */
    public  void startJobs() {
        try {
            scheduler.start();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 功能:关闭所有定时任务
     */
    public  void shutdownJobs() {
        try {
            if (!scheduler.isShutdown()) {
                scheduler.shutdown();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public List<String> getSchedulerJobs() {
        List<String> jobGroupNames=null;
       try {
            jobGroupNames = scheduler.getJobGroupNames();

        } catch (SchedulerException e) {
            e.printStackTrace();
        }
        return jobGroupNames;
    }


    public  String createJobID(int n )
    {
        String val = "";
        Random random = new Random();
        for ( int i = 0; i < n; i++ )
        {
            String str = random.nextInt( 2 ) % 2 == 0 ? "num" : "char";
            if ( "char".equalsIgnoreCase( str ) )
            { // 产生字母
                int nextInt = random.nextInt( 2 ) % 2 == 0 ? 65 : 97;
                // System.out.println(nextInt + "!!!!"); 1,0,1,1,1,0,0
                val += (char) ( nextInt + random.nextInt( 26 ) );
            }
            else if ( "num".equalsIgnoreCase( str ) )
            { // 产生数字
                val += String.valueOf( random.nextInt( 10 ) );
            }
        }
        return val;
    }
}

整合到SpringBoot时的问题

  springboot服务中使用Quartz的时候需要做一定的修改,要不然会存在即使加了@bean或者@component,job实现类注入不到spring容器,报空指。

  引入依赖

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

  Job实现类重新集成spring的QuartzJobBean

public class sendEmailJob extends QuartzJobBean {

    private static final Logger logger = LoggerFactory.getLogger(sendEmailJob.class);
    
    @Autowired
    private ProjectServiceImpl projectService;

    @Autowired
    private SendEmailUtil sendEmailUtil;

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
     ...
    }

需要往Job实现类中传参时

   因为job实现类重写了方法,不能在参数中再去添加传入的参数。这时候就需要通过任务管理器去传参,调度器在定时执行的时候再去将参数通过JobExecutionContext传入。

            JobDataMap jobDataMap = jobDetail.getJobDataMap();
            jobDataMap.put("fre",fre);
            jobDataMap.put("pushStrategyPO",pushStrategyPO);
        JobDataMap jobDataMap =jobExecutionContext.getJobDetail().getJobDataMap();
        String fre = jobDataMap.getString("fre");
        PushStrategyPO pushStrategyPO = (PushStrategyPO) jobDataMap.get("pushStrategyPO");

当服务重启时需要重新载入schedule中

   当服务在重启时,需要去读数据库,然后将每一条定时任务重新载入任务管理当中。

@Component
public class ApplicationRunnerImpl implements ApplicationRunner {

    private static final Logger logger = LoggerFactory.getLogger(ModuleController.class);

    @Autowired
    private MessageService messageService;

    @Autowired
    private Quartz quartz;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        //查询策略表,包括项目管理和合同管理
        logger.info("Querying all pushStrategies for mysql...");

        ArrayList<PushStrategyPO> pushStrategyPOS = messageService.queryAllJob();

        logger.info("pushStrategies is"+pushStrategyPOS.toString());

        logger.info("Begin to start all timed task...");
        for (PushStrategyPO ps : pushStrategyPOS) {
          //启动所有定时任务
            String jobName=ps.getJobId();
            String jobGroup=ps.getJobId()+"-Group";
            String triggerName=ps.getJobId()+"-Trigger";
            String triggerGroup=ps.getJobId()+"-TriggerGroup";
            String frequency = ps.getFrequency();
            String setDate = ps.getSetDate();
            quartz.addJob(jobName,jobGroup,triggerName,triggerGroup,setDate,frequency,ps);
        }

        List<String> schedulerJobs = quartz.getSchedulerJobs();
        logger.info("there are "+schedulerJobs.size()+"job in schedule");
        for (String schedulerJob : schedulerJobs) {
            logger.info("job is :"+schedulerJob);
        }

    }
}