Scheduling in Jakarta EE – 在雅加达EE的调度

最后修改: 2016年 6月 14日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In a previous article, we demonstrated how to schedule tasks in Spring using @Scheduled annotation. In this article, we will demonstrate how to achieve the same by using the timer service in a Jakarta EE application for each case presented in the previous article.

在之前的文章中,我们演示了如何使用 @Scheduled annotation 在 Spring 中安排任务。在这篇文章中,我们将演示如何通过在Jakarta EE应用程序中使用定时器服务来实现前面文章中介绍的每一种情况。

2. Enable Support for Scheduling

2.启用对调度的支持

In a Jakarta EE application, there is no need to enable support for timed tasks. The timer service is a container managed service that allows applications to call methods that are scheduled for time-based events. As an example, an application may have to run some daily reports at a certain hour in order to generate statistics.

在Jakarta EE应用程序中,不需要启用对定时任务的支持。定时服务是一个容器管理的服务,它允许应用程序调用为基于时间的事件安排的方法。举个例子,一个应用程序可能需要在某一小时内运行一些日常报告,以便生成统计数据。

There are two types of timers:

有两种类型的定时器。

  • Programmatic timers: the timer service can be injected into any bean (except a stateful session bean) and the business logic should be placed in a method annotated with @Timeout. The timer can be initialized by a method annotated @PostConstruct of the beans or it can also be initialized just by calling a method.
  • Automatic timers: the business logic is placed in any method annotated with @Schedule or @Schedules. These timers are initialized as soon as the application starts.

So let’s get started with our first example.

因此,让我们从第一个例子开始。

3. Schedule Task with a Fixed Delay

3.用一个固定的延迟来安排任务

In Spring this is done simply by using the @Scheduled(fixedDelay = 1000) annotation. In this case, the duration between the end of the last execution and the start of next execution is fixed. The task always waits until the previous one is finished.

在Spring中,这可以通过使用@Scheduled(fixedDelay = 1000) 注解来完成。在这种情况下,上一次执行的结束和下一次执行的开始之间的时间是固定的。该任务总是等待,直到前一个任务完成。

Doing exactly same thing in Jakarta EE is a little bit harder to achieve because there is no similar built-in mechanism provided, nevertheless, a similar scenario can be implemented with a bit of extra coding. Let’s have a look at how this is done:

在Jakarta EE中做同样的事情有点困难,因为没有提供类似的内置机制,尽管如此,通过一点额外的编码就可以实现类似的场景。让我们来看看这是如何做到的。

@Singleton
public class FixedTimerBean {

    @EJB
    private WorkerBean workerBean;

    @Lock(LockType.READ)
    @Schedule(second = "*/5", minute = "*", hour = "*", persistent = false)
    public void atSchedule() throws InterruptedException {
        workerBean.doTimerWork();
    }
}
@Singleton
public class WorkerBean {

    private AtomicBoolean busy = new AtomicBoolean(false);

    @Lock(LockType.READ)
    public void doTimerWork() throws InterruptedException {
        if (!busy.compareAndSet(false, true)) {
            return;
        }
        try {
            Thread.sleep(20000L);
        } finally {
            busy.set(false);
        }
    }
}

As you can see the timer is scheduled to be triggered every five seconds. However, the method triggered in our case simulated a 20 seconds response time by call sleep() on the current Thread.

正如你所看到的,定时器被安排为每五秒触发一次。然而,在我们的案例中,通过在当前Thread上调用sleep(),触发的方法模拟了20秒的响应时间。

As a consequence, the container will continue to call doTimerWork() every five seconds but the condition put at the beginning of the method, busy.compareAndSet(false, true), will return immediately if the previous call has not finished. In this, we ensure that the next task will be executed only after the previous one has finished.

因此,容器将继续每五秒调用一次doTimerWork(),但是如果前一次调用没有完成,放在该方法开头的条件busy.compareAndSet(false, true)将立即返回。这样,我们确保只有在前一个任务完成后才会执行下一个任务。

4. Schedule Task at a Fixed Rate

4.按固定费率安排任务

One way of doing this is to use the timer service which is injected by using @Resource and configured in the method annotated @PostConstruct. The method annotated with @Timeout will be called when the timer expires.

一种方法是使用定时器服务,该服务通过使用@Resource注入,并在注释为@PostConstruct的方法中进行配置。当定时器过期时,用@Timeout注解的方法将被调用。

As mentioned in the previous article the beginning of the task execution doesn’t wait for the completion of the previous execution. This option should be used when each execution of the task is independent. The following code snippet creates a timer that fires every second:

如前文所述,任务执行的开始并不等待前一次执行的完成。当每个任务的执行都是独立的时候,应该使用这个选项。下面的代码片段创建了一个每秒钟发射一次的定时器。

@Startup
@Singleton
public class ProgrammaticAtFixedRateTimerBean {

    @Inject
    Event<TimerEvent> event;

    @Resource
    TimerService timerService;

    @PostConstruct
    public void initialize() {
        timerService.createTimer(0,1000, "Every second timer with no delay");
    }

    @Timeout
    public void programmaticTimout(Timer timer) {
        event.fire(new TimerEvent(timer.getInfo().toString()));
    }
}

Another way is to use @Scheduled annotation. In the following code snippet we fire a timer every five seconds:

另一种方法是使用@Scheduled注解。在下面的代码片断中,我们每隔五秒就发射一个定时器。

@Startup
@Singleton
public class ScheduleTimerBean {

    @Inject
    Event<TimerEvent> event;

    @Schedule(hour = "*", minute = "*", second = "*/5", info = "Every 5 seconds timer")
    public void automaticallyScheduled(Timer timer) {
        fireEvent(timer);
    }


    private void fireEvent(Timer timer) {
        event.fire(new TimerEvent(timer.getInfo().toString()));
    }
}

5. Schedule Task with Initial Delay

5.带初始延迟的计划任务

If your use case scenario requires the timer to start with a delay we can do that too. In this case Jakarta EE allows the use of the timer service. Let’s have a look at an example where the timer has an initial delay of 10 seconds and then fires every five seconds:

如果你的用例场景要求定时器延迟启动,我们也可以这样做。在这种情况下,Jakarta EE允许使用计时器服务。让我们来看看一个例子,计时器的初始延迟为10秒,然后每5秒发射一次。

@Startup
@Singleton
public class ProgrammaticWithInitialFixedDelayTimerBean {

    @Inject
    Event<TimerEvent> event;

    @Resource
    TimerService timerService;

    @PostConstruct
    public void initialize() {
        timerService.createTimer(10000, 5000, "Delay 10 seconds then every 5 seconds timer");
    }

    @Timeout
    public void programmaticTimout(Timer timer) {
        event.fire(new TimerEvent(timer.getInfo().toString()));
    }
}

The createTimer method used in our sample is using the following signature createTimer(long initialDuration, long intervalDuration, java.io.Serializable info) where initialDuration is the number of milliseconds that must elapse before the first timer expiration notification and intervalDuration is the number of milliseconds that must elapse between timer expiration notifications.

在我们的样本中使用的createTimer方法使用以下签名createTimer(long initialDuration, long intervalDuration, java.io.Serializable info)其中initialDuration是在第一个定时器过期通知之前必须经过的毫秒数,intervalDuration是定时器过期通知之间必须经过的毫秒数。

In this example, we’re using an initialDuration of 10 seconds and an intervalDuration of five seconds. The task will be executed for the first time after the initialDuration value and it will continue to be executed according to the intervalDuration.

在这个例子中,我们使用的initialDuration是10秒,intervalDuration是5秒。任务将在initialDuration值之后第一次执行,它将根据intervalDuration继续执行。

6. Schedule Task Using Cron Expressions

6.使用Cron表达式安排任务

All the schedulers that we have seen, both programmatic and automatic, allow the use of cron expressions. Let’s see an example:

我们看到的所有调度器,无论是编程的还是自动的,都允许使用cron表达式。让我们看一个例子。

@Schedules ({
   @Schedule(dayOfMonth="Last"),
   @Schedule(dayOfWeek="Fri", hour="23")
})
public void doPeriodicCleanup() { ... }

In this example the method doPeriodicCleanup() will be called every Friday at 23:00 and on the last day of the month.

在这个例子中,方法doPeriodicCleanup()将在每周五23:00和每月的最后一天被调用。

7. Conclusion

7.结语

In this article we have looked at various ways to schedule tasks in the Jakarta EE environment using as a starting point a previous article where samples were done using Spring.

在这篇文章中,我们以之前的文章为起点,探讨了在Jakarta EE环境中安排任务的各种方法,其中使用Spring完成了样本。

Code samples can be found in the GitHub repository.

代码样本可以在GitHub资源库中找到。