1.背景、形势
能够进行Web开发的编程语言和技术很多
(1) 动态解释语言
PHP; Perl; Python (Zope, Plone); Ruby (Ruby on Rails);
(2) 编译语言
Java; .net
Java Web开发远非一枝独秀:
除了受到来自.net 这个重量级对手的最大挑战之外,更受到Zope, Ruby on Rail 等新式轻骑兵的冲击(当然,也继续受到老式轻步兵PHP, Perl的冲击)。
官方Java走的是复杂路线,Servlet -> JSP -> Taglib。.net走的也是复杂路线,依靠成熟友好的集成化开发环境取胜。Java阵营好容易应对过来,从纷纭复杂的各种开发框架基础上,发展出了重量级Web开发框架JSF,以及相应的集成化开发环境;渴望以此应对.net的攻势。胜负未分,前途未卜。这时,另一个方向又杀来了新式轻骑Zope, Ruby on Rail。
Python, Ruby等动态解释语言,面向对象特性更好,先天支持 动态绑定、AOP、函数式编程、“编程即配置”等时髦概念。开发速度更快,代码量更小,达到killer级别。
传统的HTML Web开发领域里面,Java已经是腹背受敌。领域外也展开了征战,Rich Client Architecture的兴起:AJAX(XMLHttp), Flash RIA, XUL, XAML, Smart Client(以及从前的ActiveX, Applet, Web Start)。
Web的发展趋势是 语义Web,最终目的是让整个Web成为一个巨大的数据库。
这意味着,未来的Web应用将更加的面向文本内容数据,更加搜索引擎友好 – Search Engine Friendly.
二进制的客户端插件,如Flash RIA, ActiveX, Applet, Web Start等,虽然交互性能最好,但不是以文本内容数据为中心,搜索引擎不友好。所以,我只是保持适当关注。我更关注基于文本的UI表现,如HTML, XUL, XAML等。XUL, XAML还没有广泛流行,只是保持一种有兴趣的关注。
当下关注的重点,还是 XHTML + CSS + javascript少量的 AJAX(XMLHttp)增加更好的交互性。
我一直认为:轻量、简洁、高效 才是硬道理。后面阐述我对Java Web开发的理解和构想。
2. Web开发框架层次概述
从上到下,Web开发框架的层次如下:
(1) HTML, javascript, CSS等页面资源。
(2) 页面模板层。
如JSP, Freemarker, Velocity, XSL,fastm等。用来生成HTML, javascript, CSS等页面资源。
(3) Web框架。把HTTP Request调度分派到对应的Service Entry。
(4) Business Logic.
(5) O/R Mapping.
(6) JDBC
(7) DB
根据我的经验,一个典型的Web应用中的代码比例如下:
页面逻辑约占 50%,商业逻辑约占30%, O/R 约占20%。
但事实上,页面却是最不受重视的部分,从来都被认为是脏活,累活,杂活。典型的开发过程通常是这样:
页面设计人员迅速的用Dreamweaver等生成一堆文本杂乱无章的页面,然后交给JSP程序员加入更加杂乱无章的Java代码和Taglib。
当页面布局风格需要改变的时候,页面设计人员用Dreamweaver等生成一堆新的页面。JSP程序员再重新加入更加杂乱无章的Java代码Taglib。
至于页面中的脚本逻辑调试,更是一门精深的工夫了。
根据社会规则,通常来说,工作内容越轻松,收入越高;工作内容越脏月累,收入越低;Web开发也是如此:做着最脏最累的活的页面程序员,工资一般比不上后台业务逻辑程序员。
开发框架通常会带来这样的结果:让简单的东西,变得更简单;让复杂的东西,变得更复杂。
这其中的原因在于:
一般来说,一个应用中简单重复的东西占80%,复杂特殊的东西占20%。
简单重复的东西很容易摸清规律,进行包装,通用化。但是,在包装的同时,经常就阻挡住了底层的一些灵活强大的控制能力。在复杂特殊的需求中,确实又需要这些底层控制能力,那么为了绕开框架的限制,付出的努力要比不用框架 大得多。
打个比方,一个比较极端的例子。编译语言比汇编语言的开发效率高很多,但是却无法直接操作寄存器。当需要在编译语言中操作寄存器的时候,就非常的痛苦。比如Java,也许需要JNI,写C代码,还要在C代码里面嵌入汇编。编译、连接都很麻烦。
所以,一个框架的开发效率,就在于这个80%简单 与 20%复杂之间的平衡。
假如,不用框架来开发,简单的80%要消耗 80个资源数,复杂的20%要消耗20个资源数,总资源数是100;使用了某个框架,简单的80%只要消耗10个资源数,复杂的20%要消耗40个资源数,总资源数是50。那么,我们说,这个开发框架是有效率的。
我的思路是,同时应对复杂和简单。当然,为了应对复杂,简单的东西可能就应对得不那么好。比如,做这样一个开发框架,简单的80%要消耗20个资源数,复杂的20%要消耗10个资源数,总资源数是30。
这种开发框架是有可能实现的。而且是很有意义的。尤其是在复杂部分的比例提高的时候。越复杂的系统,这种开发框架就越有意义。
后面的关于Web各层开发的论述,主要就按照这个“应对复杂、让复杂更简单”的思路展开。
3.页面资源
也许有人会说,页面资源,不就是HTML吗?太简单,太低极了,没劲。Dreamweaver、Frontpage多简单阿。随便找个人来用就可以了。文本内容乱糟糟不要紧,浏览器里面显示出来的效果好看就行。要增加炫的、酷的动画效果,那就写javascript呗。写在HTML里面,看看在IE里面能不能运行就可以了呗。
这也正是大多数公司开发页面资源的方式。因为页面的需求变化是最多、最快的,而页面的制作成本很低,人们不愿意在上面投入更多的资源。
我的看法是,万丈高楼平地起。应用程序的每一个部分都应该完善管理,结构优美。越是需求变化多的地方,越是脏乱差的地方,越应该加大力度处理好。
页面结构方面,Javaeye论坛的Dlee做了很多工作。
我再说一下把页面的 structure、presentation 和 behavior 分离开的意义。一般的人很容易理解把 structure 和 presentation 分离的意义,但是对于为什么需要把 presentation 和 behavior 分离开不是很清楚。
这三部分分离开,页面开发才有可能实现真正的重用,从而最终降低开发和维护的工作量。JS 代码是可以做自动测试的,使用 JsUnit 来做。Web 表示层代码测试困难是公认的,直到今天所有介绍 TDD 的经典教材也没有提出一个好方法。所以我问过一些朋友,都是倾向于不对表示层的代码做自动测试。为什么不做自动测试?没有重用价值的代码值得做自动测试吗?而且我们以前有个误区,认为如果做自动测试,表示层的所有东西都需要测试,其实这个想法是错误的。我们需要做自动测试的仅仅是 behavior 这一部分。以前的测试为什么很困难?就是因为 presentation 和 behavior 没有分离开,我们在 JS 代码中直接操作页面的样式(直接设置元素的 style)。我们不应该再这样做下去,我们应该把 presentation 的工作完全交给 CSS 来做。实现 presentation 和 behavior 的分离有两种方法,通过改变元素的 id 或者使用更通用的方法,通过改变元素的 className。而关于这个 id 或者这个 className 具体的样式在外部的 CSS 文件中设置。JS 文件可以生成新的 structure(createElement,etc.),但是不应该直接改变元素的 style。改变了 style,一切效果你都需要用眼睛看到了才算测试成功,这哪里可以做自动测试?而且假如用户对这个 style 不满意,你还需要去修改 JS 代码。你如果只改变元素的 id 或者 className,做自动测试就要容易得多,你只需要测试最终这个元素的 id 或者 className 是否变成了期望的值。而最终的样式是不是也是你期望的,那是 CSS 文件保证的事情,这只需要在第一次开发 CSS 的时候做一下人工测试就足够了。而这样以来,CSS 文件可以由美工来维护,他完全不需要知道 JS 是什么东西。界面程序员可以去做一些更加重要的事情。
所以在这里我们看到,把 presentation 和 behavior 彻底分离开是做 Web 表示层代码自动测试的关键。把这两部分分离开以后,自动测试的难题就迎刃而解了。再强调一下,只有 behavior 有可能做自动测试,presentation 是不需要也不大可能做自动测试的。
相关资料:
http://www.onlinetools.org/articles/unobtrusivejavascript/cssjsseparation.html
从上面的Dlee的论述和给出的资料。可以看出,页面资源分为三部分:
(1) XHTML。结构,Structure。
XHTML里面的Tag部分只应该包括 <ul> <table> <p> <div><span>等结构布局Tag,或者<strong><emphasis>表示语义的Tag。
XHTML里面不应该包括风格信息,比如字体、颜色、大小、粗细等,也不应该包括<font> <b> <i> <h> 等字体信息。
XHTML里面不应该包括javascript的定义和调用。
(2) javascript。行为,behavior。
JavaScritp应该存在于一个独立于XHTML文件的独立文件中。这样可以做自动化单元测试。javascript应该只改变HTML DOM的结构和内容,而不应该改变它的风格。
(3) CSS。style,风格。或者说,Presentation,表现。
前面说了,XHTML里面不应该包括javascript的调用。那么,XHTML的元素是如何javascript事件绑定起来?就是在CSS里面指定的。
当然,众所周知,CSS的本职工作是处理页面风格。
页面资源方面,我完全认同Dlee的观点。从技术和资源积累的长远目标看来,这方面的初期投入的回报将是非常丰厚的。
即使将来HTML消亡了,进入了XAML, XUL, RSS时代,这些结构清晰的各部分,重用的可能性都非常巨大。javascript + CSS + XML UI的这种经典设计思路,将留存很久。混杂成一团的HTML的命运只能是全盘被抛弃。
4.页面模板层
页面模板层是指Server端运行的用来生成HTML(或javascript,CSS)的Server Side Template Engine。
这一层也是著名的脏乱差楼层。著名的HTML的Java代码污染事件,就发生在这个楼层。不仅JSP有这个问题,其他的template, 如freemarker, velocity, tapestry等含有逻辑的脚本,都不同程度上有HTML的Script Logic污染问题。
Dlee的做法很美。直接就不要页面模板层,不用Server Side Template Engine。直接用javascript更改HTML DOM的结构、内容、数据。同时,会用到少量的浏览器端XSL。
这样带来的结果,Template就是很干净纯粹的HTML,不含有任何Server Side Script。这个效果,和Servier Side Template 的 Jivan,XMLC达到的一样。只是一个是在浏览器端执行,一个是在Server端执行。
我研究比较了几乎所有的Server Side Template Engine,力图采众家之长,避众家之短,写了一个Server Side Template Engine — fastm, 能够最优雅方便的实现页面模板层。关于fastm,我的Blog上有不少文章论述。
我的Blog,里面专门有个fastm 分类。
http://blog.csdn.net/buaawhl
http://buaawhl.blogdriver.com
Fastm发布在java.net上。
https://fastm.dev.java.net
我仍然对Server Side Template Engine持肯定态度。基于如下原因:
(1) javascript代码量大、文件多的时候,不容易管理,不容易进行语法检查,不容易跟踪调试。
这里有人会争辩,Server Side Template Engine也用到了很多脚本阿,比如Freemarker, Velocity, 而且嵌在HTML中,怎么管理,怎么调试?即使是JSP,也是Java Code嵌在HTML里面,怎么管理,怎么调试?
这里我要说,Jivan, XMLC, fastm,Wicket等Template Engine的逻辑都是在Java Code里面。
(2) 用javascript生成文本内容,搜索引擎不友好。
一般的网络蜘蛛程序,只根据URL获取HTML文本,搜索里面的文本内容,而不会执行里面的javascript脚本。
(3) javascript代码重用还是有些局限
比如,有两个HTML文件,一个是Table布局,一个是List布局。
我有同样的一批数据,要在这两种布局中显示。
这时候,就要给这两个HTML分别写两套javascript。这里面的DOM层次,元素,属性都不同,再怎么定义ID,Class,也无法用完全相同的一套javascript处理。
这里有人会争辩,Server Side Template Engine也无法做到。别说JSP, Velocity, Freemarker等要在两套HTML里面嵌入相同的代码,就是Jivan, XMLC, Wicket也要分别写不同的两套Java Code,因为它们的XML DOM Node / Model View (Table, List) 都是不同的。
这里我要说。fastm可以做到只用一套代码逻辑。而且只有fastm可以。fastm的代码重用率是最高的。
关于Ajax(XMLHttp),我的意见是必要时才用,而且最好采用粗粒度的用法 — javascript发出一个URL请求,返回一整段HTML,直接替换到页面的某一块,而不是用javascript来做这样的把数据填充到HTML DOM中。如果你直接在浏览器里面输入那个URL,也可以获取那整段的HTML内容。
典型的应用场合是Portal。Portal页面的每个Portlet都含有这样的 Ajax(XMLHttp) javascript代码 — 发出一个Portlet URL请求,返回一整段Portlet的内容,直接替换当前的Portlet块。
这样做的好处是:
(1) 减少javascript代码的量和复杂度。
(2) 搜索引擎友好。网络蜘蛛程序可以辨别javascript中的URL,并根据这个URL,获取整段处理好的HTML文本,进行内容搜索。
有人可能会争辩:如果URL请求返回的是XML数据,不是整段处理好的HTML,搜索引擎也可以进行内容搜索。
这点我同意。前提是XML数据的内容是足够连贯的,而不是散落的。比如,你返回的XML数据是“中国”。这个“中国”要放在HTML中的一个{country}位置,{country}足球。这个时候,结果HTML的内容含有“中国足球”。而XML数据中只含有“中国”。如果用户用“中国足球”作为关键字来搜索,就找不到这个URL。
从前面给出的fastm资料的连接中,可以得知。如同Jivan, XMLC, Wicket一样,fastm的template里面不含有逻辑,所有的逻辑都写在Java里面。
有人会争辩说:页面逻辑写在Java里面,我改变了页面逻辑,还需要重新编译。这也太不方便了。Velocity, Freemarker, JSP就不用重新编译。
这里我的看法是:业务逻辑代码改变了,不也需要重新编译吗?页面逻辑就不是逻辑了吗?HTML里面的脚本怎么语法检查、跟踪调试?业务逻辑需要语法检查、跟踪调试,页面逻辑就不需要语法检查、跟踪调试了吗?
对方可能会说:在我的应用中,页面逻辑的改动需求非常频繁,而且这些页面逻辑非常简单,不需要语法检查、跟踪调试。
这里我的意见是:
(1) 那就使用JSP, Velocity, Freemarker等脚本。
(2) fastm, Jivan, XMLC, Wicket的Java代码部分也可以写在脚本里面,比如,Server Side javascript, Jython(Python), Groovy, Bean Shell 等脚本语言都可以很方便的和Java相互调用。
fastm的生命周期将很长。
HTML, XUL, XAML都是,或将是可以在浏览器或可视化编辑工具里面显示的XML UI定义语言。Microsoft Office的Word, Excel, Powerpoint等格式都提供了相应的XML格式。这些XML文件都可以在Office里面显示,并编辑。
Adobe公司也提供了PDF的XML格式 — XDP。可以在Adobe Designer里面显示并编辑。
由于fastm是Designer Friendly的XML UI所见即所得的模板技术。这方面具有很大的潜力。
根本不需要第三方花大力气专门做个IDE,来显示自定义的Tag。目标文件格式提供商自己的阅读编辑工具就可以直接用了,而且效果就是运行后产生的结果文件的效果。
即使没有可视化要求的场合。比如,Web Service需要的XML数据。fastm同样有用武之地。比如,
<!– BEGIN DYNAMIC: users –>
<user>
<name>{name}</name>
<address>{name}</address>
</user>
<!– END DYNAMIC: users –>
可以很容易的把一个Java Object List转化为XML数据。
另外,我不得不承认。浏览器端的javascript的页面逻辑,可移植性要高于Server Side Template Engine。因为Server Side Template Engine通常是特定语言相关的。
目前fastm是用Java实现的。由于实现很简单,移植到其它的语言,也很简单。如果是移植到Python, Ruby等动态解释语言,那就更简单了。我是有这个考虑,因为Zope, Ruby on Rails 的模板还是Logic 和 HTML混杂的,fastm这个思路有很大的用武之地。
前面讲了这么多。清理了两层有名的脏乱差的老大难的楼层 — 页面资源层和页面模板层。让这两层变得和下面的楼层同样的优雅、清洁。
下面该讲到Web框架层了。在向下讲之前,由于前面提到了脚本,我想先插入一段关于“可配置”、“可编程”、“可热部署”、“脚本逻辑 vs XML Tag逻辑”的话题。把这个人们比较关心、讨论比较多的话题,先讲清楚。
5.可配置、可编程、可热部署、脚本逻辑 vs XML Tag逻辑
由于Java是编译语言,人们通常把变化的参数部分抽取出来,放到配置文件中。
这些配置文件通常是XML文件。这很好,没什么问题。XML很适合用来表达数据结构。
但是,对于某一种技术的狂热,通常引起对这种技术的过度使用,或者误用。
人们开始觉得,XML能够表达一切东西,包括for, if, else等逻辑。这方面的典型例子有 Workflow XML Definition,Logic TagLib, XSL Logic Tag等。
这点我不敢苟同。我的看法是,XML不适合表达逻辑,XML表达逻辑非常蹩脚。XML表达逻辑相当于自定义一门XML格式的脚本语言。
比如,Logic Tablib,很难自然的支持 if else, switch。只能蹩脚地支持一堆 <logic:if> <logic:ifNot> <logic:exists> <logic:notExists> <logic:ifNull> <logic:notNull>。
(注,好久没有接触过Taglib了。这些Tag Name都是凭以前的使用印象写的,也许名字不对,但表达这些意思的TagLib都还是有的)
如果要表达if () else if() else 就更蹩脚了。要进行非常麻烦的嵌套。
再比如,XSL 支持if, else 也非常蹩脚。非要多出来一个层次才行。
<xsl:choose>
<xsl:when test=”…”>
…. If ….
</xsl:when>
<xsl:otherwise>
… else …
</xsl:otherwise>
</xsl:choose>
同样,如果要表达if () else if() else 就更蹩脚了。
<xsl:choose>
<xsl:when test=”…”>
…. If ….
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test=”…”>
…. If ….
</xsl:when>
<xsl:otherwise>
… else …
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
可以看到,XML Tag 表达逻辑,非常麻烦,可读性很差,完全是一种误用,没有半点优势。当然,逻辑简单的情况下,还是可以接受的。
有人会说:XML表达逻辑,可以免编译阿。
那么我说:语法检查呢,跟踪调试呢?
对方说:只是一些简单的逻辑,不需要语法检查、跟踪调试。
我说:如果只是为了免编译,前面列出的那么多的解释执行的脚本语言更适合。XML表达的逻辑,比Java等编译语言还要麻烦很多,而脚本语言比Java等编译语言简洁多了,可读性非常好,而且脚本语言和Java语言有很好的交互性,可以相互调用。重用、结构方面都具有优势。
有人会举出Spring IoC为例子,说:你看,Spring IoC的配置文件不都是XML格式吗?
我说:
(1) Spring IoC的配置文件基本都是属性设置,Bean ID声明。没有逻辑。
(2) 我也不是很赞同Spring IoC在XML配置文件里面引用Java类的做法。这方面,其它的容器如 Pico, Nano都支持多种配置方式,其中包括了不少脚本方式。我觉得,在脚本里面定义生成Java Object,比在XML中要好。当然,Web.xml里面也引用了Java Class名字。但那是非常简单的情况。没有嵌套引用、属性赋值、构造参数等复杂的定义方式。XML适合描述一些通用的资源、数据、结构。比如,HTML, XUL, XAML,RSS就是XML用的恰当的例子。
所以,我的基本观点是这样。
(1) 纯数据,不用说,应该定义在XML中。
(2) 如果是系统中一些Java Object要用到的基本属性。比如,连接池大小等。定义在properties, XML, Script中都可以。如果定义中没有出现具体的Java Class名,倾向于定义在properties, XML文件中。如果出现了具体的Java Class名,倾向于定义在Script中。这个界限不那么明显,两者皆可。
(3) 复杂结构的Java Bean的构造生成,那是肯定会出现具体的Java Class名,应该定义在Script中。
关于“可配置 vs 可编程”,有一点要明确:只要是可编程的,一定是可配置的。但如果是可配置的,却不一定是可编程的。
这里的可编程,是指框架给程序员提供了API;可配置,是指框架给程序员提供了配置文件的格式写法。
“可编程”一定是“可配置”的。
(1) 用户至少可以自己定义配置文件,读取参数,调用API。
(2) 有那么多的解释脚本可以直接和Java互操作,完全可以直接用来当作配置文件,定义参数。
“可配置” 却不一定“可编程”的。
如果框架只给你提供了配置方式,而没有API,那意味着,你只能进行参数的静态配置。很难在动态期间改变这些参数了。你总不能尝试着用代码去改变配置文件的内容吧?即使你改动了,如果框架不进行文件的时间戳检查,就是一开始装载进来,就不再检查更改了,你不就一点办法都没有了吗?
比如,Struts Tiles的XML定义,你只能静态配置,你想在运行期间改变布局,没有办法。Site Mesh也是如此。而我们可以在运行期间任意操作XML DOM Node,别说布局了,任何东西都可以改变。
所以,一个框架首要注重的是提供API,而不是提供配置方式。这是一个重要的原则。
讨论完了“可编程”、“可配置”问题,我们来看“热部署”问题。
XML配置文件、脚本文件支持“热部署”当然要比编译语言程序的热部署容易得多。只要解释执行前,检查一下时间戳就可以了。要注意的问题,只是做好测试,因为没有编译期的语法检查。
不过,Java程序也是可以“热部署”的。只是稍微麻烦一点。典型的例子是JSP, EJB Jar等。JSP修改之后,会自动编译执行;EJB Jar丢到EJB Container里面,会被检测到并装载到JNDI命名空间。
编译语言Java程序的热部署的一个可能的技术难点是,Class或者Jar已经存在,如何监测到Class或者Jar的更改,并装载这个新版本,替换旧版本。
这个问题我具体没有研究过。从道理上讲,应该在Class Loader上下功夫。如果需要,可以参阅开源EJB Container的相关实现部分。Java还有一种“Hot Swap”技术,专门解决这个问题,可以搜索查阅一下。
这段小插曲,就到这里。下面讨论Web框架。
6.Web框架
Web框架层是一个清洁的楼层。很多优秀的程序员在这一层大展身手,做出了很多好作品。我感觉不错的有Spring MVC, Web Work。
对于Web应用来说,Web框架层是最重要的一层。SOA、Semantic Web等效果都要在这一层实现。
首先,我们来讨论,框架的编程结构。
我的Blog中有一篇《Java Web框架综述》的文章。讲解了一些流行的Web框架的编程结构,很多重复的内容不再赘述。
http://blog.csdn.net/buaawhl
Java Web框架综述
http://blog.csdn.net/buaawhl/archive/2004/12/21/224069.aspx
Spring MVC的编程接口是最清晰的。大多数简单情况下,Web Work的用法是最简单有效的,编程结构比较特殊,可以说具有一定的变革意义。
Spring MVC的Controller接口相当于Struts Action,也具有Request, Response两个参数,虽然编程接口非常清晰优雅,但是本质上没有什么变化。
WebWork的Action则失去了Controller的身份,只相当于formBean的身份,或者说相当于ActionBean的身份。WebWork Action不具有Request, Response两个参数,它只具有属性,并通过属性Setter获取HTTP Request的参数,通过属性getter把结果数据输出到HTTP Response。
可以说,WebWork的这个把握是相当到位的。95%以上的情况下,程序员是不需要Request, Response参数的。当需要这些参数的时候,WebWork并没有挡住路,可以通过实现RequestAware,ResponseAware等接口来获取,或者通过一个Thread Local获取。这种情况下,编程结构的约定,就不那么清晰了。
我从Canonical的帖子和Blog受到了很多启发。
http://canonical.blogdriver.com
jsplet:对Model 2模式的批判
http://canonical.blogdriver.com/canonical/591479.html
jsplet与webwork的概念对比
http://canonical.blogdriver.com/canonical/594671.html
从级列理论看MVC架构
http://canonical.blogdriver.com/canonical/579747.html
从Canonical的文章可以看出。JSPLet用JSP文件作为Dispatcher,然后在JSP里面注册并调用对应的Object。这个寻访Object的过程,完全是根据丰富的URL定义来做的。URL里面包括Object Scope, Object Name, Method Name, Method Parameters,天生就对事件机制有良好的支持。
Zope的一些做法也有异曲同工之妙。
Zope Object Publishing
http://www.zope.org/Documentation/Books/ZDG/current/ObjectPublishing.stx
http://www.plope.com/Books/2_7Edition/ZopeArchitecture.stx#2-3
这种通过URL获取Published Object的服务的思路,是一种实现SOA效果的有效思路。
我们首先来看Web Service的现状。目前Web Service主要分为两大阵营。SOAP和REST。关于REST,请参阅
http://www.xfront.com/REST-Web-Services.html
关于SOAP和REST的比较、互操作,网上有很多文章。如果需要请搜索查阅。
我个人比较倾向于REST风格的Web Service。
因为SOAP是一门固定的协议,如果用SOAP来编写Web Service程序,需要一个SOAP协议的解析库 ,也许还需要一些专门的“SOAP 数据 — 编程语言”映射库,如同CORBA IDL的多语言映射一样。如果你要让自己的Web应用支持SOAP,你需要把发布的服务对象、方法都包装为SOAP协议,这需要一些编程语言相关的数据结构的映射工作。
REST则只是一种风格,而不是一个协议。中心思想是简单的通过丰富的URI定义 (如XLink + XPointer等) 获取资源。如果你要让自己的Web应用支持REST,那么很简单,只要在URI上下功夫就可以了,比如,多增加一个参数format=REST,在程序中多增加一种XML输出格式就可以了。(从道理上来说,SOAP也可以这么实现,但SOAP的输入和输出都要遵守SOAP协议,SOAP的输入参数一般都包装在SOAP信封里面)
关于HTTP Get和Post,我表述一下自己的看法。
我认为,Web的精髓在于Get,而不是Post,在于获取服务器的输出,而不是输入到服务器。即,Web的精髓在于以小搏大,四两拨千斤。最经典的用法就是用一个URL,获取一个长篇的文本内容,这个内容里面充满了其他更多的资源连接。这也是超文本连接HTML发明的初衷。
至于HTTP Post,则是这上面的一个扩展。B/S结构如此流行,很多应用都要转移到Web上面,怎么办,应用总是交互的,总要让用户输入数据吧,就增加了HTTP Post协议。
HTTP Get经典、简单、有效。可以用丰富的URI定义把这个优势发挥到极致。这个实现也比较简单、优雅。就不多说了。主要的难点在于HTTP Post。下面的讨论主要应对“HTTP Post”这个复杂现象。
HTTP Post从来就不让人们满意。当输入逻辑复杂到一定程度,表单数据的繁杂、凌乱、散落,到了服务器端很难组织起来。输入方面B/S结构确实和C/S结构难以匹敌。于是,出现了XMLHttp,能够把参数在浏览器里面组织成为一个统一的XML数据结构(或其他格式),发送到服务器端,一次解析出来。SOAP做这个方面,更是拿手好戏。所以,很多XMLHttp程序直接采用SOAP作为通信协议。而REST风格的HTTP Post则和HTML form Post没有太大的本质区别。
REST在HTTP Get方面更胜一筹,SOAP在HTTP Post方面更胜一筹。可以根据Web应用的特点,根据HTTP Get / HTTP Post 页面的比例,选择适合的技术。
我们再进一步分析HTTP Post的数据内容。HTTP Post的数据,可能包含三种类型:
(1) 需要存档在服务器的数据
比如,用户注册时候,输入的基本信息,用户名、密码、电子邮件等。这些信息要存放到服务器的数据库。
对于这种基本信息,HTTP Post,XMLHttp,SOAP处理起来,难度都不大,没有很大区别。
B2B的数据交换,也属于这个类别。用何种技术区别不大。一般采用SOAP,因为SOAP是一种流行的标准协议。
(2) 服务调用参数
比如,用户进行复合条件查询的时候,输入的查询条件。这个时候,HTTP Post处理起来就非常蹩脚。而XMLHttp,SOAP则具有很大的优势。可以把复杂的查询条件很好组织成XML数据,发送到服务器端统一处理。SOAP里面甚至可以定义对象名、方法名等详细的调用信息。
(3) 指令
这种情况比较少见。上面的参数类别中提到的“对象名、方法名等详细的调用信息”,和这个指令类别有些交叉。
假如一个SOAP调用方法里面的参数也是一个自定义的对象,这个自定义对象的属性数据在SOAP信息中进行了定义。到了服务器端之后,服务端程序首先调用这个自定义参数的构造函数,生成这个参数对象,然后调用对应的服务对象,把这个参数传给服务。这个过程可以看作是一个顺序指令:[1]构造参数[2]调用服务。
这只是最简单的情况。而目前的Web Service一般也就支持到这个程度。
我的看法是,一不做,而不休。既然都把调用信息定义到这个程度了,不如做的更彻底一些,全面完善的支持指令。这个指令则意味着逻辑。前面讲过了,我不赞成用XML Tag表示逻辑,而赞成脚本。这里比较适合的脚本是javascript,因为javascript比较通用,客户端、服务器端都可以解释执行。注意,这里和一般的做法正好相反:一般的Web应用总是把javascript从服务器传到浏览器里面执行,而这里是把javascript在浏览器里组织好,发给服务器端处理;这个javascript将会在服务器端执行,调用服务器端的对象。举个SOAP含有javascript指令的例子 (只是示意,非标准格式) :
<soap envelope>
<XML Data>
<a>
<b>12</b>
</a>
<c>
<d>21</d>
</c>
<e>
<e>16</e>
</e>
</XML Data>
<script>
final_result = default;
result1 = service1.service(a.b);
if(result1.ok){
result2 = service2.service(c.d);
if(result2.ok)
final_result = service3.service(e.f);
}
</script>
< /soap envelope >
这个好处是:
[1] 发布了更多的基本Service。给客户提供了更大的灵活度。
比如,这里就发布了3个Service。由用户自己组织逻辑。
按照传统的做法,上述流程将整个包装在服务器端执行。发布给用户的Service只有最外面的一个Service,而且高度耦合(if, else, if, else流程hard code在服务器端),不灵活,不通用。
这里的方法,就可以让客户端随意组织service1, service2, service3的调用顺序和方式。
[2] 减少了通信次数。
假如这段Script在客户端执行,那么和服务器要进行3次通信。
传统Web的权限控制一般在URL级别,这种script -> server方式的权限控制则要在对象级别、方法级别、Code片断级别了,复杂很多,也许要大量应用Java的Code权限认证机制。
以上展开讨论了 Web Service, HTTP Get/Post。下面我们回到Web框架层。
前面说了,JSPLet给了我很大的启发。很多思路可以借鉴。
当然,我并不赞成用JSP作Dispatcher, Controller。(1) 因为JSP要编译成Servlet,而Servlet是Web Server管理的比较昂贵的资源。一个Web系统中JSP达到几千个,就会遇到性能瓶颈。(2) JSP中的代码重用很成问题。一般只能通过include file的方式。
可以借鉴的思路。(1) JSPLet 的入口是JSP文件,这一步的URL到处理程序的映射是Servlet/JSP Container自然支持的。这是免配置的。(2) 丰富的URL参数定义,良好的对象方法寻址能力。
我开发的开源Web框架lightweb,将具备如下特性:
(1) 支持两个层次的编程接口。
interface Action { void service(request, response, servletContext); }
这个Action比Struts Action, Spring MVC Controller高一个级别。相当于Dispatcher, 相当于JSPLet的JSP控制文件。这个用来做最外层的入口控制。
同时,也支持简单的JavaBean.method的直接调用。相当于WebWork Action,JSPLet Registered Object。这个用来做具体的事情。
(2) 支持丰富的对象寻址URI,比如http://my.com/myProject/myModule/myEntry.action?object=calculator&method=add&p1=1&p2=3
这表示要通过 myEntry.acion这个入口,调用caculator.add(1, 2)方法。
如果用URL Rewriter可以美化为
http://my.com/myProject/myModule/myEntry/calculator/add/1/3
看起来就很象XLink + XPointer了。
(3) 免配置。或者说极少的配置。
框架根据一定的匹配准则,把myModule/myEntry.action映射到
com.mycompany.mymodule.MyEntryAction 这个类的service方法。
这个service方法负责根据object, method的名字,寻找到对应的bean,并根据参数进行属性设置验证,并执行对应的bean.method。然后,把这个bean作为Model和template结合,输出结果。
同样,template的获取也是根据一定的匹配准则,根据myModule/myEntry找到
Mymodule/myentry.html 或者Mymodule/myentry/calculator.html。
这样的lightweb就能够同时对应简单和复杂。复杂控制的需求交给Action接口来做,简单的一般具体任务交给普通Java Bean去做。
Web框架层可以做的非常复杂,可以做的非常简单。Lightweb的目标,就是分成多个简单的部分;各部分合起来就能够完成从非常简单到非常复杂的需求。
接下来,我们来看O/R。
7.O/R
Hibernate, EJB Entity Bean产品,JDO产品,iBatis是比较流行的几种O/R Mapping Framework。
我做的一些工作中,经常涉及到复杂的优化过的native SQL,并且涉及到大量的批量复杂逻辑处理,现有的O/R框架都不能满足功能和性能要求。
我做出这样一个lightor框架,思路借鉴了Martin Fowler的《企业架构模式》里面讲述的一些O/R的Row Mapper, Column Mapper等概念。
最经典的用法是:
ResultSet rs = ps.executeQuery( a long complex native sql);
//will return a lot of records
A a = new A();
B b = new B();
IMapper aMapper = MapperService.getMapper(A.class);
IMapper bMapper = MapperService.getMapper(B.class);
While(rs.next()){
aMapper.populate(a, rs);
bMapper.populate(b, rs);
businessLogic(a, b);
}
可以看到,Lightor不需要一下子把所有纪录都放到一个Object List里面。完全可以随取随用。整个过程中,a, b只有一份,极大的节省了空间、时间,也极大的提高了开发效率,减少了重复代码。
没有任何一个其它O/R能够支持这种用法。这里面,lightor的mapper的populate方法需要ResultSet参数。一般的O/R不屑于这么做的,别说ResultSet,连Connection都想包装起来不给你看。
Lightor的设计思路也是同时应对简单和复杂。Lightor的Mapper实体部分是自动生成代码。类似于JDO的静态Enhance。不同的是,JDO静态Enhance直接修改bean class。而Lightor则不动原有的bean,只是多生成了对应的Mapper Source/Class。这种方式是最利于跟踪调试的。至于发布部署,和JDO的情况差不多,不如Hibernate的动态代码增强。
这里我很羡慕Python, Ruby等动态解释语言的
这一层我主要关注的是性能,缓存策略等等,而不是简便。我觉得,一个应用系统的瓶颈主要存在于O/R, DB层。不应该单纯为了追求OO结构的优雅,或者编程的方便,而牺牲了一些可能优化的地方。
关于Lightor的缓存策略, 我的Blog上有几篇文章。
http://blog.csdn.net/buaawhl
数据库对象的缓存策略
http://blog.csdn.net/buaawhl/archive/2004/12/21/224184.aspx
分页 & QueryKey & 定长预取
http://blog.csdn.net/buaawhl/archive/2005/01/08/245005.aspx
8.总结
我理想中的Web开发架构是这样的:
开发速度快,运行速度快,结构清晰优雅。
具体到每一层。
Web框架层主要追求 开发速度快。
O/R层主要追求 运行速度快。
页面资源层和页面模板层主要追求 结构清晰优雅。