当您需要群集,故障转移,负载平衡和其他一些时髦的词时,情况将发生巨大变化。 有几个用例:
- 单个服务器无法处理所需数量的并发且长时间运行的作业,并且执行需要分成几台机器-但是每个任务必须完全执行
- 我们负担不起为时过晚的工作-如果一台服务器宕机,另一台服务器应按时运行
- …或更严格地说–作业最终需要运行–即使只有一台服务器因维护而停机,延迟的作业也需要在重新启动后尽快运行
在上述所有情况下,我们都需要某种非瞬态全局存储来跟踪执行了哪些作业,以便它们由一台机器精确地运行。 关系数据库作为共享内存在这种情况下效果很好。
因此,如果您认为需要安排工作并满足上述一些要求,请继续阅读。 我将向您展示如何使用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 {@Beandef transactionManager() = new DataSourceTransactionManager(dataSource())@Bean@Primarydef dataSource() = {val proxy = new DataSourceProxy()proxy.setTargetDSDirect(dbcpDataSource())new LazyConnectionDataSourceProxy(proxy)}@Bean(destroyMethod = "close")def dbcpDataSource() = {val dataSource = new BasicDataSourcedataSource.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 {@Resourceval 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}@Beandef 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 {@BeanPropertyvar printer: Printer = _@BeanPropertyvar 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 {@Resourceval printer: Printer = null@Beandef 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从Spring在JDBCJobStore中配置Quartz 。
翻译自: https://www.javacodegeeks.com/2012/04/configuring-quartz-with-jdbcjobstore-in.html