Java EE应用程序服务器具有本机调度支持,并且在大多数应用程序中,不需要包括外部依赖项,例如著名的Quartz调度程序库。
Java EE 6和7完整配置文件中提供的Java EE 6计时器服务为我们提供了许多选项来定义调度间隔,以及如果停止并重新启动包含调度程序的应用程序会发生什么情况。
Java EE调度程序可以是:
- 持久的 :应用程序服务器在应用程序关闭时保存计划事件,以免丢失事件
- 自动 :简单的调度程序定义,大多数细节由应用程序服务器处理
- 程序化的 :我们可以完全控制所有调度程序参数。
为了确定哪个是最佳选择,我们应该首先回答以下问题:
1.是否可以错过一些调度事件?
如果我们停止或重新启动应用程序(例如在更新过程中),则调度程序将停止,并且某些调度事件可能会丢失。
可以将调度程序配置为保存错过的事件,并在应用程序再次启动时执行它们。 应用程序服务器使用内部数据库(通常是Derby之类的Java DB)来存储丢失的事件。
这是一个持久的调度程序。
注意 :应用程序服务器将在应用程序(重新)启动时生成所有丢失的事件。 事件突发的频率和延迟是可配置的。 有关详细信息,请参见应用程序服务器文档。
我们还可以选择不保留计划事件,如果应用程序未运行,则计划事件将丢失。
在非持久性情况下,调度程序生命周期与应用程序相同:它在应用程序启动时创建,然后在应用程序关闭时销毁。
相反,持久性调度程序可以保留到应用程序重新启动; 当应用程序未运行时,它只是在休眠。
如何选择?
如果计划的功能对业务至关重要,并且我们不能错过任何事件,那么持久性计划程序就是您的理想之选。
在所有其他情况下,非持久性调度程序更轻便(不使用数据库)并且易于管理(更新应用程序时的障碍更少,因为在应用程序重新启动时不会出现调度事件;在应用程序启动时始终会创建新的调度程序)。
2.该应用程序将在群集中运行吗?
在集群中,我们的应用程序有多个实例正在运行(每个集群节点一个实例),并且所有实例都有自己的调度程序副本。
但是我们只需要在所有群集节点之间运行一个调度程序,否则我们将拥有同一事件的多个副本。
每个应用程序服务器都有自己的方式来处理“多个调度程序实例”问题(例如,请参阅WebSphere的[link 2]),但是通常,当我们使用集群时,要求调度程序应该是持久的。
3.调度间隔在生产时是否可以编程?
要回答的另一个重要问题:在部署应用程序后,我们是否应该能够更改调度?
如果调度参数(它的频率)是固定的,则自动调度程序是最佳解决方案,因为它的编码非常简单:只需一个注释(如果您喜欢旧方法,则可以少写XML行)。
相反,如果调度程序应该以某种方式可配置,则最好的解决方案是编程调度程序,它使我们能够在应用程序启动期间定义所有调度程序参数,并从属性文件,数据库或我们正在使用的任何配置解决方案中读取它们。
记得:
- 自动调度程序计划是在构建时定义的
- 在应用程序启动时定义了程序化调度程序计划
自动排程器
定义自动调度程序非常容易:
- 创建在启动时执行的单例EJB
- 创建一个将在每次调度事件时调用的方法
注意:完整的代码可以在文章项目中找到[请参见链接3]。
第一步:
@Startup
@Singleton
public class MyScheduler
@ javax.ejb.Startup批注要求EJB容器在应用程序启动时创建EJB(以及我们的调度程序)。
@ javax.ejb.Singleton批注强制EJB容器仅创建一个实例。
重要提示:调度程序由应用程序服务器(EJB容器)使用; 应用程序代码的其余部分永远不要实例化它。
然后,我们需要在安排事件时调用的方法:
@Schedule(/** scheduling parameters */)
public void doSomeThing() {..}
该方法应为public,并返回void。
@ javax.ejb.Schedule注释定义:
- 计划间隔,以cron格式[请参见链接4]
- 调度程序的名称(应用程序中可以有许多调度程序)
- 一个持久的布尔标志,它定义调度程序是否持久
例如:
@Schedule(minute = "*/15",hour = "*",info = "15MinScheduler",persistent = false )
它定义了每15分钟运行一次的非持久性调度程序。
有关完整示例,请参见文章项目[链接3]中的AutomaticPersistentScheduler和AutomaticNonPersistentScheduler类。
注意 :还有@Schedules批注[请参见链接1],该批注允许定义多个@Schedule定义。
当存在无法在单个cron定义中表达的计划要求时,此功能很有用。
程序调度器
程序化调度程序的构建更为复杂,但是它为我们提供了定义调度程序参数的完全自由。
我们还有更多步骤:
- 创建在启动时执行的单例EJB
- 查找TimerService资源
- 在EJB初始化时创建调度程序
- 创建一个@Timeout方法
第一步与自动调度程序相同:
@Startup
@Singleton
public class MyScheduler
然后(第二步),我们需要查找应用程序服务器计时器服务,但是注入可以帮助我们:
@Resource
private TimerService timerService;
在应用程序启动时,EJB容器将注入一个TimerService实例,该实例允许我们与Timer服务进行交互。 例如,我们可以列出(甚至删除)为应用程序定义的所有调度程序。
在我们的例子中,Timer服务将用于创建新的调度程序,如下所示(第三步):
String minuteSchedule = "*/15";
String hourSchedule = "*";
ScheduleExpression schedule = new ScheduleExpression().minute(minuteSchedule).hour(hourSchedule);
javax.ejb.ScheduleExpression定义cron [参见链接4]时间表,例如@Schedule批注。
@Schedule和ScheduleExpression之间非常重要的区别在于,第一个固定在构建时:要更改调度参数(例如,从每15分钟更改为每30分钟),我们需要更改类代码,然后再次构建和部署应用程序。
在后一种情况下(SchedulerExpression),可以在应用程序启动时定义和更改计划参数(在示例中变量minutesSchedule和hourSchedule上方)并进行更改,例如,从以下示例中读取minutesSchedule和hourSchedule:
属性文件或连接的DBMS。
TimerConfig timerConfig = new TimerConfig();
timerConfig.setInfo("ProgrammaticPersistentScheduler");
timerConfig.setPersistent(true);
javax.ejb.TimerConfig让我们可以选择定义调度程序的名称(setInfo(String)),以及是否为持久性名称(setPersistent(boolean))。
通过使用ScheduleExpression和TimerConfig实例,我们可以使用Timer服务创建调度程序(更精确地说是日历计时器)。
timerService.createCalendarTimer(schedule, timerConfig);
createCalendarTime()方法返回一个javax.ejb.Timer实例,该实例可用于查询计时器,例如下一个将来的事件何时发生甚至破坏。
调度程序。
最后一步是在类中定义一个方法,该方法将在每次调度事件时调用
@Timeout
public void doSomeThing() {..}
该方法应为public,并返回void。
我们已经启动并运行了调度程序。
结论
Java EE标准为我们提供了许多选择来定义计划程序,该计划程序以周期性和重复的方式运行我们的代码。 不需要其他项目依赖项。
链接
- 计时器服务API上的Oracle Java EE6教程
- IBM WebSphere 8.x使用EJB计时器服务为企业bean创建计时器
- GitHub上的文章项目
- Cron在Wikipedia上
翻译自: https://www.javacodegeeks.com/2016/10/java-ee-schedulers.html