Archive for February, 2006

狂想年代

Sunday, February 12th, 2006

这是一个狂想的年代,没有想法的人是可耻的。

在元宵节的晚上,吃完奇大无比的元宵,我与往常一样写着各式各样不同的代码,继续在Fireworks中画来画去,然后与apache, python斗争。忽然与小龙聊了起来,他说,今年有10个项目要做,吓我一跳。2005整整一年,除了工作的项目,我也就做了一件事情而已。然而,想法多未见得是好事,比如我就没有看到他做成熟的任何一件产品。

有一些以外的耳闻:一个网络游戏币交易网站,一个月居然有300~400万的现金进出,这一点让我匪夷所思。我玩的网络游戏仅仅是WoW, 汗颜的时候直到现在一张点卡还没有完,所以很费解为什么这样的一个小网站能够赚到这样多的钱。看来,IT正在逐渐成为渗透力最强的行业,我们身处其中,却越来越看不透:因为IT已经成为了商业的组成部分,而写程序的我们,当然不懂商业。

让我觉得比较有趣的是,他打算做一个联合通行证。很明显一看就知道是什么:一个通行证能够访问所有支持这个通行证的网站/社区,享受所有的服务。这样的思路在许多很2的blog中出现过,但没有人做出来。我的担心一如大众:能不能取得足够多的社区的支持?而小龙的回答充满社会责任感:

这个东西本身没有什么盈利模式 但主要是提供一种方便 一种服务
一个帐户 通行神州 至少是个人网站

显然,越来越多的人意识到,人性化,方便的服务正逐渐取代技术而成为产品能否成功的关键。我发现,只要提供给消费者可用、优质的服务,真心实意地为消费者考虑,一定有出路。这是一个最好的时代,也是一个最坏的时代,一个产品得到消费者认可需要的时间越来越长,只有狂想着提高产品可用性,以提供最好用户体验为目的的人/公司,才能真正获得回报。这种回报发自真心的、忠诚度极高的回报。回报是你狂想的附加值,不是你追求的目的,就像大海,浪花只是前进标志,而不是停止的信号。

让SpringMVC也支持自定义url mapping

Friday, February 10th, 2006

[#SPR-703] Make Controller aware of the url mapping – Spring Framework

提供了一个RegexUrlMappingHandler, 可以支持类似于以下的使用:

[xml]

blogArchiveController [/xml]

在Spring controller中:

[java]
Map objects = new HashMap();
objects.put(“year”, parameters.get(0));
objects.put(“month”, parameters.get(1));
objects.put(“tag”, parameters.get(2));
return new ModelAndView(“/archive.jsp”, objects); [/java]

使用buffalo作为webwork的验证机制-实现

Wednesday, February 8th, 2006

要的人比较多,废话少说,放代码:

ValidationError.java, 主要是错误信息的一个DTO
[java]
public class ValidationError {
private String name;
private String value;

public ValidationError() {

}

public ValidationError(String name, String value) {
this.name = name;
this.value = value;
}

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}

}
[/java]

ValidationService.java, buffalo要使用的service, 代码不复杂,没有注释,原理见前一篇文章
[java]
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.opensymphony.xwork.Action;
import com.opensymphony.xwork.ActionContext;
import com.opensymphony.xwork.ActionProxy;
import com.opensymphony.xwork.DefaultActionInvocation;
import com.opensymphony.xwork.DefaultActionProxy;
import com.opensymphony.xwork.ValidationAware;
import com.opensymphony.xwork.config.entities.ActionConfig;
public class ValidationService {
public List validate(String namespace, String action, Map parameters) {
List errorList = new ArrayList();
Action requestedAction = null;
HashMap ctx = new HashMap();
ctx.put(ActionContext.PARAMETERS, parameters);
ValidatorActionProxy proxy;
try {
proxy = new ValidatorActionProxy(namespace, action, ctx);
proxy.execute();
requestedAction = proxy.getAction();
} catch (Exception e) {
e.printStackTrace();

return null;
}

if (requestedAction instanceof ValidationAware) {
ValidationAware va = (ValidationAware) requestedAction;
Map fe = va.getFieldErrors();
for (Iterator iterator = fe.entrySet().iterator(); iterator
.hasNext();) {
Map.Entry entry = (Map.Entry) iterator.next();
String name = (String) entry.getKey();
List errors = (List) entry.getValue();
for (Iterator iterator1 = errors.iterator(); iterator1
.hasNext();) {
String error = (String) iterator1.next();
errorList.add(new ValidationError(name, error));
}
}
}

return errorList;

}

public static class ValidatorActionInvocation extends
DefaultActionInvocation {
protected ValidatorActionInvocation(ActionProxy proxy, Map extraContext)
throws Exception {
super(proxy, extraContext, true);
}

protected String invokeAction(Action action, ActionConfig actionConfig)
throws Exception {
return Action.NONE; // don’t actually execute the action
}
}

public static class ValidatorActionProxy extends DefaultActionProxy {
protected ValidatorActionProxy(String namespace,
String actionName,
Map extraContext) throws Exception {
super(namespace, actionName, extraContext, false);
}

protected void prepare() throws Exception {
invocation = new ValidatorActionInvocation(this, extraContext);
}
}
}
[/java]

buffalo-service.properties
[txt]
validationService=your.package.ValidationService
[/txt]

更改webwork模板中的simple/form.vm:
[html]

[/html]

最后,加入一个validate.js。用的是buffalo 1.1版本(写的时候的版本)。
[javascript]
var currentForm = null;

var getContextPath = function() {
var ctxPath = "";
$A(document.getElementsByTagName("script")).findAll( function(s) {
return (s.src && s.src.match(/buffalo\.js(\?.*)?$/))
}).each( function(s) {
var path = s.src.replace(/script\/buffalo\.js(\?.*)?$/,'');
ctxPath = path;
});

return ctxPath;
}

function validate(theForm) {
var buffaloURL = getContextPath()+"BUFFALO";
var buffalo = new Buffalo(buffaloURL);
buffalo.events["onLoading"] = function() {}
var parameters = {};
for (var i = 0; i < theForm.elements.length; i++) {
var e = theForm.elements[i];
parameters[e.name] = e.value;
}
currentForm = theForm;
var actionName = theForm.name;
if (actionName.indexOf("do") != 0) {
actionName = "do"+actionName.charAt(0).toUpperCase() + actionName.substring(1);
}
Form.disable(theForm.id);
buffalo.remoteCall("validationService.validate",
[theForm.getAttribute("namespace"), actionName, parameters], callback) ;
return false;
}

function callback(reply) {
Form.enable(currentForm.id);
var insideTable = currentForm.getElementsByTagName("TABLE")[0];
clearErrorRows(insideTable);
clearErrorLabels(currentForm);
var errors = reply.getResult();
if (errors == null) {alert(null); return; }
if (errors.length > 0) {
for (var i = 0; i < errors.length; i++) {
var error = errors[i];
var element = currentForm.elements[error.name];
addError(element, error.value);
}

} else {
currentForm.submit();
Form.disable(currentForm.id);
}
}

function clearErrorRows(table) {
// clear out any rows with an "errorFor" attribute
var rows = table.rows;
var rowsToDelete = new Array();
for(var i = 0; i < rows.length; i++) {
var r = rows[i];
if (r.getAttribute("errorFor")) {
rowsToDelete.push(r);
}
}

// now delete the rows
for (var i = 0; i < rowsToDelete.length; i++) {
var r = rowsToDelete[i];
table.deleteRow(r.rowIndex);
}
}

function clearErrorLabels(form) {
// set all labels back to the normal class
var elements = form.elements;
for (var i = 0; i < elements.length; i++) {
var e = elements[i];
var cells = e.parentNode.parentNode.cells;
if (cells && cells.length >= 2) {
var label = cells[0].getElementsByTagName(“label”)[0];
if (label) {
label.setAttribute(“class”, “label”);
}
}
}

}
function addError(e, errorText) {
try {
var row = e.parentNode.parentNode;
var table = row.parentNode;
var error = document.createTextNode(errorText);
var tr = document.createElement(“tr”);
var td = document.createElement(“td”);
var span = document.createElement(“span”);
td.align = “center”;
td.valign = “top”;
td.colSpan = 2;
span.className=”errorMessage”;
span.appendChild(error);
td.appendChild(span);
tr.appendChild(td);
tr.setAttribute(“errorFor”, e.id);;
table.insertBefore(tr, row);
var label = row.cells[0].getElementsByTagName(“label”)[0];
label.setAttribute(“class”, “errorLabel”);
} catch (e) {
alert(e);
}
}
[/javascript]

调用流程:实际上表单提交前buffalo将整个表单送到了后台并利用ValidationService执行了一次action,看有没有错误,如果没有错误才真正提交,否则通过js对页面进行相应的提示。

注意事项:1, 一些拦截器需要ActionContext中含有servlet等信息,由于ValidationService不带有这些信息,因此这些拦截器可能会在校验的时候报错;2, webwork的某些校验器的错误提示信息有些并不返回,因此不是所有的校验错误都能返回,但绝大多数都能满足了;3, 不要期待你写的actionError信息也能通过这种方式返回——当然你可以自己写,但在这个实现中,只能返回校验错误信息.

Friendly URL应当作为web2.0应用首先要考虑的问题

Monday, February 6th, 2006

web2.0概念的提出给这个灰暗的web应用世界带来了鲜活的色彩;而ajax的使用又给用户提供了更好的操作体验。然而,在具体的使用上,他们 给开发者带来的考虑不是更少而是更多。ajax滥用比不用更加可恶——满屏的loading实在不是什么好的体验。如何真正给用户提供真正易于理解舒心畅 快的应用,是2.0应用开发者首先需要考虑的问题。

OPOA提出后,我一直在考虑一个问题:如何将操作型应用与内容型应用有机的结合起来呢?这个问题的提出并非空穴来风。实际上,除了类似于邮件这样 的简单应用不需要考虑友好URL之外,大部分web应用,无论是企业应用还是面向消费者的 web2.0应用,都存在面对友好URL的问题。例如:到底是portal/index.action?userid=10001比较好看还是 portal/michaelchen比较好看?到底是michaelchen/photo/12319 比较容易懂还是 photo/viewPhoto.action?userid=10001&photoid=12319 比较容易懂?

答案是很容易看明白,一个好的URL不应该经常变动

一个典型的web2.0应用在URL上首先是友好的:看看成功的应用大多如此:Flickr, del.icio.us, 豆瓣,等等。体贴的URL为传播、共享、搜索提供了潜在的巨大的便利,只有那些操作型应用系统才不需要考虑URL的组织方式。

因此,一个典型的web2.0应用,首先要考虑的是如何组织对外的URL——这个URL规则一旦建立,以后要进行变更的可能性就比较小(因为成本巨 大,无数 的人链接到了这个地址)。你可能采用某一个独立的模块来对这一部分进行维护。遗憾的是,传统的J2EE框架在这方面所做的事情少之又少,无论是 webwork还是struts, 都没有明显的对这方面的支持(所以说用这类框架做web2.0应用很痛苦);tapestry相对好一些,在4.0中可以对url进行定制。另外UrlRewriteFilter对传统应用进行了修饰,但遗憾的是,怎么用怎么感觉不太好。

如何解决?我能想到的一个比较清晰的架构是:从一开始就考虑URL的结构,采用一个独立的servlet来管理URL链接,它的功能与 UrlRewriteFilter类似,但是它的结合点不是类似于product.action?id=xxx路径本身,而是直接指向相对应需要运行的 Struts/Webwork Action(或者Spring MVC的Controller)。

下午的时候无意中看到了django, 这个新型的类似于rails的快速开发框架,显然更懂web2.0——直接有了一个urls.py文件来对各类url映射进行配置,能够直接将某一个正则表达式支持的路径映射到一个执行的方法:

[python]
patterns = patterns(”,
(r’^blog/$’, ‘blog.views.page’),
(r’^blog/page(?P\d+)/$’, ‘blog.views.page’),
)

# View (in blog/views.py)
def page(request, num=”1″):
# Output the appropriate page of blog entries, according to num.
[/python]

上面的例子是不是能够给我们带来一些启发?

写到这里,我有了一些结论:一个完整的web2.0应用结构上应当包含两部分:URL处理引擎和Ajax引擎。前者用于在整个WWW范围内共享传 播,后者用于改善单个用户的操作体验。这样,架构渐渐完整和清晰了。不得不说的是,目前我还没有看到任何一个JavaEE web框架明显的独立出url处理引擎的概念,tapestry做了一些,但还不够独立,SpringMVC/Webwork/Struts更不提。也许 这与java在企业应用漫长的时间有关,但是在现在,这些框架已经表现出落后了。是不是buffalo应该做这件事情呢?