什么是Quartz?
官方的介绍
Quartz是一个功能丰富的开源作业调度库,可以集成到几乎任何Java应用程序中——从最小的独立应用程序到最大的电子商务系统。Quartz可用于创建简单或复杂的调度,以执行数以万计、数百甚至数万个作业;任务被定义为标准Java组件的作业,这些组件实际上可以执行您可以编程让它们执行的任何操作。Quartz调度程序包含许多企业级特性,比如支持JTA事务和集群
简单来说就是定时调度任务的工具。
贴一下架构图:
基本概念
●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
先贴一下整体结构
调度作业对象类
- 使用 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;
|
//配置文件 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
|
- 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(); }
|
具体实现就是先从数据库表中查询数据,然后拼装成调度器,插入的测试数据。
@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(); 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");
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(); } }
|
测试效果