當您需要群集,故障轉移,負載平衡和其他一些時髦的詞時,情況將發生巨大變化。 有幾個用例:
- 單個服務器無法處理所需數量的并發且長時間運行的作業,并且執行需要分成幾臺機器-但是每個任務必須完全執行
- 我們負擔不起為時過晚的工作-如果一臺服務器宕機,另一臺服務器應按時運行
- …或更嚴格地說–作業最終需要運行–即使只有一臺服務器因維護而停機,延遲的作業也需要在重新啟動后盡快運行
在上述所有情況下,我們都需要某種非瞬態全局存儲來跟蹤執行了哪些作業,以便它們由一臺機器精確地運行。 關系數據庫作為共享內存在這種情況下效果很好。
因此,如果您認為需要安排工作并滿足上述一些要求,請繼續閱讀。 我將向您展示如何使用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