没什么用系列1?如何把Java 改造的像node.js?
前言
因为最近在上软件工程的课,学到了数据流图和其他一些设计图,我就在想能不能把数据流图或其他设计图高效地转换为JAVA代码,并且还要很方便地后期增加处理节点。我的第一个想法是用url来表示数据处理调用关系。类似node.js的中间件路由的方式。说干就干,于是就有了Java 改 node.js 的想法。
设计
我的设想是从同一条路径上看处理节点是同步的,从不同路径上看处理节点是异步的。比如url /a/b
和 /a/c
也就是说a绑定的Java类 和 b绑定的Java类 是 有先后关系的。但b 和 c 就没有了。
代码
路由类
package cn.lyf.utils.router;
import java.util.*;
import java.util.concurrent.*;
import com.alibaba.fastjson.*;
import cn.lyf.utils.event.*;
/*
* 同一节点内是同步的
* 同一个节点共用一个req,res
*/
public class Router {
private static HashMap<String, CopyOnWriteArrayList<Middleware>> nodes = new HashMap<String, CopyOnWriteArrayList<Middleware>>();
//私有消息
private static EntityEventEmitter e = new EntityEventEmitter();
static {
e.on("post", new handers());
}
// 同步方法
private static class handers extends Events {
@Override
public void run() {
JSONObject p = (JSONObject) param, res = new JSONObject();
String flow = p.getString("flow");
awaitPost(flow, p.getJSONObject("data"), res);
Middleware callBack = (Middleware) p.get("callback");
if (callBack != null)
callBack.core(p, res);
}
};
// 线程安全
private static void awaitPost(String flow, JSONObject req, JSONObject res) {
String[] paths = flow.split("/");
StringBuilder p = new StringBuilder();
CopyOnWriteArrayList<Middleware> list = null;
for (String s : paths) {
p.append(s);
p.append('/');
list = nodes.get(p.toString());
if (list != null) {
for (int i = 0; i < list.size(); i++) {
list.get(i).core(req, res);
}
}
}
}
// 只有初始化时调用,用同步简化
static public synchronized void use(String flow, Middleware mid) {
String[] paths = flow.split("/");
StringBuilder p = new StringBuilder();
CopyOnWriteArrayList<Middleware> list = null;
for (String s : paths) {
p.append(s);
p.append('/');
list = nodes.get(p.toString());
if (list == null) {
list = new CopyOnWriteArrayList<Middleware>();
nodes.put(p.toString(), list);
}
}
list.add(mid);
}
// 异步方法
public static void asyncPost(JSONObject req) {
e.emit("post", req);
}
//url
public static void asyncPost(String req) {
JSONObject param = JSONObject.parseObject(req);
e.emit("post", param);
}
//url+callback
public static void asyncPost(String req, Middleware callBack) {
JSONObject param = JSONObject.parseObject(req);
param.put("callback", callBack);
e.emit("post", param);
}
//url+data+callback
public static void asyncPost(String req,String data, Middleware callBack) {
JSONObject param = JSONObject.parseObject(req);
param.put("callback", callBack);
param.put("data",JSONObject.parseObject(data));
e.emit("post", param);
}
}
写过node.js的应该都挺好理解这个的吧。这里我用了阿里的fastjson。数据交换都是用json格式的。其中EntityEventEmitter是我自己写的局部消息发射器。仿node.js的EventEmitter。而Mideware是我自定义的中间件接口。另外因为以后主要是并发的读取(初始化基本只有一次)所以就hash表就用了CopyOnWriteArrayList。所以用了至于为什么没用线程安全的hashMap。是因为这里添加映射都是同步的。
有内味了,有没有!
- 附录
package cn.lyf.utils.router; import com.alibaba.fastjson.*; @FunctionalInterface public interface Middleware { void core(JSONObject req, JSONObject res); }
事件驱动
Events 类
package cn.lyf.utils.event;
public abstract class Events implements Runnable, Cloneable {
public Object param = null;
public Object clone() {
Object o = null;
try {
o = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
}
EventEmitter 抽象类
package cn.lyf.utils.event;
/*
*仿 Node.js 事件处理包
*/
// 在满足以下两个条件的情况下,volatile就能保证变量的线程安全问题:
// 运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。
import java.util.*;
import java.util.concurrent.*;
abstract class EventEmitter {
ThreadPoolExecutor pool;
HashMap<String, CopyOnWriteArrayList<Runnable>> handers;
Object lock;
public void on(String event, Runnable listener) {
CopyOnWriteArrayList<Runnable> list;
// 读比较多,同步 影响小
synchronized (lock) {// 同步
list = handers.get(event);
if (list == null) {
list = new CopyOnWriteArrayList<Runnable>();
handers.put(event, list);
}
}
// CopyOnWriteArrayList写互斥
list.add(listener);
}
void remove(String event) {
CopyOnWriteArrayList<Runnable> list = handers.get(event);
if (list != null && list.size() != 0) {
list.clear();
}
}
public void emit(String event) {
// 并发读取
CopyOnWriteArrayList<Runnable> list = handers.get(event);
if (list != null) {
for (int i = 0; i < list.size(); i++) {
pool.execute(list.get(i));
}
}
}
public void emit(String event, Object param) {
CopyOnWriteArrayList<Runnable> list = handers.get(event);
if (list != null) {
for (int i = 0; i < list.size(); i++) {
// 保证线程安全
Events obj = (Events) ((Events) list.get(i)).clone();
obj.param = param;
pool.execute(obj);
}
}
}
}
全局EventEmitter
package cn.lyf.utils.event;
import java.util.*;
import java.util.concurrent.*;
public class GobalEventEmitter extends EventEmitter {
// 守护线程池
static ThreadPoolExecutor pool = new ThreadPoolExecutor(16, 64, 200, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(48), new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread t = Executors.defaultThreadFactory().newThread(r);
t.setDaemon(true);
return t;
}
});
static HashMap<String, CopyOnWriteArrayList<Runnable>> handers = new HashMap<String, CopyOnWriteArrayList<Runnable>>();
final static Object lock = new Object();
public GobalEventEmitter() {
super.lock = lock;
super.pool = pool;
super.handers = handers;
}
}
实体范围有效的EventEmitter
package cn.lyf.utils.event;
import java.util.*;
import java.util.concurrent.*;
public class EntityEventEmitter extends EventEmitter {
// 非守护线程池
ThreadPoolExecutor pool = new ThreadPoolExecutor(16, 64, 200, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(48));
HashMap<String, CopyOnWriteArrayList<Runnable>> handers = new HashMap<String, CopyOnWriteArrayList<Runnable>>();
final Object lock = new Object();
public EntityEventEmitter() {
super.lock = lock;
super.pool = pool;
super.handers = handers;
}
}
上面代码主要是两个类实现了抽象类EventEmitter
。其实一点也不抽象,因为一个抽象方法也没有。主要是用子类的属性替代父类的属性来实现全局和局部事件。这种奇怪的方式我还是第一次用,不知道工程上常不常见。
其中实体范围有效的EventEmitter(EntityEventEmitter)是非守护线程池。要用户自己关闭。GobalEventEmitter是守护线程,主程序结束了就关闭了。
脚本化
这前写java.swing的 import时敲出了个 javax.script 包。我就知道事情不简单。但可惜的是 似乎JAVA 15 把 js 解析器移除了。获取解析器返回的都是null。
于是自己写一个。
思路
把字符串传进去,截获编译后的字节码,存到Map<String, byte[]> classBytes中。截获主要是自定义JavaFileManager 让他重写getJavaFileForOutput 方法调用自定义的JavaFile流。通过返回一个重写了close 方法的FilterOutputStream 把内存中的字节存到Map<String, byte[]> classBytes中
脚本加载器
package cn.lyf.utils.script;
import javax.tools.*;
import java.util.*;
public class ScriptLoader extends ClassLoader {
static Map<String, byte[]> classBytes = new HashMap<String, byte[]>();
static JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
static StandardJavaFileManager stdManager = compiler.getStandardFileManager(null, null, null);
static JavaFileObject javaFileObject;
static ScriptFileManager manager = new ScriptFileManager(stdManager, classBytes);
public static void compile(String name, String script) {
javaFileObject = manager.getJavaFileObject(name, script);
JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null,
Arrays.asList(javaFileObject));
task.call();
}
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
byte[] buf = classBytes.get(name);
if (buf == null)
return Class.forName(name);
// 节约 内存
classBytes.remove(name);
return defineClass(name, buf, 0, buf.length);
}
}
脚本文件管理器
package cn.lyf.utils.script;
import javax.tools.*;
import java.io.*;
import java.util.*;
class ScriptFileManager extends ForwardingJavaFileManager<JavaFileManager> {
Map<String, byte[]> classBytes ;
// 重写getJavaFileForOutput
public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className,
JavaFileObject.Kind kind, FileObject sibling) throws IOException {
if (kind == JavaFileObject.Kind.CLASS) {
return new Interceptor(className, classBytes);
} else {
return super.getJavaFileForOutput(location, className, kind, sibling);
}
}
// 获得 JavaFileObject
JavaFileObject getJavaFileObject(String name, String code) {
return new ScriptCode(name, code);
}
public ScriptFileManager(JavaFileManager fileManager,Map<String, byte[]> classBytes ) {
super(fileManager);
this.classBytes=classBytes;
}
}
脚本代码类
package cn.lyf.utils.script;
import javax.tools.*;
import java.nio.*;
import java.net.*;
class ScriptCode extends SimpleJavaFileObject {
private String code;
ScriptCode(String name, String code) {
super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
this.code = code;
}
public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
return CharBuffer.wrap(code);
}
}
拦截器
package cn.lyf.utils.script;
import javax.tools.*;
import java.io.*;
import java.util.*;
import java.net.*;
class Interceptor extends SimpleJavaFileObject {
private Map<String, byte[]> classBytes;
private String name;
public Interceptor(String name, Map<String, byte[]> classBytes) {
super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.CLASS);
this.name = name;
this.classBytes = classBytes;
}
// 截获 Class Bytes
public OutputStream openOutputStream() {
return new FilterOutputStream(new ByteArrayOutputStream()) {
public void close() throws IOException {
classBytes.put(name, ((ByteArrayOutputStream) out).toByteArray());
out.close();
}
};
}
}
不足
脚本导入上下文的方法还没写。但都是字符串了,还怕啥呢。拼串就完了。不知到有没有大神有更高级的写法。望不吝赐教。
后记
感觉最近想法很多,学的很少。有点思而不学则殆的感觉。轮子要造,但不要造太多。利用前人的智慧也是很重要的。