OMToolkit介绍(3) :Web Framework 实现

[align=center][size=large][b]OMToolkit介绍(3) :Web Framework 实现[/b][/size][/align]
  本文将介绍OMToolkit中的 Web Framework 的实现,包括控制器、视图渲染、事务管理、分页和权限控制等。与Web Framework有关的类主要位于com.omc.web包中。
  本文的project是建立在前一篇文章的project的基础上的,所以请先下载附件中的OMServer_Complete.rar,解压后导入到eclipse中。

[align=center][size=medium][b]1. Controller:控制器,解析Web请求并分发任务到Entity[/b][/size][/align]
[align=center][size=medium][b](1) 创建Controller[/b][/size][/align]
  首先,新建初始的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());

  现在运行程序,在浏览器中输入[url]http://localhost[/url],仍然只能看到“Hello World!”,当然这只是开始。
  接下来,就要引入Entity了,先建一个空的Entity占个位子:

package com.omc.core;

public abstract class Entity {
}

  然后新建一个Entity的子类:

package com.omc.entity;

import com.omc.core.*;

public class User extends Entity {
public String sayHello() throws Exception {
return "Hello World!";
}
}

  修改Controller的doRun()方法:

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();
}

  运行程序,效果不变。当然这还谈不上任何的灵活性,至少Controller是不应该依赖于User类的;我们接下来将会根据URL中的信息,决定加载哪个Entity。

[align=center][size=medium][b](2) 加载Entity[/b][/size][/align]
  考虑到将来可能还要从jar文件中加载Entity,我们先在Cfg中加入参数jar和pkg,分别表示Entity可能存在的jar文件和package的名称:

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.jar
pkg=com.omc.entity

  编写LoadUtil来根据名称加载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();

  在Request中加入提取Entity所属的类的方法:

private Class<?> clz;

public Class<?> clz() throws Exception {
if (clz == null) {
clz = LoadUtil.load(entity);
}

return clz;
}

  最后修改Controller的loadEntity()方法,将以下代码:

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");

  运行程序,输入[url]http://localhost/User/sayHello[/url],效果没有变化。不过现在已经可以动态加载Entity了,你不妨也试着编写一个新的Entity类?
  当然,这还不够,我们希望所调用的方法也是动态的。

[align=center][size=medium][b](3) 方法调用[/b][/size][/align]
  首先,需要在ReflectUtil中增加通过反射获取method的方法:

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;
}

  然后,在Request中增加获取所调用的Method的方法:

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中,现在先不用理会。
  最后,需要修改Controller类的doRun()方法,将以下代码:

Method method = req.clz().getMethod("sayHello");
  修改为:

Method method = req.method();

  再次运行程序,输入[url]http://localhost/User/sayHello[/url]进行测试。你也可以为User编写更多的方法,并修改URL进行测试。
  现在我们的Controller已经可以做到动态地加载Entity类,并调用相应的方法了。不过更进一步地,我们还希望根据URL(或body中的参数)对Entity的属性进行赋值。

[align=center][size=medium][b](4) 属性赋值[/b][/size][/align]
  在ReflectUtil中加入一个新的setField(...)方法:

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);
}

  在Controller的loadEntity()方法中加入以下代码:

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);
}

  接着修改User类,以便测试。在User类中加入属性name:

private String name;
  并将sayHello()函数修改为如下形式:

public String sayHello() throws Exception {
return "Hello, " + name + "!";
}

  运行程序,在浏览器中输入[url]http://localhost/User/sayHello/name/Xiaoxing[/url],可以得到如下输出:

Hello, Xiaoxing!

  最后,由于实际应用中还经常需要对session、cookie、request进行操作,我们还需要在Controller中加入一些相关的代码。

[align=center][size=medium][b](5) Session,Cookie,Request[/b][/size][/align]
  在Entity中增加request属性:

private Request request;

public void setRequest(Request request) {
this.request = request;
}

public Request getRequest() {
return request;
}

  在Controller类的loadEntity()方法中 加入以下代码:

entity.setRequest(req);
ReflectUtil.setField(entity, "session", req.session());
ReflectUtil.setField(entity, "cookies", req.cookies());

  在Contrller类中增加getCookies(...)方法:

@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);
}

  修改Controller类的Run()方法,增加获取entity的cookies的代码:

public byte[] run() throws Exception {
String res = doRun();
List<Cookie> cookies = getCookies(entity);
return response(res, cookies).getBytes();
}

  修改Controller类的response方法,将改变后的cookies传回浏览器:

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();
}

  接下来,修改User类以进行测试:

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();
}
}

  现在可以运行程序进行测试了。在浏览器中输入[url]http://localhost/User/init[/url],将得到“Init Coomplete.”的提示信息;然后输入[url]http://localhost/User/sayHello/name/Xiaoxing[/url],将得到如下输出:

role:user
password:pass
path: User/sayHello/name/Xiaoxing
Hello, Xiaoxing!


  到此为止,Controller类的代码就基本完成了。现在的代码应该类似于附件中的OMWeb_Step1.rar。我们在Controller类中完成了以下工作:
  1. 根据Web请求,动态加载Entity;
  2. 根据Web请求,动态调用Entity的方法;
  3. 根据Web请求,对Entity的属性进行赋值;
  4. 设置Entity,使其能便捷地操作session、cookie和request对象。

  不过,现在的Controller类有点乱了,在下一篇文章中介绍数据存储部分时,我们会把loadFields(...)等操作交给DataUtil。

  接下来,我们将看看视图类“View”,了解视图渲染的细节。

[align=center][size=medium][b]2. View:视图渲染[/b][/size][/align]
[align=center][size=medium][b](1) 准备工作[/b][/size][/align]
  在正式编写View类之前,还需要一些准备工作。首先是增加一个Reference类:

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中实现延迟加载。不过这属于数据存储的内容,这里暂不涉及。
  同时,需要增加TextField类:

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中实现延迟加载。

  接着,在FileUtil中增加read方法:

public static String read(String path) throws Exception {
return new String(bytes(path));
}
  这个方法用于读取文件内容并转为String。在视图渲染中经常需要读取html文件。

  然后,在StringUtil中增加以下代码:

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(...)表示判断一个字符串是否为空。

  在ReflectUtil中增加get(...)

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方法的话就查找属性本身,获取值并返回。

[align=center][size=medium][b](2) 视图渲染框架[/b][/size][/align]
  首先,看看View类的基本框架:

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"”开头,则先进行继承相关的处理,然后逐行读取,根据关键字决定解析为分支、循环或者普通语句。

[align=center][size=medium][b](3) 视图继承[/b][/size][/align]
  接下来,我们先看看doExtends(...)的实现:

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();然后更新被继承视图的内容,即进行一些替换后返回。
  overrides(...)方法:将当前的页面按照“#overrides”关键字进行划分,取得要覆盖的部分的名称,以及用于覆盖的内容。
  updateParent(...)方法:根据overrides(...)方法得到的结果,对parent进行更新。找到parent视图中用关键字“#abstract”标注的部分,根据名称进行替换。
  总之,就是用子视图的“#overrids”的内容去覆盖父视图的“#abstract”部分。

[align=center][size=medium][b](4) 分支与循环[/b][/size][/align]
  接着,是分支语句的实现:

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(...)将在后面介绍解析语法块时提到。
  分支语句的逻辑是:先提取第一行的第二个词,也就是判断条件;解析条件,如果结果为“true”,则判断成功,读取语法块并将语法块中的内容添加到渲染输出中,否则跳过语法块。
  循环语句的实现如下:

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)方法进行解析。
  parse(Object model, String var)方法:如果model是一个对象引用,就先获取被引用的对象;如果变量中带有点操作符,就对自己进行递归调用;否则,从model的属性中获取值。
  reference(Object model, String var)方法:当前model的属性或session中获取所引用的model。
  parseVar(Object model, String var)方法:依次在model的属性、Web请求参数和session中查找变量,如果都没有找到,则返回空字符串。
  fromField(Object model, String var) 方法:从model的属性中获取值,并针对TextField进行特殊处理。

  解析语法块的实现如下:

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中。
[align=center][size=medium][b](5) 解析普通语句[/b][/size][/align]
  parseLine(...)的实现如下:

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()方法进行处理;否则,将内容添加到渲染输出。

  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()方法进行处理,并将返回结果添加到渲染输出中。

  到此为止,View类的代码也基本完成了。View实现了以下功能:
  1. 基本的变量解析,包括“.”操作符,“${var}”标记等;
  2. 分支和循环语句;
  3. 静态继承与动态包含。


[align=center][size=medium][b](6) 修改其他类[/b][/size][/align]
  在Entity中添加与view相关的属性和方法:

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。

  在Controller类的loadEntity()方法中加入:

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;


  修改Request类,引入默认页面。将init()方法中的以下代码:

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();

  在Cfg中增加默认Entity和默认action的参数:

private static String defaultEntity;
private static String defaultAction;

public static String defaultEntity() {
return defaultEntity;
}

public static String defaultAction() {
return defaultAction;
}
  以及Cfg.cfg中的配置:

defaultEntity=User
defaultAction=index

[align=center][size=medium][b](7) 测试[/b][/size][/align]
  在User类中加入以下属性和方法:

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);
}

  模板页面 views/User/master.html:

<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>

  默认页面 views/User/index.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>

  测试页面 views/User/test.html:

#extends /User/master

#override title
测试页面

#override content
#if !showList
不显示列表!
#end

#if showList
#include /User/list/name/${name}/description/${description}/showList/${showList}
#end

  列表页面 views/User/test.list:

#loop
${name}<br />
${description}<br />
<br />
#end

  运行程序,在浏览器中输入[url]http://localhost[/url],效果如图:
[img]http://dl.iteye.com/upload/picture/pic/84543/71ffd843-be2f-3f8e-ad4a-b9fb71293f5e.png[/img]

[img]http://dl.iteye.com/upload/picture/pic/84547/800784e9-f773-30df-a344-a14b0ebba638.png[/img]

[img]http://dl.iteye.com/upload/picture/pic/84545/7c8337f5-9154-3332-9757-7f13d6b9b370.png[/img]

  试图渲染的说明到此为止,目前的代码应该类似于附件中的OMWeb_Step2.rar。考虑到篇幅的关系,剩下的部分将只做简单的介绍。

[align=center][size=medium][b]3. 其他:事务管理,分页和权限控制[/b][/size][/align]
[align=center][size=medium][b](1) 事务管理[/b][/size][/align]
  事务管理的实现主要在Transaction中,Transaction包含了toSave和toDelete两个集合,分别表示需要重新保存的Entity和需要删除的Entity;此外还有一个Map类型的属性cache,用于缓存已经读取过的Entity。
  OMToolkit将自动管理事务,用户也可以通过Entity.getTransaction()获取事务管理对象。为了实现自动保存更新过的对象,需要在对基本类型进行封装,这就需要引入OMField,特别是需要在OMField中是set()方法中记录更新过的Enttiy,在处理过程结束时自动进行保存或删除。
  此外,还需要提到的是@UpdateOptions注解,如果Entity中处理Web请求的方法中没有这个注解,则所有的更新和删除等操作都不会反映在数据库中,这是Tranaction的作用就仅仅是cache已经读取过的Entity了。如果带有这个注解,则所有从数据库中获取的Entity都将申请更新锁,使得其他需要进行更新操作的线程无法同时获取数据,这是为了保证数据的一致性;但同时也是一个潜在的性能瓶颈,更理想的做法是不加锁,而对多个更新操作采用特定的策略进行合并。

[align=center][size=medium][b](2) 分页处理[/b][/size][/align]
  分页处理的代码位于PageUtil类中,对于传入Reference列表,将按照Entity的id降序排列,即刚创建的Entity会排在前面;同时,读取request中的pageIndex和pageSize,进行分页处理后向request中写入empty、lastPage、nextPage等。
  当然,这种分页方式不够灵活。为了实现更自由的分页,用户需要通过References的values()方法获取Reference集合,再构造一个Comparator,连同pageIndex和pageSize,一起传入ArrayUtil.sort(...)方法中。这实际上是很繁琐的。所以OMToolkit的0.0.3版本的一个改进点就是优化这部分的API。

[align=center][size=medium][b](3) 权限控制[/b][/size][/align]
  AccessChecker类将对Web请求进行权限检查。
  对于Entity中带有@Role注解的方法,将从session中获取key “role” 对应的字符串,如果session中的role不@Role注解中著名的允许的用户的列表中,则强制返回登录页面。
  对于Entity中带有@UpdateOptions注解的方法,将检查输入的参数中,将对类型为OMField的属性进行更新的部分进行检查,看是否包含于toUpdate列表中;同时,也将检查对于不包含于allowEmpty中的OMFiled类型的属性,是否在输入的参数中没有出现,或者被指定为空;对于试图更新不被允许的属性,或者试图将不允许为空的字段设置为空的Web请求,也将强制返回登录页面。
  应该说,这种处理也缺乏灵活性。这也是0.0.3版本中需要改进的一个地方。

[align=center][size=medium][b]4. 总结[/b][/size][/align]
  OMToolkit中的 Web Framework 的实现,包括了控制器、视图渲染、事务管理、分页和权限控制等。与Web Framework有关的类主要位于com.omc.web包中。

  控制器部分主要借助反射,实现了:
  1. 根据Web请求,动态加载Entity;
  2. 根据Web请求,动态调用Entity的方法;
  3. 根据Web请求,对Entity的属性进行赋值;
  4. 设置Entity,使其能便捷地操作session、cookie和request对象。

  视图部分,主要借助反射和对文本的解析,实现了:
  1. 基本的变量解析,包括“.”操作符,“${var}”标记等;
  2. 分支和循环语句;
  3. 静态继承与动态包含。

  事务管理的部分,主要借助OMField的set()方法中记录的动作,实现了:
  1. 自动保存更新过的Entity(需@UpdateOptions注解);
  2. 自动删除被移除的Entity(需@UpdateOptions注解);
  3. cache已经读取过的对象;
  4. 申请更新多(需@UpdateOptions注解)。

  分页部分借助对Request的操作,主要实现按照id降序排列,并截取需要显示的Refeence列表,并在Request中记录分页所需信息的功能;权限控制则借助注解实现角色的检查,和更新操作中Web请求参数的合法性,并将不合法的访问强制重定向至登录页面的功能。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值