1. 概述
在之前的文章中,我们演示了如何使用Spring中的@Scheduled
注解来安排任务。本文将展示如何在Jakarta EE应用中通过定时服务来实现与上述文章中相同场景的任务调度。
2. 启用定时任务支持
在Jakarta EE应用中,无需额外启用定时任务支持。定时服务是容器管理的服务,允许应用程序调用基于时间事件的预定方法。例如,一个应用可能需要在每天的某个时间运行一些报告以生成统计数据。
定时器有两种类型:
程序化定时器:定时服务可以注入到任何非状态ful会话bean中,业务逻辑应放在带有
@Timeout
注解的方法中。定时器可以通过bean的@PostConstruct
方法初始化,也可以通过调用一个方法来初始化。自动定时器:业务逻辑放在带有
@Schedule
或@Schedules
注解的方法中。这些定时器会在应用启动时立即初始化。
现在我们开始第一个示例。
3. 固定延迟调度任务
在Spring中,这只需使用@Scheduled(fixedDelay = 1000)
即可。在这种情况下,每次执行结束到下一次执行开始之间的间隔是固定的。任务总是等待直到上一个任务完成。
在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);
}
}
}
如图所示,定时器被设置为每五秒触发一次。然而,我们的示例中,模拟了20秒的响应时间,通过在当前线程上调用sleep()
。
因此,容器会继续每五秒调用doTimerWork()
方法,但如果前一次调用尚未完成,开始方法中的条件busy.compareAndSet(false, true)
会立即返回。这样确保了只有在上一个任务完成后,才会执行下一个任务。
4. 固定频率调度任务
一种实现方式是使用@Resource
注入定时服务,并在@PostConstruct
注解的方法中配置。当定时器到期时,带有@Timeout
注解的方法会被调用。
如前文所述,任务执行的开始并不等待前一次执行完成。当每个任务的执行独立时,应选择这种方式。以下代码片段创建一个每秒触发一次的定时器:
@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()));
}
}
另一种方式是使用@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. 带有初始延迟的调度任务
如果任务需要延迟启动,Jakarta EE也支持。例如,定时器可以在10秒后开始,然后每五秒触发一次:
@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()));
}
}
在示例中使用的createTimer
方法签名是createTimer(long initialDuration, long intervalDuration, java.io.Serializable info)
,其中initialDuration
是第一次定时器到期通知前必须经过的毫秒数,intervalDuration
是两次定时器到期通知之间的时间间隔。
在这个例子中,我们使用了10秒的initialDuration
和5秒的intervalDuration
。任务将在initialDuration
值后首次执行,并根据intervalDuration
持续执行。
6. 使用Cron表达式调度任务
我们看到的所有调度器,无论是程序化还是自动的,都支持Cron表达式。让我们看一个例子:
@Schedules ({
@Schedule(dayOfMonth="Last"),
@Schedule(dayOfWeek="Fri", hour="23")
})
public void doPeriodicCleanup() { ... }
在这个例子中,doPeriodicCleanup()
方法将在每周五的23:00以及每月最后一天被调用。
7. 总结
本文探讨了在Jakarta EE环境中使用各种方法安排任务,以之前使用Spring进行的示例为基础。
代码示例可在GitHub仓库中找到。