• Pure JS scriptengine jetty


    http://xxing22657-yahoo-com-cn.iteye.com/blog/1052485

    Pure JS (1): 在 jetty 上运行 JavaScript

    Pure JS (1): 在 jetty 上运行 JavaScript


    所谓 Pure JS ,就是客户端和服务器端程序都用 JavaScript 编写。

    实现思路是:
    1. 客户端发起 Ajax 请求,请求的数据格式为 JSON ,方法为 POST
    2. 服务器端用 Jetty 接收请求
    3. 用 Java 6 ScriptEngine 执行 JavaScript 脚本
    4. 结果以 JSON 格式返回

    另外,在系统启动时需要对所有脚本进行初始加载。

    接下来就按照这个思路逐步进行。

    客户端 Ajax 请求

    在 Eclipse 中建立 Java 工程,新建 webapp 目录和 webapp/js 目录。
    这里需要用到 jQuery 1.6 和一个 $.toJSON 插件 (json.js),可以在附件中找到。

    webapp 下的 index.html 的代码如下:
    Html代码  收藏代码
    1. <script src='js/jquery.js'></script>  
    2. <script src='js/json.js'></script>  
    3. <script src='js/proxy.js'></script>  
    4. <script src='js/app.js'></script>  
    实际上只是依次引用了多个js文件。

    proxy.js 的作用是利用 jQuery 发起 Ajax 请求。
    实现如下:
    Javascript代码  收藏代码
    1. function proxy(request, success, failure) {  
    2.     request = $.toJSON(request);  
    3.   
    4.     $.post('api', { request : request }, function(result){  
    5.         if (result.success) {  
    6.             success && success(result.data);  
    7.         } else if (failure) {  
    8.             failure(result.error);  
    9.         } else {  
    10.             alert('Operation Failed!');  
    11.         }  
    12.     }, 'json');  
    13. }  

    参数解释如下:

    【requset】 请求对象,包含两个属性:
    【action】 需要服务器端执行的动作,如 'sayHello' 等
    【params】 请求参数,是一个 JavaScript Object, 如 { name: 'Pei Xiaoxing' } ,也可以是更复杂的对象

    【success】 执行成功是需要执行的函数 (服务器端返回的 result.success 为 false )
    【failure】 执行失败时需要执行的函数 (服务器端返回的 result.success 为 true )

    首先将 requset 转为字符串,
    然后调用 jQuery 的 post() 函数,url 指定为 'api' ,这是服务器端处理 AJAX 请求的 Servlet 的路径;
    最后根据返回的 result.success 分别执行回调函数 success  和 failure。


    app.js 的实现如下:
    Javascript代码  收藏代码
    1. $(function(){  
    2.     var request = {  
    3.         action: 'sayHello',  
    4.         params: { name: 'Pei Xiaoxing' }  
    5.     };  
    6.   
    7.     proxy(request, function(data) {  
    8.         $('body').html(data);  
    9.     });  
    10. });  

    构造一个requset对象作为 proxy 的第一个参数,调用刚刚实现的 proxy 函数;
    服务器端返回成功信息时,就将数据填充到 body 。

    服务器端 Jetty 接收请求

    利用 Jetty 接收服务请求,分为两类:
    1. 资源请求,如 html , js 之类的文件,映射到路径 “/”。
    2. API请求,如客户端提交的 “ sayHello ” 动作,映射到路径 “/api”。

    同时,在启动 Server 前,先执行所有服务器端脚本,以获得所有动作的定义。

    JSServer 实现如下:
    Java代码  收藏代码
    1. import org.eclipse.jetty.server.Server;  
    2. import org.eclipse.jetty.servlet.DefaultServlet;  
    3. import org.eclipse.jetty.servlet.ServletContextHandler;  
    4. import org.eclipse.jetty.servlet.ServletHolder;  
    5.   
    6. public class JSServer {  
    7.     public static void main(String[] args) throws Exception {  
    8.         JSEngine.excuteFiles("scripts");  
    9.         startServer();  
    10.     }  
    11.   
    12.     private static void startServer() throws Exception, InterruptedException {  
    13.         Server server = new Server(8080);  
    14.         configContext(server);  
    15.         server.start();  
    16.         server.join();  
    17.     }  
    18.   
    19.     private static void configContext(Server server) {  
    20.         ServletContextHandler context = new ServletContextHandler(  
    21.                 ServletContextHandler.SESSIONS);  
    22.         context.setContextPath("/");  
    23.         context.setResourceBase("webapp");  
    24.         server.setHandler(context);  
    25.   
    26.         context.addServlet(new ServletHolder(new DefaultServlet()), "/");  
    27.         context.addServlet(new ServletHolder(new JSServlet()), "/api");  
    28.     }  
    29. }  

    main 函数中做了两件事:
    1. 执行 “scripts” 文件夹下的所有 js 脚本。
    2. 启动 jetty server

    startServer() 函数新建一个监听 8080 端口的 Server ,配置 Context 并启动。
    configContext() 函数设置请求路径为 "/",以及资源路径为“webapp”,并添加两个 Servlet 的映射。

    JSEngine : Java 6 ScriptEngine 封装

    JSEngine 对 Java 6 ScriptEngine 进行简单封装,这纯粹是个人习惯了,
    因为使用 ScriptEngine 的时候总是要进行各种强制转换,干脆封装在一个类中了。

    实现如下:
    Java代码  收藏代码
    1. import java.io.File;  
    2. import java.io.FileReader;  
    3. import java.io.IOException;  
    4.   
    5. import javax.script.Compilable;  
    6. import javax.script.Invocable;  
    7. import javax.script.ScriptEngine;  
    8. import javax.script.ScriptEngineManager;  
    9. import javax.script.ScriptException;  
    10.   
    11. public class JSEngine {  
    12.     private static ScriptEngine engine;  
    13.     private static Compilable compliable;  
    14.     private static Invocable invocable;  
    15.   
    16.     static {  
    17.         ScriptEngineManager manager = new ScriptEngineManager();  
    18.         engine = manager.getEngineByName("javascript");  
    19.         compliable = (Compilable) engine;  
    20.         invocable = (Invocable) engine;  
    21.     }  
    22.   
    23.     public static void excuteFiles(String dir) throws ScriptException,  
    24.             IOException {  
    25.         for (File file : new File(dir).listFiles()) {  
    26.             FileReader reader = new FileReader(file);  
    27.             compliable.compile(reader).eval();  
    28.             reader.close();  
    29.         }  
    30.     }  
    31.   
    32.     public static String invoke(String func, Object... args)  
    33.             throws NoSuchMethodException, ScriptException {  
    34.         return (String) invocable.invokeFunction(func, args);  
    35.     }  
    36. }  

    excuteFiles 的作用是编译并执行所指定的目录下的所有 js 脚本,invoke 的作用是调用特定函数。

    JSServlet :处理 Ajax 请求

    JSServlet 用于接收 Ajax ,并执行相应的 js 脚本,实现如下:
    Java代码  收藏代码
    1. import java.io.IOException;  
    2.   
    3. import javax.script.ScriptException;  
    4. import javax.servlet.ServletException;  
    5. import javax.servlet.http.HttpServlet;  
    6. import javax.servlet.http.HttpServletRequest;  
    7. import javax.servlet.http.HttpServletResponse;  
    8.   
    9. public class JSServlet extends HttpServlet {  
    10.     private static final long serialVersionUID = -7419703636280795226L;  
    11.     private static final String ERROR  
    12.         = "{\"error\": \"Server internal error.\", \"success\": flase}";  
    13.   
    14.     @Override  
    15.     protected void service(HttpServletRequest req, HttpServletResponse res)  
    16.             throws ServletException, IOException {  
    17.         String result;  
    18.         try {  
    19.             result = JSEngine.invoke("run", req);  
    20.         } catch (NoSuchMethodException e) {  
    21.             e.printStackTrace();  
    22.             result = ERROR;  
    23.         } catch (ScriptException e) {  
    24.             e.printStackTrace();  
    25.             result = ERROR;  
    26.         }  
    27.   
    28.         res.getWriter().write(result);  
    29.     }  
    30. }  

    service() 函数将 HttpServletRequest req 直接作为参数传入,调用 js 脚本 run() 函数,
    取得返回结果,并写入到 response 中。
    出错则返回 “ Server internal error. ”。

    run() 函数是 js 脚本的入口,完成路径映射,JSON 解析和序列化等工作。

    编写服务器端脚本

    服务器端脚本有两个:
    1. api.js 包含所请求的动作的逻辑;
    2. run.js 脚本的执行入口,接收 req 参数,并执行指定脚本。

    api.js 的实现如下:
    Javascript代码  收藏代码
    1. api = {  
    2.     sayHello: function(params) {      
    3.         return 'Hello, ' + params.name + '!';  
    4.     }  
    5. };  

    返回类似 “ Hello, xxx! ” 的信息


    run.js 的实现如下:
    Javascript代码  收藏代码
    1. function run(req) {  
    2.     var ret;  
    3.     try {         
    4.         var request = JSON.parse(req.getParameter('request'));  
    5.         var action = request.action;  
    6.         var params = request.params;  
    7.   
    8.         ret = { data : api[action](params), success : true }  
    9.     } catch (e) {  
    10.         println(e);  
    11.         ret = { error : e.toString(), success : false }  
    12.     }  
    13.   
    14.     return JSON.stringify(ret);  
    15. }  

    首先将 JSON 字符串请求转换为 JavaScript 对象,并提取 action 和 params 。
    然后调用 “api” 对象的 “ action ” 方法, 参数为“ params ”,并获得返回数据。返回数据也可以是复杂对象。
    最后将返回结果序列化为字符串;出错则将出错信息输出并返回给客户端。

    其中 JSON.parse ,JSON.stringify , println 都是内置函数。(JDK 6 中 JSON 对象还不是内置的,请查看下面红字部分。)

    运行并查看结果

    选择 JSServer 类 ,右键选择 “ Run as Java Application ” , 打开浏览器,输入 “ http://localhost:8080 ” 并回车,可以看到浏览器输出结果如下:
    Html代码  收藏代码
    1. Hello, Pei Xiaoxing!  

    加入Controller?

    如果 api 的形式是 api.controller.action ,那么可以将 app.js 中的 request 对象改为:
    Javascript代码  收藏代码
    1. var request = {  
    2.     controller: 'hello'  
    3.     action: 'say',  
    4.     params: { name: 'Pei Xiaoxing' }  
    5. };  

    并修改 run.js 中参数提取的方式:
    Javascript代码  收藏代码
    1. var controller = requset.controller;  
    2. var action = request.action;  
    3. var params = request.params;  
    4.   
    5. ret = { data : api[controller][action](params), success : true }  

    api 对象的定义可以分开写,比如 api.hello.js 定义一个 controller :
    Javascript代码  收藏代码
    1. var api = api || {};  
    2.   
    3. api.hello = {  
    4.     say: function(){ ... }  
    5. }  

    api.anotherHello.js 定义另一个 controller :
    Javascript代码  收藏代码
    1. var api = api || {};  
    2.   
    3. api.anotherHello = {  
    4.     anotherSay: function(){ ... }  
    5. }  


    REST 风格 URL ? Session, Cookie ?

    对于 REST 风格 URL,可以在 run 函数中通过 req.getRequestURI() 获取请求路径,然后直接对路径进行解析。
    同样,也可以通过 req.getSession() 和 req.getCookies() 获取 session 和 cookie 。



    以上就是我们的第一个 Server 端 JS 程序了。

    当然,只是运行这种简单的脚本没有多少意义,但 ScriptEngine 的优势是可以直接导入 Java API。
    因此 Java 中能做的,在我们的服务器端也都能做。


    关于 Server 端 JavaScript 程序的后续研究的列表如下:

    热部署 (已完成)
    http://xxing22657-yahoo-com-cn.iteye.com/blog/1054496

    文件上传与下载(已完成)
    http://xxing22657-yahoo-com-cn.iteye.com/blog/1068055
    http://xxing22657-yahoo-com-cn.iteye.com/blog/1070383

    利用 MongoDB 进行数据存储(已完成)
    http://xxing22657-yahoo-com-cn.iteye.com/blog/1071205
    http://xxing22657-yahoo-com-cn.iteye.com/blog/1076016
    http://xxing22657-yahoo-com-cn.iteye.com/blog/1097596

    仿 Jquery Template 的服务器端模板引擎(已完成)
    http://xxing22657-yahoo-com-cn.iteye.com/blog/1112920
    http://xxing22657-yahoo-com-cn.iteye.com/blog/1113665
    http://xxing22657-yahoo-com-cn.iteye.com/blog/1114521

    完善框架功能(已完成 1/6)
    http://xxing22657-yahoo-com-cn.iteye.com/blog/1121314

    其他模板引擎 (FreeMarker,Velocity,仿 JSP)
    数据库操作(SQLite Driver, MySQL JDBC)
    ORM(ibatis 等)
    其他NoSQL数据库(CouchDB等)
    分布式缓存 (Memcached 等)

    有朋友指出运行时会出现 "JSON" is not defined 的错误,这是因为 JDK 6 ScriptEngine 中确实没有定义 JSON 对象。因为我自己运行时使用了 JRE 7 预览版,所以没有发现这个问题。

    请解压附件中的 json.rar ,并将解压后得到的 json.js 放到“ scripts ”目录下。
  • 相关阅读:
    理解RabbitMQ中的AMQP-0-9-1模型
    深入分析Java反射(八)-优化反射调用性能
    一张图帮你记忆,Spring Boot 应用在启动阶段执行代码的几种方式
    Java equals 和 hashCode 的这几个问题可以说明白吗?
    如何妙用Spring 数据绑定机制?
    Lombok 使用详解,简化Java编程
    Java升级那么快,多个版本如何灵活切换和管理?
    手把手教你定制标准Spring Boot starter,真的很清晰
    Java12 Collectors.teeing 你需要了解一下
    Maven optional关键字透彻图解
  • 原文地址:https://www.cnblogs.com/lexus/p/2337624.html
Copyright © 2020-2023  润新知