在服务器端运行JavaScript文件(二)
创建一个脚本运行器
在这节中,将学习怎样创建一个简单的实现URL-script映射的Java servlet,这个servlet允许来自于浏览器的服务器端的脚本调用。另外,这个servlet将向JavaScript公开几个能够在JavaScript代码中使用的Java EE对象变量。
初始化servlet
Servlet类的名字是JSServlet,它的init()放(代码10)取得几个配置参数,并且创建一个ScriptCache对象。这个servelt的脚本缓存使用getRealPath()方法获取映射个URI的脚本文件的路径。
代码10:
- package jsee.servlet;
- import javax.script.*;
- import javax.servlet.*;
- import javax.servlet.http.*;
- import java.io.*;
- import jsee.cache.*;
- public class JSServlet extends HttpServlet {
- private String cacheControlHeader;
- private String contentTypeHeader;
- private ScriptCache scriptCache;
- public void init() throws ServletException {
- ServletConfig config = getServletConfig();
- cacheControlHeader = config.getInitParameter("Cache-Control");
- contentTypeHeader = config.getInitParameter("Content-Type");
- int maxCachedScripts = Integer.parseInt(
- config.getInitParameter("Max-Cached-Scripts"));
- scriptCache = new ScriptCache(maxCachedScripts) {
- public File getScriptFile(String uri) {
- return new File(getServletContext().getRealPath(uri));
- }
- };
- }
- ...
- }
下面列出了在web.xml文件中指定的servelet的参数,这文件中的Cache-Control标题与脚本缓存无关,这两个标题是servlet返回的HTTP响应的一部分。no-cache值将告诉浏览器不要缓存servelt的响应,内容类型应该作为text/plain来处理。
web.xml文件
- <web-app ...>
- <servlet>
- <servlet-name>JSServlet</servlet-name>
- <servlet-class>jsee.servlet.JSServlet</servlet-class>
- <init-param>
- <param-name>Cache-Control</param-name>
- <param-value>no-cache</param-value>
- </init-param>
- <init-param>
- <param-name>Content-Type</param-name>
- <param-value>text/plain</param-value>
- </init-param>
- <init-param>
- <param-name>Max-Cached-Scripts</param-name>
- <param-value>1000</param-value>
- </init-param>
- <load-on-startup>1</load-on-startup>
- </servlet>
- <servlet-mapping>
- <servlet-name>JSServlet</servlet-name>
- <url-pattern>*.jss</url-pattern>
- </servlet-mapping>
- </web-app>
在上面的web.xml文件中,我们看到了映射给servlet的*.jss样式,这就意味着JSServlet将处理所有的以.jss为扩展名的URLs请求。当用户在浏览器的URL地址栏或点击一个*.jss的链接时,浏览器都会给Web服务器(如:Apache)发送一个HTTP请求,这个请求会根据配置信息派发给对应的servlet容器(如:Tomcat),如果servlet容器作为Web服务器,就不需要额外的配置信息。
当servlet获得以*.jss结尾的URL请求时,就会调用JSServlet的从javax.servlet.http.HttpServlet中继承的service()方法。这个方法即可以调用doGet()方法,也可以调用doPost()方法,这依赖于HTTP请求的方法。在本节的最后,我们会看到在JSServlet中,这两个方法都被重写了。
使用脚本环境
每个脚本引擎实例都有一个默认的环境,在这个环境中可以使用put()方法来保存变量,而且默认的情况下可以用System.out输出任意可执行的脚本。在服务器环境中,你会想让每个当前运行的脚本彼此有自己环境,java.script API可以满足这个需要,它提供了ScriptContext接口并且有SimpleScriptContext的实现。
表1:在JSServlet执行的脚本中有效的变量
脚本变量 |
描述 |
config |
Servlet的javax.servlet.ServletConfig实例 |
application |
Web应用的javax.servlet.ServletContext实例 |
session |
javax.servlet.http.HttpSession对象 |
request |
javax.servlet.http.HttpServletRequest对象 |
response |
javax.servlet.http.HttpServletResponse对象 |
out |
用于输出响应的java.io.PrintWriter对象 |
factory |
脚本引擎的javax.scriptEngineFactory实例 |
factory变量能够用来获取JavaScript引擎的信息,如语言的版本或引擎的版本。其他的变量在JSP页中都有相同的对应的角色。
代码12:JSServlet类的createScriptContext()方法- public class JSServlet extends HttpServlet {
- ...
- protected ScriptContext createScriptContext(
- HttpServletRequest request, HttpServletResponse response)
- throws IOException {
- ScriptContext scriptContext = new SimpleScriptContext();
- scriptContext.setWriter(response.getWriter());
- int scope = ScriptContext.ENGINE_SCOPE;
- scriptContext.setAttribute("config", getServletConfig(), scope);
- scriptContext.setAttribute("application", getServletContext(), scope);
- scriptContext.setAttribute("session", request.getSession(), scope);
- scriptContext.setAttribute("request", request, scope);
- scriptContext.setAttribute("response", response, scope);
- scriptContext.setAttribute("out", response.getWriter(), scope);
- scriptContext.setAttribute("factory",
- scriptCache.getEngine().getFactory(), scope);
- return scriptContext;
- }
- ...
- }
runScript()方法(代码13)从缓存中取得被编译的脚本,然后调用eval()方法,同时把脚本环境作为参数给它。
代码13:JSServlet的runScript()方法
- public class JSServlet extends HttpServlet {
- ...
- protected void runScript(String uri, ScriptContext scriptContext)
- throws ScriptException, IOException {
- scriptCache.getScript(uri).eval(scriptContext);
- }
- ...
- }
我们能够简单的调用上面的runScript()方法来执行映射给HTTP请求的URL的脚本。但是,在实际的应用中,在脚本运行之前我们可能想要做一些初始化的工作,而在脚本执行完了之后做一些善后工作。
还可能在相同的环境中顺序的运行多个脚本。例如,一个脚本定义了一组变量和函数,然后另一个脚本在同一个环境中使用前面脚本中定义的变量和函数来执行一些处理。
servlet的handleRequest()方法(代码14)做了以下处理:
・设定了HTTP的标题;
・运行init.jss脚本;
・从请求的URI中删除环境路径;
・执行已经取得的URI的脚本;
・运行另一个叫做finalize.jss的脚本。
代码14:JSServlet类的handleRequest()方法
- public class JSServlet extends HttpServlet {
- ...
- protected void handleRequest(
- HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- if (cacheControlHeader != null)
- response.setHeader("Cache-Control", cacheControlHeader);
- if (contentTypeHeader != null)
- response.setContentType(contentTypeHeader);
- ScriptContext scriptContext = createScriptContext(request, response);
- try {
- runScript("/init.jss", scriptContext);
- String uri = request.getRequestURI();
- uri = uri.substring(request.getContextPath().length());
- try {
- runScript(uri, scriptContext);
- } catch (FileNotFoundException x) {
- response.sendError(404, request.getRequestURI());
- }
- runScript("/finalize.jss", scriptContext);
- } catch (ScriptException x) {
- x.printStackTrace(response.getWriter());
- throw new ServletException(x);
- }
- }
- ...
- }
JSServlet类的handleRequest()方法调用了doGet()和doPost()方法(代码15)
代码15:JSServlet类的doGet()和doPost()方法。
- public class JSServlet extends HttpServlet {
- ...
- public void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- handleRequest(request, response);
- }
- public void doPost(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- handleRequest(request, response);
- }
- }