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

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

该脚本做了很多事情 – 处理,FTP等 – 这是一个遗留问题。

运行需要很长时间。

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

我可以想到三个select:

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

以上所有将使用servlet和Runtime.getRuntime()。exec()。

这在Java 1.4.2上使用Oracle的OC4J应用服务器在Solaris上运行。

请问有没有人有任何意见哪个是最简单的解决scheme,为什么?

还是有没有人有更好的方法? 我们已经有了Quartz,但是我们不希望将shell脚本重新实现为Java进程。

谢谢。

Solutions Collecting From Web of "从Java触发shell脚本的最佳方法"

我会选择3,特别是如果你实际上不需要知道脚本什么时候结束的话(或者有其他方法可以找到,而不是等待结束的话)。

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

你提到了石英,所以让我们去选择一个选项#4(这当然是最好的国际海事组织):

  • 使用Quartz Scheduler和一个org.quartz.jobs.NativeJob

PS:最大的问题可能是找到文档,这是我已经能够找到的最好的来源: 如何使用NativeJob?

您的应用程序是否需要评估您开始的脚本的输出,或者这是一个简单的“忘却工作”? 如果不是必需的,则可以“滥用”Runtime.getRuntime()。exec()将立即返回并继续在后台运行的事实。 如果您真的想等待脚本/进程完成,您将不得不在exec()返回的Process对象上调用waitFor()。

如果正在开始的进程向stdout或stderr写入任何内容,请务必将这些内容重定向到日志文件或/ dev / null,否则该进程将在一段时间后被阻止,因为stdout和stderr可用作具有有限缓冲能力的InputStream Process对象。

我的方法可能是类似于以下内容:

  • 在servlet中设置一个ExecutorService来执行实际的执行。
  • 使用适当的返回类型创建Callable实现,该实现包装实际的脚本执行(使用Runtime.exec() )将Java输入变量转换为shell脚本参数,并将脚本输出到适当的Java对象。
  • 当请求进入时,创建一个合适的Callable对象,将其提交给执行者服务,并将得到的Future放在持久的地方(例如,用户的会话或UID键映射将密钥返回给用户以便稍后查找,具体取决于需求)。 然后立即发送一个HTTP响应给用户,意味着脚本开始OK(包括查找键)。
  • 为用户添加一些机制来轮询他们任务的进度,根据您刚刚查找的“ Future ”状态返回“仍在运行”的响应,“失败”的响应或“成功+结果”响应。

这是一个手动的,但取决于你的web应用程序的结构,你可能可以适应这些通用组件在某个地方。

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

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

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

…使用它的一些问题和缺陷。

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

请注意,这是在开发人员计算机上的Tomcat服务器上运行的Java 5,Spring配置的环境,并在测试/生产机器上部署到Weblogic。

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

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

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

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

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

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

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

这是实现你想要的一种方式 – 它并不是所有方法中最优雅的一种方式,但实质上是由于需要使用请求/响应模型异步更新来自服务器的页面内容。

还有其他方法可以达到这个目标,但这取决于你的限制条件是多么的不灵活。 我听说过Direct Web Remoting (尽管我还没有玩过),可能值得看看使用Reverse-Ajax开发应用程序