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