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做了很多工作。
(1) 在 2005 年我们如何写 JavaScript http://forum.javaeye.com/viewtopic.php?t=12973 (2)使用 Unordered Lists 制作的下拉菜单和树 http://forum.javaeye.com/viewtopic.php?t=12995 从上面的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层主要追求 运行速度快。 页面资源层和页面模板层主要追求 结构清晰优雅。 |