Spring/Spring boot正确集成Quartz及解决@Autowired失效问题
周五检查以前Spring boot
集成Quartz
项目的时候,发现配置错误,因此通过阅读源码的方式,探索Spring
正确集成Quartz
的方式.
问题发现
检查去年的项目代码,发现关于QuartzJobBean
的实现存在不合理的地方.
(1) 项目依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
</dependency>
</dependencies>
(2) 问题代码:
@Component
public class UnprocessedTaskJob extends QuartzJobBean {
private TaskMapper taskMapper;
@Autowired
public UnprocessedTaskJob(TaskMapper taskMapper){
this.taskMapper = taskMapper;
}
}
private JobDetail generateUnprocessedJobDetail(Task task) {
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put(UnprocessedTaskJob.TASK_ID, task.getId());
return JobBuilder.newJob(UnprocessedTaskJob.class)
.withIdentity(UnprocessedTaskJob.UNPROCESSED_TASK_KEY_PREFIX + task.getId(), UnprocessedTaskJob.UNPROCESSED_TASK_JOB_GROUP)
.usingJobData(jobDataMap)
.storeDurably()
.build();
}
(3) 提炼问题:
以上代码存在错误的原因是,UnprocessedTaskJob
添加@Component
注解,表示其是Spring IOC
容器中的单例
类.
然而Quartz
在创建Job
是通过相应的Quartz Job Bean
的class
反射创建相应的Job
.也就是说,每次创建新的Job
时,都会生成相应的Job
实例.从而,这与UnprocessedTaskJob
是单例
相冲突.
查看代码提交记录,原因是当时认为不添加@Component
注解,则无法通过@Autowired
引入由Spring IOC
托管的taskMapper
实例,即无法实现依赖注入
.
然而令人感到奇怪的是,当我在开发环境去除了UnprocessedTaskJob
的@Component
注解之后,运行程序后发现TaskMapper
实例依然可以注入到Job
中,程序正常运行...
Spring托管Quartz
代码分析
网上搜索Spring
托管Quartz
的文章,大多数都是Spring MVC
项目,集中于如何解决在Job
实现类中通过@Autowired
实现Spring
的依赖注入
.
网上大多实现均依赖SpringBeanJobFactory
去实现Spring
与Quartz
的集成.
/**
* Subclass of {@link AdaptableJobFactory} that also supports Spring-style
* dependency injection on bean properties. This is essentially the direct
* equivalent of Spring's {@link QuartzJobBean} in the shape of a Quartz
* {@link org.quartz.spi.JobFactory}.
*
* <p>Applies scheduler context, job data map and trigger data map entries
* as bean property values. If no matching bean property is found, the entry
* is by default simply ignored. This is analogous to QuartzJobBean's behavior.
*
* <p>Compatible with Quartz 2.1.4 and higher, as of Spring 4.1.
*
* @author Juergen Hoeller
* @since 2.0
* @see SchedulerFactoryBean#setJobFactory
* @see QuartzJobBean
*/
public class SpringBeanJobFactory extends AdaptableJobFactory
implements ApplicationContextAware, SchedulerContextAware {
}
/**
* {@link JobFactory} implementation that supports {@link java.lang.Runnable}
* objects as well as standard Quartz {@link org.quartz.Job} instances.
*
* <p>Compatible with Quartz 2.1.4 and higher, as of Spring 4.1.
*
* @author Juergen Hoeller
* @since 2.0
* @see DelegatingJob
* @see #adaptJob(Object)
*/
public class AdaptableJobFactory implements JobFactory {
}
通过上述代码以及注释可以发现:
(1) AdaptableJobFactory
实现了JobFactory
接口,可以藉此创建标准的Quartz
实例(仅限于Quartz
2.1.4及以上版本);
(2) SpringBeanJobFactory
继承于AdaptableJobFactory
,从而实现对Quartz
封装实例的属性依赖注入.
(3) SpringBeanJobFactory
实现了ApplicationContextAware
以及SchedulerContextAware
接口(Quartz
任务调度上下文),因此可以在创建Job Bean
的时候注入ApplicationContex
以及SchedulerContext
.
Tips:
以上代码基于Spring
5.1.8版本.
在Spring 4.1.0
版本, SpringBeanJobFactory
的实现如以下代码所示:
public class SpringBeanJobFactory extends AdaptableJobFactory
implements SchedulerContextAware{
// 具体代码省略
}
因此,在早期的Spring
项目中,需要封装SpringBeanJobFactory
并实现ApplicationContextAware
接口(惊不惊喜?).
Spring老版本解决方案
基于老版本Spring
给出解决Spring
集成Quartz
解决方案.
解决方案由第三十九章:基于SpringBoot & Quartz完成定时任务分布式单节点持久化提供(大神的系列文章质量很棒).
@Configuration
public class QuartzConfiguration
{
/**
* 继承org.springframework.scheduling.quartz.SpringBeanJobFactory
* 实现任务实例化方式
*/
public static class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements
ApplicationContextAware {
private transient AutowireCapableBeanFactory beanFactory;
@Override
public void setApplicationContext(final ApplicationContext context) {
beanFactory = context.getAutowireCapableBeanFactory();
}
/**
* 将job实例交给spring ioc托管
* 我们在job实例实现类内可以直接使用spring注入的调用被spring ioc管理的实例
* @param bundle
* @return
* @throws Exception
*/
@Override
protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
final Object job = super.createJobInstance(bundle);
/**
* 将job实例交付给spring ioc
*/
beanFactory.autowireBean(job);
return job;
}
}
/**
* 配置任务工厂实例
* @param applicationContext spring上下文实例
* @return
*/
@Bean
public JobFactory jobFactory(ApplicationContext applicationContext)
{
/**
* 采用自定义任务工厂 整合spring实例来完成构建任务
* see {@link AutowiringSpringBeanJobFactory}
*/
AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
jobFactory.setApplicationContext(applicationContext);
return jobFactory;
}
/**
* 配置任务调度器
* 使用项目数据源作为quartz数据源
* @param jobFactory 自定义配置任务工厂(其实就是AutowiringSpringBeanJobFactory)
* @param dataSource 数据源实例
* @return
* @throws Exception
*/
@Bean(destroyMethod = "destroy",autowire = Autowire.NO)
public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory, DataSource dataSource) throws Exception
{
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
//将spring管理job自定义工厂交由调度器维护
schedulerFactoryBean.setJobFactory(jobFactory);
//设置覆盖已存在的任务
schedulerFactoryBean.setOverwriteExistingJobs(true);
//项目启动完成后,等待2秒后开始执行调度器初始化
schedulerFactoryBean.setStartupDelay(2);
//设置调度器自动运行
schedulerFactoryBean.setAutoStartup(true);
//设置数据源,使用与项目统一数据源
schedulerFactoryBean.setDataSource(dataSource);
//设置上下文spring bean name
schedulerFactoryBean.setApplicationContextSchedulerContextKey("applicationContext");
//设置配置文件位置
schedulerFactoryBean.setConfigLocation(new ClassPathResource("/quartz.properties"));
return schedulerFactoryBean;
}
}
通过以上代码,就实现了由SpringBeanJobFactory
的createJobInstance
创建Job
实例,并将生成的Job
实例交付由AutowireCapableBeanFactory
来托管.
schedulerFactoryBean
则设置诸如JobFactory
(实际上是AutowiringSpringBeanJobFactory
,内部封装了applicationContext
)以及DataSource
(数据源,如果不设置,则Quartz
默认使用RamJobStore
).
RamJobStore
优点是运行速度快,缺点则是调度任务无法持久化保存.
因此,我们可以在定时任务内部使用Spring IOC
的@Autowired
等注解进行依赖注入
.
Spring新版本解决方案
(1) 解释
如果你使用Spring boot
,并且版本好大于2.0
,则推荐使用spring-boot-starter-quartz
.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
Auto-configuration support is now include for the Quartz Scheduler. We’ve also added a new spring-boot-starter-quartz starter POM.
You can use in-memory JobStores, or a full JDBC-based store. All JobDetail, Calendar and Trigger beans from your Spring application context will be automatically registered with the Scheduler.
For more details read the new “Quartz Scheduler” section of the reference documentation.
以上是spring-boot-starter-quartz
的介绍,基于介绍可知,如果你没有关闭Quartz
的自动配置,则SpringBoot
会帮助你完成Scheduler
的自动化配置,诸如JobDetail
/Calendar
/Trigger
等Bean
会被自动注册至Shceduler
中.你可以在QuartzJobBean
中自由的使用@Autowired
等依赖注入
注解.
其实,不引入spring-boot-starter-quartz
,而仅仅导入org.quartz-scheduler
,Quartz
的自动化配置依然会起效(这就是第一节问题分析中,去除@Bean
注解,程序依然正常运行原因,悲剧中万幸).
(2) 代码分析
/**
* {@link EnableAutoConfiguration Auto-configuration} for Quartz Scheduler.
*
* @author Vedran Pavic
* @author Stephane Nicoll
* @since 2.0.0
*/
@Configuration
@ConditionalOnClass({ Scheduler.class, SchedulerFactoryBean.class, PlatformTransactionManager.class })
@EnableConfigurationProperties(QuartzProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
public class QuartzAutoConfiguration{
// 此处省略部分代码
@Bean
@ConditionalOnMissingBean
public SchedulerFactoryBean quartzScheduler() {
// 因为新版本SchedulerFactoryBean已经实现ApplicationContextAware接口
// 因此相对于老版本Spring解决方案中的AutowiringSpringBeanJobFactory进行封装
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
SpringBeanJobFactory jobFactory = new SpringBeanJobFactory();
// SpringBeanJobFactory中注入applicationContext,为依赖注入创造条件
jobFactory.setApplicationContext(this.applicationContext);
// schedulerFactoryBean中注入setJobFactory(注意此处没有配置DataSource,DataSource详见`JdbcStoreTypeConfiguration`)
// 以上这几个步骤,与老版本的Spring解决方案类似
schedulerFactoryBean.setJobFactory(jobFactory);
// 后续都是Quartz的配置属性设置,不再叙述
if (this.properties.getSchedulerName() != null) {
schedulerFactoryBean.setSchedulerName(this.properties.getSchedulerName());
}
schedulerFactoryBean.setAutoStartup(this.properties.isAutoStartup());schedulerFactoryBean.setStartupDelay((int) this.properties.getStartupDelay().getSeconds());
schedulerFactoryBean.setWaitForJobsToCompleteOnShutdown(this.properties.isWaitForJobsToCompleteOnShutdown());
schedulerFactoryBean.setOverwriteExistingJobs(this.properties.isOverwriteExistingJobs());
if (!this.properties.getProperties().isEmpty()) {
schedulerFactoryBean.setQuartzProperties(asProperties(this.properties.getProperties()));
}
if (this.jobDetails != null && this.jobDetails.length > 0) {
schedulerFactoryBean.setJobDetails(this.jobDetails);
}
if (this.calendars != null && !this.calendars.isEmpty()) {
schedulerFactoryBean.setCalendars(this.calendars);
}
if (this.triggers != null && this.triggers.length > 0) {
schedulerFactoryBean.setTriggers(this.triggers);
}
customize(schedulerFactoryBean);
return schedulerFactoryBean;
}
@Configuration
@ConditionalOnSingleCandidate(DataSource.class)
protected static class JdbcStoreTypeConfiguration {
// 为Quartz的持久化配置DataSource,具体代码可以翻阅Spring源码得到
}
}
下面对SpringBeanJobFactory
进行分析,它是生成Job
实例,以及进行依赖注入
操作的关键类.
/**
* Subclass of {@link AdaptableJobFactory} that also supports Spring-style
* dependency injection on bean properties. This is essentially the direct
* equivalent of Spring's {@link QuartzJobBean} in the shape of a Quartz
* {@link org.quartz.spi.JobFactory}.
*
* <p>Applies scheduler context, job data map and trigger data map entries
* as bean property values. If no matching bean property is found, the entry
* is by default simply ignored. This is analogous to QuartzJobBean's behavior.
*
* <p>Compatible with Quartz 2.1.4 and higher, as of Spring 4.1.
*
* @author Juergen Hoeller
* @since 2.0
* @see SchedulerFactoryBean#setJobFactory
* @see QuartzJobBean
*/
public class SpringBeanJobFactory extends AdaptableJobFactory
implements ApplicationContextAware, SchedulerContextAware {
@Nullable
private String[] ignoredUnknownProperties;
@Nullable
private ApplicationContext applicationContext;
@Nullable
private SchedulerContext schedulerContext;
/**
* Specify the unknown properties (not found in the bean) that should be ignored.
* <p>Default is {@code null}, indicating that all unknown properties
* should be ignored. Specify an empty array to throw an exception in case
* of any unknown properties, or a list of property names that should be
* ignored if there is no corresponding property found on the particular
* job class (all other unknown properties will still trigger an exception).
*/
public void setIgnoredUnknownProperties(String... ignoredUnknownProperties) {
this.ignoredUnknownProperties = ignoredUnknownProperties;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Override
public void setSchedulerContext(SchedulerContext schedulerContext) {
this.schedulerContext = schedulerContext;
}
/**
* Create the job instance, populating it with property values taken
* from the scheduler context, job data map and trigger data map.
*/
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
// 创建Job实例
// (1) 包含applicationContext,则通过AutowireCapableBeanFactory()创建相应Job实例,实现依赖注入
// (2) 如果applicationContext为空,则使用AdaptableJobFactory创建相应的Bean(无法实现依赖注入)
Object job = (this.applicationContext != null ?
this.applicationContext.getAutowireCapableBeanFactory().createBean(
bundle.getJobDetail().getJobClass(), AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false) :
super.createJobInstance(bundle));
if (isEligibleForPropertyPopulation(job)) {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(job);
MutablePropertyValues pvs = new MutablePropertyValues();
if (this.schedulerContext != null) {
pvs.addPropertyValues(this.schedulerContext);
}
pvs.addPropertyValues(bundle.getJobDetail().getJobDataMap());
pvs.addPropertyValues(bundle.getTrigger().getJobDataMap());
if (this.ignoredUnknownProperties != null) {
for (String propName : this.ignoredUnknownProperties) {
if (pvs.contains(propName) && !bw.isWritableProperty(propName)) {
pvs.removePropertyValue(propName);
}
}
bw.setPropertyValues(pvs);
}
else {
bw.setPropertyValues(pvs, true);
}
}
return job;
}
// 省略部分代码
}
/**
* {@link JobFactory} implementation that supports {@link java.lang.Runnable}
* objects as well as standard Quartz {@link org.quartz.Job} instances.
*
* <p>Compatible with Quartz 2.1.4 and higher, as of Spring 4.1.
*
* @author Juergen Hoeller
* @since 2.0
* @see DelegatingJob
* @see #adaptJob(Object)
*/
public class AdaptableJobFactory implements JobFactory {
/**
* Create an instance of the specified job class.
* <p>Can be overridden to post-process the job instance.
* @param bundle the TriggerFiredBundle from which the JobDetail
* and other info relating to the trigger firing can be obtained
* @return the job instance
* @throws Exception if job instantiation failed
*/
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
// 获取`QuartzJobBean`的实现`class`,通过反射工具创建相应的类实例(自然无法注入Spring托管的Bean实例)
Class<?> jobClass = bundle.getJobDetail().getJobClass();
return ReflectionUtils.accessibleConstructor(jobClass).newInstance();
}
}
此处需要解释下AutowireCapableBeanFactory
的作用.
项目中,有部分实现并未与Spring
深度集成,因此其实例并未被Spring
容器管理.
然而,出于需要,这些并未被Spring
管理的Bean
需要引入Spring
容器中的Bean
.
此时,就需要通过实现AutowireCapableBeanFactory
,从而让Spring
实现依赖注入等功能.
希望能够通过上述解释以及代码分析,让你知晓如何在老版本以及新版本Spring
中正确集成Quartz
.
此外,Spring boot
的自动化配置能够解决绝大多数配置问题,但是在时间充裕的情况下,建议通过阅读源码等方式了解配置细节
,从而做到更加的胸有成竹.
PS:
如果您觉得我的文章对您有帮助,请关注我的微信公众号,谢谢!
Spring/Spring boot正确集成Quartz及解决@Autowired失效问题的更多相关文章
- 最新 Spring 4.2.2 集成 Quartz Scheduler 2.2.2 任务调度示例
参考http://blog.csdn.net/defonds/article/details/49496895 本文将演示如何通过 Spring 使用 Quartz Scheduler 进行任务调度. ...
- Spring Boot集成quartz实现定时任务并支持切换任务数据源
org.quartz实现定时任务并自定义切换任务数据源 在工作中经常会需要使用到定时任务处理各种周期性的任务,org.quartz是处理此类定时任务的一个优秀框架.随着项目一点点推进,此时我们并不满足 ...
- Spring Boot 入门(九):集成Quartz定时任务
本片文章续<Spring Boot 入门(八):集成RabbitMQ消息队列>,关于Quartz定时任务请参考<Quartz的基本使用之入门(2.3.0版本)> spring ...
- Spring Boot集成Quartz注入Spring管理的类
摘要: 在Spring Boot中使用Quartz时,在JOB中一般需要引用Spring管理的Bean,通过定义Job Factory实现自动注入. Spring有自己的Schedule定时任务,在S ...
- [Java]Java 9运行Spring Boot项目报错的解决办法
简介 为了学习和尽快掌握 Java 9 的模块化(Module System)新特性,最近安装了 JDK 9,新建了一个 Spring Boot 进行尝试, 过程中遇到了一下报错问题,写下此文谨作为个 ...
- Spring Boot中集成Spring Security 专题
check to see if spring security is applied that the appropriate resources are permitted: @Configurat ...
- shiro 和 spring boot 的集成
1 添加依赖 使用 shiro-spring-boot-web-starter 在 spring boot 中集成 shiro 只需要再添加一个依赖 <dependency> <gr ...
- Spring Boot快速集成kaptcha生成验证码
Kaptcha是一个非常实用的验证码生成工具,可以通过配置生成多样化的验证码,以图片的形式显示,从而无法进行复制粘贴:下面将详细介绍下Spring Boot快速集成kaptcha生成验证码的过程. 本 ...
- Spring Boot 自定义 Shiro 过滤器,无法使用 @Autowired 解决方法
在 Spring Boot 中集成 Shiro,并使用 JWT 进行接口认证. 为了统一对 Token 进行过滤,所以自定义了一个 JwtTokenFilter 过滤器. 期间遇到了以下几个问题,这里 ...
随机推荐
- JS 简介
JS 简介 JavaScript 是世界上最流行的编程语言. 这门语言可用于 HTML 和 web,更可广泛用于服务器.PC.笔记本电脑.平板电脑和智能手机等设备. avaScript 是脚本语言 J ...
- R语言入门1:安装R和RStudio
R语言入门1:安装R和RStudio 曹务强 中科院遗传学博士研究生 9 人赞同了该文章 1. Windows安装R 在Windows系统上,安装R语言比较简单,直接从R的官方网站下载,按照正常的软件 ...
- hidraw设备简要分析
关键词:hid.hidraw.usbhid.hidp等等. 下面首先介绍hidraw设备主要用途,然后简要分析hidraw设备驱动(但是不涉及到相关USB/Bluwtooth驱动),最后分析用户空间接 ...
- Octave中的常用操作2
>> ones(2:3)ans = 1 1 1 1 1 1 >> 2*ones(2:3)ans = 2 2 2 2 2 2 >> rand(3,3) 产生0~1中的 ...
- 【论文阅读】Second-order Attention Network for Single Image Super-Resolution
概要 近年来,深度卷积神经网络(CNNs)在单一图像超分辨率(SISR)中进行了广泛的探索,并获得了卓越的性能.但是,大多数现有的基于CNN的SISR方法主要聚焦于更宽或更深的体系结构设计上,而忽略了 ...
- css发展史
(接着上次博客的内容,现在我们进入css基础) 外部样式表 <link> 内部样式表 <style> 行内样式表 style (多用于JS) * css注释 ...
- 小程序-小菊花loading
界面----交互 wx.showLoading() 显示loading提示框.需主动调用wx.hideLoading()才能关闭提示框 参数: 属性 类型 默认值 必填 说明 title string ...
- networkx生成网络
ER随机网络,WS小世界网络,BA无标度网络的生成 import networkx as nx import matplotlib.pyplot as plt #ER随机网络 #10个节点,连接概率为 ...
- 大话设计模式Python实现-命令模式
命令模式(Command Pattern):将请求封装成对象,从而使可用不同的请求对客户进行参数化:对请求排队或记录请求日志,以及支持可撤消的操作. 下面是一个命令模式的demo: #!/usr/bi ...
- 大话设计模式Python实现-单例模式
单例模式(Singleton Pattern):保证类仅有一个实例,并提供一个访问它的全局访问点. 下面是单例模式的demo: #!/usr/bin/env python # -*- coding:u ...