无论操作系统如何,在Java中执行计划任务的最佳解决方案是什么?
我想在我的Java桌面应用程序上生成警报:
- 警报设置的具体日期/时间可以是5分钟或5个月
- 我需要能够在触发警报时创建SWT应用程序
- 我需要这个能够在任何操作系统上工作。 软件用户可能拥有Windows(其中90%),其余的Mac OS(包括我)
- 软件许可证必须允许我在商业程序中使用它,而不需要开源它(因此,没有GPL)
- 我不能要求用户安装Cygwin,因此实现需要是Windows和Unix的原生
我正在使用Java,Eclipse,SWT进行开发,我的应用程序是使用Java Web Start从我的服务器部署的。 我正在使用Mac OS X.6进行开发。
我想我有几个选择:
- 在启动时运行我的应用程序,并自己处理一切;
- 使用系统服务。
- 在Unix上使用cron表,在Windows上使用Scheduled Tasks
在启动时运行
我真的不喜欢这个解决方案,我希望有更优雅的东西。
参考: 我想在Mac OS / Windows上的System Startup上运行我的Java程序。 我怎样才能做到这一点?
系统服务
如果我将它作为系统服务运行,我可以从中受益,因为操作系统将确保我的软件:
- 一直在运行
- 没有/需要GUI
- 失败时重新启动
我研究了一些我可以使用的资源:
- run4j – CPL – 仅在Windows上运行,似乎是一个有效的候选者
- jsvc – Apache 2.0 – 仅限Unix,似乎是一个有效的候选者
- Java服务包装 – 各种 – 我买不起付费许可证,免费的是GPL。 因此,我不想/不能使用它
我在系统服务选项中的问题是:
- 还有其他选择吗?
-
我的计划实施是否正确:
- 在应用程序启动时,检查服务是否存在
- 如果没有安装:
- 升级用户以安装服务(Unix上的root,Windows上的UAC)
- 如果主机操作系统是Windows,请使用run4j注册该服务
- 如果主机操作系统是Unix,请使用jsvc注册该服务
- 如果它没有运行,请启动它
因此,在第一次运行时,应用程序将安装该服务并启动它。 当应用程序关闭时,服务仍在运行,并且不再需要应用程序,除非它未注册。
但是,我想我仍然会错过“启动时运行”function。
我对吗? 我错过了什么吗?
cron /任务计划程序
在Unix上,我可以轻松使用cron表而无需应用程序将用户升级为root。 我不需要处理重启,系统日期更改等。看起来不错。
在Windows上,我可以使用任务计划程序 ,即使在使用At或SchTasks的命令行中也是如此。 这看起来不错,但是我需要这个兼容从XP到7,我不能轻易测试这个。
那你会怎么做? 我错过了什么? 您有什么建议可以帮助我选择最好,最优雅的解决方案吗?
Bicou:很高兴你分享了你的解决方案!
请注意,“schtasks.exe”有一些本地化问题,如果你想用它创建一个每日触发器,在英语Windows上你必须使用“每日”,在德语(例如)你有改为使用“täglich”。
为了解决这个问题,我使用/xml
-option实现了对schtasks.exe
的调用,提供了一个我用模板创建的临时xml文件。
创建此类模板的最简单方法是“手动”创建任务,并在任务管理GUI工具中使用“导出”function。
在您列出的可用选项中,恕我直言选项3更好。 由于您只需要外部触发器来执行应用程序,因此CRON或计划任务是比您列出的其他选项更好的解决方案。 通过这种方式,您可以从应用程序中删除复杂性,并且您的应用程序也无需始终运行。 它可以在外部触发,当执行结束时,您的应用程序将停止。 因此,避免了不必要的资源消耗。
您也可以尝试使用Quartz http://quartz-scheduler.org/ 。 它具有类似CRON的语法来安排作业。
这是我最终实现的内容:
public class AlarmManager { public static final String ALARM_CLI_FORMAT = "startalarm:"; public static SupportedOS currentOS = SupportedOS.UNSUPPORTED_OS; public enum SupportedOS { UNSUPPORTED_OS, MAC_OS, WINDOWS, } public AlarmManager() { final String osName = System.getProperty("os.name"); if (osName == null) { Le("Unable to retrieve OS!"); } else if ("Mac OS X".equals(osName)) { currentOS = SupportedOS.MAC_OS; } else if (osName.contains("Windows")) { currentOS = SupportedOS.WINDOWS; } else { Le("Unsupported OS: "+osName); } } /** * Windows only: name of the scheduled task */ private String getAlarmName(final long alarmId) { return new StringBuilder("My_Alarm_").append(alarmId).toString(); } /** * Gets the command line to trigger an alarm * @param alarmId * @return */ private String getAlarmCommandLine(final long alarmId) { return new StringBuilder("javaws -open ").append(ALARM_CLI_FORMAT).append(alarmId).append(" ").append(G.JNLP_URL).toString(); } /** * Adds an alarm to the system list of scheduled tasks * @param when */ public void createAlarm(final Calendar when) { // Create alarm // ... stuff here final long alarmId = 42; // Schedule alarm String[] commandLine; Process child; final String alarmCL = getAlarmCommandLine(alarmId); try { switch (currentOS) { case MAC_OS: final String cron = new SimpleDateFormat("mm HH d M '*' ").format(when.getTime()) + alarmCL; commandLine = new String[] { "/bin/sh", "-c", "crontab -l | (cat; echo \"" + cron + "\") | crontab" }; child = Runtime.getRuntime().exec(commandLine); break; case WINDOWS: commandLine = new String[] { "schtasks", "/Create", "/ST "+when.get(Calendar.HOUR_OF_DAY) + ":" + when.get(Calendar.MINUTE), "/SC ONCE", "/SD "+new SimpleDateFormat("dd/MM/yyyy").format(when.getTime()), // careful with locale here! dd/MM/yyyy or MM/dd/yyyy? I'm French! :) "/TR \""+alarmCL+"\"", "/TN \""+getAlarmName(alarmId)+"\"", "/F", }; Ld("create command: "+Util.join(commandLine, " ")); child = Runtime.getRuntime().exec(commandLine); break; } } catch (final IOException e) { Le("Unable to schedule alarm #"+alarmId, e); return; } Li("Created alarm #"+alarmId); } /** * Removes an alarm from the system list of scheduled tasks * @param alarmId */ public void removeAlarm(final long alarmId) { Li("Removing alarm #"+alarmId); String[] commandLine; Process child; try { switch (currentOS) { case MAC_OS: commandLine = new String[] { "/bin/sh", "-c", "crontab -l | (grep -v \""+ALARM_CLI_FORMAT+"\") | crontab" }; child = Runtime.getRuntime().exec(commandLine); break; case WINDOWS: commandLine = new String[] { "schtasks", "/Delete", "/TN \""+getAlarmName(alarmId)+"\"", "/F", }; child = Runtime.getRuntime().exec(commandLine); break; } } catch (final IOException e) { Le("Unable to remove alarm #"+alarmId, e); } } public void triggerAlarm(final long alarmId) { // Do stuff //... Li("Hi! I'm alarm #"+alarmId); // Remove alarm removeAlarm(alarmId); } }
用法很简单。 使用以下方式安排新警报:
final AlarmManager m = new AlarmManager(); final Calendar cal = new GregorianCalendar(); cal.add(Calendar.MINUTE, 1); m.createAlarm(cal);
触发这样的警报:
public static void main(final String[] args) { if (args.length >= 2 && args[1] != null && args[1].contains(AlarmManager.ALARM_CLI_FORMAT)) { try { final long alarmId = Long.parseLong(args[1].replace(AlarmManager.ALARM_CLI_FORMAT, "")); final AlarmManager m = new AlarmManager(); m.triggerAlarm(alarmId); } catch (final NumberFormatException e) { Le("Unable to parse alarm !", e); } } }
在Mac OS X.6和Windows Vista上测试过。 L
类是System.out.println
的助手, G
保存我的全局常量(这里,我的服务器上的JNLP文件用于启动我的应用程序)。
我相信你的情况是正确的。 由于服务是系统特定的东西,恕我直言,你不应该使用通用包来覆盖它们,但是每个系统都有一个特定的机制。