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

时间:2009-09-29 16:25:34

标签: java unix java-ee shell quartz-scheduler

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

脚本做了很多事情 - 处理,FTP等 - 这是遗留物。

运行需要很长时间。

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

我可以想到三个选择:

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

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

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

请问有没有人对哪个是最不易解决的问题有什么看法?为什么?

或者有没有人有更好的方法?我们有Quartz可用,但我们不希望将shell脚本重新实现为Java进程。

感谢。

8 个答案:

答案 0 :(得分:3)

你提到了Quartz所以让我们选择#4选项(IMO当然是最好的):

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

答案 1 :(得分:2)

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

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

答案 2 :(得分:2)

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

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

答案 3 :(得分:1)

我对此的处理可能类似于以下内容:

  • 在servlet中设置ExecutorService以执行实际执行。
  • 使用适当的返回类型创建Callable的实现,包含实际的脚本执行(使用Runtime.exec())将Java输入变量转换为shell脚本参数,并将脚本输出转换为适当的Java对象。
  • 当请求进入时,创建一个适当的Callable对象,将其提交给执行程序服务,并将生成的Future保存在某处(例如,用户会话或UID键控映射返回密钥到用户以后查找,具体取决于要求)。然后立即向用户发送HTTP响应,暗示脚本已启动OK(如果需要,还包括查找键)。
  • 添加一些机制供用户轮询其任务进度,根据Future的状态返回“仍在运行”的响应,“失败”的响应或“成功的+结果”响应那你只是抬起头来。

它有点手工但是根据你的webapp的结构,你可以在某个地方安装这些通用组件。

答案 4 :(得分:1)

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

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

答案 5 :(得分:0)

对于第二个选项,您可以使用servlet,在响应HTTP请求后,可以使用java.lang.Runtime.exec()来执行脚本。我还建议您查看此处:http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html

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

答案 6 :(得分:0)

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

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

答案 7 :(得分:0)

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

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

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

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

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

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

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

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

    还有其他方法可以实现这一目标,但这实际上取决于您的约束条件的不灵活性。我听说过Direct Web Remoting(虽然我还没玩过它),但值得一看 Developing Applications using Reverse-Ajax