什么是Quartz?

官方的介绍

Quartz是一个功能丰富的开源作业调度库,可以集成到几乎任何Java应用程序中——从最小的独立应用程序到最大的电子商务系统。Quartz可用于创建简单或复杂的调度,以执行数以万计、数百甚至数万个作业;任务被定义为标准Java组件的作业,这些组件实际上可以执行您可以编程让它们执行的任何操作。Quartz调度程序包含许多企业级特性,比如支持JTA事务和集群

简单来说就是定时调度任务的工具。

贴一下架构图:

quartz-structure

基本概念

Job:是一个接口,只有一个方法void execute(JobExecutionContext context),开发者实现该接口定义运行任务,JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中;

JobDetail:Quartz在每次执行Job时,都重新创建一个Job实例,所以它不直接接受一个Job的实例,相反它接收一个Job实现类,以便运行时通过newInstance()的反射机制实例化Job。因此需要通过一个类来描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等信息,JobDetail承担了这一角色。

Trigger:是一个类,描述触发Job执行的时间触发规则。主要有SimpleTrigger和CronTrigger这两个子类。当仅需触发一次或者以固定时间间隔周期执行,SimpleTrigger是最适合的选择;而CronTrigger则可以通过Cron表达式定义出各种复杂时间规则的调度方案:如每早晨9:00执行,周一、周三、周五下午5:00执行等;

Cron表达式

字段 允许值 允许的特殊字符
0-59 , - * /
0-59 - * /
小时 0-23 , - * /
日期 1-31 , - * ? / L W C
月份 1-12 或者 JAN-DEC , - * /
星期 1-7 或者 SUN-SAT , - * ? / L C #
年(可选) 留空, 1970-2099 , - * /

例如:

表示式 说明
“0 0 12 * * ? “ 每天12点运行
“0 15 10 ? * *” 每天10:15运行
“0 15 10 * * ?” 每天10:15运行
“0 15 10 * * ? *” 每天10:15运行
“0 15 10 * * ? 2008” 在2008年的每天10:15运行
“0 * 14 * * ?” 每天14点到15点之间每分钟运行一次,开始于14:00,结束于14:59。
“0 0/5 14 * * ?” 每天14点到15点每5分钟运行一次,开始于14:00,结束于14:55。
“0 0/5 14,18 * * ?” 每天14点到15点每5分钟运行一次,此外每天18点到19点每5钟也运行一次。
“0 0-5 14 * * ?” 每天14:00点到14:05,每分钟运行一次。
“0 10,44 14 ? 3 WED” 每年3月的星期3下午14:10和14:44分各执行一次
“0 15 10 ? * MON-FRI” 每周一,二,三,四,五的10:15分运行。
“0 15 10 15 * ?” 每月15日10:15分运行。
“0 15 10 L * ?” 每月最后一天10:15分运行。
“0 15 10 ? * 6L” 每月最后一个星期五10:15分运行。
“0 15 10 ? * 6L 2007-2009” 在2007,2008,2009年每个月的最后一个星期五的10:15分运行。
“0 15 10 ? * 6#3” 每月第三个星期五的10:15分运行。

Scheduler:代表一个Quartz的独立运行容器,Trigger和JobDetail可以注册到Scheduler中,两者在Scheduler中拥有各自的组及名称,组及名称是Scheduler查找定位容器中某一对象的依据,Trigger的组及名称必须唯一,JobDetail的组和名称也必须唯一(但可以和Trigger的组和名称相同,因为它们是不同类型的)。Scheduler定义了多个接口方法,允许外部通过组及名称访问和控制容器中Trigger和JobDetail。

Scheduler可以将Trigger绑定到某一JobDetail中,这样当Trigger触发时,对应的Job就被执行。一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job。

测试Demo

先贴一下整体结构

quartz-demo

调度作业对象类

  1. 使用 jpa 生成对应调度表格
@Entity
@Table
public class ScheduleJob {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String JobName;
private String cronExpression;
private String springId;
private String methodName;
private String jobStatus;
//省略get set
//配置文件 
server.port= 8080
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot_test?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=123456

spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql= true
  1. jpa 接口实现
@NoRepositoryBean
public interface BaseRepository<T,I extends Serializable> extends PagingAndSortingRepository<T,I>, JpaSpecificationExecutor<T> {
}


public interface ScheduleJobRepository extends BaseRepository<ScheduleJob,Long>{
List<ScheduleJob> findAllByJobStatus(String jobStatus);
}

工厂工具类

@Component
public class SpringUtil implements BeanFactoryPostProcessor {
private static ConfigurableListableBeanFactory beanFactory;

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)throws BeansException {
this.beanFactory = beanFactory;
}

public static Object getBean(String name) {
return beanFactory.getBean(name);
}
public static <T> T getBean(Class<T> clazz){
return beanFactory.getBean(clazz);
}
}

调度方法以及接口

public interface ITaskService {
public void timingTask();
}

具体实现就是先从数据库表中查询数据,然后拼装成调度器,插入的测试数据。

quartz-table

@Service
@Transactional
public class TaskServiceImpl implements ITaskService {

@Autowired
private SchedulerFactoryBean schedulerFactoryBean;
@Autowired
private ScheduleJobRepository scheduleJobRepository;

@Override
public void timingTask() {
//查询数据库是否存在需要定时的任务
List<ScheduleJob> scheduleJobs = scheduleJobRepository.findAllByJobStatus("1");
if (scheduleJobs != null) {
scheduleJobs.forEach(this::execute);
}
}

//添加任务
private void execute(ScheduleJob scheduleJob){
try {
//声明调度器
Scheduler scheduler = schedulerFactoryBean.getScheduler();
//添加触发调度名称
TriggerKey triggerKey = TriggerKey.triggerKey(scheduleJob.getJobName());
//设置触发时间
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression());
//触发建立
Trigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
//添加作业名称
JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName());
//建立作业
JobDetail jobDetail = JobBuilder.newJob(QuartzFactory.class).withIdentity(jobKey).build();
//传入调度的数据,在QuartzFactory中需要使用
jobDetail.getJobDataMap().put("scheduleJob",scheduleJob);
//调度作业
scheduler.scheduleJob(jobDetail,trigger);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

具体调度方法

这里只是简单的测试打印时间

public interface ITaskInfoService {
public void execute();
}

@Service("taskInfo")
@Transactional
public class TaskInfoServiceImpl implements ITaskInfoService {
@Override
public void execute() {
System.out.println("任务执行开始===============任务执行开始");
System.out.println("测试任务:"+new Date());
System.out.println("任务执行结束===============任务执行结束");
}
}

调度工厂

CommandLineRunner接口的作用

SpringBoot提供的一个CommandLineRunner接口,通过实现该接口可以在项目启动后执行指定任务,如果需要按照一定的顺序去执行,就需要在实体类上使用一个@Order注解,值越小优先级越高。
应用场景如:加载字典到缓存

实例

@Component
@Order(0)
public class StartupRunner2 implements CommandLineRunner {
private Logger logger = LoggerFactory.getLogger(StartupRunner2.class);

@Override
public void run(String... args) throws Exception {
logger.info(">>服务启动执行,执行加载数据等操作0<<");
}
}

项目中使用:

@Component
public class TaskSchedule implements CommandLineRunner {
@Autowired
private ITaskService taskService;

@Override
public void run(String... args) throws Exception {
System.out.println("任务调度开始==============任务调度开始");
taskService.timingTask();
System.out.println("任务调度结束==============任务调度结束");
}
}

Job类的实现

 * @Describle:任务的调度工厂(主要是实现具体的执行
*/
public class QuartzFactory implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
//获取数据调度
ScheduleJob scheduleJob = (ScheduleJob) jobExecutionContext.getMergedJobDataMap().get("scheduleJob");

//获取对应的bean service
Object object = SpringUtil.getBean(scheduleJob.getSpringId());

try{
//利用反射执行相应方法
Method method = object.getClass().getMethod(scheduleJob.getMethodName());
method.invoke(object);
}catch (Exception e){
e.printStackTrace();
}

}
}

忘记还有配置类了

@Configuration
@EnableScheduling
public class TaskConfiguration {

@Bean
public SchedulerFactoryBean schedulerFactoryBean(){
return new SchedulerFactoryBean();
}
}

测试效果

quartz-test