实现类似于rails的web开发框架
Rails的成功依赖于以下几点:
1 Web应用多年来积累的各种常见约定,convention over configuration. 这是rails简单的最根本保证。Rails将常见的一些配置信息从繁杂的xml配置文件中解脱出来,成为命名约定。这样xml的配置大大减少。
在目前几乎所有的MVC框架将xml作为配置的方式。而对于领域对象增删改查,rails采用如下的命名约定:
domainObject/view/id
domainObject/edit/id
domainObject/list
…
2 简单的Ruby语言。这也是rails成功的原因之一。
3 强大的代码生成器。加快开发速度的技巧之一,是将相同特征的操作利用代码生成机制完成大多数功能。这里不仅仅是业务代码,而且包括那些页面。页面能够根据数据库字段类型,生成相应的录入或者修改的输入框。
4 基于活动记录(ActiveRecord)的O/R映射
以上任何一个特征,采用java领域的相关知识都不难实现。那么,我们现在来看一下,如何实现这样一个特征的web框架。
基本思路
采用一个Servlet对所有的请求进行转发,路径中包括了要操作的domain object, 什么操作,以及操作所需的参数,因此,得到这些信息后,找到相应的domain object,并实例化;利用语言本身提供的反射(或称内省)特征,找到对应的方法,然后执行;将执行的结果返回到某一种界面表现中,然后显示。
大概思路就是这样,我们来看一下,在java世界里,这样的一套东西如何实现。
场景:/app/UserInfo/edit/1
编辑ID=1的用户信息。
基本路径配置:所有应用相关的文件都放在WEB-INF/app中,里面文件排放规则类似于rails,遵循MVC模式:controllers中存放控制器,views中按照domain object分别存放各种操作对应的页面(如增删改查),models中存放domain object定义。
由于在URL中保留了足够的对象、操作以及参数的各种信息,这些信息可以很容易的被获取:
1 获得对应的路径信息
String path = request.getPathInfo();
String[] args = path.split(“/”);
String domainObject = args[1]; // UserInfo
String operation = args[2] // edit
//…others are parameters // id=1
2 根据信息,找到相应的domain object. 按照rails的设计思路,这里需要最好采用某种脚本语言来实现。当然,在大型应用中,我们可以直接引用已经设计好的domain object, 但在这里,我们可以直接在某一种脚本语言中编写。jvm内的脚本语言种类繁多,你有足够多的选择:JavaScript, Python, Groovy等等,只需要选一种自己熟悉的就可以了;如果对纯粹的java更感兴趣,可以直接使用BSH(事实上,这就是大名鼎鼎的OFBiz的表现逻辑的做法)。这里我们使用Jython:
def class UserInfoController:
def edit(self,id):
objectMapper.edit(selfProerpties, id) # 这是伪代码,你可以有足够多的选择来更新对象
return {“object”:self}
3 Servlet通过某种方式来调用这段代码,并将结果返回给Servlet.
interpreter.execfile(theActionFile);
PyObject cls = interp.get(domainObject+”Controller”);
PyInstance controller = (PyInstance)cls.__call__();
PyDictionary pr = (PyDictionary)controller.invoke(operation, args);
Map map = Utils.PyDict2JavaMap(pr);
4 获得结果后,进行展现。下面就是某一种表现端一展身手的地方了:
许多模版语言都依赖于某一种类似于Map的数据结构来提供数据,这里以FreeMarker为例:
cfg = Configuration.getDefaultConfiguration();
cfg.setServletContextForTemplateLoading(getServletContext(), “WEB-INF/app/views”);
Template t = cfg.getTemplate(domainObject+”/”+operation+”.ftl”);
t.process(map, response.getWriter());
对于velocity,也可以进行同样的处理;而如果你更习惯于jsp, 则完全可以将Map中的数据放置到request中,然后输出jsp, 在jsp中使用JSTL对数据进行使用:
for (Map.Entry entry : map) {
request.setAttribute(entry.getKey().toString(), entry.getValue());
}
request.getRequestDispatcher(“views/”+ domainObject+”/”+operation+”.jsp”).forward(request, response)
这样,一个类似于Rails思想的Web框架就完成了. 当然,这里面还有需要需要细化的地方,许多代码需要推敲,在生产环境中,还有更多类似于缓存等需要注意的地方。O/R映射也没有提及,代码生成也需要进行考虑。但是无论如何,有了这样的思路,实现一个类似的web框架不是难事了。
[update] 这样的一种框架到底在什么地方使用最好呢?我的体会是在小型应用中,尽可能采用这样的轻型方式。在我目前参与的项目中,有时需要一些小型的web应用来辅助开发,这些小型应用的特点是需求紧急,需要马上使用;传统的j2ee开发肯定不能使用——等到domain object设计出来,已经不需要了;而其他的编程语言可能不是太方便;直接用jsp可以,但是不能清晰区分数据与表现,还是不佳。因此这样一种方式是比较好的——一旦设计完成,只需要编写python文件,放到特定目录即可。
另外,在大型应用中,对于服务层外围程序的开发,也可以采用这种方式。OFBiz就是这样做的,服务层内部采用普通java代码开发;web层与应用层之间的结合采用bsh脚本。这样,当需要调整页面或者添加改动量不大的新业务时,无需编译,编写出来马上就可以看到效果了。因此,采用传统j2ee方式构建服务层以及内核,采用脚本程序进行外部开发,结合了编译与运行的双重优势,这种方式值得使用。
关于脚本语言,我个人偏好python, 但在java领域,你有太多的选择:Groovy, ruby, javascript等等。
June 7th, 2005 at 11:17 am
trails
June 8th, 2005 at 9:47 pm
如果session失效,ie下会死在 reply.getResult()这里大概4、5秒,然后页面报告js错误,firefox下一切正常,马上报告session错误,很严重的问题啊
var action = reply.getResult();
if(!action || null == action) {
alert(’session过期,请重新登陆’);
}
if (action.error && “” != action.error) {
//alert(action.error);
errEle.innerHTML = action.error;
return;
}
June 8th, 2005 at 9:59 pm
此时ie的错误是258行字符2缺少对象,那个buffalo loading的进度条死在那里
firefox下立即返回action(=undified)
June 8th, 2005 at 10:07 pm
在说明一下,非常不好意思,由于自己近期没时间,只能处于使用buffalo情况,有问题只能来问问你。也不知道你信箱,所以…
June 9th, 2005 at 9:23 am
这是因为服务器端程序没有写好,没有对session进行判断。当session失效的时候,服务器端抛出NullPointerException, 客户端返回的是Fault, Buffalo目前没有对Fault进行自动处理,所以出错。只要在客户端或者服务器端加上校验就好了。
June 9th, 2005 at 9:52 am
服务端session管理是一个spring的拦截器,整个webapp里有很多页面是普通的页面刷新机制,这样怎么改?
我想应该在客户端改比较好,但是我上面的问题在firefox下是没有的.ie下怎么办呢?
public class SignonInterceptor extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
SignonSession adminSession = (SignonSession) WebUtils
.getSessionAttribute(request, SignonSession.NAME_IN_SESSION);
if (adminSession == null) {
ModelAndView modelAndView = new ModelAndView(new RedirectView(
NameSpace.VIEW_SIGNON));
throw new ModelAndViewDefiningException(modelAndView);
} else {
return true;
}
}
}
January 28th, 2007 at 9:34 pm
asian fisting
michael chen’s blog » blog archive » 实现类似于rails的web开发框架