在Spring中使用JDBCJobStore配置Quartz

本文介绍了如何在Spring中配置Quartz作业存储,使用JDBCJobStore实现集群、故障转移等需求。通过数据库存储作业状态,确保任务在多台服务器间的正确执行。示例展示了SpringBeanJobFactory的使用,以及如何处理Job依赖注入的问题,帮助读者理解Spring与Quartz的集成。
摘要由CSDN通过智能技术生成
我将开始一些有关Quartz Scheduler内部,提示和技巧的系列文章,这是第0章-如何配置持久性作业存储。 在Quartz中,您基本上可以在将作业和触发器存储在内存中以及在关系数据库中进行选择( Terracotta是最近添加的混合功能)。 我想说在90%的情况下,当您将Quartz与RAMJobStore一起使用时,您根本就不需要Quartz。 显然,此存储后端是瞬态的,并且所有重启之间都将丢失所有待处理的作业和触发器。 如果您对此感到满意,则可以使用更简单,更轻便的解决方案,包括JDK中内置的ScheduledExecutorService和Spring中的@Scheduled(cron =” * / 5 * * * * MON-FRI”)。 您能证明在这种情况下使用额外的0.5MiB JAR是合理的吗?

当您需要群集,故障转移,负载平衡和其他一些时髦的词时,情况将发生巨大变化。 有几个用例:

  • 单个服务器无法处理所需数量的并发且长时间运行的作业,并且执行需要分成几台机器-但是每个任务必须完全执行
  • 我们负担不起为时过晚的工作-如果一台服务器宕机,另一台服务器应按时运行
  • …或更严格地说–作业最终需要运行–即使只有一台服务器因维护而停机,延迟的作业也需要在重新启动后尽快运行

在上述所有情况下,我们都需要某种非瞬态全局存储来跟踪执行了哪些作业,以便它们由一台机器精确地运行。 关系数据库作为共享内存在这种情况下效果很好。

因此,如果您认为需要安排工作并满足上述一些要求,请继续阅读。 我将向您展示如何使用Spring配置Quartz并将其完全集成。 首先我们需要一个数据源:

import org.apache.commons.dbcp.BasicDataSource
import com.googlecode.flyway.core.Flyway
import org.jdbcdslog.DataSourceProxy
import org.springframework.jdbc.datasource.{DataSourceTransactionManager, LazyConnectionDataSourceProxy}
import org.h2.Driver
 
@Configuration
@EnableTransactionManagement
class Persistence {
 
    @Bean
    def transactionManager() = new DataSourceTransactionManager(dataSource())
 
    @Bean
    @Primary
    def dataSource() = {
        val proxy = new DataSourceProxy()
        proxy.setTargetDSDirect(dbcpDataSource())
        new LazyConnectionDataSourceProxy(proxy)
    }
 
    @Bean(destroyMethod = "close")
    def dbcpDataSource() = {
        val dataSource = new BasicDataSource
        dataSource.setDriverClassName(classOf[Driver].getName)
        dataSource.setUrl("jdbc:h2:mem:quartz-demo;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MVCC=TRUE")
        dataSource.setUsername("sa")
        dataSource.setPassword("")
        dataSource.setMaxActive(20)
        dataSource.setMaxIdle(20)
        dataSource.setMaxWait(10000)
        dataSource.setInitialSize(5)
        dataSource.setValidationQuery("SELECT 1")
        dataSource
    }
 
}

您可能已经猜到了,Quartz需要一些数据库表才能使用。 它不会自动创建它们,但是提供了几个数据库的SQL脚本,包括H2,您可以看到我正在使用它。 我认为Flyway是启动时运行数据库脚本的最简单方法:

@Bean(initMethod = "migrate")
def flyway() = {
 val fly = new Flyway()
 fly.setDataSource(dataSource())
 fly
}

顺便说一句,如果您没有注意到:否,我们的示例应用程序中没有XML,是的,我们正在使用Spring。

让我们继续到Quartz:

@Configuration
class Scheduling {
 
    @Resource
    val persistence: Persistence = null
 
    @Bean
    @DependsOn(Array("flyway"))
    def schedulerFactory() = {
        val schedulerFactoryBean = new SchedulerFactoryBean()
        schedulerFactoryBean.setDataSource(persistence.dataSource())
        schedulerFactoryBean.setTransactionManager(persistence.transactionManager())
        schedulerFactoryBean.setConfigLocation(new ClassPathResource("quartz.properties"))
        schedulerFactoryBean.setJobFactory(jobFactory())
        schedulerFactoryBean.setApplicationContextSchedulerContextKey("applicationContext")
        schedulerFactoryBean.setSchedulerContextAsMap(Map().asJava)
        schedulerFactoryBean.setWaitForJobsToCompleteOnShutdown(true)
        schedulerFactoryBean
    }
 
    @Bean
    def jobFactory() = new SpringBeanJobFactory
 
}

很高兴知道您可以将@Configuration注释类的实例注入到另一个此类中,以方便使用。 除此之外–没有幻想。 请注意,我们需要在Quartz调度程序工厂上使用@DependsOn(Array(“ flyway”))–否则,调度程序可能会在Flyway用Quartz数据库表触发迁移脚本之前启动,从而导致启动时出现令人不愉快的错误。 基本位是SpringBeanJobFactory和schedulerContextAsMap。 特殊工厂使Spring负责创建Job实例。 不幸的是,这个工厂非常有限,我们将在下面的示例中很快看到。 首先,我们需要一个Spring bean和一个Quartz作业:

@Service
class Printer extends Logging {
 
    def print(msg: String) {
        logger.info(msg)
    }
 
}
 
class PrintMessageJob extends Job with Logging {
 
    @BeanProperty
    var printer: Printer = _
 
    @BeanProperty
    var msg = ""
 
    def execute(context: JobExecutionContext) {
        printer print msg
    }
}

第一个意外输入是@BeanProperty,而不是@Autowired或@Resource。 事实证明,即使Job创建了一个实例,Job也不是真正的Spring bean。 相反,Spring使用可用的setter查找所需的依赖项。 那么味精字符串从何而来? 继续:

val trigger = newTrigger().
        withIdentity("Every-few-seconds", "Demo").
        withSchedule(
            simpleSchedule().
                    withIntervalInSeconds(4).
                    repeatForever()
        ).
        build()
 
val job = newJob(classOf[PrintMessageJob]).
        withIdentity("Print-message", "Demo").
        usingJobData("msg", "Hello, world!").
        build()
 
scheduler.scheduleJob(job, trigger)

Quartz 2.0附带了一个不错的内部DSL,用于以可读的方式创建作业和触发器。 如您所见,我正在传递一个额外的“你好,世界!” 工作参数。 该参数存储在每个作业或每个触发器的数据库中所谓的JobData中。 执行时将提供给作业。 这样,您就可以参数化您的工作。 但是,执行时,我们的作业将引发NullPointerException…显然没有设置打印机引用,并且忽略了该引用。 事实证明,Spring不会简单地浏览ApplicationContext中所有可用的bean。 SpringBeanJobFactory仅查看Jobs和Triggers的JobData以及所谓的调度程序上下文(已经提到)。 如果要将任何Spring bean注入Job,则必须在schedulerContext中显式放置对该bean的引用:

@Configuration
class Scheduling {
 
    @Resource
    val printer: Printer = null
 
    @Bean
    def schedulerFactory() = {
        val schedulerFactoryBean = new SchedulerFactoryBean()
        //...
        schedulerFactoryBean.setSchedulerContextAsMap(schedulerContextMap().asJava)
        //...
        schedulerFactoryBean
    }
 
    def schedulerContextMap() =
        Map(
            "printer" -> printer
        )
 
}

不幸的是,要注入作业的每个Spring bean必须在schedulerContextMap中明确引用。 更糟糕的是,如果您忘记了它,Quartz将在运行时以静默方式记录NPE。 将来,我们将编写更强大的作业工厂。 但是对于初学者来说,我们已经准备好运行的Spring + Quartz应用程序,可以进行进一步的实验,这些资源始终可以在我的GitHub帐户下获得。

您可能会问自己,我们不能简单地使用MethodInvokingJobDetailFactoryBean吗? 好吧,首先是因为它不适用于持久性作业存储。 其次-由于无法将JobData传递给Job-因此我们无法再区分不同的作业运行。 例如,我们的作业打印消息将必须始终打印在该类中硬编码的相同消息。

顺便说一句,如果有人问您:Java企业开发人员需要多少个类才能打印“ Hello world!”。 您可以自豪地回答:4个类,30个JAR占用20 MiB的空间以及一个带有10多个表的关系数据库。 认真的说,这是本文的输出……

参考: Java和社区博客上的JCG合作伙伴 Tomasz Nurkiewicz从SpringJDBCJobStore中配置Quartz


翻译自: https://www.javacodegeeks.com/2012/04/configuring-quartz-with-jdbcjobstore-in.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值