一个小的WEB项目中的实现方法讨论 - 又一篇[原创]

一个小的WEB项目中的实现方法讨论是2004年10月的一篇帖子,给了我们这些初学者一个很好的提示。

该方法使用command模式,以一个入口servlet作为controller,然后根据request的参数servicetarget决定调用哪个modle做处理,然后把结果返回到viewtarget。是一个基本的MVC实现。其controller会根据service参数,使用类的动态加载机制Service service=(Service)serviceClass.newInstance()来实现类似脚本语言的eval()功能来构建动态变量。

深入的分析一下,就会发现以下几个问题:

问题1:随着请求的增加,类的动态加载会造成很大的性能开销,所以这里有必要采取对象池来缓存创建的对象。但这就造成了代码不够简明,加大了学习难度。

问题2:变量target是传给controller供回调时用的,目的是为了把所有页面的跳转都交给controller处理,但一旦modle中的处理出现异常,将不能再返回target指定的页面,这就需要modle给出一个新的返回页面,于是modle层偶尔也要参与页面跳转。

这些问题总觉让人觉得如鲠在喉,虽然做到了MVC的实现,但却不够优雅,甚至牵强。于是我参考了一些资料,结合了以前做c/s软件的思路,本着简化设计的原则,想了以下这种模式来实现系统的设计:

--------------------------------------------------
B/S=统一C的C/S,不要忽视C的处理能力
--------------------------------------------------
1:概念

我们都熟知的MVC理论中,M就是处理业务逻辑的,V就是控制表现的,C就是控制页面跳转的。

一定要这样实现吗?

如果全部都是动态页面,这样的实现是可行和严密的,但如果混杂了静态页面,里边的<a>元素直接定义一个超链接,这样V层直接控制了页面的流向。这就显得不够严密。

这里介绍的概念是:
M=只是处理业务逻辑,并以字符串方式输出最终处理结果。(因为M是V通过script经C验证后调用的,M返回的结果直接就会到了V。)
V=只是处理页面跳转,全部采用静态页面设计,以Ajax或动态加载脚本技术请求V并接收返回值,以脚本在页面处理得到的数据。
C=只是控制V访问M的权限,实现V到M的验证和连接。

更严格的分层如下:
Server层-C+M
Client层-V+Script

这样系统处理的流程就是:
请求V-C-M,返回M-V。

2:实现

以一个Filter充当Controller,将*.do的请求以url方式转向到其他servlet或jsp处理。转向前可做权限检测。

接受转向的servlet全部继承一个接口,此接口实现2种功能,1是verify请求来源是否为Controller,2是execute,并直接将处理结果out.print出来。

V直接得到处理后的结果,并以script表示。

3:优点

A.逻辑严密
所有页面跳转都是V控制的,C只是验证是否有权限访问后台的一个控制器,M根据请求参数做业务处理,并给回返回值。

B.资源耗费最小化
B.1:服务器资源耗费最小化
传统的MVC是通过把数据层层封装,传输到前台以<%=Object.getValue()%>显示,这种MVC是直接取到数据,并以javascript结合DOM在前台动态生成。
传统MVC执行数据库的R操作,服务器的流程是V-C-M-C-V,其中新建数据传输用的临时对象n个。
这种MVC的流程是V-C-M,新建数据传输用的临时对象0个,没有后边的C-V过程。(因为M直接输出结果字符串给V了,V请求的同时就得到了这个结果。M-V的过程不需要后台参与,V的合成是在客户端完成的。)
这样因为M和C都是常驻内存的servlet(C是filter,严格来说不能算是servlet,但也是常驻内存的,jsp本质就是servlet),所以只需要注意线程安全就足够了,线程池一般的容器也都提供,几乎不必使用对象池,对待频繁访问的数据,可以由受到频繁访问的M生成静态文本文件实现cache,节约了大量资源,也简化了编程复杂度。
B.2:网络传输耗费最小化
如果采用Ajax技术,可以实现只传输必要的更新数据,节约大量数据流量。详情请参考Ajax相关资料。

C.服务器平台无关性
因为html和javascript是平台无关的,而对后台的请求得到的都是字符串,我们就可以随意选择后台的运行环境,而不必重新设计V。方便的从jsp-asp-php或其他任何语言中选择。

D.降低学习难度
用这种模式开发WEB站点,jsp/asp/php/等语言,前台编写的难易程度都变成写静态页面的难度了,后台程序员只需要关注自己的后台性能即可。
不论前台还是后台的人员,什么Taglib,什么EL的都不用去看了,甚至连jsp都不需要去学习,只要前台会一点javascript就可以熟练的操作DOM,实现动态更新页面。而后台的程序员也只要控制好各个servlet的访问权限就足够。

E.人员分工合理化
通过使用模板字符替换的技术,可以实现由脚本将数据锚点的内容动态替换成从后台取到的数据,前台美工只需要知道调用哪个url会得到什么结果就可以了,让美工去处理页面的跳转和错误提示信息,这样美工和后台终于可以完全的分割开来。
--------------------------------------------------

正如本文所说,这个构想只是“小”WEB项目的实现方法。大型的企业级分布式应用,我还没接触过。不敢妄加想象。

传统的“推”模式只适合在以前智能终端的年代,那时的客户端处理能力有限,所以是胖Server,瘦Client。
后来客户机的性能逐渐有所提高了,但在WEB方面,Browser的功能还比较单一,所以“推”习惯了的依然在推,CGI把处理好的html推给Browser,Browser只要下载处理好的html,只负责显示就可以了。
到了现在,Browser方面脚本语言的应用早已成熟,我们可以用“拉”模式,仅取出需要的数据,然后在前台合成。实现瘦Server,胖Client,减轻Server的负担。

——不要忽视C的处理能力:C已经吃够现成饭了,不需要S做好了饭菜只管吃了,S只要提供原材料,让C吃自助餐,C会吃的更好!

以上所说由M返回的字符串,可以是一段script代码,也可以是一个xml。具体应用看需求了,如果不是必须,不推荐用xml,因为xml最终也要还原成元数据,倒不如直接写入script来的痛快。构建RSS的时候只需要单独写一个生成RSS的servlet即可。

感谢您看到这里,我刚步入WEB开发行业不久,所学有限,上面的实现方法虽已构思了一段日子,但仍难免考虑不周之处,希望各位朋友看到后能及时斧正。

我喜欢来J道,因为我喜欢Taoism,中国的国教其实是道教,道学是博大精深的。我也敬佩Banq大哥的精神,鄙视一些只是提出批判而没有给出我们其他同道任何有益提示的所谓高手。

祝大家早日得道~
[该贴被s79于2007年03月15日 03:30修改过]

更正:
上边说的C/S和B/S不够严密,因为B/S只能通过Client向Server发送请求,才会得到回应,Server永远是被动的。而真正的C/S服务器可以主动发送信息给客户端的。以这个打比喻的目的是:强调多让Client做些事情,Server只是做他必须做的。

同样“推”和“拉”的说法也不够严密,所有这些说法都应该理解为对“数据”的动作上。

(QQ:396686)

[该贴被s79于2007年03月16日 06:52修改过]

鼓励

这是一个比较轻松、紧凑的“小”WEB项目的实现方法,如果多出一个专门的Model业务层,或者提供以后M复杂以后可以单独开辟一个层的接口,那么拓展性就更好了。

有些问题想不通,又来请教了。

用上述的办法,就不必使用jsp文件,只剩下静态html的V。Browser请求到达M,M把处理结果返回Browser。Browser用自己的方式去动态处理并合成V,M和V的接口就是AjaxCall之类的技术,那么是否还有必要引入C层呢?

问题1:引入C,可以使用一个Servlet使用Command模式动态加载Class模块M来处理请求,保证了Servlet的唯一性。但动态加载是耗费资源的,不知道其他MVC框架怎么解决的呢?使用了Cache还是避免了动态加载?如果功能模块暂时不需要变更,是否可以import多个class,并根据参数用if来替代动态加载?

问题2:不引入C,只能每个模块M写成一个Servlet,但在业务逐渐复杂的时候,会出现数十个Servlet模块M,这样是否浪费资源?这种浪费和动态加载那个更有害?


关于各种模式举例对比如下,上述问题1和问题2分别对应以下的模式3和模式4,其中带下划线的过程在每个模式中是相同的:

假设有一个餐馆Server,来了个客人Browser,他们走到餐桌(一个页面)前,看了看菜谱,开始点菜(请求数据)。其中有三种角色MVC。
M-厨师,根据要求作菜。
V-传菜员,把菜配上餐具供客人Browser食用。
C-吧员,协调厨师和传菜员。

该餐馆在不同的经营模式下发生的事件分别是:

经营模式1:(Mode1)
传菜员V(JSP)听到客人喊的菜名(request)后,告诉(request传递)厨师M(class)做好饭菜,厨师做好后交给(结果传递)传菜员V,传菜员V将其配上餐具(将数据嵌入html),然后送到客人的餐桌,客人开吃(浏览)。(吧员C没有出现)

经营模式2:(Mode2)
吧员C(Servlet)听到客人喊的菜名(request)后,告诉(request传递)厨师M(Class)做好饭菜,厨师做好后交给(结果传递)吧员C,吧员C叫来(结果传递)传菜员V,传菜员V将其配上餐具(将数据嵌入html),然后送到客人的餐桌,客人开吃(浏览)。(三种角色都出现)

经营模式3:
吧员C(Servlet)听到客人喊的菜名(request)后,告诉(request传递)厨师M(Class)做好饭菜,厨师做好后交给(结果传递)客人,客人根据餐桌上的指示文字(Ajax)自己去找合适的餐具,开吃(浏览)。(V没有出现)

经营模式4:
厨师M(Servlet)听到客人喊的菜名(request)后,做好饭菜交给(结果传递)客人,客人根据餐桌上的指示文字(Ajax)自己去找餐具,开吃(浏览)。(V和C都没有出现)

你之所以存在是否"引入Controller"问题,因为你原来设计中没有专门的业务层,,所以我建议你还是设计一个Model业务层,因为不可能所有业务都放在Model中,按照Evans DDD,业务层分领域层和服务层。

Controller是Mediator模式实现,是封装前后台交互中介点,只要你存在后台业务层,就需要一个Controller来介入封装前后通讯。

struts等都是使用动态加载实现的,通过缓存提升性能,所以,你可以引入struts这个MVC框架,这与AJAX并不矛盾,见我另外一个帖子的回复:


http://www.jdon.com/jive/thread.jsp?forum=61&thread=31159

至于你的四个模式都是可行的,但是如果加上系统的可维护性和可拓展性这个软件设计的标准的化,经营模式2的三种角色都出现是比较好的方案。

当然,有人会说,在一个小系统中,C出现好像挺麻烦,能不能让其出现,但是在小系统时,编程人员感觉不到C的出现,但是以后需要拓展,可以找到这个C呢?

回答可以的,比如我设计的JdonFramework中,C我就隐去了,只要进行jdonframework.xml中models方面的配置就可以,无需编码,如果以后你的C比较复杂,还是可以自己写一个Struts的Action作为C的。

多谢Banq大哥的指点,“系统的可维护性和可拓展性”是我们搞软件的追求的目标之一,我以前有点晕只想着用最直接最明了的方式实现,给这个忽略了。

比如说吃东西,如果喝粥喝习惯了,拿来馒头就喝不下去了,这时候就必须引入一个中间的层,来判断一下“方法”再执行“吃”。

这也就是分层的最初目的吧。

比如病人吃不了东西,要通过注射葡萄糖来补充能量,那就又要引入“吃”之前的层了。

再次感谢您的回复,这样的交流是很愉快的。


“关于JdonFramework表现层合作、发展的建议”看过了,此文中提到了Dorado,他们起步较早,在这个领域里可以说是领先的,但给出的演示我看了看,觉得有一个误区:
javascript毕竟是运行在客户的浏览器上,内存的GC问题是很大的问题。所以应该尽最大可能减少代码数量,去掉花哨的功能,以保证长时间运行时不会变得臃肿。
以前我做HTA软件的时候也是把很多代码都放在一个文件里,在用户点击一个命令弹出子模块的操作窗口时,同时最小化父窗口,以实现GC,但Dorado的演示为了美观,把很多表单都显示在一个窗口里,这对用户的CPU和RAM都是一个考验。
也许有人说,“我开发的js脚本运行起来,感觉和从服务器拉过来jsp文件速度一样。”之类的话,我想说那是因为你的CPU够劲,毕竟是脚本语言执行起来比不上浏览器解释html那么快。至少目前的浏览器是这样。
另外Dorado的演示里有一些项目的关联动态显示和模拟滚动条的功能,这些功能不是必须的。数据关联背后隐藏的是数据不同步的隐患,事实上是用户操作产生的事件才会引发对后台数据的查询,而不是“实时”的。动态的显示改动的数据容易误导用户,以为这是实时的显示。
模拟滚动条实属多余,产生大量事件和对象,消耗的资源是最大也是最可以省略的功能。


另外我要试图纠正Java之父“曾经”的一句话:“浏览器差异导致javascript应用障碍。”

这个障碍或许曾经存在,但在现在,关于javascript的适用性其实我们不必过虑了,且不说目前及以后IE的市场占有率,目前及以后任何版本的浏览器都有解释javascript的功能。只要注意加一个“判断”,并不用IE提供的“取巧”的方法,就可以几乎兼容全部。

如果说老浏览器,那还不如考虑为了更老的显示器、去做一个字符型的站点了。或者干脆印刷成书面形式到处邮寄,哦,可能有些盲人也无法阅读你的信息……这就是尽善难求的道理,我们的步伐总是要前进的。而迈这一步的时机早在几年前就已经成熟了。

再试图纠正您的一句话:“而且javascript是非OO语言,如果实现太复杂界面和控制,维护性拓展性就是一个疑问。”

这个疑问也非应有,首先我们不应该实现“太”复杂的界面和控制,原因如以上对Dorado的评价。然后说说维护性、扩展性:当业务扩展的时候,后台要增添模块,并提供与前台的接口,前台也要增加一些和服务器的通讯接口,这确实比全部都从后台实现要多一个步骤,但好处是后台有更少的数据传输量、前台有更好的用户体验。实现了以上两点,多出来的这一个步骤也是划算的了。而且在“代码从简,逻辑清晰”的前提下,扩展和维护javascript并不是困难的。再进一步说,扩展并不是频繁的啊。

还有javascript的OO方面,不能说他是非OO语言。只是有些地方由于先天缺陷显得不很严密罢了。清参考:JavaScript:The World's Most Misunderstood Programming Language
当然不论它是什么样的语言,实现OO与否,都不影响它在表示层的灵活的应用。我个人使用js的时间很长,对它也很喜好,所以做一些简单工具的时候都是用的js,比如文件内容的替换啦,程序的安装文件拉等等,因为在hta应用是本地安全级别,js能做的事情更多。微软也曾推出一款工具tweakomatic,实现了hta文件中运用js实现数据库和注册表的操作,这是js的重量级操作了,一般不推荐这样实现。速度相当慢。

说js的篇幅比较多了,总之吧,在web的表现层应用js,只要把握从简的原则,不沉迷于js制造的花哨效果,利是绝对大于弊的。Ajax也是同样的道理。

新问题:
我现在在家做外包软件,不知道如果开发或加入开源团体的话,有利润点吗?呵呵,希望您能指点一下。我以前做买卖的,对钱比较敏感,别见笑。

s79关于适度使用AJAX我是非常赞同的,AJAX和一些MVC框架结合将是一个趋势(可以无缝结合),如AJAX+JSF(http://jboss.org/projects/jbossAjax4jsf) AJAX+Struts等等。

其实现在RIA(Rich Internet Applications)方向确实不是很明显,Flex和AJAX等等都正在竞争,尘埃未定...

至于Javascript开发是否可以OO,我们可以以OO方式来开发它,这些都依赖IDE工具。

关于RIA方面有兴趣可以查查这方面资料,如CSDN:
http://sd.csdn.net/subject/ria.htm

2006年FLEX 网站演示:
http://www.kingda.org/archives/kingda/2007/01/2006flex_1.html


[该贴被banq于2007年03月19日 15:40修改过]

>,不知道如果开发或加入开源团体的话,有利润点吗?
开源不代表免费,看看GPL协议。

javascript也可以oo的,ajax主要解决了异步推拉的问题。
flex宣传了很长时间,不过确实没有出现让别人眼前一亮的应用。

如果,Discuz变成jsp的论坛话就不会象今天的php discuz的论坛流行.因为,服务器的价格,技术的难度是否适合一般的用户,与简单易学的php是不能相提并论的.php有他平民化的客户群,jsp/java有他企业级的客户群,想在平民化中发展,jsp没有任何优势.

我打算用一个filter[ServiceGateway]做/service/*的拦截,避免对/service/中各模块的直接访问,然后此filter的init方法里读取一个properties文件中的配置数据,简单举例如:
v[1]=true,
s[1]=CRUD,
c[1]=SELECT id,title,datetime FROM news WHERE classid=? ORDER BY id DESC LIMIT ?,?|classid.i.1.1.5,m.i.0.0.0,n.i.1.1.0

这样前台调用
/service/?o=1&classid=5&m=20&n=10
就相当于要先根据v[1]作验证,v[1]=true所以要检查权限,检查通过后将参数
request.setAttribute("serviceConfig",c[1])
最后
RequestDispatcher requestDispather=request.getRequestDispatcher("/service/"+s[1]);
requestDispather.forward(request,response);
送到CRUD模块(一个Servlet或jsp)继续操作,这时CRUD根据配置
classid.i.1.1.5,m.i.0.0.x,n.i.1.1.x
读取p[1]=int classid,默认1,最小1,最大5;p[2]=int m,默认0,最小0,最大不限;p[3]=int n...插入
SELECT id,title,datetime FROM news WHERE classid=? ORDER BY id DESC LIMIT ?,?
执行并返回文本数据。

这样就可以通过修改properties文件实现不同简单的数据库操作。

而其他模块如登陆验证、文件上传等需要的参数就更少更简单了。

可以通过附加另外一个filter在Service模块执行前检查是否存在o=1&classid=2&m=20&n=10.js文件,如果有直接返回该文件,如果没有就执行Service模块CRUD,并获取其返回码,生成并返回该文件以实现Cache。这种js文件中都是元数据,与生成.html相比占用空间小,读取快。

不知道这种做法科学吗?是否算是Mediator的一种?

其实我最关心的问题是,究竟是只用一个Servlet作为controller,在其中动态加载Service Model class的开销大,还是每个Service Model做成一个Servlet然后用这个ServiceGateway做过滤的开销大呢?

因为每个Service Mode都必须返回一个字符串,前台使用ajax读取,所以并不存在jsp文件,也就少了很多servlet,如果采用一个Servlet作为Controller并动态加载其他类,那整个系统似乎只要有一个Servlet就够了。

附:这几天用上边的想法做了几个简单的功能,仅包括CRUD模块和Parameter的自动读取,测试结果还算满意,和以前用Bean封装并传递数据的比较如下:
旧方法:30条/页,第1页=53ms,第700页=100 ms。
新方法:30条/页,第1页=40ms,第700页=73 ms。-取到数据后装入StringBuilder然后统一out.write
新方法:30条/页,第1页=40ms,第700页=73 ms。-直接out.write

注:
·旧方法用的是statment,新方法用的是prepareStatment。
·旧方法=Mode1,jsp+javabean的方法。用getXXX和setXXX操作数据并放在ArrayList里。
·操作时间本来还有个DB消耗时间,但多数时候DB的消耗时间和页面的消耗时间是一样的,就没列出来。(因为在get connection之前和close之后的操作很少)
·但新方法1在输出大量字符的时候,比新方法2表现略好,平均要快5ms。

时间关系,所作测试有限,可以肯定这种办法要简单直接快速一些,因为对元数据没有封装。对这些元数据的处理将直接交给前台的javascript。

前台方面目前写好了
sendRequestAndGetResponse.js和putDataInTemplate.js
但是还有些地方需要完善,所以代码暂时就不发上来了。

待确定这条路是可行的,再花更多的时间去完善他,并拿出来与大家交流。

>如果采用一个Servlet作为Controller并动态加载其他类,那整个系统似乎只要有一个Servlet就够了

支持这个方案,简单,不要考虑动态加载的性能,因为你的设计就是小的Web项目,这些损耗不会很大。

你可以参考一些AJAX4JSF这类开源框架设计,吸取长处,祝你成功。

DWR AJAX比较简单,在struts中应用较多:
http://www.javaworld.com/javaworld/jw-06-2005/jw-0620-dwr.html

Ajax CRUD with Struts2 and Tibco GI:
http://www.theserverside.com/tt/articles/article.tss?l=AjaxCRUDStruts2&asrc=EM_NLN_1237110&uid=5225031

DWR:
http://getahead.org/dwr
[该贴被banq于2007年04月06日 12:23修改过]

作为行业用户,从目前使用的几个JAVA大系统的情况来看,这些系统的重点一般是实现联机事务处理,将各类业务数据采集到系统中,并按业务流程进行处理,对于后期数据应用分析功能,由于其业务需求多样化,往往不能完全满足我们的需求。由于各地的个性需求不尽相同,如果全部交软件公司或省级单位来实现,会造成开发工作量过大、开发周期过长,无法应付逐步产生并且不断变化的的新的需求、省级数据库负荷过重等问题,省级单位通常的做法是将数据分发到市级单位,由各市自己实现个性需求。但省以下单位的开发力量薄弱,要让其能用JAVA实现自己的个性应用,必须有使用尽量简单的开发框架支持。使用我的框架,对一般的数据应用需求(比如,对3张数据表联结后形成的一个大表进行通用查询,并且该大表有20个编码字段在显示时需要译码;或是让各级机构自行输入重点关注对象名单,只对相关数据进行统计显示),只需要编写一个JAVA类并通过FTP上传到服务器指定目录,全市及所属各县的业务部门,立即可通过浏览器查询数据处理结果,大大方便了技术水平较低的开发人员实现个性应用。这样,将数据应用系统的开发分为的框架开发层和应用开发层。框架开发层由数量少但技术水平较高的人来不断完善,主要实现通用功能,尽力减少应用开发人员需要掌握的技术数量和技术难度;应用开发层为省以下各单位的信息技术人员,数量较多,这些人可专注于个性需求应用的开发,不需要考虑过多的技术细节。有人会说,谁会愿意去做应用开发层这些没有挑战性的工作呢?但在JAVA大系统的使用机构中的实际情况是,真正愿意开发框架的人是很少的,因为开发框架需要掌握的技术多,难度大,在大家工资都一样的情况下,除了真正爱好技术的人,有谁会愿意做吃力不讨好的事呢?
当然,通过JSP也可实现,但JSP不是强类型的,本身不支持OO,复杂功能的还要靠JavaBean配合完成。

js是一个基于对象的语言,他的继承是基于prototype的.
我们可以以oo的方式来使用js,而且象modello.js这些包,已经把js用oo的方式封装了.也存在大量的用js写的各种功能类,象collection/collections等.完全模仿java的类库来写的,功能大致相同,js是一种弱类型的语言,用久了会发现比java用起来更顺手.
lz说的web框架,是我们一直在使用的方式.使用js写了大量的类库,有几十m吧,做了一个类似画图工具的东西,只是画出来的都是web控件,控件包含各种事件,功能比较强大.这样页面只需要拖拖画画,把对应的业务对象配一下就可以了.而且除了logging界面以外,其他的所有界面都是在一个html上的,页面内容的变化,只是改变页面上一块大div里面的dom元素.
ajax只是web页面的局部刷新,能熟练使用js,dom,css,加个xmlhttprequest,就是一个ajax高手.

只是我们现在做的项目,已经离java越来越远了,做的是一个电子病历系统,功能比较简单,几乎所有功能都是使用js来完成,java代码少的可怜.
[该贴被gougou3250于2007年05月19日 10:41修改过]