测试Quartz Cron表达式

声明复杂的Cron表达式仍然让我有些头疼,尤其是当使用一些更高级的构造时。 毕竟,您能否确定以下触发器何时触发'0 0 17 L-3W 6-9 ? *' '0 0 17 L-3W 6-9 ? *' ? 由于触发器通常打算在将来运行,因此需要预先对其进行测试,并确保它们会在我们认为会触发的情况下真正触发。
Quartz Scheduler (我正在测试版本2.1.6)不提供对此的直接支持,但是基于现有的API(即CronExpression.getNextValidTimeAfter()方法CronExpression.getNextValidTimeAfter()一些简单的函数很容易。 我们的目标是定义一个方法,该方法将为给定的Cron表达式返回下一个N计划执行。 我们无法要求全部,因为某些触发器(包括上述触发器)没有结束日期,可以无限重复。 我们只能依靠前面提到的getNextValidTimeAfter() ,该方法将日期作为参数并返回该日期之后最接近的触发时间T 1 。 因此,如果要查找第二个计划执行,则必须询问第一个计划执行之后的下一个执行( T 1 )。 等等。 让我们将其放入代码中:

def findTriggerTimesIterative(expr: CronExpression, from: Date = new Date, max: Int = 100): Seq[Date] = {
    val times = mutable.Buffer[Date]()
    var next = expr getNextValidTimeAfter from
    while (next != null && times.size < max) {
        times += next
        next = expr getNextValidTimeAfter next
    }
    times
}

如果没有下一次触发时间(例如,假设触发器仅在2012年运行,而我们询问2013年1月1日之后的触发时间),则返回null 。 一点崩溃测试:

findTriggerTimesRecursive(new CronExpression('0 0 17 L-3W 6-9 ? *')) foreach println

产量:

Thu Jun 27 17:00:00 CEST 2013
Mon Jul 29 17:00:00 CEST 2013
Wed Aug 28 17:00:00 CEST 2013
Fri Sep 27 17:00:00 CEST 2013
Fri Jun 27 17:00:00 CEST 2014
Mon Jul 28 17:00:00 CEST 2014
Thu Aug 28 17:00:00 CEST 2014
Fri Sep 26 17:00:00 CEST 2014
Fri Jun 26 17:00:00 CEST 2015
Tue Jul 28 17:00:00 CEST 2015
Fri Aug 28 17:00:00 CEST 2015
Mon Sep 28 17:00:00 CEST 2015
Mon Jun 27 17:00:00 CEST 2016
...

希望我们复杂的Cron表达式的含义现在更清楚: 6月至9月( 6-9 )的月末( L-3 )前三天最近的工作日( W )在17:00:00( 0 0 17 。 现在,我开始尝试不同的实现方式,以找到最优雅,最适合此相当简单的问题的方法。 首先,我注意到问题不是迭代的,而是递归的:找到下一个100次执行时间等同于找到第一个执行并在第一个执行之后找到99个剩余执行:

def findTriggerTimesRecursive(expr: CronExpression, from: Date = new Date, max: Int = 100): List[Date] = 
    expr getNextValidTimeAfter from match {
        case null => Nil
        case next =>
            if (max > 0)
                next :: findTriggerTimesRecursive(expr, next, max - 1)
            else
                Nil
    }

似乎实现起来要简单得多:没有匹配项–返回空列表( Nil )。 找到匹配项–除非我们已经收集了足够的日期,否则将其返回到下一个匹配项。 但是,此实现有一个问题,它不是尾递归的 。 通常,可以通过引入第二个函数并将中间结果累加到参数中来更改此设置:

def findTriggerTimesTailRecursive(expr: CronExpression, from: Date = new Date, max: Int = 100) = {

    @tailrec def accum(curFrom: Date, curMax: Int, acc: List[Date]): List[Date] = {
        expr getNextValidTimeAfter curFrom match {
            case null => acc
            case next =>
                if (curMax > 0)
                    accum(next, curMax - 1, next :: acc)
                else
                    acc
        }
    }

    accum(from, max, Nil)
}

稍微复杂一点,但是至少StackOverflowError不会在深夜唤醒我们。 顺便说一句,我刚刚注意到IntelliJ IDEA不仅显示标识递归的图标(请参见行号),而且在采用尾部调用优化时也使用不同的图标(!):

所以我认为这是最好的,当我想到另一个想法时。 首先,人为的max限制(默认为100)似乎很尴尬。 如果我们可以一次又一次地动态计算所有结果,又为什么还要累积所有结果呢? 当我意识到我不需要SeqList ,我需要一个Iterator[Date]

class TimeIterator(expr: CronExpression, from: Date = new Date) extends Iterator[Date] {
    private var cur = expr getNextValidTimeAfter from

    def hasNext = cur != null

    def next() = if (hasNext) {
        val toReturn = cur
        cur = expr getNextValidTimeAfter cur
        toReturn
    } else {
        throw new NoSuchElementException
    }
}

我已经花了一些toReturnif true分支简化为单一代码,并避免使用中间的toReturn变量。 这是可能的,但是为了清楚起见(为了您的眼睛),我不会透露它* 。 但是,为什么众所周知的迭代器使用起来较不灵活且令人愉悦呢? 好吧,首先,它使我们可以延迟生成下一个触发时间,因此我们无需为不使用的东西付费。 此外,中间结果不会存储在任何地方,因此我们也可以节省内存。 而且由于适用于序列的所有内容也都适用于迭代器,因此我们可以轻松地在Scala中使用迭代器,例如打印( 获取 )前10个日期:

new TimeIterator(expr) take 10 foreach println

尝试比较不同的实现方式做一点基准测试(在这里使用caliper ):

object FindTriggerTimesBenchmark extends App {
    Runner.main(classOf[FindTriggerTimesBenchmark], Array('--trials', '1'))
}

class FindTriggerTimesBenchmark extends SimpleBenchmark {

    val expr = new CronExpression('0 0 17 L-3W 6-9 ? *')

    def timeIterative(reps: Int) {
        for (i <- 1 to reps) {
            findTriggerTimesIterative(expr)
        }
    }

    def timeRecursive(reps: Int) {
        for (i <- 1 to reps) {
            findTriggerTimesRecursive(expr)
        }
    }

    def timeTailRecursive(reps: Int) {
        for (i <- 1 to reps) {
            findTriggerTimesTailRecursive(expr)
        }
    }

    def timeUsedIterator(reps: Int) {
        for (i <- 1 to reps) {
            (new TimeIterator(expr) take 100).toList
        }
    }

    def timeNotUsedIterator(reps: Int) {
        for (i <- 1 to reps) {
            new TimeIterator(expr)
        }
    }
}

似乎实现更改对时间的影响可以忽略不计,因为大多数CPU大概都在内部烧毁了。
getNextValidTimeAfter()

今天我们学到了什么?

  • 除非您确实有问题,否则不要对性能进行过多考虑。 力求最佳设计和最简单的实现。 想一想您要用来表示问题和解决方案的数据结构。 在这个(一见钟情)问题中, Iterator (懒惰地评估,可能是无限的项目流)被证明是最好的方法

*好的,这是方法。 提示:分配具有Unit类型,这里涉及(Date, Unit)元组:

def next() = if (hasNext)
    (cur, cur = expr getNextValidTimeAfter cur)._1
else
    throw new NoSuchElementException

参考:Java和社区博客上,从我们的JCG合作伙伴 Tomasz Nurkiewicz 测试Quartz Cron表达式

翻译自: https://www.javacodegeeks.com/2012/10/testing-quartz-cron-expressions.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值