关于视图层技术的选择,很多年来,也是争议颇多的一个话题。对于选择.NET技术的公司来说,这个问题还是很好选择的,跟着微软就可以了。微软阵营的问题是选择太少,出了问题不知道怎么办,只能网上找控件,碰到收费的控件,又爱又恨,最后还是放弃,不了了之。痛恨微软和别人的不开源,自己的代码却从不给人看,这是微软阵营的特点。
如果说微软阵营抱怨封闭不开源,选择太少的话,那么J2EE阵营最大的问题则是选择太多了,不仅普通程序员无法从纷繁复杂的各类开源框架中做出正确的选择,即使对于系统架构师这类老手,从数十种开源框架中,找到符合自己公司和项目特点的那个,也是相当挠头的。比如说现在最流行的Ajax开源框架JQuery,仅最基本最常用的DataGrid控件,解决方案就有十几个。如何从这十几个DataGrid控件中选择最合适的一个,也就是所谓的“银弹”,是相当的痛苦啊。当然,开源的源代码也多得是,可惜的是绝大部分程序员从来不看别人的源代码,虽然张口闭口大家都在谈着Struts、Spring、Hibernate,有几个人把他们的源代码读过一遍?甚至开发文档阅读过一遍?
我教给大家一个框架的秘诀,如果自己实在不知道如何选择,那就遵循两个原则:一是是否得到IBM、Oracle、微软这类大型软件公司的支持;二是别问为什么,就选择大家最常用的,随大流就可以了。
对于UI这一层的选择,J2EE阵营有3类选择。
(1)Ajax:包括JQuery、ExtJs以及ZK等Ajax框架。国内的用友、金蝶、阿里软件等和其他一些传统管理软件转过来的公司,一般会采用ExtJs作为自己视图层解决方案。
(2)Flex:Flex最终生成的还是Flash,Flex的出现,是真正的富客户端解决方案(Rich Internet Application)。界面基于标准的XML标签,非常华丽,再加上功能强大的ActionScript,不仅适用于传统MIS开发,而且还可以开发网络游戏,像著名的网络游戏开心农场,就是基于Flash开发的。不过,Flash也面临巨大的挑战。首先是没有得到风头正猛的苹果公司的支持,Flash在移动开发领域,也遇到了强大的阻力。如果说苹果公司只是给了Adobe背后一刀,尚未伤其筋骨的话,那么微软在最新的技术路线图中,则明确表示IE9也将不再支持Flash,那么微软会不会给Adobe致命的一击?微软和苹果的理由是它们都将只支持HTML5.0。
(3)JSF:JSF一般在大公司使用,比如Oracle经典的J2EE开发框架。不过,在国内由JSP转向JSF的公司并不多见。
我这里给个建议,对于进销存MIS系统的开发,如果考虑跟以前的兼容,首选是ExtJS,其次是Flex,最后是ZK和JBoss的企业级开发框架Seam,而对于网站类开发,则只选择JQuery就可以了。
为什么我们还选择Flex作为自己的UI层技术解决方案呢?首先我们可以排除的是JSF,JSF仅有少数公司在用,JSF是用Swing的解决思路去解决Web难题,Swing首先就在桌面领域败给了SWT,在Web领域,这个失败的技术架构,同样也没有得到大家的认可。那么在ExtJs和Flex之间如何选择呢?我之所以选择Flex,是因为Flex支持Restful风格的技术架构。Flex跟后台的通信机制,事实上有3种:Remote Object、Web Service和HttpService组件。第三种HttpService组件,就是Restful风格的。采用Restful后,后台接收数据的代码跟前台技术无关,这套代码同样可适用于ASP、JSP、PHP,只要它们支持Restful就可以了,真正可以做到mushup(混合语言)编程,这就是我所说的自己的“银弹”。当然,选择ExtJS你可以选择JSON格式的数据进行前后台通信。
如果IE以后真的不支持Flash了,这个还真的是一个问题,好在我们的架构,跟视图层无关,视图层是随时可以替换掉的,目前来看,只有Flex对Restful支持得最好,所以我们选择Flex作为自己的技术解决方案,而且不存在浏览器不兼容的问题。IE、Chrome以及Firefox等主流浏览器都是支持Flash的,而且Adobe也做了一个Flash转化为HTML格式的工具,Flash一样可以在iPAD上运行。
本节我们将模仿Struts解决问题的思路,设计出自己的一套MVC框架。
首先,我们先提出自己的需求,在满足这个特定需求前提下,再设计自己的“山寨轮子”。在提出自己的需求之前,我们要先明白使用我们这个框架的用户是谁。明确了自己的用户,才能更好地满足他们的需求,为他们服务。在这里,我们假设框架的用户就是自己公司或者对这个框架感兴趣的程序员。在明确了这个框架的使用者之后,我们再来看看它要满足的功能上的需求。
Struts是一个MVC框架,视图层没有疑问了,指的是JSP、ASP、PHP这类可以动态生成HTML、XML或其他格式文档的Web网页的语言。上面我们提到,Struts的伟大之处,在于它用Set和Get这类面向对象的技术来存取网页数据,而不是以前的统统都是黑盒式的request.getParameter()方法。我们采用的是Restful技术架构,所以,对于View层,我们是没有自己特定选择的,JSP、ASP、PHP、Flex,理论上都可以满足,前提是这类脚本语言必须得有Restful风格的框架支持。Flex内置实现了Restful,所以我们这里采用Flex作为自己视图层的解决方案。对于Model层,我曾经面试过上百位程序员,问Struts的Model层指的是什么,回答正确的不到30%,Struts的Model层,其实就是ActionForm,Struts的MVC仅仅是经典J2EE框架Web层的MVC框架,所以它的Model层并不是指的J2EE的多层框架中的Model层。
我们要重点介绍的是控制(Control)层,控制层是我们这个框架的核心。Struts的控制层其实是一个过渡层,它把网页数据自动转化为ActionForm,拿到了数据,我们再写自己的商业逻辑,通过多种运算,把结果保存到数据库当中。
由于我们要做的是多租户的SaaS架构的MVC框架,所以与Struts要满足的用户需求还是非常不一样的。多租户意味着用户业务逻辑和界面有可能是不一样的。标准的进销存和网店系统,往往屏蔽掉了最终客户的特性需求和行业特点,试图一套软件走遍天下,这是不符合国情的,也是目前SaaS软件难以普及的一个重要原因。软件公司试图引导客户标准化作业,我认为是行不通的。我们将从另外一个角度,从技术架构上解决SaaS架构下客户个性化定制的问题。
对于视图层,我们并不限制客户个性化定制自己的页面,所以,我们的Model层,一定不是有确认属性的ActionForm,我们必须支持动态的属性添加。这里我们选择HashMap。读者看到这里,可能会有疑惑,Struts的ActionForm把我们从黑盒中解放出来,我们这次为啥又进入了一个黑盒HashMap当中?这句话问得好,这叫有破有立,为了要满足个性化的需求,所以我们要屏蔽确认的属性方法,只能用HashMap。否则,我们就不能满足这个特性。
基于以上需求和原则,我们设计出自己的MVC架构。先看增加、修改、删除功能的实现,代码如下所示。
<?xml version="1.0" encoding="UTF-8"?>
<mappings>
<url-mapping action="simplecreate.do" class="com.softbnd.jetblue.saas. webframework.action.SimpleCreateAction"/>
<url-mapping action="simpleupdate.do" class="com.softbnd.jetblue.saas. webframework.action.SimpleUpdateAction"/>
<url-mapping action="create.do" class="com.softbnd.jetblue.saas.webframework. action.CreateAction"/>
<url-mapping action="update.do" class="com.softbnd.jetblue.saas.webframework.action.UpdateAction"/>
<url-mapping action="suspend.do" class="com.softbnd.jetblue.saas.webframework.action.SuspendAction"/>
<url-mapping action="simplesuspend.do" class="com.softbnd.jetblue.saas. webframework.action.SimpleSuspendAction"/>
<url-mapping action="remove.do" class="com.softbnd.jetblue.saas.webframework.action.RemoveAction"/>
<url-mapping action="querycountry.do" class="com.softbnd.jetblue.saas. webframework.action.LogonQueryAction">
<query SQL="select * from country "/>
</url-mapping>
<url-mapping action="queryprovince.do" class="com.softbnd.jetblue.saas. webframework.action.LogonQueryAction">
<query SQL="select * from province order by seq "/>
</url-mapping>
<url-mapping action="querycity.do" class="com.softbnd.jetblue.saas. webframework.action.LogonQueryAction">
<query SQL="select * from city where provinceno='?' order by seq" />
</url-mapping>
</mappings>
其中,url-mapping标签表示我们把http的URL转换为Action。我们也要满足增加、删除、查询和修改动作的自动化。对于单表的增删和修改,我们定义几个缺省的类。
(1)CreateAction类:满足单表的数据新增功能。
(2)UpdateAction类:满足所有的修改功能,包括主子表连带的修改。
(3)RemoveAction类:满足所有的删除功能,包括主子表连带的删除。
再看查询功能的实现,代码如下。
<url-mapping description="查询入库单"action="queryfeedin.do" class="com. softbnd.jetblue.saas.webframework.action.LogonQueryAction">
<query SQL="select feedin.*,DATE_FORMAT(entrydate,'%Y-%m-%d') entrydate2, warehousename from feedin,warehouse where feedin.warehouseno= warehouse. warehouseno and feedin.tenantno in ('#tenantno#') and warehouse. tenantno in ('#tenantno#','0000000000')">
<subquery SQL="select feedinlineitem.*,goodsname,gtname ,puname
from feedinlineitem,goods,packageunit,goodstype
where feedinlineitem.goodsno=goods.goodsno and goods.goodsunitno= packageunit.puno and goods.goodstypeno=goodstype.gtno
and feedinlineitem.feedinno='?' and feedinlineitem.tenantno in ('#tenantno#') and goods.tenantno in ('#tenantno#') and packageunit.tenantno in ('#tenantno#') and goodstype.tenantno in ('#tenantno#','0000000000')"/>
<parameter requestname="date1" name="entrydate" type="Date" op=" >=" or=" feedin.status='0' "/>
<parameter requestname="date2" name="entrydate" type="Date" op= "<=" or=" feedin.status='0' "/>
<parameter requestname="feedinno" name="feedinno" type="String"/>
<parameter requestname="status" name="feedin.status" type="String" op="="/>
</query>
</url-mapping>
这是一个满足单表查询功能的标签。其中parameter是跟UI层查询参数密切相关的。UI层有多少个查询条件,这里就有多少个查询标签一一对应。Class则代表这个查询action的实现是默认的LogonQueryAction类,LogonQueryAction类能满足80%的查询需求。
Query标签定义的是查询SQL语句,查询条件能跟parameter自动匹配上,从而动态生成SQL语句传给后台,并把结果放在一个HashTable表里。其类图如图1所示。
图1 查询类图
以上这些类只是标签的具体实现,我们还需要一个runtime引擎把这些标签串起来,其类图如图2所示。
其中,ControlServlet类负责截获所有后缀是abc.do或者abc.action的请求,并通过URLMappingsXmlDAO类,把XML文件解析为SubQuery、Link、Parameter、Query等标签,跟具体的SQL对应起来,并把请求转换到LogonQueryAction类,LogonQueryAction类负责动态解析SQL,把Parameter标签里面的参数动态转换为具体的查询语句,并交给后台的DAO处理,还包括分页查询等功能,从而自动实现一次完整的查询动作。
由于我们是SaaS架构的,面向的是多租户,所以还有一个数据隔离的问题。对于数据隔离的实现,也有好几种架构可供选择。比如独立Schema,或者每个业务表增加一个租户ID等。如果你使用的是Oracle数据库,你的应用程序可能不需要做什么改变,只需要利用Oracle数据库的特性,就可以实现多租户下数据隔离的策略,前提必须是Oracle9i以后的版本,并且绑定到Oracle数据库之上。在这里,我们用最简单的方法实现数据隔离,就是每个业务表增加一个租户ID(tenantid)。所以,我们每个SQL语句里面,每个表都会多出一个tenantid。也许有的读者会问,这么做个性化是满足了,但是性能上会不会有影响?是的,我们又回到了上面所述的“银弹”原则,当我们满足某个特定需求的时候,必然是以牺牲另外的特性为代价的,解析XML文件,动态调用Java类,肯定没有直接的Java静态编译和调用速度快。
本文摘自电子工业出版社出版的《B2B2C网上商城开发指南——基于SaaS和淘宝API开放平台》一书。本书由邢波涛、郭娟著。
相关文章: