摘要

spring ,springboot整合quartz-2.3.2,实现spring管理jobBean

本文不涉及 JDBC存储的方式,springboot yml配置也没有 可自行百度 谷歌

本项目源码gitee地址 quartz-demo

需求

比如发送邮件消息 在夜晚空闲时大批量更新统计数据,定时更新数据

1.0 spring scheduling

在看quartz之前想要先说一下 spring自带的定时任务框架 spring-scheduling org.springframework.scheduling.annotation.Scheduled

相比于quartz,spring scheduling更加的轻量级 使用配置非常的简单(基于注解开发) 是实现简单需求时的最佳选择

1.1 开启scheduling配置

      <task:annotation-driven />

或者配置类添加注解 @EnableScheduling 使用@Scheduled

官方注释如下

  Processing of {@code @Scheduled} annotations is performed by
registering a {@link ScheduledAnnotationBeanPostProcessor}. This can be
done manually or, more conveniently, through the {@code <task:annotation-driven/>}
element or @{@link EnableScheduling} annotation.

1.2 @Scheduled 内容

其中 cron fixedDelay(fixedDelayString) fixedRate(fixedRateString) 这三个属性有且只能配置一个 配置错误会有类似提示

2.0 下面说说 quartz的配置使用

       <!--  springboot项目引入  -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<!-- spring项目引入 -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.3.2</version>
</dependency>

2.1 quartz api

job: 定时任务执行的业务代码层 可以通过实现job接口 或者 继承 QuartzJobBean 实现
JobDetail: 用来描述任务的分组,名称 可以通过jobbuilder(推荐) 或者 factoryBean实现,需要将job.class 传入JobDetail中
trigger: 执行任务的触发条件 子类SimpleTrigger,CronTrigger.可由TriggerBuilder构建
可以设置 触发器的的名称 和分组 dataMap,trigger是载体
SimpleScheduleBuilder,CronScheduleBuilder 这俩builder才是设置执行周期的类
Scheduler: 负责调度 job 含有 Trigger 和job信息 spring框架中 由 SchedulerFactoryBean创建
JobListener: job trigger Scheduler均有对应的Listener 在任务初始化,执行异常 ,执行结束 可以插入具体的动作
Listener 在 scheduler添加job时可以绑定 scheduler.getListenerManager().addJobListener(new OneJobListener());

2.2 测试先行

先写一个简单的测试类


/**
* demo class
*/
public class HelloJob implements Job { @Override
public void execute(JobExecutionContext context) throws JobExecutionException { JobDataMap jobDataMap = context.getTrigger().getJobDataMap();
Object t1 = jobDataMap.get("t1");
Object t2 = jobDataMap.get("t2");
Object j1 = jobDataMap.get("j1");
Object j2 = jobDataMap.get("j2"); Object sv = null ; try { sv = context.getScheduler().getContext().get("skey"); }catch (Exception e){
e.printStackTrace();
}
String limiter = ":" ; System.out.println(t1+limiter+j1);
System.out.println(t2+limiter+j2);
System.out.println(sv); System.out.println("hello"+ LocalDateTime.now()); }
}
测试 public class QuartzTestSchedule { @Test
@SneakyThrows
public void test01(){ Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); scheduler.start(); scheduler.shutdown(); }
@Test
@SneakyThrows
public void test02(){ Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); SchedulerContext context = scheduler.getContext();
context.put("skey","this is svalue"); SimpleTrigger simpleTrigger = TriggerBuilder.newTrigger()
.withIdentity("trigger01", "group01")
.usingJobData("t1", "t1_value")
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).repeatForever())
.build(); JobDetail jobDetail1 = JobBuilder.newJob(HelloJob.class)
.usingJobData("j1", "j1_value")
.withIdentity("myjob", "jobgroup01")
.build(); scheduler.scheduleJob(jobDetail1, simpleTrigger);
scheduler.start();
//防止主线程结束 不执行定时任务
Thread.sleep(10_000);
}
}

控制台输出

t1_value:null
null:null
this is svalue
hello2020-12-27T16:57:01.508
16:57:04.484 ['定时任务'_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'jobgroup01.myjob', class=site.culater.quartz.HelloJob
16:57:04.484 ['定时任务'_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
16:57:04.484 ['定时任务'_Worker-2] DEBUG org.quartz.core.JobRunShell - Calling execute on job jobgroup01.myjob
t1_value:null
null:null
this is svalue
hello2020-12-27T16:57:04.484
16:57:07.488 ['定时任务'_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'jobgroup01.myjob', class=site.culater.quartz.HelloJob
16:57:07.488 ['定时任务'_Worker-3] DEBUG org.quartz.core.JobRunShell - Calling execute on job jobgroup01.myjob
t1_value:null
null:null
this is svalue

3.0 创建demo项目

首先我们要先创建 quartz demo项目 直接创建springboot项目 引入依赖 过程略......

引入spring-boot-starter-quartz依赖后 因为springboot的自动配置 可以直接用quartz

quartz 默认使用 内存存储方式 JDBC存储的方式本文不涉及,如果是分布式部署 必须使用jdbc存储的方式 .

选择因项目需求定各有优劣

使用 quartz.properties文件

quartz-jar内置一份文件 位置:quartz-2.3.2.jar!\org\quartz\quartz.properties

# 实例名称 标识 无意义可随意设置
org.quartz.scheduler.instanceName: '定时任务'
org.quartz.scheduler.instanceId: 'new-quartz'
# 远程管理
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
# 是否启用事务 企业级功能
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false
#线程池实现类
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
# 线程池数
org.quartz.threadPool.threadCount: 5
# 执行优先级
org.quartz.threadPool.threadPriority: 5
#设置程序启动不执行
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
# 未执行确认时间
org.quartz.jobStore.misfireThreshold: 60000
# 默认内存 存储任务数据
org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore

quartz.properties配置文件并不是添加到resources目录下自动加载的 需要手动配置

指定 SchedulerFactoryBean 使用自己创建的

      /**
* 直接注入 Scheduler 是无效的
* 对应的xml'配置
* <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
* <property name="configLocation" value="classpath:quartz.properties" />
* // ...
* </bean>
* @return
*/
@Primary
@SneakyThrows
@Bean
public SchedulerFactoryBean schedule(CulaterSpringBeanJobFactory culaterSpringBeanJobFactory){ SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(); ClassPathResource configLocation = new ClassPathResource("quartz.properties"); schedulerFactoryBean.setConfigLocation(configLocation);
//配置spring管理创建 job 不必每次执行实例 创建job实例
schedulerFactoryBean.setJobFactory(culaterSpringBeanJobFactory);
// schedulerFactoryBean.afterPropertiesSet();
return schedulerFactoryBean ;
}

项目启动日志 可以看到设置的定时任务实例名称

2020-12-27 15:20:48.816  INFO 22532 --- [           main] org.quartz.impl.StdSchedulerFactory      : Quartz scheduler ''定时任务'' initialized from an externally provided properties instance.
2020-12-27 15:20:48.816 INFO 22532 --- [ main] org.quartz.impl.StdSchedulerFactory : Quartz scheduler version: 2.3.2
2020-12-27 15:20:48.816 INFO 22532 --- [ main] org.quartz.core.QuartzScheduler : JobFactory set to: site.culater.quartz.config.CulaterSpringBeanJobFactory@1aa61f3
2020-12-27 15:20:48.969 INFO 22532 --- [ main] o.s.s.c.ThreadPoolTaskScheduler : Initializing ExecutorService 'taskScheduler'
2020-12-27 15:20:49.001 INFO 22532 --- [ main] o.s.s.quartz.SchedulerFactoryBean : Starting Quartz Scheduler now
2020-12-27 15:20:49.001 INFO 22532 --- [ main] org.quartz.core.QuartzScheduler : Scheduler '定时任务'_$_'new-quartz' started.
2020-12-27 15:20:49.016 INFO 22532 --- [ main] site.culater.quartz.QuartzApplication : Started QuartzApplication in 2.02 seconds (JVM running for 4.17)

3.1 schedulerFactoryBean

schedulerFactoryBean 用来创建 Scheduler ,创建完成后再对 schedulerFactoryBean 是无效的,

但是我们从 spring容器中获得schedulerFactoryBean, get Scheduler是唯一的, 通过scheduler可以动态的添加 修改 删除 job的执行

创建 triggerBean配置类

/**
* 创建Trigger jobdetail使用
*/
@Configuration
public class TtriggerBean { @Bean("CulaterJob01")
public CulaterJob getCulaterJob01(){ JobDetail jobDetail = JobBuilder.newJob(OneJob.class).build();
SimpleTrigger trigger = TriggerBuilder.newTrigger()
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withRepeatCount(10).withIntervalInSeconds(3))
//.forJob(jobDetail)
.startNow().build(); CulaterJob culaterJob = CulaterJob.builder().trigger(trigger).jobDetail(jobDetail).build();
return culaterJob;
} @Bean("CulaterJob02")
public CulaterJob getCulaterJob02(){ JobDetail jobDetail = JobBuilder.newJob(OneJob02.class).build();
SimpleTrigger trigger = TriggerBuilder.newTrigger()
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withRepeatCount(10).withIntervalInSeconds(3))
//.forJob(jobDetail)
.startNow().build(); CulaterJob culaterJob = CulaterJob.builder().trigger(trigger).jobDetail(jobDetail).build();
return culaterJob;
}
}

TriggerStart实现了ApplicationRunner接口 springboot在项目启动后 会自动执行run方法,

通过构造方法(lombok注解)注入 的 schedulerFactoryBean获取Scheduler

culaterJobList 获得所有的 CulaterJob在spring容器中的所有对象实例

trigger JobBuilder.*.forJob(jobDetail) 这种绑定任务的方法是无效的,必须使用 Scheduler同时添加 jobdetail和trigger

如果没有添加job 则会提示job不能为null ,job可以继承QuartzJobBean

注意:不能使用接口匿名内部类 或者内部类的方式创建Job 否则提示 newJob 方法执行失败

所以创建了 CulaterJob 用来传递上述jobdetail,trigger对象

/**
* springboot启动后添加定时任务
*/
@Component
@RequiredArgsConstructor
public class TriggerStart implements ApplicationRunner { private final SchedulerFactoryBean schedulerFactoryBean; private final List<CulaterJob> culaterJobList ; @Override
public void run(ApplicationArguments args) throws Exception { Scheduler scheduler = schedulerFactoryBean.getScheduler();
for (CulaterJob culaterJob : culaterJobList) {
scheduler.scheduleJob(culaterJob.getJobDetail(),culaterJob.getTrigger());
} }
}

这样一个定时任务就配置完成了 可以运行试试哦,下面我们看看 quartz的任务执行

3.2 从 SchedulerFactoryBean 看quartz

quartz默认每次定时任务运行时创建新的job实例执行后丢弃掉

SchedulerFactoryBean从字面上看就知道是创建Scheduler的工厂方法,这是spring 官方提供的

SchedulerFactoryBean在创建的时候可以设置读取 properties,可以配置jdbc 数据源

其中一个方法 setJobFactory 如果不设置 则默认AdaptableJobFactory

      prepareScheduler(){
.....
Scheduler scheduler = createScheduler(schedulerFactory, this.schedulerName);
populateSchedulerContext(scheduler); if (!this.jobFactorySet && !(scheduler instanceof RemoteScheduler)) {
// Use AdaptableJobFactory as default for a local Scheduler, unless when
// explicitly given a null value through the "jobFactory" bean property.
// 此处设置 默认
this.jobFactory = new AdaptableJobFactory();
}
if (this.jobFactory != null) {
if (this.applicationContext != null && this.jobFactory instanceof ApplicationContextAware) {
((ApplicationContextAware) this.jobFactory).setApplicationContext(this.applicationContext);
}
if (this.jobFactory instanceof SchedulerContextAware) {
((SchedulerContextAware) this.jobFactory).setSchedulerContext(scheduler.getContext());
}
scheduler.setJobFactory(this.jobFactory);
}
return scheduler; }
AdaptableJobFactory中创建实例的方法,打断点可以看到每次执行都会创建新的job实例
```java protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
Class<?> jobClass = bundle.getJobDetail().getJobClass();
return ReflectionUtils.accessibleConstructor(jobClass).newInstance();
}

添加测试类打印job类地址

public class OneJob02 extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException { Job jobInstance = context.getJobInstance();
System.out.println("OneTrigger----00002->"+jobInstance);
} }

控制台打印 可以看到每次打印的地址都不同 每次执行job的实例都是新创建的

OneTrigger----00002->site.culater.quartz.job.OneJob02@fdf5a4
OneTrigger----00002->site.culater.quartz.job.OneJob02@4b06ee
OneTrigger----00002->site.culater.quartz.job.OneJob02@3c4abb
OneTrigger----00002->site.culater.quartz.job.OneJob02@8ce917
OneTrigger----00002->site.culater.quartz.job.OneJob02@5806db

3.3 改进job实例创建 托管spring

如果定时任务执行的很频繁 我们不希望频繁的创建销毁实例 可以 继承SpringBeanJobFactory 重写 createJobInstance方法

schedulerFactoryBean.setJobFactory(culaterSpringBeanJobFactory);

只要任务通过spring创建实例则不需要再创建 否则创建新的 实例 也可以都创建实例 将单例多例的控制交给spring

@Component
public class CulaterSpringBeanJobFactory extends SpringBeanJobFactory { @Autowired
ApplicationContext applicationContext; @Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception { AutowireCapableBeanFactory autowireCapableBeanFactory = applicationContext.getAutowireCapableBeanFactory();
Class<? extends Job> jobClass = bundle.getJobDetail().getJobClass(); Object jobInstance = null;
try {
//如果可以从spring容器中获得job实例则不需调用父方法创建
jobInstance = autowireCapableBeanFactory.getBean(jobClass);
}
catch (BeansException e) {
// 此处屏蔽异常 没有找到更好的根据class 获得bean的方法
}
if (jobInstance == null) {
jobInstance = super.createJobInstance(bundle);
}
return jobInstance;
}
}

3.5 控制并发调度

quartz 默认是并发调度 可能会出现 上一个定时任务执行时间过长还没结束下一个任务就开始了

如果没有这方面的需求 则可以在job子类添加@DisallowConcurrentExecution 关闭并发执行

最后控制台的输出如下: 可以看到 OneJob一直是同一个实例 而且不进行并发调度,OneJob02每次都会创建一个新的实例

OneTrigger=>site.culater.quartz.job.OneJob@e8de5c
OneTrigger----00002->site.culater.quartz.job.OneJob02@fdf5a4
OneTrigger----00002->site.culater.quartz.job.OneJob02@4b06ee
OneTrigger=>site.culater.quartz.job.OneJob@e8de5c
OneTrigger----00002->site.culater.quartz.job.OneJob02@3c4abb
OneTrigger=>site.culater.quartz.job.OneJob@e8de5c
OneTrigger----00002->site.culater.quartz.job.OneJob02@8ce917
OneTrigger----00002->site.culater.quartz.job.OneJob02@5806db

4 使用idea开发能遇到的问题

  1. 控制台输出中文乱码 可以通过 File Encoding 设置 项目和系统编码为utf-8
  2. quartz.properties文件没有被更新至 编译后的目录 这时候可以 重新rebuild项目 然后增加删除文件就可以同步更新了(稍有延迟)
本项目源码gitee地址 quartz-demo

扑克牌的四种花色分别叫红桃、梅花、方块和黑桃。

The suits are called hearts, clubs, diamonds and spades

spring-quartz整合的更多相关文章

  1. Spring+quartz整合问题

    今天一开始在弄quartz的时候用的2.0.2的jar包整合Spring3.0.5的时候报错 Java.lang.IncompatibleClassChangeError: class org.spr ...

  2. Spring+Quartz 整合二:调度管理与定时任务分离

    新的应用场景:很多时候,我们常常会遇到需要动态的添加或修改任务,而spring中所提供的定时任务组件却只能够通过修改xml中trigger的配置才能控制定时任务的时间以及任务的启用或停止,这在带给我们 ...

  3. Hibernate + Spring (quartz) 整合懒(延迟)加载问题

    开发项目的时候 在一个Job中执行了数据库操作, 用的是懒加载,但是如下错误 org.hibernate.LazyInitializationException: failed to lazily i ...

  4. spring quartz整合实现定时器自动注解

    1.web.xml中添加侦听器 <listener>    <listener-class>org.springframework.web.context.ContextLoa ...

  5. SpringBoot之旅 -- 定时任务两种(Spring Schedule 与 Quartz 整合 )实现

    相关文章 Spring Boot 相关文章目录 前言 最近在项目中使用到定时任务,之前一直都是使用Quartz 来实现,最近看Spring 基础发现其实Spring 提供 Spring Schedul ...

  6. spring mvc 整合Quartz

    Quartz是一个完全由java编写的开源作业调度框架.不要让作业调度这个术语吓着你.尽管Quartz框架整合了许多额外功能, 但就其简易形式看,你会发现它易用得简直让人受不了!Quartz整合在sp ...

  7. spring boot 整合quartz ,job不能注入的问题

    在使用spring boot 整合quartz的时候,新建定时任务类,实现job接口,在使用@AutoWire或者@Resource时,运行时出现nullpointException的问题.显然是相关 ...

  8. spring boot整合quartz实现多个定时任务

        版权声明:本文为博主原创文章,转载请注明出处. https://blog.csdn.net/liuchuanhong1/article/details/78543574 最近收到了很多封邮件, ...

  9. Spring quartz Job不能依赖注入,Spring整合quartz Job任务不能注入

    Spring quartz Job不能依赖注入,Spring整合quartz Job任务不能注入 Spring4整合quartz2.2.3中Job任务使用@Autowired不能注入 >> ...

  10. Quartz与Spring的整合使用

    之前说到过Quartz的基本使用(猛戳这里看文章).在实际使用中,我们一般会将定时任务交由spring容器来管理.所以今天我们来说说Quartz与spring的整合. 咱们还是依照Quartz的三大元 ...

随机推荐

  1. 编曲技巧:使用FL Studio来制作停顿的效果

    停顿效果是一种在音乐创作中非常常用的音效,它能起到缓冲的作用,而且能使这段旋律更具节奏感,在比较激情的歌曲中尤为常见.例如知名歌手王力宏演唱的<火力全开>中就使用了停顿效果,为歌曲加了不少 ...

  2. LaTex源文件的基本结构

    默认编译器设置: Utf-8设置: 相关代码与注释: 显示效果:

  3. C#中的WinForm问题——如何设置窗体的大小为超过屏幕显示的最大尺寸?

    今天在学习C#时遇到了一个问题,在此记录下来,留作日后总结复习之用,也分享给有同样问题和困扰的园友. 我手上的电脑是笔记本电脑,屏幕的尺寸大小为1366*768,然而项目所使用的屏幕大小为1920*1 ...

  4. 2014_07_11_VGA基础及封装

    -- VR1201 Color Filter LCOS Microdisplays QVGA (320*240 Pixel) Color Filter LCOS Microdisplays -- VR ...

  5. Generalizing from a Few Examples: A Survey on Few-Shot Learning 小样本学习最新综述 | 三大数据增强方法

    目录 原文链接:小样本学习与智能前沿 01 Transforming Samples from Dtrain 02 Transforming Samples from a Weakly Labeled ...

  6. 【GDOI2007】JZOJ2020年8月10日提高组T1 夏娜的菠萝包

    [GDOI2007]JZOJ2020年8月10日提高组T1 夏娜的菠萝包 题目 Description 夏娜很喜欢吃菠萝包,她的经纪人RC每半个月就要为她安排接下来的菠萝包计划.今天是7月份,RC又要 ...

  7. Cassandra与职业发展 | 阿里云栾小凡 &#215; 蔚来汽车张旭东 &#215; 网龙阙乃祯

    # 活动精彩实录 | Cassandra与职业发展 点击此处观看完整活动录像​ 大家好,我叫邓为,我目前在DataStax担任领航架构师.我在DataStax工作了7年多的时间,也有7年多的Cassa ...

  8. Linux之【安装系统后的调优和安全设置】

    关闭SElinux功能 •修改配置文件使其永远生效 第一种修改方法vi vi /etc/sysconfig/selinuc 或者 vi /etc/selinux/config修改: SELINUX=d ...

  9. IAR编译错误Error[e16]: Segment ISTACK (size: 0xc0 align: 0) is too long for segment definition. At least 0x8 more bytes needed. The problem occurred while processing the segment

    问题:个人使用的是IARV9.10编译CC2541的工程,没有做任何修改,直接编译出现如下错误 Error[e16]: Segment ISTACK (size: 0xc0 align: 0) is ...

  10. vue中两行代码实现全选及子选项全部选中,则全选按钮选中,反之有一个没选中,就取消选中全选按钮

    every() 方法使用指定函数检测数组中的所有元素: 如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测. 如果所有元素都满足条件,则返回 true. 逻辑 ...