代码之家  ›  专栏  ›  技术社区  ›  A_M

从Java中触发shell脚本的最佳方法

  •  1
  • A_M  · 技术社区  · 16 年前

    我有一个shell脚本,我想从J2EE Web应用程序触发它。

    这个脚本做了很多事情——处理、ftping等等——这是一个遗留的事情。

    跑起来要花很长时间。

    我想知道最好的方法是什么。我希望用户能够单击一个链接,触发脚本,并向用户显示一条消息,说明脚本已经启动。我希望HTTP请求/响应周期是即时的,不管我的脚本运行起来需要很长时间。

    我可以想到三种选择:

    • 在处理用户单击的过程中生成一个新线程。但是,我认为这不符合J2EE规范。
    • 沿着HTTP响应流发送一些输出,并在触发脚本之前提交它。这给人一种错觉,认为HTTP请求/响应循环已经完成,但实际上处理请求的线程仍然在那里等待shell脚本完成。所以我基本上是为了自己的目的劫持了容器HTTP处理线程。
    • 创建一个包装脚本,它在后台启动我的主脚本。这将使请求/响应循环在容器中正常完成。

    以上所有操作都将使用servlet和runtime.getruntime().exec()。

    这是在Oracle的OC4J应用服务器上运行的,在Java1.4.2上运行。

    请有人对哪一个是最简单的解决方案有什么看法吗?为什么?

    或者有人有更好的方法吗?我们已经得到了石英,但是我们不想再将shell脚本重新实现为Java进程。

    谢谢。

    8 回复  |  直到 16 年前
        1
  •  2
  •   Laurence Gonsalves    16 年前

    我将使用选项3,特别是如果您实际上不需要知道脚本何时完成(或者除了等待进程结束之外,您还可以使用其他方法来发现)。

    选项1浪费了一个等待脚本完成的线程。选项2似乎是个坏主意。我不会劫持servlet容器线程。

        2
  •  3
  •   Pascal Thivent    16 年前

    你提到了石英,所以我们来选一个选项4(这是我的最佳选择):

    PS:最大的问题可能是找到文档,这是我能找到的最好的来源: How to use NativeJob?

        3
  •  2
  •   jarnbjo    16 年前

    您的应用程序是否需要评估您正在启动的脚本的输出,或者这是一个简单的fire-and-forget作业?如果不需要,您可以“滥用”runtime.getruntime().exec()将立即返回,进程将继续在后台运行这一事实。如果您实际上希望等待脚本/进程完成,则必须对exec()返回的进程对象调用wait for()。

    如果要开始的进程向stdout或stderr写入任何内容,请确保将这些内容重定向到日志文件或/dev/null,否则该进程将在一段时间后阻塞,因为stdout和stderr可以作为通过进程对象的缓冲能力有限的输入流使用。

        4
  •  1
  •   Andrzej Doyle    16 年前

    我的方法可能如下:

    • 建立一个 ExecutorService 在servlet中执行实际执行。
    • 创建的实现 Callable 使用适当的返回类型,包装实际的脚本执行(使用 Runtime.exec() 将Java输入变量转换为shell脚本参数,并将脚本输出到适当的Java对象。
    • 当一个请求出现时,创建一个适当的 Callable 对象,提交给执行者服务并将结果 Future 在某个持久的地方(例如用户的会话,或uid键映射,根据需要将密钥返回给用户以供以后查找)。然后立即向用户发送一个HTTP响应,表示脚本启动正常(如果需要,包括查找键)。
    • 为用户添加一些机制来轮询任务的进度,根据任务的状态返回“仍在运行”响应、“失败”响应或“成功+结果”响应。 未来 你刚才抬头看的。

    它有点手摇,但根据你的webapp的结构,你可能可以在某处安装这些通用组件。

        5
  •  1
  •   matt b    16 年前

    如果您的HTTP响应/用户不需要看到脚本的输出,或者不需要知道脚本何时完成,那么您最好的选择是在您提到的某种包装脚本中启动线程,这样它就可以作为一个整体在servlet容器环境之外运行。这意味着您可以免除自己在容器中管理线程的需要,或者像您提到的那样劫持线程等。

    只有当需要通知用户脚本何时完成和/或监视脚本的输出时,我才会考虑选项1或2。

        6
  •  0
  •   Alex Marshall    16 年前

    对于第二个选项,您可以使用servlet,并且在您响应HTTP请求之后,可以使用java. Lang.RunTime.Excor()执行脚本。我还建议你看看这里: http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html

    …对于使用它的一些问题和陷阱。

        7
  •  0
  •   Adriaan Koster    16 年前

    异步后端进程最健壮的解决方案是使用消息队列IMO。最近,我使用Spring嵌入式ActiveMQ代理实现了这一点,并安装了一个生产和消费bean。当需要启动一个作业时,我的代码会调用生产者,后者会将消息放到队列中。使用者订阅了队列,并被消息在单独的线程中启动。这种方法巧妙地将UI与排队机制(通过生产者)和异步进程(由使用者处理)分开。

    注意,这是一个Java 5,Spring配置的环境,运行在开发机器上的Tomcat服务器上,并部署到测试/生产机器上的WebLogic上。

        8
  •  0
  •   Dinuk    16 年前

    您的问题源于这样一个事实:您正试图违背J2EE中的“每个请求一个响应”模型,并在后端任务执行时动态更新最终用户的页面。

    除非您想继续介绍基于Ajax的解决方案,否则必须强制用户浏览器上呈现的页面定期“轮询”服务器以获取信息,直到后端任务完成。

    这可以通过以下方式实现:

    1. 当J2EE容器收到请求时,生成一个线程,该线程引用会话对象(将用于编写脚本的输出)。

    2. 初始化响应servlet以编写一个HTML页面,该页面将包含一个javascript函数,以便定期(每隔10秒左右)从服务器重新加载该页面。

    3. 在每个请求上,轮询会话对象以显示步骤1中生成的线程存储的输出。

    4. [可以添加清理逻辑,以便在线程完成后删除会话中存储的内容(如果需要),还可以在会话中为脚本执行的标记状态转换设置任何其他标志]

    这是一种实现您想要的东西的方法——它不是所有方法中最优雅的方法,但它本质上是由于需要使用请求/响应模型从服务器异步更新页面内容。

    实现这一点还有其他方法,但这实际上取决于您的约束有多不灵活。我听说过 Direct Web Remoting (虽然我还没玩过),也许值得一看 Developing Applications using Reverse-Ajax

    推荐文章