OMToolkit介绍(3) :Web Framework 实现
OMToolkit介绍(3) :Web Framework 实现
本文将介绍OMToolkit中的 Web Framework 的实现,包括控制器、视图渲染、事务管理、分页和权限控制等。与Web Framework有关的类主要位于com.omc.web包中。
本文的project是建立在前一篇文章的project的基础上的,所以请先下载附件中的OMServer_Complete.rar,解压后导入到eclipse中。
1. Controller:控制器,解析Web请求并分发任务到Entity
(1) 创建Controller
首先,新建初始的Controller类:
package com.omc.web;import com.omc.server.*;/** * Extracting task message from {@link Request} and employ an {@link Entity} to * handle the task. * * @see Request * @see Entity */public class Controller {private Request req;public Controller(Request request) {req = request;}public byte[] run() throws Exception {String res = doRun();return response(res).getBytes();}public String doRun() throws Exception {return "Hello World!";}private String response(String response) {StringBuilder result = new StringBuilder();result.append("HTTP/1.1 200 OK\n");result.append(req.session() + "\n");result.append("\n" + response);return result.toString();}}然后,修改Worker.Processing类,删除response(...)、oldCookie(...)、newCookie(...),修改doRun()方法,将以下代码:
toWrite(response(req).getBytes());修改为:
toWrite(new Controller(req).run());
package com.omc.core;public abstract class Entity {}
package com.omc.entity;import com.omc.core.*;public class User extends Entity {public String sayHello() throws Exception {return "Hello World!";}}
public String doRun() throws Exception {Method method = User.class.getMethod("sayHello");loadEntity();try {Object res = method.invoke(entity);return (String) res;} catch (Exception e) {e.printStackTrace();return "";}}private void loadEntity() throws Exception {entity = (Entity) User.class.getConstructor().newInstance();}
private static String jar;private static String pkg;public static String jar() {return jar;}public static String pkg() {return pkg;}并在Cfg.cfg中增加参数:
jar=file:web.jarpkg=com.omc.entity
package com.omc.util;import java.net.*;/** * A wrap for a URL class loader. The loaded jar path and the package name can * be configured in the file "Cfg.cfg". */public class LoadUtil {private static ClassLoader loader;public static void init() throws Exception {URL[] urls = new URL[] { new URL(Cfg.jar()) };loader = new URLClassLoader(urls);}public static Class<?> load(String name) throws ClassNotFoundException {return loader.loadClass(Cfg.pkg() + "." + name);}}同时在Server类的init()中增加以下代码:
LoadUtil.init();
private Class<?> clz;public Class<?> clz() throws Exception {if (clz == null) {clz = LoadUtil.load(entity);}return clz;}
entity = (Entity) User.class.getConstructor().newInstance();修改为:
entity = (Entity) req.clz().getConstructor().newInstance();并修改doRun()方法,将以下代码:
Method method = User.class.getMethod("sayHello");修改为
Method method = req.clz().getMethod("sayHello");
public static Method getMethod(Class<?> clz, String name) {for (Method method : clz.getMethods()) {if (method.getParameterTypes().length == 0&& name.equals(method.getName())) {return method;}}return null;}
private Method method;public Method method() throws Exception {if (method == null) {method = getMethod(clz(), action());return method == null ? getMethod(clz(), "toView") : method;}return method;}这里需要注意的是,当找不到method时,会调用toView()方法,表示以当前的Entity为Model,渲染与方法名称相同的视图。我们在后面会将这个方法添加到Entity中,现在先不用理会。
Method method = req.clz().getMethod("sayHello");修改为:
Method method = req.method();
public static void setField(final Object obj, String name,final Object value) throws Exception {FieldSetter setter = new FieldSetter() {public void set(Field field) throws Exception {field.set(obj, value);}};doSetField(obj.getClass(), name, setter);}
private void loadEntity() throws Exception {Map<String, String> params = req.params();entity = (Entity) req.clz().getConstructor().newInstance();loadFields(params);}private void loadFields(Map<String, String> params) throws Exception {for (Entry<String, String> param : params.entrySet()) {loadField(param);}}private void loadField(Entry<String, String> param) throws Exception {String name = param.getKey();String value = param.getValue();for (Field field : req.clz().getDeclaredFields()) {field.setAccessible(true);if (field.getName().equals(name)) {Object obj = parseField(value, field);ReflectUtil.setField(entity, name, obj);}}}private Object parseField(String value, Field field) {return ReflectUtil.parseField(field.getType(), value);}
private String name;并将sayHello()函数修改为如下形式:
public String sayHello() throws Exception {return "Hello, " + name + "!";}
Hello, Xiaoxing!
private Request request;public void setRequest(Request request) {this.request = request;}public Request getRequest() {return request;}
entity.setRequest(req);ReflectUtil.setField(entity, "session", req.session());ReflectUtil.setField(entity, "cookies", req.cookies());
@SuppressWarnings("unchecked")private List<Cookie> getCookies(Entity entity) throws Exception {return (List<Cookie>) ReflectUtil.getField(entity, "cookies");}其中用到的ReflectUtil.getField(...)的实现如下:
public static Object getField(Object obj, String name) throws Exception {return doGetField(obj, obj.getClass(), name);}private static Object doGetField(Object obj, Class<?> clz, String name)throws Exception {for (Field field : clz.getDeclaredFields()) {if (field.getName().equals(name)) {field.setAccessible(true);return field.get(obj);}}Class<?> superClass = clz.getSuperclass();if (superClass.getSimpleName().equals("Object")) {return null;}return doGetField(obj, clz.getSuperclass(), name);}
public byte[] run() throws Exception {String res = doRun();List<Cookie> cookies = getCookies(entity);return response(res, cookies).getBytes();}
private String response(String response, List<Cookie> cookies) {StringBuilder result = new StringBuilder();result.append("HTTP/1.1 200 OK\n");result.append(req.session() + "\n");if (cookies != null) {for (Cookie cookie : cookies) {result.append(cookie + "\n");}}result.append("\n" + response);return result.toString();}
package com.omc.entity;import java.util.*;import com.omc.core.*;import com.omc.server.*;public class User extends Entity {private String name;private Session session;private List<Cookie> cookies;public String init() throws Exception {session.set("role", "user");cookies.add(new Cookie("password", "pass"));return "Init Coomplete.";}public String sayHello() throws Exception {StringBuilder result = new StringBuilder();result.append("role:" + session.get("role") + "<br />");result.append("password:" + Cookie.get(cookies, "password") + "<br />");result.append("path: " + getRequest().path() + "<br />");result.append("Hello, " + name + "!");return result.toString();}}
role:userpassword:passpath: User/sayHello/name/XiaoxingHello, Xiaoxing!
package com.omc.core.field;import com.omc.core.*;public class Reference<T extends Entity> {private T value;public T get() {return value;}public void set(T value) {this.value = value;}}Reference表示对其他Entity的引用,后面我们还将在Reference中实现延迟加载。不过这属于数据存储的内容,这里暂不涉及。
package com.omc.core.field;public class TextField {private String value;public String getText() {return value;}public void setText(String value) {this.value = value;}}同样地,将来会在TextField中实现延迟加载。
public static String read(String path) throws Exception {return new String(bytes(path));}这个方法用于读取文件内容并转为String。在视图渲染中经常需要读取html文件。
public static String upperFirst(String input) {return input.substring(0, 1).toUpperCase() + input.substring(1);}public static boolean isEmpty(Object s) {return s == null || s.equals("");}upperFirst(...)表示首字母大写,这在将一个属性名称转换为get方法的名称时将用到;isEMpty(...)表示判断一个字符串是否为空。
public static Object get(Object obj, String name) throws Exception {String methodName = "get" + StringUtil.upperFirst(name);Method m = getMethod(obj.getClass(), methodName);Object value = m == null ? getField(obj, name) : m.invoke(obj);return value == null ? "" : value;}这个方法结合运用getMethod(...)和getField(...),以获取对象的属性;即先查找get方法,没有get方法的话就查找属性本身,获取值并返回。
package com.omc.web;import static com.omc.util.FileUtil.*;import java.util.*;import java.util.regex.*;import com.omc.core.field.*;import com.omc.server.*;import com.omc.util.*;/** * Proving methods to render a HTML page form specific model and view name. */public class View {public static final Pattern ABSTRACT = Pattern.compile("#abstract (.+?)\n");public static final Pattern PROPERTY = Pattern.compile("\\$\\{(.+?)\\}");private Object model;private String content;private Request req;private List<String> lines;private int index;private String s;private StringBuilder block;private StringBuilder result = new StringBuilder();public static String render(Object model, String view, Request request)throws Exception {String content = read("views/" + view + ".html").replace("\r", "");return new View(model, content, request).render();}private View(Object model, String content, Request request) {this.model = model;this.content = content;this.req = request;}public String render() throws Exception {if (content.startsWith("#extends")) {content = doExtends(content);return new View(model, content, req).render();}lines = StringUtil.split(content, '\n');while (index < lines.size()) {readLine();if (s.startsWith("#if")) {branch();} else if (s.startsWith("#loop")) {loop();} else {parseLine();}}return result.toString();}// codes...private void readLine() {s = lines.get(index++).trim();}}可以看到,视图文件是逐行进行解析的,如果视图文件以“"#extends"”开头,则先进行继承相关的处理,然后逐行读取,根据关键字决定解析为分支、循环或者普通语句。
private String doExtends(String content) throws Exception {String[] parts = content.split("\n", 2);String view = parts[0].split(" ")[1];String parent = read("views/" + view + ".html").replace("\r", "");if (parent.startsWith("#extends")) {parent = doExtends(parent);}return updateParent(parent, overrides(parts[1]));}private Map<String, String> overrides(String body) {Map<String, String> overrides = new HashMap<String, String>();for (String override : body.split("#override ")) {if (!override.trim().isEmpty()) {String[] oParts = override.split("\n", 2);overrides.put(oParts[0].trim(), oParts[1] + '\n');}}return overrides;}private String updateParent(String parent, Map<String, String> overrides) {Matcher macher = ABSTRACT.matcher(parent);while (macher.find()) {String name = macher.group(1).trim();String o = overrides.get(name);if (o != null) {parent = parent.replace("#abstract " + name + '\n', o);}}return parent;}doExtends(...)方法:先提取第一行的第二个词,也就是被继承的视图的名称,获取被继承的视图文件的内容;如果该视图又继承了另一个视图,则递归调用doExtends();然后更新被继承视图的内容,即进行一些替换后返回。
private void branch() throws Exception {String v = s.split(" ")[1];boolean record = v.startsWith("!") ? !bool(v.substring(1)) : bool(v);readBlock(record);if (record) {View view = new View(model, block.toString(), req);result.append(view.render());}}private boolean bool(String var) throws Exception {return parse(var).equals("true");}这里用的的parse(...)方法将在后面介绍解析变量名称的时候提到,而readBlock(...)将在后面介绍解析语法块时提到。
private void loop() throws Exception {Iterable<?> iter = s.contains(" ") ? fromField() : fromModel();boolean record = iter.iterator().hasNext();readBlock(record);if (record) {for (Object item : iter) {Reference<?> r = (Reference<?>) item;View view = new View(r.get(), block.toString(), req);result.append(view.render());}}}private Iterable<?> fromField() throws Exception {String listName = s.split(" ")[1];return (Iterable<?>) parse(listName);}private Iterable<?> fromModel() {return (Iterable<?>) model;}首先,提取被循环的Iterable对象,如果“#loop”关键之后带有变量名称,则先解析变量名称作为被循环的Iterable对象;否则,以当前的model作为被循环的对象;对于Iterable对象包含的每个对象,都重新构造一个View对象,并将渲染结果添加到渲染输出,这实际上是一种间接递归。
private Object parse(String var) throws Exception {if (var.contains(".")) {String[] parts = var.split("\\.", 2);return parse(reference(model, parts[0]), parts[1]);}return parseVar(model, var);}private Object parse(Object model, String var) throws Exception {if (model instanceof Reference<?> && !var.equals("id")) {Reference<?> reference = (Reference<?>) model;return parse(reference.get(), var);}if (var.contains(".")) {String[] parts = var.split(".", 2);model = ReflectUtil.get(model, var);return parse(model, parts[1]);}return fromField(model, var);}private Reference<?> reference(Object model, String var) throws Exception {Object field = ReflectUtil.get(model, var);return (Reference<?>) (field == null ? req.session().get(var) : field);}private Object parseVar(Object model, String var) throws Exception {Object value = fromField(model, var);if (!StringUtil.isEmpty(value)) {return value;}String param = req.params().get(var);if (param != null) {return param;}Object sessionObj = req.session().get(var);return sessionObj == null ? "" : sessionObj;}private Object fromField(Object model, String var) throws Exception {Object value = ReflectUtil.get(model, var);if (value instanceof TextField) {return ((TextField) value).getText();}return value;}parse(String var)方法:如果变量中带有“.”操作符,则获取所引用的model,并调用parse(Object model, String var)方法进行解析;否则调用parseVar(Object model, String var)方法进行解析。
private void readBlock(boolean record) {if (record) {block = new StringBuilder();}int deep = 1;while (true) {readLine();if (s.startsWith("#if") || s.startsWith("#loop")) {++deep;} else if (s.startsWith("#end")) {--deep;}if (deep == 0) {break;}if (record) {block.append(s);block.append('\n');}}}先将表示层次深度的变量deep设置为1,再根据关键字“#if”“#loop”“#end”更新deep;当deep为0是退出,否则将改行内容添加到block中。
private void parseLine() throws Exception {Matcher matcher = PROPERTY.matcher(s);while (matcher.find()) {String group = matcher.group(1);Object value = parse(group);s = s.replace("${" + group + "}", value.toString());}if (s.startsWith("#include")) {include();} else {result.append(s);result.append('\n');}}先对语句中形如“#{var}”的部分进行解析并替换,然后如果以关键字“#inclue”开头,则调用include()方法进行处理;否则,将内容添加到渲染输出。
private void include() throws Exception {String path = s.split(" ")[1].substring(1);Request request = new Request(req.head(), path);result.append(new Controller(request).doRun());}即重新构造一个请求,调用Controller的doRun()方法进行处理,并将返回结果添加到渲染输出中。
private String view;public void setView(String view) {this.view = view;}public String toView() throws Exception {return toView(this);}public String toView(Object model) throws Exception {return toView(model, view);}public String toView(String view) throws Exception {return toView(this, view);}public String toView(Object model, String view) throws Exception {if (view.charAt(0) != '/') {view = this.getClass().getSimpleName() + '/' + view;}return View.render(model, view, request);}public String action(String action) throws Exception {if (action.startsWith("/")) {Request r = new Request(request.head(), action.substring(1));return new Controller(r).doRun();} else {view = action;Method method = this.getClass().getMethod(action);return (String) method.invoke(this);}}toView()用于显示视图,而action(...)表示转向,可能会改变当前的view。
entity.setView(req.action());并将Controller类的parseField(...)方法修改为:
if (field.getType().equals(TextField.class)) {TextField textField = new TextField();textField.setText(value);obj = textField;} else {obj = ReflectUtil.parseField(field.getType(), value);}return obj;
entity = parts[0].isEmpty() ? "" : parts[0];修改为:
entity = parts[0].isEmpty() ? Cfg.defaultEntity() : parts[0];并将action()方法中的以下代码:
return parts.length >= 2 ? parts[1] : "";修改为:
return parts.length >= 2 ? parts[1] : Cfg.defaultAction();
private static String defaultEntity;private static String defaultAction;public static String defaultEntity() {return defaultEntity;}public static String defaultAction() {return defaultAction;}以及Cfg.cfg中的配置:
defaultEntity=UserdefaultAction=index
private TextField description;public String list() throws Exception {Reference<User> r = new Reference<User>();r.set(this);List<Reference<User>> list = new ArrayList<Reference<User>>();list.add(r);list.add(r);list.add(r);return toView(list);}
<html><head><meta http-equiv = "content-type" content = "text/html; charset=GBK" /><title>#abstract title</title><link rel="stylesheet" type="text/css" href="/resources/style.css" /></head><body><div align="center"><img src="/resources/banner.jpg" /></div><div align="center" id="box">#abstract content</div></body><script defer type="text/javascript" src="/resources/script.js"></script></html>
#extends /User/master#override title首页#override content测试表单<form action="/User/test" method="post"><table><tr><td>用户名:</td></tr><tr><td><input name="name" id="用户名" maxlength="20" /><br /></td></tr><tr><td>描述:</td></tr><tr><td><textarea name="description" id="描述"maxlength="500" rows="3" cols="50"></textarea></td></tr><tr><td>显示列表:<input type="checkbox" name="showList" value="true" /></td></tr><tr><td align="center"><input type="submit" value="提交"></td></tr></table></form>
#extends /User/master#override title测试页面#override content#if !showList不显示列表!#end#if showList#include /User/list/name/${name}/description/${description}/showList/${showList}#end
#loop${name}<br />${description}<br /><br />#end