Quartz入门,看这篇就够了

一文教会你使用quartz动态创建定时任务 quart动态创建定时任务 阅读详情

前言

最近学习了定时任务相关的内容,了解了下Quartz框架,但是原生的一些API用起来不太方便,所以按照我自己的使用场景做了一些封装。这篇文章就带小伙伴们了解一下Quartz的基本使用。包括基本概念以及如何动态地对定时任务进行CRUD,并且如何实现定时任务的持久化以及任务恢复。

一、Quartz的基本使用

Quartz 是一个开源的作业调度框架。在使用这个框架之前,我们需要知道几个基本的概念Job,Trigger以及Schedule:

Job和JobDetail

既然是作业调度,那么肯定要有作业呀,这个作业就是Job。在定义我们自己的Job的时候,只需要实现Job接口,然后在execute方法里编写具体的业务逻辑即可。也可以继承QuartzJobBean类并重写executeInternal方法,因为QuartzJobBean类也是实现了Job接口。

 public class TemplateJob implements Job {
 ​
     @Override
     public void execute(JobExecutionContext context) throws JobExecutionException {
         // 欢迎关注我的微信公众号:Robod
     }
 ​
 }

JobDetail为Job实例提供了许多设置属性,以及JobDataMap成员变量属性,它用来存储特定Job实例的状态信息,调度器需要借助Jobdetail对象来添加Job实例。

Trigger

有了任务之后,就该设置任务的触发事件了,在Quartz中使用Trigger来描述触发Job执行的时间触发规则。一个Job可以添加多个Trigger,但是一个Trigger只能绑定一个Job。

Quartz中一共有四种Trigger:

  • SimpleTrigger:这种触发器可以在给定时刻触发作业,并且可选择以指定的时间间隔重复。
  • CronTrigger:用过定时任务的小伙伴应该会猜到这个是干什么的吧。这个触发器可以设置一个Cron表达式,指定任务的执行周期。
  • CalendarIntervalTrigger:用于根据重复的日历时间间隔触发。
  • DailyTimeIntervalTrigger:用于根据每天重复的时间间隔触发任务的触发器。

后面两种我并没有用过,所以下文的介绍主要基于前两种。后两种感兴趣的小伙伴可以自行研究。

Schedule

Schedule所扮演的是一个执行者的角色,将JobDetail和Trigger注册到Schedule中,它就会按照指定的规则去执行任务。

定义一个定时任务

如果是Springboot的项目,添加如下依赖即可:

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

定义任务分为三步:定义一个JobDetail、定义一个Trigger、使用Scheduler去执行任务。

 JobDetail jobDetail = JobBuilder.newJob(TemplateJob.class).withIdentity("任务名", "任务组名").build();
 SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
         .withIdentity("触发器名", "触发器组名")
         .startNow()
         .build();
 Scheduler scheduler = new StdSchedulerFactory().getScheduler();
 scheduler.scheduleJob(jobDetail, trigger);
 scheduler.start();

上面这段代码中,将触发器设为startNow,也就是立即执行。也可以使用startAt以及endAt方法设置开始时间以及结束时间等。

在Job中获取自定义参数

在实际的使用过程中,我们可能需要在创建一个Job时指定一些参数用于具体的业务场景,就可以借助JobDataMap。比如指定一个时间给某个人发送奖品,那么在创建任务时就需要用户id,奖品名称,奖品id等信息。我们就可以在定义JobDetail时,使用usingJobData方法设置一些参数,或者使用setJobData方法将定义好的jobDetail填入进去。

 JobDetail jobDetail = JobBuilder.newJob(TemplateJob.class)
         .usingJobData("userId", "VIP2345678")
         .usingJobData("awardName", "100元优惠券")
         .usingJobData("awardId", "YHQ675567687765")
         .usingJobData("awardValue",6.6)
         .withIdentity("任务名", "任务组名")
         .build();
 //-----------或者---------------
 JobDataMap jobDataMap = new JobDataMap();
 jobDataMap.put("userId", "VIP5465756453");
 jobDataMap.put("awardName", "100元优惠券");
 jobDataMap.put("awardId", "YHQ67354747443");
 jobDataMap.put("awardValue",6.6);
 JobDetail jobDetail2 = JobBuilder.newJob(TemplateJob.class)
         .setJobData(jobDataMap)
         .withIdentity("任务名", "任务组名")
         .build();

JobDataMap就可以将它理解为HashMap,用法都是类似的。然后在Job中就可以获取到JobDataMap,从而拿到相应的参数:

 public class AwardJob implements Job {
 ​
     @Override
     public void execute(JobExecutionContext context) throws JobExecutionException {
         JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
         String userId = jobDataMap.getString("userId");
         String awardName = jobDataMap.getString("awardName");
         String awardId = jobDataMap.getString("awardId");
         double awardValue = jobDataMap.getDouble("awardValue");
         //…………发奖品…………
     }
 ​
 }

二、Quartz工具类实现定时任务的动态CRUD

定义一个定时任务要分为三步,但是在一个项目中必然会在多处都用到定时任务,如果每次定义都要写这样一段代码显然不够优雅。为了方便使用,可以写一个通用的工具类去实现定时任务的CRUD。

每个Job的jobName都必须是唯一的,建议使用和业务相关的主键id作为jobName。这样即可以保证唯一性,也可以通过jobName判断这个某个定时任务具体是做什么操作的,还可以在Job的execute方法中获取到这个主键id而执行相关操作,就不用额外传递参数了。比如订票系统中在发车前给用户发短信,就可以将订单的id作为jobName。

JobDetail和Trigger还需要指定一个分组,一个项目中使用到定时任务的分组应该是固定数量的,定义一个枚举类将需要用到的分组名称放在里面,在不同的业务场景下创建定时任务时选用不同的分组即可,需要额外的再往里面添加就行。

 @Getter
 @AllArgsConstructor
 public enum QuartzGroupEnum {
 ​
     T1( "测试分组1"),
     T2("测试分组2");
 ​
     private final String value;
 ​
 }

添加一个定时任务

 @Component
 public class QuartzUtil {
 ​
     private static final SchedulerFactory SCHEDULER_FACTORY = new StdSchedulerFactory();
 ​
     @Autowired
     private QuartzService quartzService;
 ​
     /**
      * 添加一个定时任务
      *
      * @param name      任务名。每个任务唯一,不能重复。方便起见,触发器名也设为这个
      * @param group     任务分组。方便起见,触发器分组也设为这个
      * @param jobClass  任务的类类型  eg:TemplateJob.class
      * @param startTime 任务开始时间。传null就是立即开始
      * @param endTime   任务结束时间。如果是一次性任务或永久执行的任务就传null
      * @param cron      时间设置表达式。传null就是一次性任务
      */
     public boolean addJob(String name, String group, Class<? extends Job> jobClass,
                        LocalDateTime startTime, LocalDateTime endTime, String cron, JobDataMap jobDataMap) {
         try {
             // 第一步: 定义一个JobDetail
             JobDetail jobDetail = JobBuilder.newJob(jobClass).
                     withIdentity(name, group).setJobData(jobDataMap).build();
             // 第二步: 设置触发器
             TriggerBuilder<Trigger> triggerBuilder = newTrigger();
             triggerBuilder.withIdentity(name, group);
             triggerBuilder.startAt(toStartDate(startTime));
             triggerBuilder.endAt(toEndDate(endTime)); //设为null则表示不会停止
             if (StrUtil.isNotEmpty(cron)) {
                 triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));
             }
             Trigger trigger = triggerBuilder.build();
             //第三步:调度器设置
             Scheduler scheduler = SCHEDULER_FACTORY.getScheduler();
             scheduler.scheduleJob(jobDetail, trigger);
             if (!scheduler.isShutdown()) {
                 scheduler.start();
             }
         } catch (Exception e) {
             e.printStackTrace();
             return false;
         }
         return true;
     }
 ​
     private static Date toEndDate(LocalDateTime endDateTime) {
         // 结束时间可以为null,所以endDateTime为null,直接返回null即可
         return endDateTime != null ?
                 DateUtil.date(endDateTime) : null;
     }
 ​
     private static Date toStartDate(LocalDateTime startDateTime) {
         // startDateTime为空时返回当前时间,表示立即开始
         return startDateTime != null ?
                 DateUtil.date(startDateTime) : new Date();
     }
 ​
 }

在需要添加定时任务的地方,我们只需要调用这个方法,将指定的几个参数传入进去,就可以定义并开始一个定时任务了。现在我们只需要关注如何在Job中编写自己的业务代码而不需要去关心如何创建一个定时任务了。

在定义触发器时,如果startTime参数传过来为null的话,就表示是立即执行,那么就在startAt中将现在的时间传入。不用判断是应该用startAt还是startNow,因为从源码中可以看到,startNow方法也是将时间设为现在。

这里有一个地方需要注意,在定义触发器时,写的是Trigger而不是SimpleTrigger或者CronTrigger。这是因为我想用这个方法去添加一个一次性任务或者周期性任务,这样写的话,由于Java多态的特点,如果不指定cron,在运行时就是自动转型为SimpleTrigger,指定了cron后,运行时就会自动转型为CronTrigger。这样我们就不用关心是该用SimpleTrigger还是CronTrigger了。这一点需要注意,因为在下面的小节中会用到这个知识点。

触发器设置时间用的是Date,但是我平时用LocalDateTime比较多,所以在传参时我选择使用LocalDateTime然后调用一个方法进行转换。

修改一个定时任务

 /**
  * 修改一个任务的开始时间、结束时间、cron。不改的就传null
  *
  * @param name         任务名。每个任务唯一,不能重复。方便起见,触发器名也设为这个
  * @param group        任务分组。方便起见,触发器分组也设为这个
  * @param newStartTime 新的开始时间
  * @param newEndTime   新的结束时间
  * @param cron         新的时间表达式
  */
 public boolean modifyJobTime(String name, String group, LocalDateTime newStartTime,
                          LocalDateTime newEndTime, String cron) {
     try {
         Scheduler scheduler = SCHEDULER_FACTORY.getScheduler();
         TriggerKey triggerKey = TriggerKey.triggerKey(name, group);
         Trigger oldTrigger = scheduler.getTrigger(triggerKey);
         if (oldTrigger == null) {
             return false;
         }
         TriggerBuilder<Trigger> triggerBuilder = newTrigger();
         triggerBuilder.withIdentity(name, group);
         if (newStartTime != null) {
             triggerBuilder.startAt(toStartDate(newStartTime));   // 任务开始时间设定
         } else if (oldTrigger.getStartTime() != null) {
             triggerBuilder.startAt(oldTrigger.getStartTime()); //没有传入新的开始时间就不变
         }
         if (newEndTime != null) {
             triggerBuilder.endAt(toEndDate(newEndTime));   // 任务结束时间设定
         } else if (oldTrigger.getEndTime() != null) {
             triggerBuilder.endAt(oldTrigger.getEndTime()); //没有传入新的结束时间就不变
         }
         if (StrUtil.isNotEmpty(cron)) {
             triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));
         } else if (oldTrigger instanceof CronTrigger) {
             String oldCron = ((CronTrigger) oldTrigger).getCronExpression();
             triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(oldCron));
         }
         Trigger newTrigger = triggerBuilder.build();
         scheduler.rescheduleJob(triggerKey, newTrigger);    // 修改触发时间
     } catch (Exception e) {
         e.printStackTrace();
         return false;
     }
     return true;
 }

修改任务其实就是重新设置一个Trigger。先通过触发器名和触发器组名(也是任务名和任务组名)将旧的触发器 oldTrigger 查询出来,因为我们会用到其中的一些信息,然后定义一个新的触发器,对于不需要修改的参数就继续使用 oldTrigger 中的。

这里有段代码注意一下:

if (StrUtil.isNotEmpty(cron)) {
        triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));
} else if (oldTrigger instanceof CronTrigger) {
        String oldCron = ((CronTrigger) oldTrigger).getCronExpression();
    triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(oldCron));
}

前面提过,设置了Cron就是CronTrigger,未设置就是SimpleTrigger。所以这里就可以通过Trigger的类型来判断是哪一种,传过来的cron为null表示不需要修改,如果之前是一次性任务就不用管,如果之前就是周期性任务,那么肯定是CronTrigger,在不需要修改的情况下,就将cron设为之前的。

取消一个定时任务

public boolean cancelJob(String jobName, String groupName) {
    try {
        Scheduler scheduler = SCHEDULER_FACTORY.getScheduler();
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, groupName);
        scheduler.pauseTrigger(triggerKey); // 停止触发器
        scheduler.unscheduleJob(triggerKey);    // 移除触发器
        scheduler.deleteJob(JobKey.jobKey(jobName, groupName)); // 删除任务
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
    //将数据库中的任务状态设为 取消
    return true;
}

取消就比较简单了,直接将触发器停止并移除,最后删除任务即可。

查询所有的定时任务

public List<QuartzEntity> getAllJobs() throws SchedulerException {
    Scheduler scheduler = SCHEDULER_FACTORY.getScheduler();

    List<QuartzEntity> quartzJobs = new ArrayList<>();
    try {
        List<String> triggerGroupNames = scheduler.getTriggerGroupNames();
        for (String groupName : triggerGroupNames) {
            GroupMatcher<TriggerKey> groupMatcher = GroupMatcher.groupEquals(groupName);
            Set<TriggerKey> triggerKeySet = scheduler.getTriggerKeys(groupMatcher);
            for (TriggerKey triggerKey : triggerKeySet) {
                Trigger trigger = scheduler.getTrigger(triggerKey);
                JobKey jobKey = trigger.getJobKey();
                JobDetail jobDetail = scheduler.getJobDetail(jobKey);
                //组装数据
                QuartzEntity entity = new QuartzEntity();
                entity.setJobName(jobDetail.getKey().getName());
                entity.setGroupName(jobDetail.getKey().getGroup());
                entity.setStartTime(LocalDateTimeUtil.of(trigger.getStartTime()));
                entity.setEndTime(LocalDateTimeUtil.of(trigger.getStartTime()));
                entity.setJobClass(jobDetail.getJobClass().getName());
                if (trigger instanceof CronTrigger) {
                    entity.setCron(((CronTrigger) trigger).getCronExpression());
                }
                entity.setJobDataMapJson(JSONUtil.toJsonStr(jobDetail.getJobDataMap()));
                quartzJobs.add(entity);
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return quartzJobs;
}

先获取所有的触发器组名,再遍历获取每个触发器组中的触发器Set集合,最后遍历触发器Set集合获取JobDetail信息,然后用一个QuartzEntity对象对数据进行封装返回,再将entity放入List中。

三、在Job中注入Bean

在执行具体的定时任务时,肯定会用到相应的Service,但是通过@Autowired或者构造器注入的方式都会注入失败。可以通过一个工具类去实现在Job中注入Bean。在Service的实现类上一定要添加@Service("xxxxService")注解,不然会注入失败。

@Component
public class SpringContextJobUtil implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override
    @SuppressWarnings("static-access")
    public void setApplicationContext(ApplicationContext context)
            throws BeansException {
        this.context = context;
    }

    public static Object getBean(String beanName) {
        return context.getBean(beanName);
    }

}

调用getBean方法就可以正常注入了。

public class TemplateJob implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        QuartzService quartzService = (QuartzService) SpringContextJobUtil.getBean("quartzService");
        //…………欢迎关注我的微信公众号:Robod
    }

}

四、持久化Job并实现程序启动时任务恢复

当遇到更新版本等情况时,肯定要将程序给停了,但是程序停止后那些还未开始或者没执行完的定时任务就没了。所以我们需要将任务持久化到数据库中,然后在程序启动时将这些任务进行恢复。

在数据库中添加一张表,用于存储Job的信息。

然后在QuartzUtil中定义一个 recoveryAllJob 方法用于恢复定时任务:

public void recoveryAllJob() {
    List<QuartzEntity> tasks = quartzService.notStartOrNotEndJobs();
    if (tasks != null && tasks.size() > 0) {
        for (QuartzEntity task : tasks) {
            try {
                JobDataMap jobDataMap = JSONUtil.toBean(task.getJobDataMapJson(), JobDataMap.class);
                JobDetail jobDetail = JobBuilder.newJob((Class<? extends Job>) Class.forName(task.getJobClass()))
                        .withIdentity(task.getJobName(), task.getGroupName())
                        .setJobData(jobDataMap).build();
                TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
                triggerBuilder.withIdentity(task.getJobName(), task.getGroupName());
                triggerBuilder.startAt(toStartDate(task.getStartTime()));
                triggerBuilder.endAt(toEndDate(task.getEndTime()));
                if (StrUtil.isNotEmpty(task.getCron())) {
                    triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(task.getCron()));
                }
                Trigger trigger = triggerBuilder.build();
                Scheduler scheduler = SCHEDULER_FACTORY.getScheduler();
                scheduler.scheduleJob(jobDetail, trigger);
                if (!scheduler.isShutdown()) {
                    scheduler.start();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

首先从数据库中将需要恢复的任务查询出来,然后遍历任务将其挨个创建出来。

然后在前面的CRUD方法中添加对数据库的一些操作,也别忘了在一次性任务的Job中执行完成后调用quartzService.modifyTaskStatus(jobName, "1") 方法将任务的状态修改为已完成,不然程序启动后任务又恢复过来了:

@Component
public class QuartzUtil {

    public boolean addJob(String name, String group, Class<? extends Job> jobClass,
                       LocalDateTime startTime, LocalDateTime endTime, String cron, JobDataMap jobDataMap) {
        //…………
        //存储到数据库中
        QuartzEntity entity = new QuartzEntity();
        entity.setJobName(name);
        entity.setGroupName(group);
        entity.setStartTime(startTime != null ? startTime : LocalDateTime.now());
        entity.setEndTime(endTime);
        entity.setJobClass(jobClass.getName());
        entity.setCron(cron);
        entity.setJobDataMapJson(JSONUtil.toJsonStr(jobDataMap));
        entity.setStatus("0");
        quartzService.save(entity);
        return true;
    }

    public boolean modifyJobTime(String name, String group, LocalDateTime newStartTime,
                             LocalDateTime newEndTime, String cron) {
        //…………
        // 修改数据库中的记录
        QuartzEntity entity = new QuartzEntity();
        entity.setJobName(name);
        entity.setGroupName(group);
        if (newStartTime != null) {
            entity.setStartTime(newStartTime);
        }
        if (newEndTime != null) {
            entity.setEndTime(newEndTime);
        }
        if (StrUtil.isNotEmpty(cron)) {
            entity.setCron(cron);
        }
        return quartzService.modifyJob(entity);
    }

    public boolean cancelJob(String jobName, String groupName) {
        //…………
        //将数据库中的任务状态设为 取消
        return quartzService.modifyTaskStatus(jobName, "2");
    }

}

在保存和恢复任务时,将jobDataMap以Json的方式进行存储。

QuartzService中的代码就是一些基本的CRUD,没有什么好说的,就不在这里进行说明了,小伙伴们可以下载完整代码进行查看。(链接在文末)

那么有了恢复方法后,怎样在程序启动时调用这个方法呢?

很简单,只需要修改启动类,让其实现ApplicationRunner接口并实现run方法,在run方法中调用恢复方法即可。

@SpringBootApplication
@MapperScan("com.robod.quartzdemo.mapper")
public class QuartzDemoApplication implements ApplicationRunner {

    @Autowired
    private QuartzUtil quartzUtil;

    public static void main(String[] args) {
        SpringApplication.run(QuartzDemoApplication.class, args);
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        quartzUtil.recoveryAllJob();
    }
}

这样在程序启动时,就会自动地调用recoveryAllJob方法去恢复定时任务了。

五、小案例

现在通过一个具体的案例来简单模拟一下该怎么用。假设有这样一个场景:在火车票的订票系统中,在创建订单时设立一个定时任务,在发车前两个小时给乘客发送提醒乘车的短信,用户可能改签或者取消订单,那么也应该同样的对定时任务进行修改。

先来看一下Service中是如何使用QuartzUtil来操作定时任务的吧:

@Service("orderService")
public class OrderServiceImpl implements OrderService {

    @Autowired
    private QuartzUtil quartzUtil;

    @Override
    public String bookTicket(String userId, String ticketId) {
        //TODO 查询余票下订单等一些列操作
        Order order = new Order();
        //…………
        //创建一个定时任务
        LocalDateTime noticeTime = order.getDepartureTime().minusHours(2); //通知时间为发车前两小时
        quartzUtil.addJob(String.valueOf(order.getId()), QuartzGroupEnum.DEPARTURE_NOTICE.getValue(),
                DepartureNoticeJob.class, noticeTime, null, null, null);
        return "";
    }

    @Override
    public String rebook(Order order) {
        //TODO 修改订单等一系列操作
        //修改定时任务
        LocalDateTime noticeTime = order.getDepartureTime().minusHours(2); //通知时间为发车前两小时
        quartzUtil.modifyJob(String.valueOf(order.getId()), QuartzGroupEnum.DEPARTURE_NOTICE.getValue(),
                noticeTime, null, null);
        return "";
    }

    @Override
    public String cancelOrder(Order order) {
        //TODO 取消订单等一系列操作
        //取消定时任务
        quartzUtil.cancelJob(String.valueOf(order.getId()), QuartzGroupEnum.DEPARTURE_NOTICE.getValue());
        return "";
    }
}

首先将QuartzUtil给注入进来,然后调用其中相应的方法并将参数传入进去就可以操作定时任务了。这里的JobName设置为了订单的id,一方面是为了避免重复,另一方面是免去了额外传参的麻烦,因为在Job中只需要用到订单id。如果只有订单id这一个参数不够用,那么再使用JobDataMap 设置自定义的参数也是OK的,具体用法前面也有说明。GroupName则是在枚举类中定义的。

再来看一下DepartureNoticeJob中都做了些什么:

public class DepartureNoticeJob implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        QuartzService quartzService = (QuartzService) SpringContextJobUtil.getBean("quartzService");
        OrderService orderService = (OrderService) SpringContextJobUtil.getBean("orderService");

        String jobName = context.getJobDetail().getKey().getName();
        long orderId = Long.parseLong(jobName);
        // TODO 获取订单及用户信息,封装短信内容,调用短信发送模块发送短信
        quartzService.modifyTaskStatus(jobName, "1");
    }

}

在这个Job中,由于Job的jobName被设为的订单的id,所以我们可以通过订单的id查询到订单以及用户的相关信息,然后封装短信的内容,进行发送短信操作。由于发短信是一次性任务,那么在结束后应该修改这条任务的状态为已结束,不然程序重启后这个任务又被恢复了,又会给用户发送重复的信息。

QuartzUtil的使用大概就是这样,用起来还是非常简单的。

【Quartz】Quartz定时任务框架 Quartz是一个框架,是SpringBoot御用的定时任务框架,有三大核心部分组成,分别是JobTriggerScheduler。Job:任务,可以理解成你要做什么事,具体要执行的业务逻辑,比如:发送短信、同步数据、发送邮件等。Trigger:触发器,可以理解成你什么时候去做,用来定义Job触发条件、触发时间、触发间隔、终止时间等。Scheduler:调度器,可以理解成你什么时候要去做什么事,它会启动Trigger去执行Job。/*** @author : 黎剑。 阅读详情

相关推荐

Quartz完全开发手册(一篇学会Quartz所有知识点)

Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz是开源且具有丰富特性的"任务调度库",能够集成于任何的java应用,小到独立的应用,大至电子商业系统。Quartz能够创建亦简单亦复杂的调度,以执行上十、上百,甚至上万的任务。任务job被定义为标准的java组件,能够执行任何你想要实现的功能。quartz调度框架包含许多企业级的特性,如JTA事务、集群的支持。

hao_kkkkk的专栏 2191

SpringBoot整合Quartz超详细完整教程

SpringBoot整合Quartz全流程 一、开发环境准备 二、Quartz的实现 2.2、创建一个定时任务 创建的定时任务类需要继承QuzrtzJobBean,新建一个定时任务类:TaskForEvery

weixin_44966143的博客 6222

从零到一:OptimizePI Pre-layout PDN前仿真实战指南

本文详细介绍了如何使用OptimizePI进行电源完整性(PDN)前仿真,从软件安装配置到元器件建模、去耦电容配置及仿真参数设置,提供了实战技巧和常见问题排查指南。通过OptimizePI的Pre-layout仿真,工程师可以在PCB布局前优化电源分配网络,显著提升设计效率并降低成本。

weixin_28304023的博客 509

quartz详解

一名程序员网友发帖晒出了自己写的一段代码,是一段定时代码,根据他的语气,可以看出他对自己写的代码感觉很好,是一段java代码,好家伙,代码中多线程都用上了,还有sleep,然后自己这样写了就直接被老板赶走了,走之前为了面子还说到,你这公司我还看不上呢,其实一想写的确实没问题功能能实现,但是.........

weixin_50098749的博客 1万+

通俗易懂的图解二叉搜索树、AVL树和红黑树

搜索是一种很常见的需求,当数据较多时,如何找到目标对象时,即需要搜索查找。 那么如何快速找到目标呢,最朴素的思想就是暴力查找,一个一个对比。 显然,当数据量增大时,我们需要优化查找方法获取更快的查找速度,我们可以通过维护一个有序数组,从而实现二分查找,查找速度从O(n)降为O(longn)。 但是数组并不便于插入和删除,链表便成为了最好的选择。 既然是采用二分查找,那么不如直接按照查找的步骤维护一种数据结构,于是二叉搜索树出现了。 二叉搜索树的性质为: 若左子树不为空,则左子树上所有节点值都小于根结点值;

暗色光的博客 758

别再到处找包了!Keil5芯片支持包(Pack)最全管理指南:安装、更新、迁移与离线备份

本文详细解析了Keil5芯片支持包(Pack)的全生命周期管理,包括安装、更新、迁移与离线备份的工程化解决方案。针对STM32F10x等常见芯片,提供了高效获取Pack的策略、版本管理技巧及团队协作方案,帮助开发者解决常见的'Device Family Pack missing'等问题,提升嵌入式开发效率。

weixin_28729205的博客 542

Quartz 快速入门案例,看这一篇就够了

Quartz 是基于 Java 实现的任务调度框架,对任务的创建、修改、删除、触发以及监控这些操作直接提供了 api,这意味着开发人员拥有最大的操作权,也带来了更高的灵活性。

~~~~~~~BUG FLY~~~~~~~~~ 1万+

苹果iOS设备解锁软件:iToolab UnlockGo

iToolab UnlockGo是Mac上一款具有解锁屏幕密码、移除iCloud激活锁、移除Apple ID、关闭FMI、解锁屏幕使用时间和绕过MDM功能的苹果设备解锁工具,UnlockGo只需要几分钟,就能够快速为您解决IOS设备的密码遗忘、设备被禁用、脸部/触控ID不可用、密码已过期等问题! 即时解锁iPhone / iPad的密码 使用iToolab UnlockGo,您可以轻松绕过iPhone密码并获得对该设备的完全访问权限。它适用于以下情况,并且完全支持各种密码。UnlockGo支持轻松删除所有

macxingchendahai的博客 1万+

Quartz入门看这一篇文章就够了

1Quartz [kwɔːts]是一个完全由Java编写的开源的作业调度框架Job接口1首先我们需要定义实现一个定时功能的接口,我们可以称之为Task(或Job)如定时发送邮件的task(Job)重启机器的task(Job)优惠券到期发送短信提醒的task(Job)Trigger [ˈtrɪɡə(r)] 触发器1有了任务之后,还需要一个能够实现触发任务去执行的触发器,触发器Trigger最基本的功能是指定Job的执行时间,执行间隔,运行次数等。

IT枫斗者的博客 2340

C#实战:通过ZPL指令与斑马打印机交互及状态监控全解析

本文全面解析了在C#中通过ZPL指令与斑马打印机进行交互及状态监控的实战方法。文章详细介绍了TCP/IP网络打印和Windows Raw打印两种核心连接方式,并深入讲解了ZPL指令的构建。重点阐述了通过SGD命令和斑马官方SDK实现实时打印状态监控的策略,为工业自动化场景下的可靠打印提供了完整解决方案。

weixin_29294395的博客 117

深入理解 ABAP CDS Analytical Query 中的 Measures,从金额语义到缩放、精度与符号显示

在 ABAP CDS Analytical Query 中,Measure 不仅是数值字段,更是承载聚合、计算与语义的分析对象。它需通过 @Semantics.amount.currencyCode 和 @Semantics.quantity.unitOfMeasure 显式绑定币种与单位,确保计算正确性。复杂查询中,虚拟字段(如 virtual CurrencyClosedSalesOrders)用于维护结果的语义上下文。此外,@UI.hidden 与 @Consumption.hidden 控制显示与使

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 25

【Quartz】Quartz概述及入门实例

Quartz 在开源任务调度框架中的翘首,它提供了强大任务调度机制,难能可贵的是它同时保持了使用的简单性。Quartz 允许开发人员灵活地定义触发器的调度时间表,并可以对触发器和任务进行关联映射。此外,Quartz提供了调度运行环境的持久化机制,可以保存并恢复调度现场,即使系统因故障关闭,任务调度现场数据并不会丢失。此外,Quartz还提供了组件式的侦听器、各种插件、线程池等功能。

Evankaka的专栏 7675

【LLM数据篇】预训练数据集+指令生成sft数据集

在《Aligning Large Language Models with Human: A Survey》综述中对LLM数据分类为典型的人工标注数据、self-instruct数据集等 优秀的开源sft数据集:alpaca_data、belle、千言数据集、firefly、moss-003-sft-data多轮对话数据集等 文章目录 note 构造指令实例的3种方式 sft:Aligning Large Language Models with Human: A Survey Alpaca 1. self

发现问题,并解决问题,批判性思维 1万+

【Quartz篇】Quartz快速入门以及简单原理概述

我们需要明白 Quartz 的几个核心概念,这样理解起 Quartz 的原理就会变得简单了。1.先使用springboot脚手架创建一个springboot项目(不再赘述)3.定义一个自己的job类实现job接口。5.运行可以看到我们自己定义的。

m0_53732433的博客 611

IgH EtherCAT 从入门到精通:第 2 章 环境搭建与编译安装

环境搭建与编译安装指南 本章详细介绍了搭建IgH EtherCAT Master开发环境的完整流程。内容包括: 硬件要求:支持多种Intel和Realtek网卡的原生驱动,以及通用网卡的generic驱动选项 软件依赖:列出Linux系统下所需的工具链和开发包 源码获取:通过Git克隆官方仓库并切换稳定版本的方法 配置选项:详细解析bootstrap脚本和configure的核心参数,包括路径设置、功能开关和实时框架选项 编译流程:从生成构建系统到最终编译安装的完整步骤 通过本章指导,读者可以完成一个可运行

从事嵌入式linux android驱动开发,拥有全栈能力 482

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

Quartz - Quartz简单入门

一、相关介绍      Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,是完全由Java开发的一个开源的任务日程管理系统 二、概念和类     Job:要执行的作业类所实现的接口     JobDetail:定义作业实例的详细信息属性     Trigger:触发器接口,Quartz中提供了SimpleTrigger、CronTirgg

袭冷 604

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

Quartz框架快速入门

创建一个quartz包,创建自定义的Job任务类使用@Scheduled 定义某个定时作业(或工作)@Component@Slf4j”)log.info(“任务正执行:” + System.currentTimeMillis());这就是敏捷式开发,就是这么开心。Quartz是一个开源的任务调度框架。作用是基于定时、定期的策略来执行任务。它是OpenSymphony开源组织在Job scheduling领域又一个开源项目。“任务进度管理器”就是一个。

llg3189609554的博客 1600

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

Quartz入门

Quartz入门Quartz介绍Quartz的触发器存储方式JDBCJobStor存储中表关系和说明核心类和核心类之间关系配置文件quartz.propertiesJDBC插入表顺序Quartz简单入门Demo(RAM方式),版本quartz.2.2.1cronExpression表达式解释 Quartz介绍     任务调度框架“Quartz”是OpenSymphony开源组织在Job scheduling领域又一个开源项目,是完全由java开发的一个开源的任务日程

TZyybhs的博客 398

quartz 每月一次_入门Java开源任务调度框架-Quartz(后篇)

1) 读前须知本文是《入门Java开源任务调度框架-Quartz(前篇)》的后续文章,是对前篇的补充,请结合前篇阅读!2)关于JobDataMap数据获取的补充在前篇中,我们讲述了JobDataMap的数据获取方式,尽管后面通过使用getMergeJobDataMap方法使得数据获取简单了很多,但是我们还有另一种选择,那就是在Job的实现类中定义对应于JobDetail和Trigger中JobDa...

weixin_31682257的博客 166

【任务调度】Quartz入门

Quartz 是一个开源的作业调度框架,它完全由 Java 写成。Quartz 自带了集群方案。它通过将作业信息存储到关系数据库中,并使用关系数据库的行锁来实现执行作业的竞争,从而保证多个进程下,同一个任务在相同时刻,不能重复执行。Scheduler :调度器Trigger :触发器Job :任务Quartz 分成【单机模式】和【集群模式】。在生产环境下,一定一定一定要使用 Quartz 的集群模式,保证定时任务的高可用。JobStore。

沉梦听雨的博客 1408

Quartz快速入门

一、Quartz概念 Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与2EE与|2SE应用程序相结合也可以单独使用。 quartz是开源且具有丰富特性的"任务调度库",能够集成于任何的java应用,小到独立的应用,大至电子商业系统。quartz能够创建亦简单亦复杂的调度,以执行上十、上百,甚至上万的任务。任务job被定义为标准的java组件,能够执行任何你想要实现的功能。quartz调度框架包含许多企业级的特性,如JTA事务、集群的支持。 简而言

程序猿_杨Sir 3930

quartz框架_精进Quartz—Quartz简单入门Demo(二)

欢迎关注我的公众号: Java编程技术乐园。分享技术,一起精进Quartz!做一个积极的人编码、改bug、提升自己我有一个乐园,面向编程,春暖花开!要学习Quartz框架,首先大概了解了Quartz的基本知识后,在通过简单的例子入门,一步一个脚印的走下去。 下面介绍Quartz入门的示例,由于Quartz的存储方式分为RAM和JDBC,分别对这两种进行简单的说明。并附上代码! 首先需要添加Quar...

weixin_39994461的博客 101

Quartz 入门

Quartz是一个Java开源作业调度框架。用于创建及管理定时任务。

KEEP CODING 997

Quartz_框架快速入门

Quartz 框架快速入门(一)       创建一个 Java 工程,引入几个 JAR 到工程中才能成功构建它们。首先,你需要 Quartz 的二进制版本,包的名字是 quartz-.jar。Quartz 还需要几个第三方库;这依赖于你要用到框架的什么功能而定,Commons Digester 库可以在 /lib/core 和 /lib/optional 目录中找到。如果出现java.

Hello World! 1430
上一篇: Java (JVM) 内存模型
下一篇: 看完阿里神作Spring Security应用到源码手册,已经给跪了
Java技术那些事儿
博客等级 码龄5年 2306粉丝 2584原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值