2009-09-29 35 views
1

我有一个我想从J2EE Web应用程序触发的shell脚本。从Java触发shell脚本的最佳方法

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

运行需要很长时间。

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

我能想到的三个选项:

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

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

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

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

或者没有人有更好的方法吗?我们已经有了Quartz,但我们不想将shell脚本重新实现为Java过程。

谢谢。

回答

2

我会选择3,特别是如果你实际上并不需要知道脚本何时结束(或者有其他方法来寻找,而不是等待进程结束)。

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

+0

另一个想法是使用单独的处理线程,该线程在队列中等待(或用于Object.notify()+ Object.wait())的通知,并且具有HTTP请求处理程序推入队列(或调用通知)。 – ash 2013-08-30 06:51:20

0

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

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

+0

谢谢,但我假设你会意识到我会使用java.lang .Runtime.exec()在我上面提出的所有解决方案中。此外,我还假设他们都需要使用servlet(或类似的)。 – 2009-09-29 16:36:38

+0

我编辑了我的问题来澄清。 – 2009-09-29 16:37:45

1

我对这种做法很可能是类似以下内容:

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

这是一个有点handwavy但根据您的web应用程序是如何组织的,你可以大概在什么地方适合这些通用部件。

+0

对不起 - 忘了说,它在1.4.2 JRE中,所以并发包默认是不可用的。 – 2009-09-29 17:17:52

+0

有一个可用于Java 1.4的JSR 166(java.util.concurrent)的回送。请参阅http://backport-jsr166.sourceforge.net/ – 2009-09-29 18:18:07

1

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

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

2

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

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

+0

我很担心JavaDocs中关于缓冲区可能阻塞的进程的声明,所以我并不想这么做 - 以防万一出现stdout/stderr。所以,如果你可以保证所有的stdout/stderr被重定向,那么这个方法是否可以保证工作?但我想这将涉及修改shell脚本,这在理想情况下我不想做。 – 2009-09-30 07:07:53

0

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

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

0

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

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

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

  1. 当J2EE容器接收该请求,生成一个线程这需要给会话对象的引用(其将被用来编写脚本的输出)

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

  3. 在每个请求,轮询会话对象来显示由产生的线程在步骤存储在输出1

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

这是为了实现你想要的一种方式的标记状态转换 - 这是不是最优雅的所有方法,但实质上是由于需要异步更新服务器中的页面内容r,带有请求/响应模型。

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

相关问题