Quartz是一个定时任务框架,相比较于spring boot 中的@Scheduler可以指定复杂的调度并且可以持久化任务(数据库),其开发难度也比Scheduler难一些.
spring boot @Scheduler创建异步定时任务链接
本文只做springboot与quartz整合的案例,详细了解quartz请看另外一篇博文
quartz使用连接
一 依赖
1 如果是使用的boot与quartz整合的包,只需要添加进去即可
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
</dependencies>
2 如果使用单独的quartz包,还需要添加其他依赖,并需要配置dataSource
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
</parent>
<!-- 依赖管理 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Freemark support -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--c3p0-->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!-- mysql driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 任务调度 -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.3</version>
</dependency>
这里是基于c3p0,虽然我们开发中都会配置数据源连接池,但是如果单独开发quartz没有配置dataSource的话创建schedulerFactoryBean会报错.
@Configuration
public class DatasourceConfiguration {
@Bean(name = "dataSource")
@Qualifier(value = "dataSource")
@Primary
@ConfigurationProperties(prefix = "c3p0")
public DataSource dataSource()1
{
return DataSourceBuilder.create().type(com.mchange.v2.c3p0.ComboPooledDataSource.class).build();
}
}
二 Quatz的重要组成部分:
API
Scheduler:与被调度的job交互的主要API,负责Job调度执行监控
JobDetail:接收一个job实例并包含job的其他信息
Job:任务执行的逻辑
Trigger:触发器,即任务执行计划,规定了任务的执行策略
了解java多线程的话,可以把jobDetail想象成Thread,job想象成runnable(只是这样想象)
配置文件quartz.poperties(resource目录下)
# 固定前缀org.quartz
# 主要分为scheduler、threadPool、jobStore、plugin等部分
# 实例化ThreadPool时,使用的线程类为SimpleThreadPool
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
# threadCount和threadPriority将以setter的形式注入ThreadPool实例
# 并发个数
org.quartz.threadPool.threadCount = 300
# 优先级
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
org.quartz.jobStore.misfireThreshold = 5000
# 默认存储在内存中
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
#持久化
#org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
#org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#org.quartz.jobStore.tablePrefix = QRTZ_
#org.quartz.jobStore.dataSource = qzDS
#org.quartz.dataSource.qzDS.driver = com.mysql.cj.jdbc.Driver
#org.quartz.dataSource.qzDS.URL = jdbc:mysql://10.108.6.123:3306/anjiplus_datacollect?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
#org.quartz.dataSource.qzDS.user = dev
#org.quartz.dataSource.qzDS.password = dev
#org.quartz.dataSource.qzDS.maxConnections = 10
#org.quartz.dataSource.qzDS.connectionProvider.class=com.anjiplus.datacollect.schedule.DruidConnectionProvider
注:这里没有做持久化(如果需要做持久化,到官网中去找建表语句)
三 实际开发
首先添加一个配置类来获取Sheculer实例(直接贴入的项目中即可)
@Configuration
public class SchedulerConfig {
@Bean(name="SchedulerFactory")
public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setQuartzProperties(quartzProperties());
return factory;
}
@Bean
public Properties quartzProperties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
//在quartz.properties中的属性被读取并注入后再初始化对象
propertiesFactoryBean.afterPropertiesSet();
Properties properties = propertiesFactoryBean.getObject();
properties.put("org.quartz.scheduler.instanceName", "DefaultQuartzScheduler");
return properties;
}
/*
* quartz初始化监听器
*/
@Bean
public QuartzInitializerListener executorListener() {
return new QuartzInitializerListener();
}
/*
* 通过SchedulerFactoryBean获取Scheduler的实例
*/
@Bean(name="Scheduler")
public Scheduler scheduler() throws IOException, SchedulerException {
return schedulerFactoryBean().getScheduler();
//return StdSchedulerFactory.getDefaultScheduler();
}
}
创建一个类用于启动scheduler并初始化job
@Component
public class ScheduleJobManager {
private static Logger logger = LogManager.getLogger(ScheduleJobManager.class);
//加入Qulifier注解,通过名称注入bean
@Autowired
@Qualifier("Scheduler")
private Scheduler scheduler;
@Autowired
private MonitorJob monitorJob;
@PostConstruct
//此注解是实例化这个类时,执行init方法,并且只执行一次
//把job添加到scheduler中监控执行
public void init() {
// 启动调度器
try {
if (scheduler != null && !scheduler.isStarted()) {
scheduler.start();
logger.info("ScheduleJobManager任务调度服务启动成功!!");
///启动monitorjob
scheduleMonitorJob();
}
} catch (Exception e) {
logger.error("ScheduleJobManager任务调度服务启动出错,err=", e);
}
}
/**
* 添加job
*/
private void scheduleMonitorJob() {
try {
//构建任务
String jobName = "monitorJobProxy";
JobDetail jobDetail = JobBuilder.newJob(MonitorJobProxy.class).withIdentity(jobName).build();
//传递job,不传递的话可以在MonitorJobProxy中直接写入MonitorJob中的处理过程
jobDetail.getJobDataMap().put("monitorJob", monitorJob);
//表达式调度构建器(即任务执行的时间)
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("*/1 * * * * ?");
//按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName)
.withSchedule(scheduleBuilder).build();
//SimpleTrigger用来创建简单调度
// SimpleTrigger trigger = TriggerBuilder.newTrigger()
// .withIdentity(jobName)
// .startAt(new Date())
// .withSchedule(
// SimpleScheduleBuilder.simpleSchedule()
// .withIntervalInSeconds(1)
// .withRepeatCount(0)
// .build();
scheduler.scheduleJob(jobDetail, trigger);
logger.info("MonitorJob成功!");
} catch (Exception e) {
logger.error("MonitorJob失败 ,err=" + e);
}
}
}
///
@Component
public class MonitorJob {
private static Logger logger = LogManager.getLogger(MonitorJob.class);
@Autowired
private ScheduleJobManager scheduleManagement;
public String getJobName() {
return "主监控job,定时监控&分配任务";
}
public void work(JobExecutionContext content) {
logger.info("任务开始");
logger.info("任务结束");
}
}
///
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class MonitorJobProxy implements Job{
private MonitorJob monitorJob;
@Override
public void execute(JobExecutionContext content) throws JobExecutionException {
JobDataMap jobDataMap = content.getJobDetail().getJobDataMap();
monitorJob = (MonitorJob)jobDataMap.get("monitorJob");
monitorJob.work(content);
}
}
@PersistJobDataAfterExecution
此直接是允许JobDataMap能够更新值(当前job更新,下次job使用新值)
JobDataMap是JobDetail与每次Job实例沟通的介质,每次执行的job实例都可以从jobDatail中的JobDataMap获取里面有的变量值.
@DisallowConcurrentExecution
不允许job并行执行,注意这里说的是job而不是JobDetail,也就是说同一个job多次执行时,前一个job不执行完,后一个job是不允许执行的.
四 Job类中注入Spring Bean问题:
在MonitorJobProxy 中,我们如果想要注入spring boot 容器管理的bean,一种方式是通过其他地方注入并通过JobDataMap传递,第二种方式是new出来,因为在调度时,Job是Scheduler通过反射出来的,所以不受spring boot容器管理,自然就不能自动够注入spring boot的bean.
解决方法有两个:
.第一种是自定义JobFactory,使用AutowireCapableBeanFactory 在Job创建好后,将其中@Autowired的bean注入.
@Compenent
public class JobFactory extends AdaptableJobFactory {
@Autowired
private AutowireCapableBeanFactory beanFactory;
@Override
protected Object createJobInstance(TriggerFiredBundle bundle)
throws Exception {
// TODO Auto-generated method stub
Object jobInstance = super.createJobInstance(bundle);
//AutowireCapableBeanFactory将job中的bean注入
beanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
重写上方SchedulerConfig配置类
@Autowired
JobFactory jobFactory;
@Bean(name="SchedulerFactory")
public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setJobFactory(jobFactory);
factory.setStartupDelay(5);
factory.setQuartzProperties(quartzProperties());
return factory;
}
上方MonitorJob 添加了@Component所以受spring管理,重写MonitorJobProxy
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class MonitorJobProxy implements Job{
//直接注入就好,JobFactory在创建MonitorJobProxy实例后把MonitorJob注入进来
@Autowired
private MonitorJob monitorJob;
@Override
public void execute(JobExecutionContext content) throws JobExecutionException {
monitorJob.work(content);
}
}
第二种,我们通过代码中去获取spring容器管理的bean,创建一个Utils,用来获取springboot容器中的bean
@Component
public final class SpringUtils implements BeanFactoryPostProcessor
{
/** Spring应用上下文环境 */
private static ConfigurableListableBeanFactory beanFactory;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException
{
SpringUtils.beanFactory = beanFactory;
}
/**
* 获取对象
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) throws BeansException
{
return (T) beanFactory.getBean(name);
}
}
DataProcessJobProxy :
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class DataProcessJobProxy implements Job{
private DataProcessJob dataProcessJob ;
@Override
public void execute(JobExecutionContext content) throws JobExecutionException {
dataProcessJob = SpringUtils.getBean("DataProcessJob");
dataProcessJob.work(content);
}
}