日期:2014-05-16  浏览次数:20609 次

Pure JS (2): 热部署 (利用 JDK 7 NIO 监控文件变化)
Pure JS (2): 热部署 (利用 JDK 7 NIO 监控文件变化)


  接着上一篇文章(http://xxing22657-yahoo-com-cn.iteye.com/blog/1052485)的话题,我们来谈谈服务器端 JS 的热部署问题。

  由于 JavaScript 是动态语言,动态编译并执行脚本并不困难。所以关键是监控文件变化。
  这里推荐使用 JDK 7 NIO 中新增的 Watch Service API。Watch Service API 将尽可能使用操作系统的文件 API ,当操作系统不支持时,则使用轮询。简而言之,就是 JNI  + 轮询,自己实现轮询倒不难,但要实现跨平台的 JNI 调用就很啰嗦了。

  JDK 7 还没有正式发布,可以先到这里下一个 Early Access 版 :
  http://jdk7.java.net/
  安装完后可以通过 eclipse 的 Window -> Preference -> Java -> Intalled JREs 添加刚安装的 JRE,并勾选为默认的 JRE 环境。

  关于 Watch Service API 的使用说明可以参考这里:
  http://download.oracle.com/javase/tutorial/essential/io/notification.html

前期准备

  首先,我们在 src 目录下创建 purejs package ,将之前编写的 JSServer,JSEngine 和 JSServlet 都移动到这个 package 下。因为后面将会用到 import static 的写法,没有 package 名称的话会报错。
  然后,我们需要在 JSEngine 类中增加执行单个文件的方法( excuteFile(File file) ),并在 excuteFiles(String dir) 方法中引用它。

  现在的 JSEngine 应该是这样的:
public class JSEngine {
	// Declaration of engine, compliable and invocable ...

	static {
		// Initialize of engine, compliable and invocable ...
	}

	public static void excuteFiles(String dir) throws ScriptException,
			IOException {
		for (File file : new File(dir).listFiles()) {
			excuteFile(file);
		}
	}

	public static void excuteFile(File file) throws ScriptException,
			IOException {
		FileReader reader = new FileReader(file);
		compliable.compile(reader).eval();
		reader.close();
	}

	// Defination of invoke ...
}

增加文件监控类 FileChangeMonitor

  实现思路是:
  1. 创建一个新的线程。
  2. 在线程中创建 Watch Service 的实例 watcher 。
  3. 在 watcher 上注册文件的创建和修改事件。
  4. 在循环中使用 watcher.take() 等待事件的发生。
  5. 遍历捕捉到的所有事件,获取文件名,通知 Lisener 文件的更改。

  具体实现如下:
package purejs;

import static java.nio.file.StandardWatchEventKinds.*;

import java.io.*;
import java.nio.file.*;

public class FileChangeMonitor {
	public interface FileChangeListener {
		public void fileChanged(File file);
	}

	public static void monit(final String dir,
			final FileChangeListener listener) {
		new Thread() {
			public void run() {
				Path path = new File(dir).toPath();
				WatchService watcher;
				try {
					watcher = FileSystems.getDefault().newWatchService();
					path.register(watcher, ENTRY_CREATE, ENTRY_MODIFY);
				} catch (IOException e) {
					e.printStackTrace();
					return;
				}

				while (true) {
					WatchKey key;
					try {
						key = watcher.take();
					} catch (InterruptedException _) {
						return;
					}

					processKey(path, key, listener);
				}
			};
		}.start();
	}

	private static void processKey(Path path, WatchKey key,
			FileChangeListener listener) {
		for (WatchEvent<?> event : key.pollEvents()) {
			if (event.kind() == OVERFLOW) {
				continue;
			}

			Path fileName = (Path) event.context();
			File file = path.resolve(fileName).toFile();
			listener.fileChanged(file);
		}

		key.reset();
	}
}

在 JSServer 中引入 FileChangeMonitor

  将 main 函数的执行分成两个步骤:
  1. 加载脚本。这里又分为两个步骤:
    【执行脚本】 直接调用 JSEngine 执行“ scripts ”目录下的脚本。
    【设置监控】 调用 FileChangeMonitor.monit(...),文件变更时再次执行脚本。
  2. 启动 Server,与之前相同。
public class JSServer {
	public static void main(String[] args) throws Exception {		
		loadScripts();
		startServer();
	}

	private static void loadScripts() throws ScriptException, IOException {
		JSEngine.excuteFiles("scripts");
		FileChangeMonitor.monit("scripts", new FileChangeListener() {