在Java中,无论操作系统是什么,执行计划任务的最佳解决方案是什么?

在Java中,无论操作系统是什么,执行计划任务的最佳解决方案是什么?,java,service,cron,scheduled-tasks,Java,Service,Cron,Scheduled Tasks,我想在Java桌面应用程序上生成警报: 报警设置为特定日期/时间,可以是5分钟或5个月 我需要能够创建一个SWT应用程序时,报警被触发 我需要这个能够在任何操作系统上工作。软件用户可能有Windows(90%)和其他Mac操作系统(包括我) 软件许可证必须允许我在商业程序中使用它,而不需要开源(因此,没有GPL) 我不能要求用户安装Cygwin,因此实现需要是Windows和Unix的本机实现 我正在使用Java、Eclipse、SWT进行开发,我的应用程序是使用JavaWebStart从服

我想在Java桌面应用程序上生成警报:

  • 报警设置为特定日期/时间,可以是5分钟或5个月
  • 我需要能够创建一个SWT应用程序时,报警被触发
  • 我需要这个能够在任何操作系统上工作。软件用户可能有Windows(90%)和其他Mac操作系统(包括我)
  • 软件许可证必须允许我在商业程序中使用它,而不需要开源(因此,没有GPL)
  • 我不能要求用户安装Cygwin,因此实现需要是Windows和Unix的本机实现
我正在使用Java、Eclipse、SWT进行开发,我的应用程序是使用JavaWebStart从服务器部署的。我正在使用MacOSX.6进行开发


我想我有几个选择:

  • 在启动时运行我的应用程序,自己处理所有事情
  • 使用系统服务
  • 在Unix上使用cron表,在Windows上使用计划任务

  • 启动时运行 我真的不喜欢这个解决方案,我希望有更优雅的解决方案。
    参考文献:

    系统服务 如果我将其作为系统服务运行,我可以从中受益,因为操作系统将确保我的软件:

    • 他总是在跑步
    • 没有/不需要GUI
    • 故障时重新启动
    我研究了一些我可以使用的资源:

    • -CPL-仅在Windows上运行,似乎是一个有效的候选者
    • -Apache2.0-仅限Unix,似乎是一个有效的候选者
    • -各种-我负担不起付费许可证,免费许可证是GPL。因此,我不想/不能使用这个
    我在系统服务选项中的问题是:

  • 还有其他选择吗
  • 我的计划实施是否正确:

    • 在应用程序启动时,检查服务是否存在
    • 如果未安装:
      • 升级用户以安装服务(Unix上为root用户,Windows上为UAC)
      • 如果主机操作系统是Windows,请使用run4j注册服务
      • 如果主机操作系统是Unix,请使用jsvc注册服务
    • 如果它没有运行,请启动它
  • 因此,在第一次运行时,应用程序将安装并启动服务。当应用程序关闭时,服务仍在运行,不再需要该应用程序,除非该应用程序已注销。
    但是,我想我仍然怀念“启动时运行”功能

    我说得对吗?我错过什么了吗

    cron/任务调度器 在Unix上,我可以轻松地使用cron表,而无需应用程序将用户升级到root用户。我不需要处理重新启动、系统日期更改等。看起来不错

    在Windows上,我可以使用,甚至可以在命令行中使用或。这看起来不错,但我需要它从XP兼容到7,我不能轻易测试它



    那你会怎么做?我错过什么了吗?您有什么建议可以帮助我选择最好、最优雅的解决方案吗?

    我相信您的方案是正确的。因为服务是特定于系统的东西,所以IMHO您不应该使用一个通用包来涵盖所有服务,而是应该为每个系统提供一个特定的机制

    在您列出的可用选项中,IMHO选项3更好。
    由于您只寻找外部触发器来执行应用程序,因此CRON或计划任务是比您列出的其他选项更好的解决方案。通过这种方式,您消除了应用程序的复杂性,并且应用程序不必总是运行。它可以在外部触发,当执行结束时,应用程序将停止。因此,可以避免不必要的资源消耗。

    您也可以尝试使用Quartz。它具有类似于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) {
                L.e("Unable to retrieve OS!");
            } else if ("Mac OS X".equals(osName)) {
                currentOS = SupportedOS.MAC_OS;
            } else if (osName.contains("Windows")) {
                currentOS = SupportedOS.WINDOWS;
            } else {
                L.e("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",
                    };
                    L.d("create command: "+Util.join(commandLine, " "));
                    child = Runtime.getRuntime().exec(commandLine);
                    break;
                }
            } catch (final IOException e) {
                L.e("Unable to schedule alarm #"+alarmId, e);
                return;
            }
    
            L.i("Created alarm #"+alarmId);
        }
    
        /**
         * Removes an alarm from the system list of scheduled tasks
         * @param alarmId
         */
        public void removeAlarm(final long alarmId) {
            L.i("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) {
                L.e("Unable to remove alarm #"+alarmId, e);
            }
        }
    
        public void triggerAlarm(final long alarmId) {
            // Do stuff
            //...
            L.i("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) {
                L.e("Unable to parse alarm !", e);
            }
        }
    }
    

    在Mac OS X.6和Windows Vista上测试。类
    L
    System.out.println
    G
    的助手,它们保存着我的全局常量(这里是我服务器上用于启动我的应用程序的JNLP文件)。

    Bicou:很高兴您共享了您的解决方案

    请注意,“schtasks.exe”有一些本地化问题,如果您想使用它创建每日触发器,在英语窗口上您必须使用“daily”,在德语窗口(例如)上您必须使用“täglich”

    为了解决这个问题,我使用
    /xml
    -选项实现了对
    schtasks.exe
    的调用,提供了一个由模板创建的临时xml文件


    创建这样一个模板最简单的方法是“手动”创建一个任务,并使用任务管理GUI工具中的“导出”功能。

    我喜欢unix上的cron,因为它已经安装,不需要root权限。如果我能使用类似cron的语法,Quartz看起来不错,但是我不想让我的用户安装额外的软件来运行我的(运行cygwin也一样)。好吧,它当然可以让你的生活更轻松,而且不需要太多exra JARs。哦,等等,我误读了产品的描述。看起来真不错!我一整天都会仔细看看,现在就试试。非常感谢。除非我弄错了,否则我仍然需要找到一种方法,在启动时自动启动一个空程序,该程序将启动Quartz调度程序,当我需要时,这些作业将实际运行。因此Quartz解决了报警/调度部分,而不是实际的后台进程管理。这仍然需要用户首先启动我的程序,对吗?是的,没错。此外,如果使用Quartz的应用程序/服务在根据类似cron的调度器表达式触发时未运行,则将错过该事件。