如何在Grails中配置Quartz触发器以允许不同的测试和生产计划

如何在Grails中配置Quartz触发器以允许不同的测试和生产计划,grails,quartz-scheduler,grails-plugin,Grails,Quartz Scheduler,Grails Plugin,我试图采用Grails2.2.4中的Quartz插件(:Quartz:1.0.1),并试图找出如何允许开发和测试使用不同于生产中所需的时间表,而无需更改部署到每个插件的代码 以下是我的经验。 我正在使用JDBC JobStore和Quartz Monitor插件(:Quartz Monitor:1.0)。我的工作定义如下: class TestJob { static triggers = { cron name: 'testTrigger', startDelay: 1

我试图采用Grails2.2.4中的Quartz插件(:Quartz:1.0.1),并试图找出如何允许开发和测试使用不同于生产中所需的时间表,而无需更改部署到每个插件的代码

以下是我的经验。

我正在使用JDBC JobStore和Quartz Monitor插件(:Quartz Monitor:1.0)。我的工作定义如下:

class TestJob {
    static triggers = {
        cron name: 'testTrigger', startDelay: 1000, cronExpression: '0 0/1 * * * ?'
    }
    ...
}
当我运行Grails应用程序时,将为作业设置触发器,并将其存储在数据库中,然后开始执行。如果我进入并通过Quartz监视器有意更改cron表达式,它将反映在数据库和执行计划中

如果现在重新启动应用程序,触发器将更改回作业中定义的内容。因此,使用这种方法,我几乎被工作的触发器块中的任何内容所困扰。如果我从代码中完全删除触发器块,那么数据库定义将保持不变,并控制调度,这似乎是一种改进

因此,我认为在作业中不定义任何触发器是有意义的,但这让我试图找出如何首先加载触发器,并且以一种不会覆盖有意更改的方式加载触发器。似乎向配置(可能在外部配置文件中)添加一个块来定义触发器是有意义的。据我所知,我需要在启动时编写一些东西来解析它(在引导中?),并通过Quartz API应用它

在我阅读文档和谷歌搜索时,是否已经存在这样的东西?或者我在更根本的方面犯了错误

更新一些实施细节

汉斯给了我一些关于我的情况下应该采取什么措施的想法

我最终关闭了JDBC作业存储,因为我决定配置应该是触发的权限。我将作业/触发器信息放入如下所示的外部配置文件中

quartzJobs: [
    'TestJob': [
        cronTriggers: [
            cronExpression: '0 0 7 ? * 2-6'
        ]
    ]
]
def jobs = grailsApplication.config.quartzJobs
if (jobs) {
    jobs.each { job, details ->
        List triggers = (details?.cronTriggers instanceof Map) ? [details.cronTriggers]: details.cronTriggers
        if (triggers) {
            def j = grailsApplication.mainContext.getBean(job)
            triggers.each { trigger ->
                String cronExpression = trigger.cronExpression ?: '1 1 1 1 1 ? 2099'
                j.schedule(cronExpression)
            }
        }
    }
}
然后我有一些在引导中调用的代码,看起来像这样

quartzJobs: [
    'TestJob': [
        cronTriggers: [
            cronExpression: '0 0 7 ? * 2-6'
        ]
    ]
]
def jobs = grailsApplication.config.quartzJobs
if (jobs) {
    jobs.each { job, details ->
        List triggers = (details?.cronTriggers instanceof Map) ? [details.cronTriggers]: details.cronTriggers
        if (triggers) {
            def j = grailsApplication.mainContext.getBean(job)
            triggers.each { trigger ->
                String cronExpression = trigger.cronExpression ?: '1 1 1 1 1 ? 2099'
                j.schedule(cronExpression)
            }
        }
    }
}

这可能不是您想要的,但是您可以在作业的
execute()
方法中添加一条条件语句。此条件可用于基于当前Grails环境以及单独定义的cron表达式跳过执行。作为一个非常简单的例子:

import grails.util.Environment
import org.quartz.CronExpression

class TestJob {
    CronExpression testExp = new CronExpression("0 0/5 * * * ?")  // could be moved to config...
    // ...
    def execute() {
       // in non-prod environments, return immediately unless the current date & time matches our "test" cron expression
       if (Environment.current != Environment.PRODUCTION && !testExp.isSatisfiedBy(new Date()) { return }

       // ...

    }
}

可以根据当前环境执行以下操作来更改触发器:

static triggers = {
    if (Environment.current == Environment.TEST) {
        simple repeatInterval: 5000l
    }
    else {
        simple repeatInterval: 1000l
    }
}
更新

Hans的解决方案可能更简单一些,但这里有另一个解决方案,如果触发器已经存在,则不会重新创建它。:)

我重命名了
触发器
块,以便插件找不到任何触发器

package stackoverflow

class MyJob {
    static defaultTriggers = {
        cron name: 'testTrigger', startDelay: 1000, cronExpression: '0 0/1 * * * ?'
    }
....
}
然后我重用了
QuartzGrailsPlugin.groovy
中的代码,从
defaultTriggers
闭包创建触发器,如果它还不存在,则对其进行调度

通过查看
QuartzGrailsPlugin.groovy
中的
doWithApplicationContext
,扩展代码以循环所有作业应该不会太困难

不过有点吵。我认为最好将它隐藏在服务中,并从
Bootstrap
调用它,而不是内联调用它

Bootstrap.groovy

import grails.plugins.quartz.CustomTriggerFactoryBean
import grails.plugins.quartz.GrailsJobClassConstants
import grails.plugins.quartz.config.TriggersConfigBuilder
import org.quartz.JobKey
import org.quartz.Trigger
import stackoverflow.MyJob

class BootStrap {
    def quartzScheduler

    def init = { servletContext ->

        def builder = new TriggersConfigBuilder(MyJob.canonicalName)
        Map triggers = builder.build MyJob.defaultTriggers

        triggers.each { name, Expando descriptor ->
            Trigger trigger = createTrigger (descriptor, MyJob.canonicalName)

            if (! quartzScheduler.checkExists(trigger.getKey ())) {
                quartzScheduler.scheduleJob (trigger)
            }
        }
    }

    Trigger createTrigger (Expando descriptor, String jobName) {
        CustomTriggerFactoryBean factory = new CustomTriggerFactoryBean()
        factory.triggerClass = descriptor.triggerClass
        factory.triggerAttributes = descriptor.triggerAttributes
        factory.jobDetail = quartzScheduler.getJobDetail (new JobKey (jobName, GrailsJobClassConstants.DEFAULT_GROUP))
        factory.afterPropertiesSet()
        factory.object
    }
}

您可以将配置放入
Config.groovy
或从
grails.Config.locations
读取的属性文件中

然后在
BootStrap.groovy
中,您可以执行以下操作:

在此
cronExpression中,
是您在属性文件中选择的属性的名称


有关可用的不同的
作业.schedule()
方法,请参阅。

如果太晚,我很抱歉。以上答案是正确的。然而,当需要从配置中提取周期时,我有理由

class ImportJob {


static triggers = {
    final Long period = Long.valueOf(Holders.grailsApplication.getConfig().grails.your.config.value as String)
    simple repeatInterval: period
}

def execute() {
    log.debug "E starting processing exposures at:${new Date()}"

}

反馈很好,马丁。问题是,在生产中,我的系统管理员负责运行作业的计划,我真的不希望我的代码干扰这一点。如果他们决定更改计划(如通过Quartz Monitor UI),我不希望我的代码在下次重新启动服务器时将其更改回去,这似乎是触发器块出现时发生的情况。这是我最终的结果,但部分原因是它使我重新考虑我的方法。具体地说,我决定,如果要在外部配置文件中配置触发器,那么使用只保存相同信息的JDBC作业存储并没有多大意义。也许我以后会重新打开它,并解决如何将其与配置合并,但现在如果您想更改计划,您将在配置中进行。我将用一些实现细节更新我的问题。