JavaScript EE,第 1 部分: 在服务器端运行 JavaScript 文件

如何运行JavaScript代码:多种方法探讨 在学习和开发JavaScript时,了解如何执行代码是一个基本但至关重要的技能。常见的运行JavaScript代码的方式包括在HTML文件中调用代码、使用Code Runner插件、以及在集成开发环境(IDE)中配置任务等。但是,JavaScript的运行方式远不止这些,下面将介绍几种常见的执行JavaScript代码的方式,帮助你更高效地开发。 阅读详情

   典型的 Ajax 应用程序在客户端一般都使用 JavaScript,而在服务器端常常使用另外一种语言,比如 Java。因此,开发人员必须将其中一些例程实现两次,一次用于在 Web 浏览器使用 JavaScript,另一次用于在服务器使用另外一种语言。这种双重编码问题实际上可以通过将 JavaScript 和服务器端的 Java 代码结合起来加以避免,而对脚本语言的完整支持可以通过 javax.script API 获得。此外,Java SE Development Kit (JDK) 6 已经包含了 Mozilla 的 Rhino JavaScript 引擎,这意味着您将无需进行任何设置。

在本系列的第一篇文章中,将使用一个简单的脚本运行程序来在一个 Jave EE 应用程序内执行 JavaScript 文件。这些脚本将能访问被用在 JSP 页面内的所谓的 “隐式对象”,比如 application、session、request 和 response。本文中的大多数示例均包含可重用代码,这样一来,您可以在自己的应用程序中轻松地将 JavaScript 应用于服务器上。

使用 javax.script API

本节给出了 javax.script API 的概览,展示了如何执行脚本来访问 Java 对象、如何从 Java 代码调用 JavaScript 函数,以及如何为所编译的脚本实现缓存机制。

执行脚本

javax.script API 十分简单。可以先创建一个 ScriptEngineManager 实例,有了这个实例就能用下列方法中的任一个来获得 ScriptEngine 对象(参见清单 1):

getEngineByName()
getEngineByExtension()
getEngineByMimeType()

清单 1. 获得一个 ScriptEngine 实例
    
import javax.script.*;
...
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
...
engine.eval(...);


此外,还可以通过 getEngineFactories() 获得可用脚本引擎的列表。目前,只有 JavaScript 引擎是与 JDK 6 捆绑的,不过 ScriptEngineManager 实现了一种发现机制,能发现支持 JSR-223 Scripting for the Java Platform 的第三方引擎(参见 参考资料)。只需将脚本引擎的 JAR 文件放入 CLASSPATH 即可。

获得了 javax.script.ScriptEngine 实例后,就可以调用 eval() 来执行脚本了。也可以将 Java 对象作为脚本变量导出,其间要将 Bindings 实例传递给 eval() 方法。清单 2 所示的 ScriptDemo.java 示例导出两个名为 demoVar 和 strBuf 的变量、执行 DemoScript.js 脚本,然后让这些变量输出它们修改后的值。


清单 2. ScriptDemo.java 示例
    
package jsee.demo;

import javax.script.*;
import java.io.*;

public class ScriptDemo {

    public static void main(String args[]) throws Exception {
        // Get the JavaScript engine
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");

        // Set JavaScript variables
        Bindings vars = new SimpleBindings();
        vars.put("demoVar", "value set in ScriptDemo.java");
        vars.put("strBuf", new StringBuffer("string buffer"));
       
        // Run DemoScript.js
        Reader scriptReader = new InputStreamReader(
            ScriptDemo.class.getResourceAsStream("DemoScript.js"));
        try {
            engine.eval(scriptReader, vars);
        } finally {
            scriptReader.close();
        }
       
        // Get JavaScript variables
        Object demoVar = vars.get("demoVar");
        System.out.println("[Java] demoVar: " + demoVar);
        System.out.println("    Java object: " + demoVar.getClass().getName());
        System.out.println();
        Object strBuf = vars.get("strBuf");
        System.out.println("[Java] strBuf: " + strBuf);
        System.out.println("    Java object: " + strBuf.getClass().getName());
        System.out.println();
        Object newVar = vars.get("newVar");
        System.out.println("[Java] newVar: " + newVar);
        System.out.println("    Java object: " + newVar.getClass().getName());
        System.out.println();
    }
   
}


DemoScript.js 文件(如清单 3 所示)包含一个 printType() 函数,该函数可用来输出每个脚本变量的类型。这个示例会调用 strBuf 对象的 append() 方法、修改 demoVar 的值并设置一个名为 newVar 的新变量脚本。

如果传递给 printType() 的对象具有 getClass() 方法,那么它一定是个 Java 对象,该对象的类名由 obj.getClass().name 获得。这个 JavaScript 表达式调用此对象的 java.lang.Class 实例的 getName() 方法。如果此对象不具备 getClass,那么 printType() 就会调用 toSource() 方法,而该方法是所有 JavaScript 对象都有的。


清单 3. DemoScript.js 示例
    
println("Start script /r/n");

// Output the type of an object
function printType(obj) {
    if (obj.getClass)
        println("    Java object: " + obj.getClass().name);
    else
        println("    JS object: " + obj.toSource());
    println("");
}

// Print variable
println("[JS] demoVar: " + demoVar);
printType(demoVar);

// Call method of Java object
strBuf.append(" used in DemoScript.js");
println("[JS] strBuf: " + strBuf);
printType(strBuf);

// Modify variable
demoVar = "value set in DemoScript.js";
println("[JS] demoVar: " + demoVar);
printType(demoVar);

// Set a new variable
var newVar = { x: 1, y: { u: 2, v: 3 } }
println("[JS] newVar: " + newVar);
printType(newVar);

println("End script /r/n");


清单 4 是 ScriptDemo.java 示例的输出。值得注意的是 demoVar 作为 JavaScript String 导出,而 strBuf 的类型仍然是 java.lang.StringBuffer。原始变量和 Java 字符串均作为本地 JavaScript 对象导出。任何其他的 Java 对象(包括数组)均原样导出。


清单 4. ScriptDemo.java 的输出
    
Start script

[JS] demoVar: value set in ScriptDemo.java
    JS object: (new String("value set in ScriptDemo.java"))

[JS] strBuf: string buffer used in DemoScript.js
    Java object: java.lang.StringBuffer

[JS] demoVar: value set in DemoScript.js
    JS object: (new String("value set in DemoScript.js"))

[JS] newVar: [object Object]
    JS object: ({x:1, y:{u:2, v:3}})

End script

[Java] demoVar: value set in DemoScript.js
    Java object: java.lang.String

[Java] strBuf: string buffer used in DemoScript.js
    Java object: java.lang.StringBuffer

[Java] newVar: [object Object]
    Java object: sun.org.mozilla.javascript.internal.NativeObject


运行该脚本后,此引擎就会接受所有变量(包括新变量)并执行反转变换,将 JavaScript 原始变量和字符串转变成 Java 对象。其他的 JavaScript 对象则被包装成 Java 对象,这些对象能使用某种特定于引擎的内部 API,比如 sun.org.mozilla.javascript.internal.NativeObject。

有时,可能会只想使用那些标准的 API,因此 Java 代码和所执行脚本间的全部数据转换都应通过原始变量、字符串和 Java 对象(比如 bean)完成,这是因为在 JavaScript 代码内可以很容易地访问到它们的属性和方法。简言之,就是不要试图在 Java 代码内访问本地 JavaScript 对象,相反,应该在 JavaScript 代码内使用 Java 对象。

调用函数

在之前的例子中,您已经看到了从 JavaScript 调用 Java 方法是可行的。现在您将会了解如何从 Java 代码调用 JavaScript 函数。首先,必须先执行包含想要调用的那个函数的脚本。然后,再将 ScriptEngine 实例强制转换为 javax.script.Invocable,后者还提供了 invokeFunction() 和 invokeMethod()。如果脚本实现了 Java 接口的全部方法,那么也可以使用 getInterface() 获得一个 Java 对象,该对象的方法用此脚本语言编码。

InvDemo.java 示例(如清单 5 所示)执行一个名为 InvScript.js 的脚本,它包含 demoFunction() 例程。在进行强制类型转换以将 ScriptEngine 实例转换为 Invocable 之后,这个 Java 示例才能将函数名和参数传递给引擎的 invokeFunction() 方法,而此方法会返回由 demoFunction() 返回的值。


清单 5. InvDemo.java 示例
    
package jsee.demo;

import javax.script.*;
import java.io.*;

public class InvDemo {

    public static void main(String args[]) throws Exception {
        // Get the JavaScript engine
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");
       
        // Run InvScript.js
        Reader scriptReader = new InputStreamReader(
            InvDemo.class.getResourceAsStream("InvScript.js"));
        try {
            engine.eval(scriptReader);
        } finally {
            scriptReader.close();
        }
       
        // Invoke a JavaScript function
        if (engine instanceof Invocable) {
            Invocable invEngine = (Invocable) engine;
            Object result = invEngine.invokeFunction("demoFunction", 1, 2.3);
            System.out.println("[Java] result: " + result);
            System.out.println("    Java object: "
                    + result.getClass().getName());
            System.out.println();
        } else
            System.out.println("NOT Invocable");
    }
   
}


InvScript.js 文件(如清单 6 所示)包含 demoFunction() 例程和之前的脚本示例所用的相同 printType() 函数。


清单 6. InvScript.js 示例
    
println("Start script /r/n");

function printType(obj) {
    if (obj.getClass)
        println("    Java object: " + obj.getClass().name);
    else
        println("    JS object: " + obj.toSource());
    println("");
}

function demoFunction(a, b) {
    println("[JS] a: " + a);
    printType(a);
    println("[JS] b: " + b);
    printType(b);
    var c = a + b;
    println("[JS] c: " + c);
    printType(c);
    return c;
}

println("End script /r/n");


InvDemo.java 的输出如清单 7 所示,注意到其中的数值参数均被转换成了 JavaScript 对象,并且由 demoFunction() 返回的值是作为 Java 对象获得的。这种转换只会针对原始变量和字符串进行。任何其他的对象在 JVM 和 Javascript 引擎之间都是原样传递的,反之亦然。


清单 7. InvDemo.java 的输出
    
Start script

End script

[JS] a: 1
    JS object: (new Number(1))

[JS] b: 2.3
    JS object: (new Number(2.3))

[JS] c: 3.3
    JS object: (new Number(3.3))

[Java] result: 3.3
    Java object: java.lang.Double


请注意 javax.script.Invocable 是一个可选接口,有些脚本引擎可能不会实现该接口。不过,JDK 6 所带的 JavaScript 引擎提供对该接口的支持。

编译脚本

脚本在每次执行时都进行解析会浪费 CPU 资源。在多次执行相同的脚本时,若能编译脚本,就可以显著减少执行时间,而脚本编译所需要的方法可由另外一个可选接口 javax.script.Compilable 提供,JDK 6 所带的 JavaScript 引擎亦支持该接口。

CachedScript 类(参见清单 8)接受一个脚本文件并只有当源代码有修改时才会进行重编译。getCompiledScript() 方法会调用此脚本引擎的 compile(),进而返回 javax.script.CompiledScript 对象,该对象的 eval() 方法会执行脚本。


清单 8. CachedScript 类
    
package jsee.cache;

import javax.script.*;
import java.io.*;
import java.util.*;

public class CachedScript {
    private Compilable scriptEngine;
    private File scriptFile;
    private CompiledScript compiledScript;
    private Date compiledDate;

    public CachedScript(Compilable scriptEngine, File scriptFile) {
        this.scriptEngine = scriptEngine;
        this.scriptFile = scriptFile;
    }
   
    public CompiledScript getCompiledScript()
            throws ScriptException, IOException {
        Date scriptDate = new Date(scriptFile.lastModified());
        if (compiledDate == null || scriptDate.after(compiledDate)) {
            Reader reader = new FileReader(scriptFile);
            try {
                compiledScript = scriptEngine.compile(reader);
                compiledDate = scriptDate;
            } finally {
                reader.close();
            }
        }
        return compiledScript;
    }

}


ScriptCache 类(参见清单 9)使用 java.util.LinkedHashMap 对象为所编译的脚本实现存储库。map 的初始容量被设为所缓存脚本的最大数量并且加载系数是 1。这两个参数就确保了该 cacheMap 不需要重新处理。

默认地,LinkedHashMap 类会使用条目的插入顺序。若不想使用默认顺序,LinkedHashMap() 构造函数的第三个参数必须是 true 以便使用条目的访问顺序。

达到缓存的最大容量后,removeEldestEntry() 方法就会开始返回 true,以便当每次向此缓存添加一个新的编译了的脚本时,一个条目都会自动从 cacheMap 删除。

通过联合使用 LinkedHashMap 的自动删除机制和访问顺序,ScriptCache 就确保了当添加了新脚本时,最近最少使用的(Least Recently Used,LRU)的脚本将能够从缓存中删除。


清单 9. ScriptCache 类
    
package jsee.cache;

import javax.script.*;

import java.io.*;
import java.util.*;

public abstract class ScriptCache {
    public static final String ENGINE_NAME = "JavaScript";
    private Compilable scriptEngine;
    private LinkedHashMap<String, CachedScript> cacheMap;

    public ScriptCache(final int maxCachedScripts) {
        ScriptEngineManager manager = new ScriptEngineManager();
        scriptEngine = (Compilable) manager.getEngineByName(ENGINE_NAME);
        cacheMap = new LinkedHashMap<String, CachedScript>(
                maxCachedScripts, 1, true) {
            protected boolean removeEldestEntry(Map.Entry eldest) {
                return size() > maxCachedScripts;
            }
        };
    }

    public abstract File getScriptFile(String key);

    public synchronized CompiledScript getScript(String key)
            throws ScriptException, IOException {
        CachedScript script = cacheMap.get(key);
        if (script == null) {
            script = new CachedScript(scriptEngine, getScriptFile(key));
            cacheMap.put(key, script);
        }
        return script.getCompiledScript();
    }
   
    public ScriptEngine getEngine() {
        return (ScriptEngine) scriptEngine;
    }
   
}


下一节将使用 ScriptCache 类、实现抽象的 getScriptFile() 方法并使用 getScript() 从缓存检索所编译的脚本。

构建一个脚本运行程序

在本节中,您将了解如何创建一个简单的 Java servlet 来实现 URL-脚本的映射以便能够从 Web 浏览器调用服务器端脚本。此外,servlet 还将会把几个 Java EE 对象公开为可在 JavaScript 代码内使用的变量。您还将了解如何使用脚本上下文来用单一一个 JavaScript 引擎运行多个并发的脚本。

初始化 servlet

servlet 类的名称是 JSServlet。其 init() 方法(参见清单 10)会获得几个配置参数并创建一个 ScriptCache 对象。servlet 的脚本缓存使用 getRealPath() 获得与给定 URI 相映射的脚本文件的路径。


清单 10. JSServlet 的 init() 方法
    
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));
            }
        };
    }
    ...
}


清单 11 中包含一些 servlet 的参数,这些参数在 web.xml 文件内指定。Cache-Control 头与脚本缓存毫无关系。两个头都是由 servlet 返回的此 HTTP 响应的一部分。no-cache 值告知浏览器不要缓存此 servlet 的响应,该响应应被作为 text/plain 对待。


清单 11. 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>


从清单 11 可以看出,*.jss 模式被映射给此 servlet。这意味着 JSServlet 将会处理 URL 以 .jss 扩展名结束的所有请求。当用户在 Web 浏览器内输入这样的一个 URL 或是单击了一个 .jss 链接时,浏览器就会发送此 HTTP 请求给 Web 服务器(例如, Apache),而此服务器应该被配置成将该请求分派给 servlet 容器(比如,Tomcat)。如果 servlet 容器也充当 Web 服务器,就无需额外的配置。

当 servlet 容器获得了 URL 以 .jss 结束的这个请求时,就会调用 service() 方法,该方法是由 JSServlet 继承自 javax.servlet.http.HttpServlet 的。此方法再进而调用 doGet() 或 doPost(),最终调用哪一个取决于此请求的 HTTP 方法。两个方法都可由 JSServlet 覆盖,这一点在本节稍后的部分还会看到。

使用脚本上下文

 脚本引擎的线程模型

JSR-223 Scripting for the Java Platform(参见 参考资料)定义了三类脚本引擎:

能执行并发脚本的多线程引擎,可以修改由其他线程看到的变量
线程隔离引擎,也是多线程的,但每个线程都具有其自身的引擎范围来保存变量
无状态引擎,作为线程隔离引擎定义,但引擎范围在任何脚本执行后均保持不变
脚本引擎的类型可通过 engine.getFactory().getParameter("THREADING") 获得,而返回的结果可能会是 "MULTITHREADED"、"THREAD-ISOLATED" 或 "STATELESS"。
 
 
每个脚本引擎实例都具有一个默认的上下文,在其中,可以用 put() 方法存储变量,而所执行的脚本的输出则被默认定向到 System.out。在服务器环境内,常希望运行具有各自上下文的并发脚本。javax.script API 能满足这个需求,它能提供 ScriptContext 接口和 SimpleScriptContext 实现。

Mozilla 的 Rhino JavaScript 引擎是个多线程引擎(参见侧栏),允许执行共享相同上下文的并发线程。不过,在本例中,我们想要隔离这些引擎范围以及运行在不同线程内的那些脚本的输出,这意味着必须要针对每个 HTTP 请求创建一个新的 ScriptContext 实例。

清单 12 给出了 JSServlet 类的 createScriptContext() 方法。此方法设置了上下文的 writer 属性以便在脚本执行时将脚本的输出发送给 response 对象的编写者。这意味着传递给脚本内的 print() 或 println() 的任何东西都将会包含在此 servlet 的响应内。

此外,createScriptContext() 还通过脚本上下文的 setAttribute() 方法定义了如下的脚本变量:


表 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.script.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 的 doGet() 和 doPost() 方法(参见清单 15)用来调用 handleRequest()。


清单 15. JSServletdo 的 Get() 和 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);
    }

}


开发服务器端脚本

本节包含了服务器端脚本的几个例子,向您展示了如何获得请求参数、访问 JavaBeans 的属性并生成 JSON 响应。

预处理和后处理

在前一节中,曾提及在执行所请求的脚本之前,JSServlet 会调用 init.jss(如清单 16 所示)。若想估量执行脚本所需时间,可以将开始时间存储到一个变量内,如下所示。


清单 16. init.jss 脚本
    
var debug = true;
var debugStartTime = java.lang.System.nanoTime();


之后,可以在 finalize.jss 内(参见清单 17)计算执行时间。该时间作为 JavaScript 注释打印以便 JSServlet 能够生成有效的 JSON 响应。


清单 17. finalize.jss 脚本
    
var debugEndTime = java.lang.System.nanoTime();
if (debug)
    println("// Time: " + (debugEndTime - debugStartTime) + " ns");


本系列后面的文章将向 init.jss 和 finalize.jss 添加更多的代码。

获得请求参数

借助 JSServlet 调用的脚本可以通过 request.getParameter() 和 request.getParameterValues() 访问请求参数,这二者会返回 Java 对象。若想使语法更简短或处理 JavaScript 对象而非 Java 字符串和数组,也不难,只需将下面这些代码行加入到 init.jss(参见清单 18)。


清单 18. 在 init.jss 内获得请求参数。
    
var param = new Object();
var paramValues = new Object();

function initParams() {
    var paramNames = request.getParameterNames();
    while (paramNames.hasMoreElements()) {
        var name = paramNames.nextElement();
        param[name] = String(request.getParameter(name));
        paramValues[name] = new Array();
        var values = request.getParameterValues(name);
        for (var i = 0; i < values.length; i++)
            paramValues[name][i] = String(values[i]);
    }
}

initParams();


假设您使用清单 19 所示的 URL 请求一个名为 ParamDemo.jss 的脚本。


清单 19. 请求一个脚本的 URL 示例
    
http://localhost:8080/jsee/ParamDemo.jss?firstName=John&lastName=Smith


在 ParamDemo.jss(参见清单 20)内,用 param.firstName 和 param.lastName 可以得到这两个请求参数。


清单 20. ParamDemo.jss 示例
    
println("Hello " + param.firstName + " " + param.lastName);


访问 JavaBean

Web 应用程序的 application、session 和 request 范围可存储 bean 实例,可以分别使用 ServletContext、 HttpSession 和 HttpServletRequest 的 getAttribute() 和 setAttribute() 来获得或替代这些实例。也可以使用 getBean() 和 setBean() 函数(如清单 21 所示),但这两个函数必须位于 init.jss 文件内以便任何脚本均可调用它们。scope 参数应是如下字符串中的一个:

"application"
"session"
"request"

清单 21. init.jss 的 getBean() 和 setBean() 函数
    
function getBean(scope, id) {
    return eval(scope).getAttribute(id);
}

function setBean(scope, id, bean) {
    if (!bean)
        bean = eval(id);
    return eval(scope).setAttribute(id, bean);
}


现在,假设您想要在 session 范围内保存 DemoBean(参见清单 22)的一个实例。


清单 22. DemoBean.java 示例
    
package jsee.demo;

public class DemoBean {
    private int counter;

    public int getCounter() {
        return counter;
    }

    public void setCounter(int counter) {
        this.counter = counter;
    }

}


BeanDemo.jss 脚本(如清单 23 所示)用 importPackage(Packages.jsee.demo) 导入了包含此 JavaBean 的那个包。之后,脚本试图用 getBean() 从 session 范围获得这个 bean 实例。如果这个 bean 没有找到,那么 BeanDemo.jss 就会创建一个对象并利用 setBean() 将其放入 session 范围。最终,此脚本会进行增量处理并输出这个 bean 的 counter 属性。


清单 23. BeanDemo.jss 示例
    
importPackage(Packages.jsee.demo);
var bean = getBean("session", "demo");
if (!bean) {
    bean = new DemoBean();
    setBean("session", "demo", bean);
}
bean.counter++;
println(bean.counter);


如果所要导入的包是以 java、javax、org、edu、com 或 net 开头的,那么无须在 importPackage() 使用 Packages 前缀。此外,还可以使用 importClass() 导入单个类。

返回 JSON 响应

清单 24 所示的示例会生成一个 JSON 响应,该响应会包含有关 JavaScript 引擎和此脚本 URI 的某些信息。此示例使用 JavaScript 语法来创建 json 对象,其源代码则用 toSource() 方法以 String 形式获得。


清单 24. JsonDemo.jss 示例
    
var json = {
    engine: {
        name: String(factory.engineName),
        version: String(factory.engineVersion),
        threading: String(factory.getParameter("THREADING"))
    },
    language: {
        name: String(factory.languageName),
        version: String(factory.languageVersion)
    },
    script: {
        uri: String(request.requestURI)
    }
};

println(json.toSource());


在本例中,从 factory 和 request 的属性中检索到的所有 Java 对象都必须转变为 JavaScript 对象,以便 toSource() 能够正常工作。清单 25 包含了此脚本的输出:


清单 25. JsonDemo.jss 的输出
    
({language:{name:"ECMAScript", version:"1.6"},
engine:{name:"Mozilla Rhino", threading:"MULTITHREADED",
version:"1.6 release 2"}, script:{uri:"/jsee/JsonDemo.jss"}})


结束语

在本文中,您了解了如何使用 javax.script API 编译和执行 JavaScript 文件。您还了解了如何基于 java.util.LinkedHashMap 实现 LRU 缓存、如何将 Java 对象作为脚本变量导出、如何实现 URL-脚本映射以及如何构建在服务器端执行 的 JavaScript 文件。请您继续关注本系列的下一篇文章,在该文章中,您将了解如何用 Ajax 调用远程 JavaScript 函数。

 

【STM32Cube HAL】输入捕获(六)——脉宽测量 1 阅读详情

相关推荐

用LightRAG+Ollama搭建本地知识库:从文档上传到智能问答的完整流程

本文详细介绍了如何利用LightRAG与Ollama构建企业级本地知识库,涵盖从文档预处理到智能问答的完整流程。通过实战案例展示,该方案能高效处理海量文档,实现语义搜索与知识图谱关联分析,显著提升信息检索效率。特别适合金融、医疗等领域的技术团队快速搭建智能知识管理系统。

weixin_42535461的博客 212

jquery.ajax加载js文件,JQuery动态加载Javascript文件

largeQ我意识到我在这里有点晚了,(5年左右),但我认为有一个比接受的更好的答案如下:$("#addComment").click(function(){if(typeofTinyMCE==="undefined"){$.ajax({url:"tinymce.js",dataType:"script",...

weixin_31036839的博客 870

用Python爬取公众号历史所有文章,看这篇就够了

手把手教你爬取公众号历史所有文章

u010522887的专栏 1万+

JavaScript 异常处理及异常收集

前端工程师都知道,JavaScript 拥有基本的异常处理方法,但是一般前端工程师都不会处理异常。其实情有可原,对于普通网站来说,异常是非常可控的,基本可以刷新页面就解决问题,运行环境+代码运行也很少带来异常。但是对于业务富集的webApp/大型组件来说,没有异常处理会是异常灾难。无论在用户使用场景或者在开发/调试 作为出发点,异常处理都是非常必要的 异常处理非常重要,它可以提高程序健壮性,对于其他软件

Sheen_oh的博客 9734

Spring AI Alibaba Skill 实战:定义、注册与渐进式披露的企业级落地

摘要:很多团队在接入大模型时,第一步做到的是“能对话”,第二步遇到的瓶颈却是“不能可靠执行”。Skill 的本质不是给模型多几个函数,而是把企业系统中的可控能力,以可治理、可审计、可扩展的方式交给 Agent。本文围绕 Spring AI Alibaba Skill 的定义、注册与渐进式披露,结合电商客服与售后场景,系统讲清楚其底层原理、架构设计、生产级实现、高并发治理与工程实践。@Component@Tool(description = "根据订单号查询订单状态、支付状态和物流状态")扫描包含。

一起修行,不孤单。这里有编程语言、开发工具、学习方法论和踩坑笔记。用简单的话说复杂的事,陪你从小白到进阶。周更干货,欢迎一起成长! 623

告别sgmediation!Stata中介效应分析新选择:mediation包完整操作指南

本文介绍了Stata中介效应分析从传统sgmediation命令向mediation包的范式升级。重点阐述了如何使用medeff和medsens命令进行更严谨的因果推断,并执行关键的敏感性分析,以评估结果对未观测混杂的稳健性,为学术研究提供发表级的分析工具。

redis7keeper的博客 1160

jquery getScript取消每次重复加载

这个方法是jquery自身提供的一个用于动态加载js的方法。当网站需要加载大量js时,动态的加载js就是一个比较好的方法,当需要某个功能时再将相应的js加载进来。  但是自己在使用过程中却发现了一些不尽如意的地方。  每次需要执行该功能的时候都会去请求一次这个js,这样不是在帮倒忙嘛?  于是找到Jquery官网的API说明 http://api.jquery.com/jQu

lmx87755138的专栏 4407

RK3588 Linux启动优化至亚秒级全链路剖析

本文深入探讨RK3588平台Linux系统启动加速技术,涵盖从BL1/ATF安全固件、U-Boot剪裁到内核精简化等全链路优化方案,实现从上电到用户脚本800ms内完成的实战经验。​​文末提供完整解决方案获取方式​​。由于在 ARMv6 之后引入了 TrustZone 模块, 目前市面上 ARMv7 和 ARMv8 之后的系统都会包含了该模块, 这个模块由 BL1 启动, 且在 ATF(ARM Trust Firmware) 固件中, 既安全世界中引导到 BL33 (UBOOT)的系统。

韩若明溪 1777

JSEE总结(一)——JNDI

一、什么是JNDI 早就听说J2EE有13个规范,今天接触了第一个——JNDI,这里所说的规范,其实是一种标准,就像Web开发中接触到的W3C和IE类似。规定了开发人员在一个领域开发时使用某种技术所必须遵循的开发标准或开发方法,具有一定的强制性。       而JNDI也是这13个规范之中的一个:是Java平台的一个标准扩展,提供了一组接口、类和关于命名空间的概念。如同其它很多Java技术一样

littlecolor 3087

STM32CubeMX HAL实战:JY901S串口数据解析与姿态解算

本文详细介绍了如何使用STM32CubeMX HAL库实现JY901S六轴姿态传感器的串口数据解析与姿态解算。从基础硬件连接到USART3配置,再到数据接收状态机实现和传感器校准方法,最后深入讲解互补滤波与Mahony滤波算法在姿态解算中的应用。通过实战代码演示,帮助开发者快速掌握无人机、机器人等项目中关键姿态数据处理技术。

weixin_28703489的博客 432

python爬取巨潮资讯网所有股票年报数据

爬虫获取巨潮资讯网数据

qq_42752524的博客 6117

JavaScript EE,第1部分,在服务器端运行JavaScript文件

存档日期:2019年4月22日 | 首次发布:2008年12月16日 将JavaScript与服务器上的Java™代码结合使用,可以自由在服务器和客户端上使用相同JavaScript例程。 另外,本系列中介绍的技术将使您可以为Ajax和非Ajax客户端维护单个代码库。 由于许多服务器端代码仍将用Java语言编写,因此您将有必要将Java平台企业版(Java EE)功能公开给JavaScri...

cusi77914的博客 179

MATLAB基础应用精讲-【数模应用】信用评分模型(附R语言和python代码实现)

信用评分模型是近年来兴起的一种为了保障银行和其他金融部门的金融安全而设立的一种关于人身金融权限的划定模型。该模型指根据客户的信用历史资料,利用一定的信用评分模型,得到不同等级的信用分数,根据客户的信用分数,来决定客户所可以持有的金额权限,从而保证还款等业务的安全性。而随着在现代社会和公司中,贷款,信用卡的作用日渐突出,信用评分模型的发展前景不可估量。信贷风险是指借款人因偿还贷款或履行合同承诺而破产而产生损失的可能性。传统上,它与贷款人无法偿还所欠利息和本金、影响现金流和增加装配成本所产生的风险有关。

qq_36130719的博客 364

JavaScript EE:在服务器端运行JavaScript文件

       本系列文章围绕的主旨是了解如何在 Ajax 和 Java EE 应用程序内使用 javax.script API。如何将 JavaScript 与服务器上的 Java™ 代码结合起来,从而能够在服务器和客户机上使用相同的 JavaScript 例程。此外,本系列所展示的这些技术将让您能为 Ajax 客户机和非 Ajax 客户机维护同一个代码库。由于服务器端的大部分代码依然用 Java...

ChocooM 369

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

服务端JAVA执行js_在服务器端运行JavaScript文件(一)

简介:把JavaScript脚本与服务器上Java代码相结合,从而获得在服务器端和客户端都能够自由使用的JavaScript脚本。另外,经过这一系列的被展现技术,无论是基于Ajax还是非Ajax的客户端,都将允许你维护一个单一的代码,因为大多数的服务器端的代码依然是用Java来编写的,同时你还会发现公开给JavaScript的Java EE(Java企业版)的功能特征。在这一系列的技术中,将学习一...

weixin_39661405的博客 316

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

js格式文件用什么打开好,javascript文件怎么运行

在 Move Mirror 的运行过程中,数据没有上传到服务器,所有的运算都是在手机本地,基于手机的 CPU/GPU 完成的,而这项技术,将使 Servreless 与 AI 应用结合起来成为可能。本章,我们将基于 TensorFlow.js 1.0,向大家简单地介绍如何基于 ES6 的 JavaScript 进行 TensorFlow.js 的开发,然后提供两个例子,并基于例子进行详细的讲解和介绍,最终实现使用纯 JavaScript 进行 TensorFlow 模型的开发、训练和部署。

chatgpt001的博客 649

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

GEE数据类型实战:从ee.String到ee.List的5个高频应用场景解析

本文深入解析Google Earth Engine中核心数据类型ee.String、ee.Number、ee.List和ee.Dictionary的实战应用。通过5个高频场景,如动态构建查询、影像波段运算、管理时间序列等,详细阐述了如何利用这些服务器端对象进行高效、稳定的并行遥感数据处理,帮助开发者规避常见错误,提升GEE编程效率与代码可靠性。

v6b7n8m9q0的博客 870

Node.js是一个基于Chrome V8引擎的JavaScript运行环境,让JavaScript可以在服务器端运行

Node.js 是一个开源、跨平台的 JavaScript 运行时环境,它允许开发者在服务器端运行 JavaScript 代码。Node.js 基于 Chrome V8 引擎,具有高性能和高并发的特点,广泛应用于构建高性能的后端服务、API、实时通信应用、命令行工具等多种场景。以下是对 Node.js 的详细介绍:

[Blog][Domain] programb.blog.youkuaiyun.com 565

JavaScript EE,第 2 部分: 用 Ajax 调用远程 JavaScript 函数

在本系列的第 1 部分中,您学习了如何在 Asynchronous JavaScript and XML(Ajax)和 Java™ Platform, Enterprise Edition(Java EE)应用程序中使用 javax.script API。本文将展示如何为同时在服务器和客户机上使用 JavaScript 的 Web 应用程序实现远程过程调用(Remote Proced...

aolangtb243484的博客 238

如何在个人电脑上运行服务器端软件_如何在服务器端运行 JavaScript 文件?

典型的 Ajax 应用程序在客户端一般都使用 JavaScript,而在服务器端常常使用另外一种语言,比如 Java。因此,开发人员必须将其中一些例程实现两次,一次用于在 Web 浏览器使用 JavaScript,另一次用于在服务器使用另外一种语言。这种双重编码问题实际上可以通过将 JavaScript 和服务器端的 Java 代码结合起来加以避免,而对脚本语言的完整支持可以通过jav...

weixin_42619448的博客 716

JavaScript EE,第2部分,使用Ajax调用远程JavaScript函数

存档日期:2019年4月22日 | 首次发布:2009年3月31日 在本系列的第1部分中,您学习了如何在异步JavaScript和XML(Ajax)和Java™平台企业版(Java EE)应用程序中使用javax.script API,以及如何构建可以运行服务器的Java servlet。侧JavaScript文件。 本文介绍如何为在服务器和客户端上都使用JavaScript的Web应用程...

cusi77914的博客 155
上一篇: 为网站设置自定义404错误页面
下一篇: J2ME与Google Map
lysanderK
博客等级 码龄19年 7粉丝 22原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值