站内搜索: 请输入搜索关键词

当前页面: 开发资料首页Java 专题通过Web scripting使用自己的程序语言

通过Web scripting使用自己的程序语言

摘要: 通过Web scripting使用自己的程序语言
内容: JSR 223中Web scripting框架介绍

摘要
在Char Wu的上一篇文章"为Java创建你自己的脚本语言-JSR 223介绍" 中,他设计实现一个称为BoolScript的简单的布尔语言,并用它展示了除了Web scripting以外的JSR 223的各个方面。在本文中改造了这个语言,使他能在servlet容器中执行BoolScript代码。

JSR 223中的Web scripts规定了能在servlet容器中执行脚本的脚本引擎必须的实现。本文中我们将改造BoolScript(一个简单的布尔语言,在我的上一篇"为Java创建你自己的脚本语言-JSR 223介绍" 中有介绍),使之符合Web scripting。如果我们对BoolScript和JSP作关于Web scripting的比较的话,那就能发现在本质上我们要做的就是将BoolScript引擎做类似JSP引擎的改造。JSP引擎允许servlet容器执行JSP代码,同样,BoolScript引擎允许servlet容器执行BoolScript代码

在我们开始之前有一点必须注意:JSR 223仍在制订中。Web scripting未包含在最新的Java平台Standard Edition 6 beta build 80中。除了Java SE beta,另一个JSR 223的实现是它的参考实现。但是,这个参考实现太老了,以至于在build 80上无法使用。在本文的示例中,我花了点精力在修正了这些不能在beta版的Java SE上无法执行的错误。虽然我不能代表Java SE开发论坛,但我相信JSR 223的制定和实现仍会有一些变化,但是我也相信它的概念和总体结构不会有变化。而且在Java SE 6发行版中,那些暂时的错误也会被修正。目前,我们做的这些修改就当作是了解JSR 223内部如何运作的机会吧。

本文不需要太多的Java servlet知识。只需要知道如何在servlet中使用servlet上下文,以及有如何在servlet容器中部署sevlet的基础知识就可以了。


版权声明:任何获得Matrix授权的网站,转载时请务必保留以下作者信息和链接
作者:Char Wu;Niuji
原文:http://www.javaworld.com/javaworld/jw-05-2006/jw-0522-scripting.html?lsrc=jwrss
Matrix:http://www.matrix.org.cn/resource/article/2006-09-19/Web+scripting_435bffc7-4775-11db-af0b-0f766c077b58.html
关键字:Web scripting

建立示例
建立示例的运行环境需要下面三个软件:
1.Java SE 6 beta build 80。你可以在java.net找到下载。
2.一个servlet容器。本文中我使用了Tomcat 5.5。你可以去Apache Tomcat下载。
3.JSR 223参考实现。你可以在JSR 223 Webpage找到下载。

在资源里可找到本文示例代码的下载,其中包含了下面两个文件:
1.BoolScript-src.zip,包含了BoolScript引擎的源代码
2.BoolScript-binary.zip,包含了BoolScript引擎的二进制代码和在详细讨论“配置”章节时需要的几个文件。

当你下载了所有必须的软件后,你需要按如下步骤建立和运行示例:
•安装Java SE 6 beta build 80和Tomcat 5.5。按照Tomcat的约定,我使用 {CATALINA_HOME}表示Tomcat的安装目录。
•解压缩BoolScript-binary.zip到{CATALINA_HOME}\webapps。如果你使用了不同的servlet容器,你需要将BoolScript-binary.zip解压缩到发布Web应用的目录。
•将JSR 223参考实现解压缩到一个临时目录,拷贝文件jars\script.jar到{CATALINA_HOME}\webapps\BoolScript\WEB_INF\lib。
•打开{CATALINA_HOME}\conf\server.xml,根据下面的代码增加一个context:

...

...

...

...
debug="0" reloadable="true" />
...





现在可以开始测试示例了。运行Tomcat运行后,使用Web浏览器打开http://localhost:8080/BoolScript/logic.bool。如果一切正常,你可以看到:[true, false, true]。

下图和随后的列表描述了当你执行上面的测试时事件发生的顺序。下文将详细解释这个图的各个部分。


示例概览。点击放大。
1. Web浏览器向Web服务器发送HTTP请求
2. Servlet容器将HTTP请求分派给我们的脚本servlet
3. 脚本servlet调用HTTP脚本上下文的getScriptSource()方法得到脚本文件logic.bool
4. HTTP脚本上下文定位logic.bool
5. HTTP脚本上下文读取文件logic.bool
6. HTTP脚本上下文将logic.bool的内容返回给脚本servlet
7. 脚本servlet将这个内容传递给脚本引擎执行
8. 脚本引擎将执行结果返回给脚本servlet
9. 脚本servlet返回结果给servlet容器
10. Web服务器返回结果给Web浏览器

从上面的图例和列表可以看出:脚本servlet和HTTP脚本上下文在servlet容器执行脚本中扮演着重要的角色。

脚本servlet
JSR 223的Web scripting框架主要由三部分组成:脚本servlet、HTTP脚本上下文和配置。(注:虽然这三个不是标准术语,但它们对我们的讨论有很大的帮助。)我们从脚本servlet开始依次讨论它们。脚本servlet实际上就是一个servlet(例如,一个GenericServlet的实例)。Servlet在service()方法里执行任务,脚本servlet也是这样。和典型的servlet一样,脚本servlet在它的service()里执行的任务有:处理HTTP请求和在HTTP相应里组织Web内容。和典型的servlet不同的是,脚本servlet在执行任务的同时会在一个或多个脚本引擎里运行脚本代码。

既然我已经在概念上解释了脚本servlet,现在我们看下代码。Servlet必须直接或间接地继承javax.servlet.GenericServlet。本文中我们实现的脚本servlet名为BoolScriptServlet。它继承了GenericHttpScriptServlet(GenericHttpScriptServlet继承了GenericServlet)。通过这种方法,我们就能确保我们的脚本servlet是满足servlet规定的。

如果你看下JSR 223的参考实现,你会注意到它提供了一个GeericServlet的子类HttpScriptServlet,这个类就在javax.script.http包中。HttpScriptServlet是一个非常有用的类,脚本引擎提供者可以在它的基础上编写他们的脚本servlet。但不幸的是,HttpScriptServlet太老旧了,无法再Java SE 6 build 80上正常运行。否则的话我就不用继承GenericHttpScriptServlet了。我将实现GenericHttpScriptServlet作为解决HttpScriptServlet的上述问题的快速解决方法。
在我实现HttpScriptServlet的时候,我作了一点小的改进:在其中声明了一个抽象方法runScript()。由于HttpScriptServlet的service()方法为了使脚本servlet符合JSR 223,必须使用固定的流程,因此我这样做是有帮助的。当一个脚本servlet继承GenericHttpScriptServlet的时候,只有如何调用脚本引擎执行脚本代码的部分需要改变,这样的话service()方法里的代码就可以不用改动,只需要修改runScript()的代码。

BoolScriptServlet.java里另一个重要的方法是getContext():
public HttpScriptContext getContext(HttpServletRequest request, HttpServletResponse response) throws ServletException {
if (ctx == null)
ctx = new SimpleHttpScriptContext();

ctx.initialize(this, request, response);
return ctx;
}


getContext()方法返回一个HTTP脚本上下文(例如,一个实现了接口javax.script.http.HttpScriptContext的类的实例)。HTTP脚本上下文是下一章的主题,现在重要的是明确脚本servlet对每一个接收到的HTTP请求都需要为其初始化HTTP上下文。而初始化HTTP上下文的代码就写在getContext()里。如上面的代码所示,getContext()方法在返回HTTP脚本上下文前执行了它的initialize()方法。我们将在下一章看到这个初始化做了什么,以及为什么脚本servlet必须为每个HTTP请求初始化HTTP脚本上下文。

HTTP脚本上下文
servlet在servlet上下文中运行。因为脚本servlet也是servlet,因此也不例外。但是除了servlet上下文,脚本servlet还有一个典型的servlet没有的上下文——HTTP脚本上下文。Servlet上下文的作用是给servlet提供运行它的Web应用的视图。HTTP脚本上下文的作用是给脚本(用脚本语言写的代码)提供运行它的脚本引擎的视图。

因为HTTP脚本上下文就是一个脚本上下文(上一篇文章中已有讨论),所以所有对脚本上下文的讨论对HTTP脚本上下文也是一样的。回想一下我们已经学习的:一个脚本上下文拥有0或多个范围;一个范围实质上是一个名称-值对的集合。上一篇文章里,我讨论了全局范围和引擎范围。

HTTP脚本上下文和一般的脚本上下文不同的是:它有三个特殊的范围,同时还有一些操作配置信息的方法(这个我们稍候会讨论)。三个特殊的范围分别是:request范围、session范围、application范围。为什么需要这三个范围而且对它们做什么样的操作呢?回忆上一篇文章,我将脚本上下文解释为Java应用程序和脚本引擎传递数据的工具。同样的,HTTP脚本上下文是Web应用和脚本引擎传递数据的工具。当Web应用相应HTTP请求执行相应的servlet的时候,会将这个HTTP请求和相应的HTTP相应传递个这个servlet。

当所有的都用Java实现的时候,没有任何数据需要在Java和脚本语言间穿越。这种情况是不需要脚本上下文的。但是如果是脚本servlet使用脚本引擎执行脚本代码,这时就需要HTTP脚本上下文在不同语言间传递Web相关的数据。简单来说,JSR 223的Web scripting框架要求我们将Web相关的数据放在HTTP脚本上下文的request范围、session范围和application范围中。
这些范围的名字已经说明了它们的作用。Request范围包含了HTTP请求的信息,如下代码所示:
//Type of httpReq is HttpServletRequest
httpReq.setAttribute(nameX, valueX);

//Type of httpScriptCtx is HttpScriptContext
httpScriptCtx.setAttribute(nameX, valueX, ScriptContext.REQUEST_SCOPE)

//The two method calls above have the same effect

valueX1 = httpReq.getAttribute(nameX)

valueX2 = httpScriptCtx.getAttribute(nameX, ScriptContext.REQUEST_SCOPE)

//The two method calls above have the same effect
//and valueX1 = valueX2 = valueX


同样,Session范围包含了HTTP会话的信息,如下代码所示:
//Type of httpSession is HttpSession
httpSession.setAttribute(nameX, valueX);

//Type of httpScriptCtx is HttpScriptContext
httpScriptCtx.setAttribute(nameX, valueX, ScriptContext.SESSION_SCOPE)

//The two method calls above have the same effect

valueX1 = httpSession.getAttribute(nameX)

valueX2 = httpScriptCtx.getAttribute(nameX, ScriptContext.SESSION_SCOPE)

//The two method calls above have the same effect
//and valueX1 = valueX2 = valueX


Application范围包含了Web应用的信息,如下代码所示:
//Type of servletCtx is ServletContext
servletCtx.setAttribute(nameX, valueX);

//Type of httpScriptCtx is HttpScriptContext
httpScriptCtx.setAttribute(nameX, valueX, ScriptContext.APPLICATION_SCOPE)

//The two method calls above have the same effect

valueX1 = servletCtx.getAttribute(nameX)

valueX2 = httpScriptCtx.getAttribute(nameX, ScriptContext.APPLICATION_SCOPE)

//The two method calls above have the same effect
//and valueX1 = valueX2 = valueX


Servlet上下文和HTTP脚本上下文的异同通过解释application范围就可以区分:servlet上下文和HTTP脚本上下文是为不同目的而存在的不同的上下文。他们通过HTTP脚本上下文的application范围相关联,原因是application范围就是servlet上下文。
我已经讨论了application范围、session范围和request范围各自包含的名称-值对。但是我还没有说什么时候设置这些值。回忆上一章我曾说过:脚本servlet在getContext()方法里为每个收到的HTTP请求初始化了HTTP脚本上下文。getContext()方法里调用了HTTP脚本上下文的initialize()方法来对这三个范围设置名称-值对。

HTTP脚本上下文保持了Servlet、HttpServletRequest和HttpServletResponse的引用,同时还提供了getServlet()、getRequest()和getResponse()用来得到这些引用。当getContext()调用initialize()的时候,将Servlet、HttpServletRequest以及HttpServletResponse传递给了initialize()方法。然后initialize()方法使用这三个参数初始化HTTP脚本上下文的Servlet、HttpServletRequest和HttpServletResponse引用。脚本servlet需要为每个HTTP请求初始化HTTP脚本上下文,否则request范围、session范围和application范围里的数据可能会过期。

在结束本章前,让我们更详细的讨论下代码。JSR 223定义的HTTP脚本上下文的模型结构是javax.script.http.HttpScriptContext。注意HttpScriptContext继承了javax.script.ScriptContext。也就是说HTTP脚本上下文也是一个脚本上下文。和脚本上下文一样,也可以通过getBindings()和setBindings()方法得到和设置HTTP脚本上下文的范围;通过调用ScriptContext的getAttribute()和setAttribute()或者范围的get()和put()得到和设置名称-值对。
如果你看一下JSR 223的参考实现的话,你可以发现它提供了HttpScriptContext的实现:javax.script.http.GenericHttpScriptContext。很不幸,它太老了,无法在Java SE 6 beta build 80上运行。因此我实现了SimpleHttpScriptContext作为GenericHttpScriptContext的修正。GenericHttpScriptContext的问题是没有实现getBindings()和setBindings()方法。我作的修改就是 让SimpleHttpScriptContext继承了GenericHttpScriptContext的所有的方法,然后再增加了那两个缺少的方法。

除了那些从ScriptContext继承的操作范围和名称-值对的方法,在JSR 223d的Web scripting框架还定义了HttpScriptContext获取各种配置的方法。

配置
Web scripting框架标准定义了一些配置,通过设置这些配置管理员可以控制脚本引擎的执行。这个管理员我指的是在servlet容器里部署脚本引擎的人员。他们担任的角色和脚本引擎提供者不同。到现在为止,我们已经讨论了脚本servlet和HTTP脚本上下文。这两个概念的需求和他们之间的具体关系是脚本引擎提供者需要考虑的。如果你的角色是管理员,并且你学习Web scripting的主要目的是知道如何在servlet容器里部署脚本引擎,你可以忽略本文前面的部分,直接从本章开始。

对于脚本引擎提供者,在脚本引擎中实现配置对应的行为是你的责任。否则管理员在部署脚本引擎的时候会对引擎未正常运行感到十分惊讶。首先我们将讨论管理员如果在servlet容器里部署脚本引擎。然后我们再来看脚本引擎提供者对配置需要做哪些实现。

对管理员来说,在servlet容器里部署脚本引擎分为两步:部署引擎的脚本servlet和设置配置。在我们对脚本servlet的讨论中,我们已经知道脚本servlet就是一个servlet。所以不用惊讶,在servlet容器里部署脚本servlet和部署典型的servlet完全相同。
我们拿Tomcat作为例子,下面是我部署BoolScript引擎的脚本servlet的步骤:首先,我在{CATALINA_HOME}\conf\server.xml文件里增加了一个上下文(context)条目,这个上下文条目表示了运行脚本引擎和执行脚本代码的Web应用(在“建立示例”章里我们已经讨论了这个条目的内容,而且知道了这个条目应该放在文件的哪个位置);其次,我需要告诉servlet容器所有对BoolScript文件的请求(例如扩展名是.bool的文件)都交给BoolScript的脚本servlet处理。这需要在
{CATALINA_HOME}\webapps\BoolScript\WEB-INF\web.xml中加入以下代码:

BoolScriptServlet

net.sf.model4lang.boolscript.engine.BoolScriptServlet




BoolScriptServlet
*.bool


如你所见,上面的XML设置引用了Java类BoolScriptServlet。这个类在boolscript.jar中,而这个文件需要放在{CATALINA_HOME}\webapps\BoolScript\WEB-INF\lib文件夹下以便让servlet容器能访问到。
现在,BoolScript引擎已经部署完成了,我们可以开始为其设置配置来控制它在Web应用中的执行情况。下表列出了JSR 223的web scripting框架定义的所有的配置。

名称 可能的值 默认值 描述
script-disable true, false false 脚本servlet是否可以执行脚本
script-use-session true, false true 在脚本servlet中执行的脚本是否可以操作HTTP的会话(session)信息
script-methods GET, POST GET, POST 脚本servlet接受的HTTP请求方法,逗号分隔
script-directory /etc/xyz no directory 脚本文件存放的目录
script-display-results true, false false 脚本servlet是否输出脚本文件的执行结果
allow-languages bool script Any language 脚本servlet接受的脚本语言,逗号分隔

大多数的配置都可以从名称看出它的作用。这我只详细讨论script-disable和script-directory这两个配置。如果需要其它配置到详细信息可以查看JSR 223的web scripting框架。

在{CATALINA_HOME}\webapps\BoolScript\WEB-INF\web.xml中你可以看到如下的配置代码:

script-disable
false



script-use-session
false



script-methods
GET,POST



script-display-results
true


script-disable控制BoolScript脚本servlet是否可以执行脚本。如果设置为true,在使用浏览器打开http://localhost:8080/BoolScript/logic.bool时你将会看到“访问被拒绝”或者类似的信息。
对于脚本引擎提供的读者,我们看下如何在BoolScript引擎中实现这个配置的行为。下面是GenericHttpScriptServlet.java中的部分代码:
 //Check Web Scripting configurations.
//We only perform check on script-disable here as an example.
if(httpScriptCtxt.disableScript()) {
httpRes.setStatus(403);
return;
}

//Perform check on the rest of configurations such as
//script-use-session, script-methods, allow-languages here.


前面已经提到,这段是脚本servlet必须实现的样板代码,在Java SE 6的发行版中,我们会通过继承类似于GenericHttpScriptServlet的类得到这些样板代码。作为临时的修改,GenericHttpScriptServlet并没有检查所有的配置。示例中它只检查了disable-script。

除了disable-script,另一个我想详细讨论的是script-directory。在web.xml中,我没有设置script-directory的值,这意味着javax.script.http.HttpScriptContext的getScriptSource()方法遵循定位JSP代码的规则定位脚本代码。文件BoolScript-binary.zip包含了示例脚本代码文件logic.bool。我将其放在了{CATALINA_HOME}\webapps\BoolScrip目录下,这也是我们放置Web应用的JSP代码的目录。

结论
本文中,我使用了上一篇文章中的BoolScript代码,针对Web scripting能力作了改进后,部署在Tomcat的servlet容器中。然后使用浏览器访问我们的示例脚本文件来测试我们的工作。你可以在开源项目Model4Lang找到BoolScript语言的最新更新。那个项目下还包含了Scheme程序语言的JSR 223实现。

作者介绍
Chaur Wu是一个软件开发人员,并且出版过技术书籍。他参与了设计模式和软件建模的编写。同时他是开源项目Model4Lang的负责人,这个项目专注于使用基于模型的方法设计和编写程序语言。

资源
Matrix Java 社区:http://www.matrix.org.cn
本文使用的代码下载:http://www.javaworld.com/javaworld/jw-05-2006/scripting/jw-0522-scripting.zip
Model4Lang,一个Scheme语言的JSR 223脚本引擎:http://model4lang.sourceforge.net
JSR 223:Java平台脚本:http://www.jcp.org/en/jsr/detail?id=223
Java, java, J2SE, j2se, J2EE, j2ee, J2ME, j2me, ejb, ejb3, JBOSS, jboss, spring, hibernate, jdo, struts, webwork, ajax, AJAX, mysql, MySQL, Oracle, Weblogic, Websphere, scjp, scjd JSR 223中Web scripting框架介绍

摘要
在Char Wu的上一篇文章"为Java创建你自己的脚本语言-JSR 223介绍" 中,他设计实现一个称为BoolScript的简单的布尔语言,并用它展示?
↑返回目录
前一篇: 解读字节码(PDF)
后一篇: 捕获屏幕-编写一个基于Java Robot类的屏幕捕获工具