关于作者

用户名:xiaoning_2005
笔名:china dragon
地区: 北京
行业:其他

日历  

快速登录

+ 用户名:
+ 密 码:

在线留言



访问统计:
文章个数:47
评论个数:8
留言条数:4




Powered by BlogDriver 2.1

xiaoning的博客

 

文章

计算机行业研究报告
  1 行业发展回顾与展望 
  1.1 2001年及2002年上半年行业发展回顾 
  1.1.1 2001年国内计算机市场延续了平稳增长态势,但总体增长幅度开始回落 
  20世纪90年代后,我国计算机市场在国家经济高速发展的背景下,随着信息化建设的启动和发展,进入了前所未有的高速发展阶段,在1991年至1997年间的平均增长速度高达56.9%。1998年,由于受到亚洲金融危机和我国经济出现通货紧缩等国内外宏观经济环境的影响,增长速度有所下降。
  2001年,随着全球网络经济泡沫的破灭,美国的新经济神话走下神坛,美国经济增速减缓并陷入衰退,欧洲经济增长放缓,日本经济持续低迷,全球经济蒙上了厚重的阴影。全球经济环境恶化导致IT产业与市场遭遇寒流ˉ全球PC销售量首次出现负增长;股票下跌、紧缩开支以及裁员风潮席卷世界各大IT企业。
  虽然全球经济不景气,但是国家“以信息化带动工业化”战略已开始进入实施阶段,金融、电信、教育、政府、社保等行业的信息化建设继续深入。电子政务、电子商务工程继续实施,这对计算机市场的持续增长起到了重要作用,而与西部大开发相关的通信基础设施建设和交通设施建设也对IT市场的增长起到了积极的作用。 
  2001年,良好的国内经济环境使中国计算机市场继续保持平稳的增长。中国计算机工业全年实现工业总产值约3400亿元,比上年增长21.4%;计算机市场实现销售额2502亿元,比上年增长16.4%。2001年中国计算机市场延续了几年来的平稳增长态势,但总体增长幅度开始回落。整个硬件产品的增长幅度远远低于软件和信息服务的增长幅度:台式PC增长缓慢,服务器增长相对稳定,笔记本电脑市场销量呈现了快速增长势头,网络及部分外设产品也保持了较快的增长速度。 
  2001年,在中国计算机市场总销售额中,硬件市场取得了13.5%的增长,软件和信息服务市场继续保持较快的发展速度,分别比上年增长23.9%和25.5%;同时,软件与信息服务在计算机市场中的份额继续增加,由2000年的25.6%提高至2001年的27.4%。这进一步说明,在中国政府有关政策的鼓励和大力扶持下,软件产业规模迅速扩大,已成为拉动中国计算机市场持续增长的巨大动力。 
  1.1.2 2002年上半年中国计算机市场处于相对平缓的增长期  
  进入2002年,美国经济第一季在美国政府为了反恐战争的巨额财政和军事开支的刺激下,同时利率降至历史低位,出现了5.6%的高成长,正当人们认为美国经济已见底并将带动世界经济走向复苏时,接连出现的美国公司财务丑闻导致美国股市大幅下挫和美元的下跌,投资人信心大受伤害,而美国经济的第二季数据之差表明美国经济的全面复苏远未来临,并很有可能陷入二次衰退之中。
  美林公司对全球计算机市场的前景预测认为,2002年企业在技术方面的投资将仅增长4.6%,比2001年的增长率略高一点点。美林的研究小组认为,技术投资在近期不会有大幅度的增加。他们还指出,尽管大多数用户认为技术投资不会进一步地减少,但40%的接受调查的人士表示可能减少在技术方面的投资。 而中国整个国民经济在出口高速增长和国债项目带动的强劲内需增长下,GDP的增长在上半年达到了7.8%,另外,2002年是中国加入世贸的第一年,大量的外资开始涌向中国,特别是电子信息产品制造业向中国大量地转移,导致利用外资和电子信息产品出口方面增长相当迅速。 
  2002年上半年,中国计算机市场实现销售额1312.5亿元,比2001年同期增长15.8%,低于2001年17.9%的增长率。其中,硬件市场实现销售额955.6亿元,同比增长12.8%;而软件和信息服务市场分别实现23.5%和25.6%的增长,使得软件与信息服务市场的份额持续扩大,已分别由2001年的10.2%和15.0%上升到10.9%和16.3%,软件和信息服务市场的快速成长,成为IT产业关注的焦点,推动国内计算机市场结构的日趋软化以及IT企业的业务转型,也推动着国内IT产业结构的优化。由于上半年电信、金融等主要行业信息化建设采购力度增幅减缓,其它传统行业如教育、建筑、医疗等采购力度虽有较大增长,但其市场规模不大,对整体市场影响较小,因此,中国计算机市场处于相对平缓的增长期。 
  2001-2002年上半年中国计算机市场规模与增长 (单位︰亿元人民币) 
  产品 2001年上半年  2002年上半年 增长率 
  硬件     847.4           955.6      12.8% 
  软件     116            143.3       23.5% 
  信息服务 170            213.6       25.6% 
  总计     1133.4          1312.5      15.8%  
  1.1.3 2002年上半年中国计算机市场运行的特点 
  1)上半年在计算机硬件市场方面,PC产品总计销售410.8万台,实现销售额334.51亿元,分别比去年同期增长17.2%和11.3%。其中,笔记本电脑增长最快,销量增长达44.3%,在PC市场中的份额也由2001年上半年的7.2%迅速上升到8.9%,成为PC市场中一道亮丽的风景线。各主要外设产品销量均比2001年同期有一定增长,其中磁盘存储系统增长率达33.3%;但各产品销售额的增长远远低于销量的增长,其中显示器的销售额甚至出现了下降。这表明,在目前国内计算机市场供大于求的状况下,硬件产品销量的增长一方面是市场需求增长的必然结果,另一方面,价格的持续下滑在某种程度上已成为推动硬件产品销售的必要条件。
  2)2002年上半年,各种软件产品也保持了快速的增长,其中中间软件和应用软件的增长速度均高于平台软件的增  长,这使得中间软件和应用软件在软件市场中的份额持续增加。这从另一个角度反映出,在计算机产品应用市场规模不断扩大的同时,市场应用需求的重心正在逐步由新系统的建设为主转向现有系统应用水平提高为主的变化过程。
  3)在计算机服务领域,服务已经成为用户新的需求热点,也成为厂商业务拓展的重要方向。联想以5500万现金以及联想原有的IT咨询业务注入汉普国际咨询有限公司,“科技联想”迈开了向“服务联想”转变的步伐。用友、金蝶等软件公司,纷纷提出服务标准化和产业化策略等等;金融、保险、电信、制造、服务等领域的各类企业纷纷签约IT服务,许多IT企业纷纷转向IT服务行业,中关村数据、世纪互联等熟悉或陌生的IT公司,也纷纷逐鹿IT服务市场,表明信息服务正在逐步取代传统的PC制造业,成为新世纪中国计算机厂商可持续发展的重要途径。
  4)价竞争仍然是厂商之间竞争的重要手段。尽管渠道和服务竞争日益引起厂商的重视,但随着计算机产品更新换代速度的加快,同时由于国内计算机制造处于整个行业产业链的低端,各厂商在产品的特性上的缺失,国内计算机市场基本上陷入了价格竞争的泥潭。这与上游厂商处理器芯片和内存的大幅降价有关,同时也反映出厂商和渠道为了谋求生存和发展,在激烈的市场竞争中不得不采用微薄利润的无奈。
  5)行业与企业用户成为计算机市场的主要消费者。国家“十五”规划中“以信息化带动工业化”战略的实施以及金融、电信等行业信息化建设的逐步深化,不但带动了服务器、存储设备以及网络设备等产品市场需求的增长,同时也加大了管理软件、行业应用软件和解决方案应用的市场需求,在保持国内计算机市场持续稳定增长方面起到了重要作用。 
  6)家庭数字消费概念备受关注,随着台式PC、笔记本电脑、打印机、扫描仪等计算机产品不断进入家庭用户,家庭消费市场也日益成为计算机企业关注的焦点,宽带技术的普及和网络服务内容的丰富更加快了家庭信息化的进程。包括数码相机、PDA在内的家庭数码产品逐步成为家庭信息消费的又一个热点。 
  1.2 2002年下半年展望
  1.2.1 全球IT市场在今年四季度会有较大的反弹
  从国际市场看,尽管全球经济复苏的形势依然不是很明朗,但是普遍认为全球IT市场在今年四季度会有较大的反弹,今年企业对计算机软件、硬件、服务的投资将略高于去年。
  Forrester分析公司发表的一份报告中指出,今年北美的企业在信息技术上的投资将比去年高出2.3%。这也将是该地区的信息技术投资在经历一年多的负增长后首次出现增长。  预计投资增长的领域包括基础性的硬件产品,例如存储系统和服务器,以及与数据仓库和数据挖掘相关的项目,与能够将信息由数据仓库中传递给决策者的系统相关的领域都会出现增长。
  IDC发布的一份全球市场调查报告结果显示,由于今年第四季度用于IT领域的消费将出现激增,预计全球2002年信息技术领域的消费支出将超出2001年的水平。今年IT领域的消费增长将集中的体现在第四季度--传统的消费购物季节,对于大多数公司而言,它们都将一年的购物计划定在了此间。去年第四季度的黄金购物季节遭受了“911恐怖袭击”事件影响,众多公司的购买热情一落千丈。IDC称,与2001年IT领域的消费低迷相比,2002年的IT消费将显得格外强劲。尽管2002年的IT领域消费将得到全面反弹,但并非计算机全部领域都将受益,IDC称,电脑硬件消费预计将比去年下降4%。
  1.2.2国内市场计算机及相关设备制造业保持20%增长,软件及信息服务约增长25.3% 
  在国内市场上,由于出口形势依然是谨慎乐观,全年GDP增长将继续保持稳定的增长水平,完成年初制定的7%的增长目标应该没有问题。下半年我国经济继续稳定的增长,扩大内需的工作方针以及扩张性的宏观经济政策将继续保持,国民经济和社会的信息化、高技术产业化、传统产业的信息化改造、重大装备国产化,以及西部大开发过程中通信、交通、电网等基础设施的建设和广播电视、教育等行业的现代化发展步伐的加快,这些都将为我国信息产业稳定增长奠定良好基础。 
  目前,我国已形成了以微型机生产为主体,在主机、外设、应用产品、网络产品、零配件与耗材等5大类硬件产品方面,具有大规模生产配套能力的工业体系,在某些计算机产品领域如显示器等已成为世界上最主要的生产基地,同时由于中国在劳动力成本方面的比较优势,加入WTO后由于政府市场准入承诺的履行而导致外国直接投资的急剧增加,全球IT业持续不景气和大规模裁员导致大量技术和管理人才成为“海归派”,为国内电子信息业走向国际化提供了很好的发展机遇,但由于缺乏像‘千年虫’或是网络泡沫那样的强大的市场推动力,而短时期内国内厂商开拓国际市场的能力不足,依然以内需为主,所以计算机和相关设备制造行业在下半年不会出现2000年那样的高速成长,有可能保持20%左右的增长率,高于去年的成长率。
  下半年软件产业将继续高速增长,应用软件仍将扮演主力军角色。企业信息化、社会生产信息化加快、政府政策支持、互联网下的以多媒体为载体的教育、影像、游戏等软件市场需求扩大,以上多种因素都为中国软件业快速发展提供了良好的外部环境。据估计,2002年中国软件市场销售额预计达到357亿元,增速为25.3%。
  我国的IT服务市场将面对着巨大的市场需求,一是大量涌入的外企急需组建在中国的信息化管理中心,而更大的市场则来自国内企业的信息化建设。这表明中国的服务市场增长潜力巨大。同时,我国的IT服务市场日益成熟。激烈的行业竞争和专业化分工的趋势使传统企业无暇、无力也不愿意再为IT建设耗费更多的精力,将企业的IT需求整体外包成为了一种趋势。他们不再关心具体的产品和技术,而更注重IT应用的具体效果。他们希望专业的IT服务商能满足他们从前瞻性业务咨询、IT战略规划、IT架构实施到系统管理维护与技术支持的立体化需求,再加上用户服务消费观念的进步,增值服务意识的从无到有,中国IT服务市场的前景非常诱人,预计今年增长幅度会高于2001年,约为26.8%,且在行业中的比重将继续提高。
  总的来看,2002年,我国计算机总体市场规模预计可达3300亿元,比2001年同期增长22.2%。
  2002年我国IT产品市场预测 

2002年我国IT产品市场预测
 
市场构成
 销售额(亿元)
 增长率(%)
 
硬件
 2380
 19.6
 
软件
 400
 33.3
 
信息服务
 520
 26.8
 
合计
 3300
 22.2
 

  1.3 未来几年行业的发展
  1.3.1强大的社会内需将拉动着中国IT市场的快速稳定增长
  预计在未来几年中,国际经济将逐步走向回升,国际电子信息产业呈现缓慢复苏的势态,在加入世贸的推动下,中国IT市场与国际市场全面接轨,以信息化带动工业化的国家发展战略的贯彻执行,电子政府、企业信息化、城市信息化等一系列重大信息化工程的实施,有力地推动着中国国民经济和社会信息化的加速发展,同时进一步加快西部大开发的步伐。在未来5年内,中国将以发展高新技术、特别是信息技术带动经济增长,以实现国内生产总值年均增长7%的预期目标,强大的社会内需将拉动着中国IT市场的快速稳定增长。 
  1.3.2中国IT市场的巨大的发展潜力
  我国的各行业信息化水平较低,中国北京、上海等大城市个人电脑普及率不到30%,全国则不足10%,Internet的普及和国民经济信息化的加速都对计算机产品产生持续的市场需求,中国计算机市场仍是世界上潜力最大的市场。今后若干年内,面对世界跨国企业进入中国的挑战,国内金融、保险、电信以及一些传统行业将会加快行业的信息化建设步伐,加大在IT设备上的投入,以尽快提高自己的信息化水平,增强竞争力;西部大开发计划已逐步进入实施阶段,国家也继续在政策上向其倾斜,西部地区通信、交通、电网等基础设施的建设和广播电视、教育等行业的现代化发展都将为IT产业带来巨大的商机。同时2001年中国北京申奥成功,今后5-6年内,北京市将会投入巨资改善基础设施,加快现代化建设步伐,这也会在一定程度上拉动IT业的增长。 
  1.3.3我国IT制造方面的优势明显
  经过20年的发展,PC正从一个技术主导型产业走向一个消费主导型产业,其中一个基本的特点是生产和技术成本在总成本中的比重很小,而产品销售成本占到总成本的60%~70%,这种成本结构与店铺经营、脆弱的家电业成本结构非常相似,这也意味着用家电模式做PC具有可行性。从我国家电产业的发展来看,我国在世界格局中有不可动摇的优势,这也是国外一些IT巨头纷纷将生产制造中心转移到中国的主要原因了,目前在我国的珠江三角洲和长江三角洲地区,已经形成了IT产业的聚集效应,IT制造产业的规模不断发展,并将带动上游和下游产业以及相关服务业的发展壮大,年增长率将保持在18%以上。
  1.3.4中国软件和信息服务市场将将继续保持30%左右的增长率
  中国软件和信息服务市场将保持快速增长的发展态势,将继续保持在30%左右的增长率,未来几年仍将成为中国IT市场的强劲拉动力。中国政府在去年6月向全国各地区政府布了“18号文件”,即《鼓励软件产业和集成电路产业发展的若干政策》。其主要内容是着眼于国内市场与国际市场,将人才培养与国产软件的开发作为重点,争取到2005年达到2500亿元的产业规模,实现软件出口20亿美元,培养出100个以上的软件品牌,有80万名人员从事开发工作。从发展来看软件技术,目前正处在逐渐地走向大众化时期,应该说较硬件发展的较慢,信息服务的成熟将会滞后于硬件和软件的发展,目前软件产品的技术大众化趋势还不明显,而软件产品市场会由导入期走向成长期,由于内需的不断拉动,整个软件和信息服务市场,未来几年仍将快速增长。
  根据赛迪顾问依据数学模型对今后几年中国计算机市场的规模的定量预测,表明在2002-2005年,中国计算机市场销售额将以22.0%的年均复合增长率保持快速的增长态势。从各种产品来看,计算机硬件市场复合增长率为19.6%,而软件和信息服务增长率分别为27.3%和27.7%。2002-2005年中国计算机市场规模预测(单位︰亿元)
  产品    2002年    2003年   2004年   2005年    年均复合增长率
  硬件     2080      2497     3074     3719         19.6%
  软件     360.2      460      583      748         27.3%
  信息服务   501      639      822     1070         27.7%
  计算机市场 2941     3596     4479     5537         22.0%
  2 行业投资建议
  从前面的分析和预测来看,我国的计算机制造和软件和信息服务业的发展前景相当看好,按照赛迪顾问的预测,计算机硬件市场复合增长率为19.6%,而软件和信息服务增长率分别为27.3%和27.7%,而按照业内某些专家的预测软件和信息服务增长率将高达30%,前景更是诱人,但具体到在沪深上市的相关公司上,其发展态势却要具体分析。
  2.1计算机类上市公司的行业代表性不太够
  按2001年末上市公司年报披露的数据,信息技术类上市公司占整个信息技术行业的比重同其他行业相比处于中等水平,其中主营业务收入总额所占比重为13.89%, 净资产总额所占比重为26.73%, 利润总额所占比重为11.14%, 资产总额所占比重为22.33%, 固定资产净值总额 所占比重为15.9%,而从公司的收益能力等指标来看,上司公司的净资产收益率、 销售利润率、 成本费用利润率均比全行业要低,这些数据表明上市公司的行业代表性不太够,而且行业中还有更好的公司没有在上市公司之列,这主要是因为大多数行业中百强企业是新技术产业,发展起步相对较晚,市场认可及股份制改造还需进一步成熟,规模企业发行上市还需逐步进行。
  2.2 计算机类上市公司二级市场的表现落后于大盘
  计算机类上市公司近两年的二级市场的表现落后于大盘,从2000年开始基本上是以下跌为主,大部分公司的跌幅在50%以上,主要原因是在1999年的科技股革命和2000年的网络热潮中计算机类公司的涨幅过大,而且许多的计算机公司通过借壳上市,使许多的绩差的公司‘乌鸡变凤凰’,但重组和借壳上市后公司的资产质量并不好,资产净值不高,原来的历史包袱很重,因此在高位产生了较多的套牢盘后,走上了漫长价值回归之路。从根本上也反映了这些公司的成长性被市场透支炒作,而且反映出计算机行业从高速成长转入了相对稳定的成长期。
  从计算机类公司2001年的业绩和2002年中期业绩预告来看,软件公司的每股收益、净利润率、主营收入增长率等三项主要指标均是硬件类公司的一倍以上。从产业发展状况来看,2001年我国计算机PC硬件销售增长16%,低于前年34%增速,而软件产业销售增速仍接近28%,高速增长态势依旧。考虑到我国软件与服务市场份额仅占全球软件销售市场的1.5%左右,以及软件市场占到计算机总体销售的20%,与全球软件占计算机总体50%比例相比,国内软件产业仍有较大的增长潜力。
  从今年一季度季报情况看,计算机板块主营收入继续增长,但板块盈利能力继2001年滑坡之后仍处在较快的下降通道之中。平均主营收入为2.32亿元,与去年同季度相比增长12.48%。平均净利润450万元,同比下降34%,下降速度在加快,平均净利润仅占2001年度净利润的8.5%。
  2.3 硬件类的上市公司利润率还将保持在较低的水平,但还有发展空间
  对于硬件类的上市公司来说,由于受到来自国内的强敌如联想公司等和来自国外的IT巨头如戴尔和IBM等公司的激烈竞争,经营压力相当大,利润率将保持在较低的水平,对于销量在30万台以下的公司来说,亏损将不可避免。有业内人士指称目前的计算机制造业的状况正如前两年的家电行业,而有关PC市场衰退的传闻不断。但是宏基 、浪潮、四通、长城等一批企业重新杀回PC市场;不仅如此,新的投资者也不断涌入,新的细分市场催生了笔记本、移动PC、平板PC等形态各异的产品,表明市场前景并非一片黑暗。
  随着中国加入世贸和行业的利润的下降,由于在PC产业链条上,中国在制造环节仍然具有相当的优势,计算机制造业将如同我国的家电行业一样走向复苏,这已经可从IBM公司放弃在机器制造方面同国内企业的竞争,而专著于品牌的开拓和信息服务领域看出端倪,而且随着国内公司注重品牌的经营和国际市场的拓展,相信在几年后会看到计算机硬件出口的大增,并在稳定增长的国内需求支持下走向新的快速成长。值得关注的是国内市场排名居前的龙头公司如方正科技、清华同方、长城电脑、实达电脑和清华紫光等。
  2.4 软件和信息服务类公司前景较好
  2.4.1竞争加剧导致行业利润下降
  在软件和信息服务类方面,国内软件企业相对规模较小,与国际知名企业存有较大差距,缺乏具有自主知识产权的软件产品,主要以内需为主,软件和服务的出口所占比重很小。目前大部分软件上市公司主要业务是系统集成,核心竞争力并不突出。各公司为了保持业务增长,营业费用增长迅速,随竞争加剧,行业平均利润逐步下降。
  2.4.2面对跨国公司加快在中国软件市场的本地化进程的压力和机遇
  从去年底到今年上半年,IBM 、微软、Oracle、摩托罗拉等跨国公司纷纷加快在中国软件市场的本地化进程。这些本地化工作表现在其产品本地化、服务本地化,以及人员管理的本地化。上演“本地化”大战的根本原因就是:中国加入了WTO、中国经济持续不断地快速增长、国发[2000]18号文件对软件发展的支持等等,使软件在中国具有越来越好地发展环境;未来2008年北京数字奥运、现在中国政府采购的实施将给软件带来无限商机。特别是政府采购,它在很大程度上催化了“本地化”大战的发生。
  中国加入WTO后,国内软件企业与跨国软件企业在同等条件下进行竞争时,由于资金不足、人才匮乏、没有核心技术让许多企业处于下风。但是跨国公司进入中国软件市场,将使中国软件产业真正融入国际软件产业,从而拥有了发展成为现代软件产业的机会,中国软件企业由此也有机会加入到全球软件产业链条中。跨国公司本地化开发、采购更会带动中国软件企业走向国际市场。在全球经济一体化的背景下,跨国软件企业在华投资、建立合资企业、本地化采购,还会引起经济上的连锁反应,拉动软件出口,给中国软件创造出新的经济增长点。中国软件企业在面对新一轮的“本地化”浪潮时,可以采取三种策略:一是在合作中共生,如联想、中软、用友等企业就是采用这种做法;二是寻求成为跨国公司分工体系中的一员,中小企业多采取此策略;三是在竞争中成长,如中科红旗、金山等软件企业。
  2.4.3 国内软件企业在行业应用软件和ERP类软件市场发展看好
  在中国软件市场,国外品牌的产品仍然占据高端系统软件、数据库软件的绝大部分市场份额,并占据中间件、行业应用软件、ERP软件的大部分市场份额;国内品牌产品则在ERP和财务管理软件、防杀毒软件、中文信息处理软件及部分行业应用领域占据优势。
  在今后几年,随着我国信息化进程的不但深入,行业应用软件和ERP类软件的需求发展势头很猛,主要表现在政府的电子政务,电信业的计费、容灾、安全需求,银行业推进网络化和银行卡通用,证券业的集中化和网上交易的发展,能源业加快电子商务进程,社保业的应用和解决方案,交通业从硬件转向软件和应用采购,教育业的校园网和教育信息服务,从目前来看金融和电信行业是主要的市场,这从上半年两大行业由于种种原因减少和推迟某些项目的投资,使得一些相应的行业软件公司业绩下滑,但从下半年来看相关项目的推出会使情况有所好转。目前国内软件企业纷纷向行业应用软件和ERP类软件方向转型,这也是各公司的财务费用上涨较快的原因。而从目前的趋势来看,国内企业在这个方面发展较好,据有关统计,用友软件在国内ERP市场的份额已排在了第一位。此外,进军金融类行业的亿阳信通,进军社保行业的东软股份、创智科技、浙大网新等公司值得关注。
  2.4.4 面向出口,开拓国际市场
  而北京软件出口联盟下设的方正软件产品出口联盟的成立,表明我国软件企业充分认识到软件“单打时代”已成过去,只有联合起来参与国际软件市场竞争,才能具备承接大型国际软件开发项目的能力,企业才有更大的发展机会。象东软股份、浙大网新等建立大型面向出口的软件园,未来发展前景较好。
  2.5计算机类公司存在下列一些投资机会
  1) 随着国际IT制造业中心向中国转移,和中国加入世贸后国际市场的开拓,目前在行业中排名靠前的,有良好的品牌形象的公司,在将来有稳定成长的潜力,如清华同方、方正科技、实达电脑、长城电脑、清华紫光等公司。
  2) 在上市的软件类公司中,仅有中软股份、东软股份、用友软件、亿阳信通、宏智科技等少数公司以IPO形式上市,其余公司通过借壳上市,而借壳上市的炎黄在线、新宇软件、浪潮软件等每股资本公积金、每股净资产较低,而以高价首发上市的用友软件、亿阳信通和曾高价增发的托普软件等每股资本公积金高,有能力采用转增股本方式迅速扩张,而从这些公司的定位来看,目前市盈率也相对高于市场的平均市盈率,但是考虑到未来软件市场的高速成长性,建议关注上市后未进行过大比例送配的公司,如中软股份、用友软件、亿阳信通和宏智科技。同时,从公司经营的稳健性和发展方向上,建议关注东软股份和浙大网新。
  3) 从计算机类公司的二级市场表现来看,目前基本上处于较低的位置,考虑到同其他行业相比,行业的成长速度还是较快的,而且加入WTO后,国际IT产业加速向中国转移,目前的上市公司面临着很多的重组和整合的可能,而且既有来自国内行业内外的公司,也包括国外的大型IT公司,可关注新太科技等。 

- 作者: china dragon 2005年12月19日, 星期一 21:12  回复(0)引用(0) 加入博采

职场成功必须经过六项修炼
一、永远不要拒绝改变

  如果你不想成为被慢慢煮死的青蛙,就不要害怕变革。是的,变革意味着你将放弃拥有的东西,面对未知的风险,可变革同样让你拥有重新开始的可能,让你获得推翻从前的机会。想想,当j.k罗琳在廉价咖啡馆里写作的时候,她还只是个贫穷、离异、相貌平平且带着孩子的女人,而现在,她已经是英国的第二大富婆,拥有的财富比英女王还要多。当然,她同时还拥有了名望、地位和新的爱情。

  如果说罗琳的成功充满了命运的眷顾,那么桑得斯的故事则饱含奋斗的艰辛。作为一个退役军人,桑得斯上校当过消防员,卖过保险,翻修过轮胎,开过加油站,到66岁那年,他仍然只是一个领取每月105美元生活保障的退休老人。也就是这一年,桑得斯上校开了一家小小的快餐店,这无疑是一项成功的投资,那是世界上第一家肯德基。现在,你可以在全世界看到这家快餐连锁店,和店门口桑得斯上校的身影。在华尔街,没有破产过三次以上的人不是好的投资家,因为唯有经历过失败,仍然不畏变革的人,才能成为最后的赢家。

二、让“青蛙”老板注意你

  不,这不是说老板大腹便便,刮噪刺耳,而是告诉你一个小小的科学常识:青蛙只能看到运动中的物体。所以,无论如何,保持自己忙碌是第一要务。尽管你已经连续熬夜把策划书写得尽善尽美,尽管你刚刚加班加点完成两个月的工作量,你也绝对不可以作体力透支状,倒在桌子上补充睡眠。要知道,任何一个老板都愿意看到员工时刻处于无比繁忙的工作状态中。所以,不要对已经结束的工作沾沾自喜,在台子上放一两份没有完成的文件,把涂涂画画的日程表夹在电脑上,甚至把袖子卷起来,也能让你看上去更实干一些。

  当然,如果你是躲在高高的隔断板后面忙碌,那和你偷偷泡bbs其实没有区别。科层制逐级负责的管理体制,使得老板对你的工作缺少了解,他对你的所有印象,可能都只来源于人力部门的汇报。所以,和老板保持沟通至关重要,让他知道你在做什么,你的想法和方案,并且提出建议。在工作中,也可以适当询问老板的意见,让他不知不觉参与到你的工作中来。

  三、品牌你自己

  为什么你要喝百事可乐而不是非常可乐?为什么你要穿耐克而不是匹克?为什么你老板提拔了跟你一起到公司的 lucy,对经常加班到半夜的你却视而不见?因为,被选择的不仅仅是一件饮料、服饰或者某个抽象的id,还包括一点一滴建立起来的品牌内涵。对于你来说,选择百事可乐和耐克代表你认同青春的、积极的生活方式;对于老板来说,选择lucy代表投资获得稳定回报的可能。

  新经济时代最负盛名也最具争议性的作家汤姆?彼特斯5年前就提出了“brand you”的全新理论。他谆谆教诲说,我们每个人都是ceo,任职的公司叫做“me”,职业生涯中最大的任务,是把公司唯一的品牌“you”,打造成职场的领先品牌。你需要的是对自己的充分认识和一份清晰的品牌推广方案。盖乐普对员工说,每个人都有一件事情,做得比一万个人都好。你知道自己的优势所在吗?作为一个要着力推广的品牌,你和别人的诉求点有什么不同呢?明白自己的优势,不断强化自己的优势,才能让你成为职场里一个响当当的“名牌”。


  四、注意每一个工作细节

  你可以举出很多例子来反驳说,成功人士不拘小节,比如爱因斯坦。但是,不得不承认的是,更多时候细节具有决定性的力量。电梯里和老板简短的几句聊天,可能让他坚定提拔你的念头;在谈判中一个错误的用语,也许让你最后痛失快要到手的合同。完美的细节代表着永不懈怠的处事风格,正是个人品牌价值的最佳体现。

  接电话时,先主动向对方问好;打电话时,先询问对方是否方便;给老板的报告里,总是精心预备一份简短的概要供快速浏览……这样的细节还有很多很多,重要的原则是,你必须成为一个积极、实干、优质的象征,你的任何言行都要与此相适应。

  五、培养杰出的公关技巧

  良好的公关意识向来是树立形象的优质润滑剂,更是成为职场成功者的必要条件。公关的第一原则是:善待任何人。要知道,在白领的工作圈里,没有永远的朋友,也没有永远的敌人,没有永远的上司,也没有永远的下属。生活变幻莫测,不会永远符合你的预期。被你狠狠得罪的客户,可能是你下一个老板;你颐指气使的下属,可能摇身一变成了你的上司。在你的个人品牌形象上,“温和”、“彬彬有礼”之类的标签,贴得越多越妙。

  办公室以外的非正式场合,是沟通交流的最好场所。在非正式场合里,人们通常比较放松,不太具有戒备心理,更容易互相妥协,这就是为什么外交通常先在非正式场合展开的原因吧。和老板的沟通更是如此。升职加薪的微妙关头,搭电梯遇到老板,说上20秒钟话,却有可能彻底击败竞争对手!这可不是跟老板套套近乎就能搞定的,你看人家job的沟通技巧:“我昨天去看过公司产品的专卖店了,顾客对产品反映不错,但是销售有一定困难。marketing部门印的单页,不是很有针对性,不像上次那么好。”短短几句话,让老板知道你在工作,拿到了第一手信息,发现了问题,还提出了建议。非正式场合几十秒的交谈,可能比一个小时辛苦汇报工作,收益还要大得多!


六、三分钟推销你自己

  在塑造个人品牌的过程中,如何提升品牌认知度至关重要。要让别人在第一时间记住你,你就必须提供引人注目的细节。总是用mont blanc、明快的衣着搭配、得体的幽默甚至漂亮的签名,都能让你脱颖而出。当然,每家公司里都有一个著名的糊涂虫,如果你不幸被贴上了这样的品牌,那么迅速跳槽、重新开始也许是个不错的选择。

  过于谦虚会让人觉得信心不足,轮到你做介绍的时候,大可不必太古板老套。只要说的都是事实,就大胆推销你自己!告诉别人你的成绩和努力,利用一切出现在媒体上的机会,让现在的老板注意你,让潜在的老板被你吸引。作为一个成功个人品牌的维护者,你必须时刻在营销自己。

  通用ceo杰克。韦尔奇的办公室里挂着一副画:非洲的大草原上,旭日初升。在每个黎明,羚羊都要从梦中惊醒,拼命奔跑,时刻警惕,来摆脱狮子的猎食;而狮子只有比羚羊跑得更快,才能让自己不被饿死。在职场中,情况同样如此。无论你是羚羊还是狮子,停顿下来就意味着职业生命的死亡。现在,开始奔跑吧。

- 作者: china dragon 2005年12月6日, 星期二 13:44  回复(0) |  引用(0) 加入博采

iBatis 学习 之一 DAO部分


iBatis作者不喜欢在类前加详尽的说明,批评一下.

首先看一下包 com.ibatis.dao.client
1 定义了一个声明式接口 Dao
2 一个DaoManager接口,它有一个创建Dao实例的工厂方法:
  public Dao getDao(Class type); 是为了将用户自己写的Dao接口与sqlMap实现类解藕,通过在dao.xml中指定二者的映射。
  public DaoTransaction getTransaction(Dao dao); 这个是返回一个负责事务管理的类(DaoTransaction 也是一个明式接口)
    另外,有三个关于事务的操作,启动 提交 回滚。 

3 DaoManagerBuilder 工厂类,负责从xml配置文件创建DaoManager对象. 

带着问题,下来看 com.ibatis.dao.engine.impl包
1  类 StandardDaoManager 中聚合了一个DaoContext 类型的 map对象。每一个DaoContext 对应着dao.xml中的一个 context 节点的映射内容,这很好理解。
2  类 DaoContext,一个字典类,用一个Map(typeDaoImplMap)变量存储Dao接口及其具体实现类,不,等等. 在这个map变量中,用户的具体的SqlMapDao类在置入时应该已被置换为DaoImpl类. DaoContext的另一个重要责任是处理事务,你应该注意到dao.xml每一个context节点中所包含的transactionManager节点内容了吧。看一下以下方法:

    public Dao getDao(Class iface) {
    DaoImpl impl = (DaoImpl) typeDaoImplMap.get(iface);
    if (impl == null) {
      throw new DaoException("There is no DAO implementation found for " + iface + " in this context.");
    }
    return impl.getProxy();
  }


  从工厂类的具体实现 XmlDaoManagerBuilder 开始追溯吧:


    private DaoImpl parseDao{
     ...

     //具体的sqlMapDao子类一般是继承自SqlMapDaoTemplate,它的构造器使用DaoManager实例作为参数,而DaoTemplate实现了Dao声明式接口。
     Constructor constructor = daoClass.getConstructor(new Class[]{DaoManager.class});
     Dao dao = (Dao) constructor.newInstance(new Object[]{daoManager});
     ...
     daoImpl.setDaoInstance(dao);

     daoImpl.initProxy();

     return daoImpl;
  }
 
  在解析dao.xml时,使用了动态代理将用户的Dao实现类匹配为一个DaoImpl类实例(为了管理事务),看来得关注一下 DaoImpl类的方法initProxy():


 
  public void initProxy() {
    proxy = DaoProxy.newInstance(this);
  }

 
  再追踪到DaoProxy类:
 
    public static Dao newInstance(DaoImpl daoImpl) {
    return (Dao) Proxy.newProxyInstance(daoImpl.getDaoInterface().getClassLoader(), new Class[]{Dao.class, daoImpl.getDaoInterface()},new DaoProxy(daoImpl));
  }
 
  这里,iBatis使用了动态代理。产生一个proxy实例,它实现了iBatis的声明式Dao和用户自己定义的Dao双重接口。每一个用户所书写的数据存取方法,通过proxy调用时都会包裹上事务处理机制,可以看出:每一个Dao子类中的方法体,天然是一个事务。到此本人有个疑问,众所周知,事务应该放在service层,如果放在Dao层,会造成性能的缺失,不过,我想iBatis肯定想到这一点了,在service层应该有相应的方案统一调度众多Dao的事务,避免嵌套事务。

 注 : 动态代理,从jdk1.3开始支持,“动态代理”是这样一种类,它可以实现在运行时指定的一组接口。对代理类的方法调用,被分配给调用处理程序,而调用处理程序是在代理类生成的时候指定的。它有一个限制,要包装/扩展的对象必须实现一个接口,该接口定义了准备在包装器中使用的所有方法,我们都知道应该面向接口编程,这应该不算过分。Proxy.newProxyInstance() 方法有三个参数:动态代理定义的类加载器 classloader;Class 数组,里面包含动态代理要实现的所有接口,以及处理方法调用的调用处理程序。

 这里提一下Spring,在Spring中基于JDK1.3提供的dynamic  proxy机制,可以配置这个生成的子类,来代理原始目标类的方法调用。子类是用Decorator设计模式 置入的。


  先到这儿吧,有点小晕。
  心得体会:
  1 声明式接口(即没有具体接口方法)的用法
  2 良好的设计是基于接口的,易于扩展。
  3 动态代理的用法。

- 作者: xiaoning_2005 2005年10月13日, 星期四 09:31  回复(0) |  引用(0) 加入博采

J2EE应用系统Jdon JPetstore


以IBatis.com的iBATIS-Jpetstore为例,我们使用Jdon框架对其重构成为Jdon-JPetstore,本章开发环境是Eclipse(本章案也适用其他开发工具),部署运行环境是JBoss。

Eclipse是一个非常不错的开源开发工具,使用Eclipse开发和使用JBuilder将有完全不同的开发方式。我们使用Eclipse基于Jdon框架开发一个完全Web应用,或者可以说,开发一个轻量(lightweight)的J2EE应用系统。

通过这个轻量系统开发,说明Jdon框架对完全POJO架构的支持,因为EJB分布式集群计算能力,随着访问量提升,可能需要引入EJB架构,这时只要使用EJB session Bean包装POJO服务则可以无缝升级到EJB。使用Jdon框架可实现方便简单地架构升迁。

Eclipse安装简要说明
1.下载Eclipse:在http://www.eclipse.org 的下载点中选择tds ISP 比较快。

2.安装免费插件:

编辑Jsp需要lomboz : http://www.objectlearn.com/projects/download.jsp

注意对应的Eclipse版本。

编辑XML,使用Xmlbuddy: http://xmlbuddy.com/

基本上这两个插件就够了。

 

如果希望开发 Hibernate,插件:

http://www.binamics.com/hibernatesync

 

代码折叠

http://www.coffee-bytes.com/eclipse/update-site/site.xml

 

3. 关键学习ant的编写build.xml,在build.xml将你的jsp javaclass打包成war或jar或ear就可以。都可以使用ant的jar打包,build.xml只要参考一个模板就可以:SimpleJdonFrameworkTest.rar 有一个现成的,可拷贝到其它项目后修改后就可用.

然后在这个模板上修改,参考 ant的命令参考:

http://ant.apache.org/manual/tasksoverview.html

网上有中文版的ant参考,在google搜索就能找到。

关键是学习ant的build.xml编辑,SimpleJdonFrameworkTest.rar 有一个现成的,可拷贝到其它项目后修改后就可用.

用ant编译替代Eclipse的缺省编译:选择项目属性-->Builders ---> new --> Ant Builder --->选择本项目的build.xml workspace 选择本项目

eclipse开发就这些,非常简单,不象Jbuilder那样智能化,导致项目目录很大。eclipse只负责源码开发,其它都由ant负责

架构设计要点
Jdon-JPetstore除了保留iBATIS-JPetstore 4.0.5的域模型、持久层ibatis实现以及Jsp页面外,其余部分因为使用了Jdon框架而和其有所不同。

保留域模型和Jsp页面主要是在不更改系统需求的前提下,重构其架构实现为Jdon框架,通过对比其原来的实现或Spring的JPetstore实现,可以发现Jdon框架的使用特点。

在原来jpetstore iBatis包会延伸到表现层,例如它的分页查询PaginatedList,iBatis只是持久层框架,它的作用范围应该只限定在持久层,这是它的专业范围,如果超过范围,显得 ….。所以,在Jdon-Jpetstore中将iBatis封装在持久层(砍掉PaginatedList这只太长的手),Jdon框架是一种中间层框架,联系前后台的工作应该由Jdon这样的中间层框架完成。

在iBatis 4.0.5版本中,它使用了一个利用方法映射Reflection的小框架,这样,将原来需要在Action实现方法整入了ActionForm中实现,ActionForm变成了一个复杂的对象:页面表单抽象以及后台Service交互,在ActionForm中调用后台服务是通过单态模式实现,这是在一般J2EE开发中忌讳的一点,关于单态模式的讨论可见:http://www.jdon.com/jive/article.jsp?forum=91&thread=17578

Jdon框架使用了微容器替代单态,消除了Jpetstore的单态隐患,而且也简化了ActionForm和服务层的交互动作(通过配置实现)。

用户注册登陆模块实现
用户域建模(Model)
首先,我们需要从域建模开始,建立正确的领域模型,以用户账号为例,根据业务需求我们确立用户账号的域模型Account,该模型需要继承Jdon框架中的com.jdon.controller.model.Model。

 

public class Account extends Model {

 

  private String username;

  private String password;

  private String email;

  private String firstName;

  private String lastName;

  ……

 

}

username是主键。

域模型建立好之后,就可以花开两朵各表一支,表现层和持久层可以同时开发,先谈谈持久层关于用户模型的crud功能实现。

持久层Account crud实现
主要是用户的新增和修改,主要用于注册新用户和用户资料修改。

public interface AccountDao {

  Account getAccount(String username);  //获得一个Account

  void insertAccount(Account account);  //新增

  void updateAccount(Account account); //修改

}

持久层可以使用多种技术实现,例如Jdon框架的JdbcTemp代码实现比较方便,如果你的sql语句可能经常改动,使用iBatis的sql语句XML定义有一定好处,本例程使用Jpetstore原来的持久层实现iBatis。见源码包中的Account.xml

 

表现层Account表单创建(ModelForm)
这是在Domain Model建立后最重要的一步,是前台表现层Struts开发的起步,表单创建有以下注意点:

表单类必须继承com.jdon.model.ModelForm

表单类基本是Domain Model的影子,每一个Model对应一个ModelForm实例,所谓对应:就是字段名称一致。ModelForm实例是由Model实例复制获得的。

 

public class AccountForm extends ModelForm {

 

  private String username;

  private String password;

  private String email;

  private String firstName;

  private String lastName;

  ……

 

}

当然AccountForm可能有一些与显示有关的字段,例如注册时有英文和中文选择,以及类别的选择,那么增加两个字段在AccountForm中:

  private List languages;

  private List categories;

这两个字段需要初始化值的,因为在AccountForm对应的Jsp的页面中要显示出来,这样用户才可能进行选择。选择后的值将放置在专门的字段中。

有两种方式初始化这两个字段:

1. 在AccountForm构造方法中初始化,前提是:这些初始化值是常量,如:

public AccountForm() {

    languages = new ArrayList();

    languages.add("english");

    languages .add("japanese");

}

2.如果初始化值是必须从数据库中获取,那么采取前面章节介绍的使用ModelHandler来实现,这部分又涉及配置和代码实现,缺省时我们考虑通过jdonframework.xml配置实现。

 

Account crud的struts-config.xml的配置
第一步配置ActionForm:

上节编写了ModelForm代码,ModelForm也就是struts的ActionForm,在struts-config.xml中配置ActionForm如下:

 <form-bean name="accountFrom" type="com.jdon.framework.samples.jpetstore.presentation.form.AccountForm"/>

第二步配置Action:

这需要根据你的crud功能实现需求配置,例如本例中用户注册和用户修改分开,这样,配置两套ModelViewAction和ModelSaveAction,具体配置见源码包中的struts-config-security.xml,这里将struts-config.xml根据模块划分成相应的模块配置,实现多模块开发,本模块是用户注册登陆系统,因此取名struts-config-security.xml。

Account crud的Jdonframework.xml配置
    <model key="username" class ="com.jdon.framework.samples.jpetstore.domain.Account">

      <actionForm name="accountForm"/>

      <handler>

        <service ref="accountService">

          <initMethod   name="initAccount" />                           

          <getMethod    name="getAccount" />

          <createMethod name="insertAccount" />

          <updateMethod name="updateAccount" />

          <deleteMethod name="deleteAccount" />

        </service>

      </handler>

    </model> 

.其中有一个initMethod主要用于AccuntForm对象的初始化。其他都是增删改查的常规实现。

Account crud 的Jsp页面实现
在编辑页面EditAccountForm.jsp中加入:

<html:hidden name="accountFrom" property="action" value="create" />

在新增页面NewAccountForm.jsp加入:

<html:hidden name="accountFrom" property="action" value="edit" />

所有的字段都是直接来自accountFrom。

 

整理模块配置
商品模块功能完成,struts提供了多模块开发,因此我们可以将这一模块单独保存在一个配置中:/WEB-INF/struts-config-security.xml,这样以后扩展修改起来方便。

 

商品查询模块实现
在iBATIS-JPetstore中没有单独的CategoryForm,而是将三个Model:Category、Product、,Item合并在一个CatalogBean中,这样做的缺点是拓展性不强,将来这三个Model也许需要单独的ActionForm。

由于我们使用Jdon框架的crud功能配置实现,因此,不怕细分这三个Model带来代码复杂和琐碎。

由于原来的Jpetstore“偷懒”,没有实现Category Product等的crud功能,只实现它们的查询功能,因此,我们使用Jdon框架的批量查询来实现查询。

持久层 Product批量查询实现
商品查询主要有两种批量查询,根据其类别ID:CategoryId查询所有该商品目录下所有的商品;根据关键字搜索符合条件的所有商品,下面以前一个功能为例子:

iBatis-jpetstore使用PaginatedList作为分页的主要对象,该对象需要保存到HttpSession中,然后使用PaginatedList的NextPage等直接遍历,这种方法只适合在小数据量合适,J2EE编程中不推荐向HttpSession放入大量数据,不利于cluster。

根据Jdon批量查询的持久层要求,批量查询需要两种SQL语句实现:符合条件的ID集合和符合条件的总数:以及单个Model查询。

  //获得ID集合

 List getProductIDsListByCategory(String categoryId, int pagessize);

  //获得总数

  int getProductIDsListByCategoryCount(String categoryId);

  //单个Model查询

  Product getProduct(String productId) ;

这里我们需要更改一下iBatis原来的Product.xml配置,原来,它设计返回的是符合条件的所有Product集合,而我们要求是Product ID集合。

修改Product.xml如下:

<resultMap id="productIDsResult" class="java.lang.String">

    <result property="value" column="PRODUCTID"/>

</resultMap>              

                  

<select id="getProductListByCategory" resultMap="productIDsResult" parameterClass="string">

    select PRODUCTID from PRODUCT where CATEGORY = #value#

</select>

        

<select id="getProductListByCategoryCount"  resultClass="java.lang.Integer" parameterClass="string">

    select count(1) as value from PRODUCT where CATEGORY = #value#

</select>

    ProductDao是IBatis DAO实现,读取Product.xml中配置:

    public List  etProductIDsListByCategory(String categoryId, int start, int pagessize) {

        return sqlMapDaoTemplate.queryForList(

                "getProductListByCategory", categoryId, start, pagessize);

    }

 

    public int getProductIDsListByCategoryCount(String categoryId){

        Integer countI = (Integer)sqlMapDaoTemplate.queryForObject(

                "getProductListByCategoryCount", categoryId);

        return countI.intValue();

    }  

这样,结合配置的iBatis DAO和Jdon框架批量查询,在ProductManagerImp中创建PageIterator,当然这部分代码也可以在ProductDao实现,创建PageIterator代码如下:

      public PageIterator getProductIDsListByCategory(String categoryId, int start, int count)

       {

           PageIterator pageIterator = null;

            try {

                List list = productDao.getProductIDsListByCategory(categoryId, start, count);

                int allCount = productDao.getProductIDsListByCategoryCount(categoryId);

                int currentCount = start + list.size();

                pageIterator = new PageIterator(allCount, list.toArray(), start,

                        (currentCount < allCount)?true:false);

 

         } catch (DaoException daoe) {

             Debug.logError(" Dao error : " + daoe, module);

         }

       

       return pageIterator;

表现层Product批量查询实现
    根据批量查询的编程步骤,在表现层主要是实现ModelListAction继承、配置和Jsp编写,下面分步说:

    第一步,创建一个ModelListAction子类ProductListAction,实现两个方法:getPageIterator和findModelByKey,getPageIterator方法如下:

      public PageIterator getPageIterator(HttpServletRequest request, int start,

            int count) {

        ProductManager productManager = (ProductManager) WebAppUtil.getService(

                "productManager", request);

        String categoryId = request.getParameter("categoryId");

        return productManager.getProductIDsListByCategory(categoryId, start, count);

                

    }

    findModelByKey方法如下:

      public Model findModelByKey(HttpServletRequest request, Object key) {

        ProductManager productManager = (ProductManager) WebAppUtil.getService(

                "productManager", request);

        return productManager.getProduct((String)key);

    }

    由于我们实现的是查询一个商品目录下所有商品功能,因此,需要显示商品目录名称,而前面操作的都是Product模型,所以在显示页面也要加入商品目录Category模型,我们使用ModelListAction的customizeListForm方法:

    public void customizeListForm(ActionMapping actionMapping,

            ActionForm actionForm, HttpServletRequest request,

            ModelListForm modelListForm) throws Exception {

        ModelListForm listForm = (ModelListForm) actionForm;

        ProductManager productManager = (ProductManager) WebAppUtil.getService(

                "productManager", request);

        String categoryId = request.getParameter("categoryId");

        Category category = productManager.getCategory(categoryId);

        listForm.setOneModel(category);

    }

    第二步,配置struts-config.xml,配置ActionForm和Action:

    <form-bean name="productListForm" type="com.jdon.strutsutil.ModelListForm"/>

    action配置如下:

    <action path="/shop/viewCategory"

type="com.jdon.framework.samples.jpetstore.presentation.action.ProductListAction"

      name="productListForm" scope="request"

      validate="false" >

      <forward name="success" path="/catalog/Category.jsp"/>

    </action>

  第三步,编写Category.jsp

  从productListForm中取出我们要显示两个模型,一个是oneModel中的Category;另外一个是Product Model集合list,Jsp语法如下:

<bean:define id="category" name="productListForm" property="oneModel" />

<bean:define id="productList" name="productListForm" property="list" />

我们可以显示商品目录名称如下:

<h2><bean:write name="category" property="name" /></h2>

这样我们就可以遍历productList中的Product如下:

<logic:iterate id="product" name="productList" >

  <tr bgcolor="#FFFF88">

  <td><b><html:link paramId="productId" paramName="product" paramProperty="productId" page="/shop/viewProduct.shtml"><font color="BLACK"><bean:write name="product" property="productId" /></font></html:link></b></td>

  <td><bean:write name="product" property="name" /></td>

  </tr>

</logic:iterate>

加上分页标签库如下:

  <MultiPages:pager actionFormName="productListForm " page="/shop/viewCategory.do"

               paramId="categoryId" paramName="category" paramProperty="categoryId">

<MultiPages:prev><img src="../images/button_prev.gif" border="0"></MultiPages:prev>

<MultiPages:index />

<MultiPages:next><img src="../images/button_next.gif" border="0"></MultiPages:next>

</MultiPages:pager>

至此,一个商品目录下的所有商品批量查询功能完成,由于是基于框架的模板编程,直接上线运行成功率高。

商品搜索批量查询:
参考上面步骤,商品搜索也可以顺利实现,从后台到前台按照批量查询这条线索分别涉及的类有:

持久层实现:ProductDao中的三个方法:

List searchProductIDsList(String keywords, int start, int pagessize); //ID集合

 

int searchProductIDsListCount(String keywords); //总数

 

Product getProduct(String productId) ; //单个Model

表现层:建立ProductSearchAction类,配置struts-config.xml如下:

    <action path="/shop/searchProducts"

type="com.jdon.framework.samples.jpetstore.presentation.action.ProductSearchAction"

      name="productListForm" scope="request"

      validate="false">

      <forward name="success" path="/catalog/SearchProducts.jsp"/>

    </action>

与前面使用的都是同一个ActionForm:productListForm

编写SearchProducts .jsp,与Category.jsp类似,相同的是ActionForm;不同的是action。

商品条目Item批量查询
条目Item批量实现与Product批量查询类似:

持久层:ItemDao提供三个方法:

List getItemIDsListByProduct(String productId, int start, int pagessize);//ID集合

 

  int getItemIDsListByProductCount(String productId);//总数

 

  Item getItem(String itemId); //单个Model

表现层:创建一个ItemListAction继承ModelListAction:完成getPageIterator和findModelByKey,如下:

public class ItemListAction extends ModelListAction {

 

    public PageIterator getPageIterator(HttpServletRequest request, int start,

            int count) {

        ProductManager productManager = (ProductManager) WebAppUtil.getService(

                "productManager", request);

        String productId = request.getParameter("productId");

        return productManager.getItemIDsListByProduct(productId, start, count);

    }

 

    public Model findModelByKey(HttpServletRequest request, Object key) {

        ProductManager productManager = (ProductManager) WebAppUtil.getService(

                "productManager", request);

        return productManager.getItem((String)key);

    }

 

    public void customizeListForm……….

}

与前面的ProductListAction相比,非常类似,不同的是Model名称不一样,一个是Product一个是Item;

struts-config.xml配置如下:

    <form-bean name="itemListForm" type="com.jdon.strutsutil.ModelListForm"/>

 

    <action path="/shop/viewProduct"

type="com.jdon.framework.samples.jpetstore.presentation.action.ItemListAction"

      name="itemtListForm" scope="request"

      validate="false">

      <forward name="success" path="/catalog/Product.jsp"/>

    </action>

比较前面product的配置,非常类似,其实itemListForm和productListForm是同一个ModelListForm类型,可以合并起来统一命名为listForm,节省ActionForm的配置。

Product.jsp页面与前面的Category.jsp SearchProdcuts.jsp类似。

<bean:define id="product" name="itemListForm" property="oneModel" />

<bean:define id="itemList" name="itemListForm" property="list" />

分页显示:

  <MultiPages:pager actionFormName="itemListForm" page="/shop/viewProduct.do"

              paramId="productId" paramName="product" paramProperty="productId">

    …..

商品条目Item单条查询
单个显示属于crud中的一个查询功能,我们需要建立Model对应的ModelForm,将Item的字段拷贝到ItemForm中。配置这个ActionForm如下:

    <form-bean name="itemForm"

type="com.jdon.framework.samples.jpetstore.presentation.form.ItemForm"/>

第二步:因为这个功能属于crud一种,无需编程,但是需要配置jdonframework.xml:

   <model key="itemId" class ="com.jdon.framework.samples.jpetstore.domain.Item">

      <actionForm name="itemForm"/>

      <handler>

        <service ref="productManager">

          <getMethod name="getItem" />

        </service>

      </handler>

    </model>

配置中只要一个方法getMethod就可以,因为只用到crud中的读取方式。

第三步:配置struts-config.xml如下:

    <action  path="/shop/viewItem"  type="com.jdon.strutsutil.ModelDispAction"

                   name="itemForm" scope="request"

                  validate="false">

      <forward name="success" path="/catalog/Item.jsp" />

      <forward name="failure" path="/catalog/Product.jsp" />

    </action>

第四步编辑Item.jsp,现在开始发现一个问题,Item.jsp中不只是显示Item信息,还有Product信息,而前面我们定义的是Item信息,如果使得Item.jsp显示Product信息呢,这就从设计起源Domain Model上考虑,在Item的Model中有Product引用:

  private Product product;

  public Product getProduct() { return product; }

  public void setProduct(Product product) { this.product = product; }

Item和Product的多对一关系其实应该在域建模开始就考虑到了。

那么,我们只要在持久层查询Item时,能够将其中的Product字段查询就可以。在持久层的iBatis的Product.xml实现有下列SQL语句:

  <select id="getItem" resultMap="resultWithQuantity" parameterClass="string">

    select

      I.ITEMID, LISTPRICE, UNITCOST, SUPPLIER, I.PRODUCTID, NAME,

      DESCN, CATEGORY, STATUS, ATTR1, ATTR2, ATTR3, ATTR4, ATTR5, QTY

    from ITEM I, INVENTORY V, PRODUCT P where P.PRODUCTID = I.PRODUCTID and I.ITEMID = V.ITEMID and I.ITEMID = #value#

  </select>

这段语法实际在查询Item时,已经将Product查询出来,这样Item Model中已经有Product数据,因为ActionForm是Model映射,因此,前台Jsp也可以显示Product数据。

在Item.jsp中,进行下面定义:

<bean:define id="product" name="itemForm " property="product" />

<bean:define id="item" name="itemForm " />

将itemForm中product属性定义为product即可;这样不必大幅度修改原来的Item.jsp了。

整理模块配置
商品模块功能完成,struts提供了多模块开发,因此我们可以将这一模块单独保存在一个配置中:/WEB-INF/struts-config-catalog.xml,这样以后扩展修改起来方便。

 

购物车模块实现
购物车属于一种有状态数据,也就是说,购物车的scope生命周期是用户,除非这个用户离开,否则购物车直在内存中存在。

有态POJO服务
现在有两种解决方案:

第一,将购物车状态作为数据类,保存到ActionForm中,设置scope为session,这种形式下,对购物车的数据操作如加入条目等实现不很方便,iBatis-jpetstore 4.0.5就采取这个方案,在数据类Cart中存在大量数据操作方法,那么Cart这个类到底属于数据类Model?还是属于处理服务类呢?

在我们J2EE编程中,通常使用两种类来实现功能,一种是数据类,也就是我们设计的Model;一种是服务类,如POJO服务或EJB服务,服务属于一种处理器,处理过程。使用这两种分类比较方便我们来解析业务需求,EJB中实体Bean和Session Bean也是属于这两种类型。

iBatis-jpetstore 4.0.5则是将服务和数据类混合在一个类中,这也属于一种设计,但是我们认为它破坏了解决问题的规律性,而且造成数据和操作行为耦合性很强,在设计模式中我们还使用桥模式来分离抽象和行为,因此这种做法可以说是反模式的。那么我们采取数据类和服务分离的方式方案来试试看:

第二.购物车功能主要是对购物车这个Model的crud,与通常的crud区别是,数据是保存到HttpSession,而不是持久化到数据库中,是数据状态保存不同而已。所以如果我们实现一个CartService,它提供add或update或delete等方法,只不过操作对象不是数据库,而是其属性为购物车Cart,然后将该CarService实例保存到HttpSession,实现每个用户一个CartService实例,这个我们成为有状态的POJO服务。

这种处理方式类似EJB架构处理,如果我们业务服务层使用EJB,那么使用有态会话Bean实现这个功能。

现在问题是,Jdon框架目前好像没有提供有状态POJO服务实例的获得,那么我们自己在WebAppUtil.getService获得实例后,保存到HttpSession中,下次再到HttpSession中获得,这种有状态处理需要表现层更多代码,这就不能使用Jdon框架的crud配置实现了,需要我们代码实现ModelHandler子类。

考虑到可能在其他应用系统还有这种需求,那么能不能将有状态的POJO服务提炼到Jdon框架中呢?关键使用什么方式加入框架,因为这是设计目标服务实例的获得,框架主要流程代码又不能修改,怎么办?

Jdon框架的AOP功能在这里显示了强大灵活性,我们可以将有状态的POJO服务实例获得作为一个拦截器,拦截在原来POJO服务实例获得之前。在Jdon框架设计中,目标服务实例的获得一般只有一次。

创建有状态POJO服务拦截器com.jdon.aop.interceptor. StatefulInterceptor,再创建一个空接口:com.jdon.controller.service.StatefulPOJOService,需要实现有状态实例的POJO类只要继承这个接口就可以。

配置aspect.xml,加入这个拦截器:

  <interceptor name="statefulInterceptor" class="com.jdon.aop.interceptor.StatefulInterceptor"

pointcut="pojoServices" />      

这里需要注意的是:你不能让一个POJO服务类同时继承Poolable,然后又继承Stateful,因为这是两种不同的类型,前者适合无状态POJO;后者适合CartService这样有状态处理;这种选择和EJB的有态/无态选择是一样的。

Model和Service设计
购物车模块主要围绕域模型Cart展开,需要首先明确Cart是一个什么样的业务模型,购物车页面是类似商品条目批量查询页面,不过购物车中显示的不但是商品条目,还有数量,那么我们专门创建一个Model来指代它,取名为CartItem,CartItem是Item父集,多了一个数量。

这样购物车页面就是CartItem的批量查询页面,然后还有CartItem的crud操作,所以购物车功能主要是CartItem的crud和批量查询功能。

iBatis 4.0.5原来设计了专门Cart Model,其实这个Cart主要是一个功能类,因为它的数据项只有一个Map和List,这根本不能代表业务需求中的一个模型。虽然iBatis 4..0.5也可以自圆其说实现了购物车功能,但是这种实现是随心所欲,无规律性可遵循,因而以后维护起来也是困难,维护人员理解困难,修改起来也没有章程可循,甚至乱改一气。

CartItem可以使用iBatis原来的CartItem,这样也可保持Cart.jsp页面修改量降低。删除原来的Cart这个Model,建立对应的CartService,实现原来的Cart一些功能。

public interface CartService {

       CartItem getCartItem(String itemId);

       void addCartItem(EventModel em);

       void updateCartItem(EventModel em);

       void deleteCartItem(EventModel em);

       PageIterator getCartItems();

}

CartServiceImp是CartService子类,它是一个有状态POJO服务,代码简要如下:

public class CartServiceImp implements CartService, Stateful{

    private ProductManager productManager;

    //将原来iBatis 中Cart类中两个属性移植到CartServiceImp中

    private final Map itemMap = Collections.synchronizedMap(new HashMap());

    private  List itemList = new ArrayList();

 

    public CartServiceImp(ProductManager productManager) {

        super();

        this.productManager = productManager;

    }

    ……

}

itemMap是装载CartItem的一个Map,是类属性,由于CartServiceImp是有状态的,每个用户一个实例,那么也就是每个用户有自己的itemMap列表,也就是购物车。

CartServiceImp中的 getCartItemIDs是查询购物车当前页面的购物条目,属于批量分页查询实现,这里有一个需要考量的地方,是getCartItems方法还是getCartItemIDs方法?也就是返回CartIem的实例集合还是CartItem的ItemId集合?按照前面标准的Jdon框架批量分页查询实现,应该返回CartItem的ItemId集合,然后由Jdon框架的ModelListAction根据ItemId首先从缓存中获得CartItem实例,但是本例CartItem本身不是持久化在数据库,而也是内存HttpSession中,所以ModelListAction这种流程似乎没有必要。

如果将来业务需求变化,购物车状态不是保存在内存而是数据库,这样,用户下次登陆时,可以知道他上次购物车里的商品条目,那么采取Jdon框架标准查询方案还是有一定扩展性的。

这里,我们就事论事,采取返回CartIem的实例集合,展示一下如何灵活应用Jdon框架的批量查询功能。下面是CartServiceImp 的getCartItems方法详细代码:

 public PageIterator getCartItems(int start, int count) {

        int offset = itemList.size() - start; //获得未显示的总个数

        int pageCount = (count < offset)?count:offset;

        List pageList = new ArrayList(pageCount); //当前页面记录集合

        for(int i=start; i< pageCount + start;i++){

            pageList.add(itemList.get(i));

        }

        int allCount = itemList.size();

        int currentCount = start + pageCount;

        return new PageIterator(allCount, pageList.toArray(new CartItem[0]), start,

                (currentCount < allCount)?true:false);

}

getCartItems方法是从购物车所有条目itemList中查询获得当前页面的条目,并创建一个PageIterator。

注意,现在这个PageIterator中keys属性中装载的不是数据ID集合,而是完整的CartItem集合,因为上面代码中pageList中对象是从itemList中获得,而itemList中装载的都是CartItem。

表现层购物车显示功能
由于PageIterator中封装的是完整Model集合,而不是ID集合,所以现在表现层有两种方案,继承框架的ModelListAction;或重新自己实现一个Action,替代ModelListAction。

这里使用继承框架的ModelListAction方案,巧妙地实现我们的目的,省却编码:

public class CartListAction extends ModelListAction {

 

    public PageIterator getPageIterator(HttpServletRequest request, int arg1, int arg2) {

        CartService cartService = (CartService)WebAppUtil.getService("cartService", request);

        return cartService.getCartItems();

    }

 

    public Model findModelByKey(HttpServletRequest arg0, Object key) {

        return (Model)key; //因为key不是主键,而是完整的Model,直接返回

    }

   

    protected boolean isEnableCache(){

        return false;  //无需缓存,CartItem本身实际是在内存中。

    }

 

}

配置struts-config.xml:

<form-beans>  nbsp; 

   <form-bean name="listForm" type="com.jdon.strutsutil.ModelListForm"/>

</form-beans>   

 <action-mappings>

    <action path="/shop/viewCart"

type="com.jdon.framework.samples.jpetstore.presentation.action.CartListAction"

      name="listForm" scope="request"

      validate="false">

      <forward name="success" path="/cart/Cart.jsp"/>

    </action>

    ……

  </action-mappings>

上面是购物车显示实现,只要调用/shop/viewCart.shtml就可以显示购物车了。

在Cart.jsp页面插入下面标签:

<logic:iterate id="cartItem" name="listForm" property="list">

    ….

</logic:iterate>

分页显示标签如下:

<MultiPages:pager actionFormName="listForm" page="/shop/viewCart.shtml">

<MultiPages:prev name="<font color=green><B>&lt;&lt; Prev</B></font>"/>

<MultiPages:index />

<MultiPages:next name="<font color=green><B>Next &gt;&gt;</B></font>"/>

</MultiPages:pager>

 

 

购物车新增删除条目功能
前面完成了购物车显示功能,下面是设计购物车的新增和删除、修改功能。

参考Jdon框架的crud功能实现,Model是CartItem,配置jdonframework.xml使其完成新增删除功能:

                   <model key="workingItemId"

                            class="com.jdon.framework.samples.jpetstore.domain.CartItem">

                            <actionForm name="cartItemForm"/>

                            <handler>

                                     <service ref="cartService">

                                               <createMethod name="addCartItem"/>

                                               <deleteMethod name="deleteCartItem"/>

                                     </service>

                            </handler>

                   </model>

在这个配置中,只有新增和删除方法,修改方法没有,因为购物车修改主要是其中商品条目的数量修改,它不是逐条修改,而是一次性批量修改,这里的Model是CartItem,这是购物车里的一个条目,因此如果这里写修改,也只是CartItem一个条目的修改,不符合我们要求。下面专门章节实现这个修改。

表现层主要是配置,没有代码,代码都依靠cartService中的addCartItem和deleteCartItem实现:例如:

    public void addCartItem(EventModel em) {

        CartItem cartItem = (CartItem) em.getModel();

        String workingItemId = cartItem.getWorkingItemId();

        ……

    }

注意addCartItem中从EventModel实例中获取的Model是CartItem,这与我们在jdonframework.xml中上述定义的Model类型是统一的。

Struts-config.xml中定义是crud的标准定义,注意,这里只有ModelSaveAction无需ModelViewAction,因为将商品条目加入或删除购物车这个功能没有专门的显示页面:

    <action path="/shop/addItemToCart" type="com.jdon.strutsutil.ModelSaveAction"

      name="cartItemForm" scope="request"

      validate="false">

      <forward name="success" path="/cart/Cart.jsp"/>

    </action>

 

    <action path="/shop/removeItemFromCart" type="com.jdon.strutsutil.ModelSaveAction"

      name="cartItemForm" scope="request"

      validate="false">

      <forward name="success" path="/cart/Cart.jsp"/>

    </action>

注意,调用删除功能时,需要附加action参数:

/shop/removeItemFromCart.shtml?action=delete

而/shop/addItemToCart.shtml是新增属性,缺省后面无需跟参数。

购物车条目批量修改功能
上面基本完成了购物车主要功能;购物车功能一个复杂性在于其显示功能和修改功能合并在一起,修改功能是指修改购物车里所有商品条目的数量。

既然有修改功能,而且这个修改功能比较特殊,我们需要设计一个独立的ActionForm,用来实现商品条目数量的批量修改。

首先设计一个ActionForm(ModelForm),该ModelForm主要用来实现购物车条目数量的更改,取名为CartItemsForm,其内容如下:

public class CartItemsForm extends ModelForm {

    private String[] itemId;

    private int[]quantity;

    private BigDecimal totalCost;

    …..

}

itemId和quantity设计成数组,这样,Jsp页面可以一次性提交多个itemId和quantity数值。

现在CartItemsForm已经包含前台jsp输入的数据,我们还是将其传递递交到服务层实现处理。因此建立一个Model,内容与CartItemsForm类似,这里的Model名为CartItems,实际是一个传输对象。

public class CartItems extends Model{

    private String[] itemId;

    private int[] quantity;

    private BigDecimal totalCost;

    ……

}

表现层在jdonframework.xml定义配置就无需编码,配置如下:

                   <model key=" "

                            class="com.jdon.framework.samples.jpetstore.domain.CartItems">

                            <actionForm name="cartItemsForm"/>

                            <handler>

                      &nbp;              <service ref="cartService">

                                               <updateMethod name="updateCartItems"/>

                                     </service>

                            </handler>

                   </model>

上面配置中,Model是CartItems,ActionForm是cartItemsForm,这两个是专门为批量修改设立的。只有一个方法updateMethod。因为在这个更新功能中,没有根据主键从数据库查询Model的功能,因此,这里model的key可以为空值。

服务层CartServiceImp的updateCartItems方法实现购物车条目数量更新:

    public void updateCartItems(EventModel em) {

        CartItems cartItems = (CartItems) em.getModel();

        try {

            String[] itemIds = cartItems.getItemId();

            int[] qtys = cartItems.getQuantity();

            int length = itemIds.length;

            for (int i = 0; i < length; i++) {

                updateCartItem(itemIds[i], qtys[i]);//逐条更新购物车中的数量

            }

        } catch (Exception ex) {

            logger.error(ex);

        }

    }

 

注意updateCartItems中从EventModel取出的是CartItems,和前面addCartItem方法中取出的是CartItem Model类型不一样,这是因为这里我们在jdonframework.xml中定义与updateCartItems相对应的Model是CartItems.

最后一步工作是Cat.jsp中加入CartItemsForm,能够在购物车显示页面有一个表单提交,客户按提交按钮,能够立即实现当前页面购物车数量的批量修改。Cat.jsp加入如下代码:

<html:form action="/shop/updateCartQuantities.shtml" method="post" >

<html:hidden property="action" value="edit" />

……

<input type="hidden" name="itemId" value="<bean:write name="cartItem" property="workingItemId"/>">

  <input type="text" size="3" name="quantity" value="<bean:write name="cartItem"

property="quantity"/>" />

…….

注意,一定要有action赋值edit这一行,这样提交给updateCartQuantities.shtml实际是ModelSaveAction时,框架才知道操作性质。

购物车总价显示功能
最后,还有一个功能需要完成,在购物车显示时,需要显示当前购物车的总价格,注意不是显示当前页面的总价格,所以无法在Cart.jsp直接实现,必须遍历购物车里所有CartItem计算总数。

该功能是购物车显示时一起实现,购物车显示是通过CartListAction实现的,这个CartListAction实际是生成一个ModelListForm,如果ModelListForm能够增加一个getTotalPrice方法就可以,因此有两种实现方式:继承ModelListForm加入自己的getTotalPrice方法;第二种无需再实现自己的ModelListForm,ModelListForm可以携带一个Model,通过setOneModel即可,这个方法是在ModelListAction的子类CartListAction可以override覆盖实现的,在CartListAction加入下列方法:

  protected Model setOneModel(HttpServletRequest request){

        CartService cartService = (CartService)WebAppUtil.getService("cartService", request);

        CartItems cartItems = new CartItems();

        cartItems.setTotalCost(cartService.getSubTotal());       

        return cartItems;

    }

我们使用空的CartItems作为携带价格总数的Model,然后在Cart.jsp中再取出来显示:

<bean:define id="cartItems " name="listForm" property="oneModel" />

<b>Sub Total: <bean:write name="cartItems" property="subTotal" format="$#,##0.00" />

将当前页面listForm中属性oneModel定义为cartItems,它实际是我们定义的CartItems,

下一行取出总价即可。

用户喜欢商品列表功能
在显示购物车时,需要一起显示该用户喜欢的商品列表,很显然这是一个批量分页查询实现,但是它有些特殊,它首先显示的第一页不是由URL调用的,而是嵌入在购物车显示中,那么只能在购物车显示页面的ModellistForm中做文章。

在上节中,在CartListAction中setOneModel方法中,使用CartItems作为价格总数的载体,现在恐怕我们也要将之作为本功能实现载体。

还有一种实现载体,就是其他scope为session的ActionForm,AccountForm很适合做这样的载体,而且和本功能意义非常吻合,所以在AccountForm/Account中增加一个myList字段,在myList字段中,放置的是该用户喜欢的商品Product集合,注意不必放置Product的主键集合,因为我们只要显示用户喜欢商品的第一页,这一页是嵌入购物车显示页面中,所以第一页显示的个数是由程序员可事先在程序中定义。

这样在Account获得时,一起将myList集合值获得。

订单模块实现
我们还是从域模型开始,Order是订单模块的核心实体,其内容可以确定如下:

public class Order extends Model {

 

  /* Private Fields */

 

  private int orderId;

  private String username;

  private Date orderDate;

  private String shipAddress1;

  private String shipAddress2;

  …..

}

第二步,建立与Model对应的ModelForm,我们可以称之为边界模型,代码从Order拷贝过来即可。当然OrderForm还有一些特殊的字段以及初始化:

public class OrderForm extends ModelForm

private boolean shippingAddressRequired;

    private boolean confirmed;

    static {

        List cardList = new ArrayList();

        cardList.add("Visa");

        cardList.add("MasterCard");

        cardList.add("American Express");

        CARD_TYPE_LIST = Collections.unmodifiableList(cardList);

      }   

   

    public OrderForm(){

        this.shippingAddressRequired = false;

        this.confirmed = false;       

    }

   …..

}

第三步,建立Order Model的业务服务接口,如下:

public interface OrderService {

       void insertOrder(Order order);

       Order getOrder(int orderId);

       List getOrdersByUsername(String username);

}

第四步,实现OrderService的POJO子类:OrderServiceImp。

第五步,表现层实现,本步骤可和第四步同时进行。

OrderService中有订单的插入创建功能,我们使用Jdon框架的crud中create配置实现,配置struts-config.xml和jdonframework.xml:

      <form-bean name="orderForm"

type="com.jdon.framework.samples.jpetstore.presentation.form.OrerForm"/>

 

         <model key="orderId"

                            class="com.jdon.framework.samples.jpetstore.domain.Order">

                            <actionForm name="orderForm"/>

                            <handler>

                                     <service ref="orderService">

                                               <createMethod name="insertOrder"/>

                                     </service>

                            </handler>

         </model>

第六步:根据逐个实现界面功能,订单的第一个功能创建一个新的订单,在新订单页面NewOrderForm.jsp推出之前,这个页面的ActionForm已经被初始化,是根据购物车等Cart其他Model数据初始化合成的。

新订单页面初始化
根据Jdon框架中crud功能实现,初始化一个ActionForm有两种方法:一继承ModelHandler实现initForm方法;第二通过jdonframework.xml的initMethod方法配置。

这两个方案选择依据是根据用来初始化的数据来源什么地方。

订单表单初始化实际是来自购物车信息或用户账号信息,这两个都事先保存在HttpSession中,购物车信息是通过有态CartService实现的,所以这些数据来源是和request相关,那么我们选择第一个方案。

继承ModelHandler之前,我们可以考虑首先继承ModelHandler的子类XmlModelHandler,只要继承initForm一个方法即可,这样节省其他方法编写实现。

public class OrderHandler extends XmlModelHandler {

   

    public ModelForm initForm(HttpServletRequest request) throws

    Exception{

        HttpSession session = request.getSession();

        AccountForm accountForm = (AccountForm) session.getAttribute("accountForm");

        OrderForm orderForm = createOrderForm(accountForm);

        CartService cartService = (CartService)WebAppUtil.getService("cartService", request);

       

        orderForm.setTotalPrice(cartService.getSubTotal());

 

        //below can read from the user's creditCard service;

        orderForm.setCreditCard("999 9999 9999 9999");

        orderForm.setExpiryDate("12/03");

        orderForm.setCardType("Visa");

        orderForm.setCourier("UPS");

        orderForm.setLocale("CA");

        orderForm.setStatus("P");

 

        Iterator i = cartService.getAllCartItems().iterator();

        while (i.hasNext()) {

          CartItem cartItem = (CartItem) i.next();

          orderForm.addLineItem(cartItem);

        }

        return orderForm;        

    }

   

    private OrderForm createOrderForm(AccountForm account){

      ……

    }

}

ModelHandler的initForm继承后,因为这使用了Jdon的crud功能实现,这里我们只使用到crud中的创建功能,因此,findModelBykey方法就无需实现,或者可以在jdonframework.xml中配置该方法实现。

考虑到在initForm执行后,需要推出一个NewOrderForm.jsp页面,这是一个新增性质的页面。所以在struts-config.xml

                  <action path="/shop/newOrderForm" type="com.jdon.strutsutil.ModelViewAction"

      name="orderForm" scope="request"      validate="false">

        <forward name="create" path="/order/NewOrderForm.jsp"/>

    </action>      

订单确认流程
新的订单页面推出后,用户需要经过两个流程才能确认保存,这两个流程是填写送货地址以及再次完整确认。这两个流程实现的动作非常简单,就是将OrderForm中的shippingAddressRequired字段和confirm字段赋值,相当于简单的开关,这是一个很简单的动作,可以有两种方案:直接在jsp表单中将这两个值赋值;直接使用struts的Action实现。后者需要编码,而且不是非有这个必要,只有第一个方案行不通时才被迫实现。

第一步:填写送货地址

使用Jdon框架的推出纯Jsp功能的Action配置struts-config.xml:

    <action path="/shop/shippingForm" type="com.jdon.strutsutil.ForwardAction"

      name="orderForm" scope="session"      validate="false">

      <forward name="forward" path="/order/ShippingForm.jsp"/>         

    </action>

这是实现送货地址页面的填写,使用的还是OrderForm。更改前面流程NewOrderForm.jsp中的表单提交action值为本action path: shippingForm.shtml:

<html:form action="/shop/shippingForm.shtml" styleId="orderForm" method="post" >

  ……

</html:form>

在ShippingForm.jsp中增加将shippingAddressRequired赋值的字段:

<html:hidden name="orderForm"  property="shippingAddressRequired" value="false"/>

第二步:确认订单

类似上述步骤,配置struts-config.xml:

   <action path="/shop/confirmOrderForm" type="com.jdon.strutsutil. ForwardAction"

      name="orderForm" scope="session"  validate="false">

      <forward name="forward" path="/order/ConfirmOrder.jsp"/>          

   </action>        

将上一步ShippingForm.jsp的表单action改为本action的path: confirmOrderForm.shtml:

<html:form action="/shop/confirmOrderForm.shtml" styleId="orderBean" method="post" >

修改ConfirmOrder.jsp中提交的表单为最后一步,保存订单newOrder.shtml:

<html:link page="/shop/newOrder.shtml?confirmed=true"><img border="0" rc="../images/button_continue.gif" /></html:link>

第三步:下面是创建数据保存功能实现:

    <action path="/shop/newOrder" type="com.jdon.strutsutil.ModelSaveAction"

      name="orderForm" scope="session"

      validate="true" input="/order/NewOrderForm.jsp">

      <forward name="success" path="/order/ViewOrder.jsp"/>

    </action>

ModelSaveAction是委托ModelHandler实现的,这里有两种方式:配置方式:在jdonframework.xml中配置了方法插入;第二种是实现代码,这里原本可以使用配置方式实现,但是因为在功能上有要求:在订单保存后,需要清除购物车数据,因此只能使用代码实现方式,在ModelHandler中实现子类方法serviceAction:

public void serviceAction(EventModel em, HttpServletRequest request) throws java.lang.Exception {

   try {

      CartService cartService = (CartService) WebAppUtil.getService("cartService", request);

cartService.clear(); //清楚购物车数据

 

      OrderService orderService = (OrderService) WebAppUtil.getEJBService("orderService", request);

      switch (em.getActionType()) {

            case Event.CREATE:

                Order order = (Order) em.getModel();

                orderService.insertOrder(order);

                cartService.clear();

                break;

        case Event.EDIT:

                break;

        case Event.DELETE:

                break;

       }

   } catch (Exception ex) {

            throw new Exception(" serviceAction Error:" + ex);

   }

}

用户订单列表
用户查询自己的订单列表功能很明显可以使用Jdon框架的批量查询事先。

在struts-config.xml中配置ModelListForm如下:

      <form-bean name="listForm" type="com.jdon.strutsutil.ModelListForm"/>

建立继承ModelListAction子类OrderListAction:

public class OrderListAction extends ModelListAction {

 

    public PageIterator getPageIterator(HttpServletRequest request, int start, int count) {

        OrderService orderService = (OrderService) WebAppUtil.getService("orderService", request);

        HttpSession session = request.getSession();

        AccountForm accountForm = (AccountForm) session.getAttribute("accountForm");

        if (accountForm == null) return new PageIterator();

        return orderService.getOrdersByUsername(accountForm.getUsername(), start, count);

    }

 

    public Model findModelByKey(HttpServletRequest request, Object key) {

        OrderService orderService = (OrderService) WebAppUtil.getService("orderService", request);

        return orderService.getOrder((Integer)key);

    }

 

}

修改OrderService, 将获得Order集合方法改为:

public class OrderService{

 

PageIterator getOrdersByUsername(String username, int start, int count)

….

}

根据Jdon批量查询要求,使用iBatis实现返回ID集合以及符合条件的总数。

最后编写ListOrders.jsp,两个语法:logic:iterator 和MultiPages

配置jdon框架启动
目前我们有四个struts-config.xml,前面每个模块一个配置:

/WEB-INF/struts-config.xml 主配置

/WEB-INF/struts-config-catalog.xml  商品相关配置

/WEB-INF/struts-config-security.xml 用户相关配置

/WEB-INF/struts-config-cart.xml 购物车相关配置

/WEB-INF/struts-config-order.xml 订单相关配置

本项目只有一个jdonframework.xml,当然我们也可以创建多个jdonframework.xml,然后在其struts-config.xml中配置。

  <plug-in className="com.jdon.strutsutil.InitPlugIn">

    <set-property property="modelmapping-config"  value="jdonframework_iBATIS.xml" />

  </plug-in>

修改iBatis的DAO配置
iBatis 4.0.5中原来的配置过于扩张(从持久层扩张到业务层),AccountDao每个实例获得都需要通过daoManager.getDao这样形式,而使用Jdon框架后,AccountDao等DAO实例获得无需特别语句,我们只要在AccountService直接引用AccountDao接口,至于AccountDao的具体实例,通过Ioc注射进入即可。

因此,在jdonframework.xml中有如下配置:

         <pojoService name="accountDao"

                   class="com.jdon.framework.samples.jpetstore.persistence.dao.sqlmapdao.AccountSqlMapDao"/>

         <pojoService name="accountService"

                            class="com.jdon.framework.samples.jpetstore.service.bo.AccountServiceImp"/>

         <pojoService name="productManager"

                            class="com.jdon.framework.samples.jpetstore.service.bo.ProductManagerImp"/>

而AccountServiceImp代码如下:

public class AccountServiceImp implements AccountService, Poolable {

    private AccountDao accountDao;

    private ProductManager productManager;

   

    public AccountServiceImp(AccountDao accountDao,

                             ProductManager productManager){

        this.accountDao = accountDao;

        this.productManager = productManager;

    }

 

AccountServiceImp需要两个构造方法实例,这两个中有一个是AccountDao。

按照iBatis原来的AccountDao子类AccountSqlMapDao有一个构造方法参数是DaoManager,但是我们无法生成自己的DaoManager实例,因为DaoManager是由dao.xml配置文件读取后生成的,这是一个动态实例;那只有更改AccountSqlMapDao构造方法了。

根源在于BaseSqlMapDao类,BaseSqlMapDao是一个类似JDBC模板类,每个Dao都继承它,现在我们修改BaseSqlMapDao如下:

public class BaseSqlMapDao extends DaoTemplate implements SqlMapExecutor{

    …..

}

BaseSqlMapDao是XML配置和JDBC模板的结合体,在这个类中,这两者搭配在一起,在其中实现SqlMapExecutor各个子方法。

我们再创建一个DaoManagerFactory,专门根据配置文件创建DaoManager实例:

主要方法如下:

Reader reader = Resources.getResourceAsReader(daoResource);

daoManager = DaoManagerBuilder.buildDaoManager(reader);

其中daoResource是daoxml配置文件,这个配置是在jdonframework.xml中配置:

<pojoService name="daoManagerFactory"

                            class="com.jdon.framework.samples.jpetstore.persistence.dao.DaoManagerFactory">

                            <constructor 

value="com/jdon/framework/samples/jpetstore/persistence/dao/sqlmapdao/sql/dao.xml"/>

         </pojoService>

这样,我们可以通过改变jdonframework.xml配置改变dao.xml配置。

Dao.xml配置如下:

<daoConfig>

  <context>

    <transactionManager type="SQLMAP">

      <property name="SqlMapConfigResource"       

value="com/jdon/framework/samples/jpetstore/persistence/dao/sqlmapdao/sql/sql-map-config.xml"/>

    </transactionManager>

 

    <dao interface="com.ibatis.sqlmap.client.SqlMapExecutor"  

implementation="com.jdon.framework.samples.jpetstore.persistence.dao.sqlmapdao.BaseSqlMapDao"/>

 

  </context>

</daoConfig>

在dao.xml中,我们只配置一个JDBC模板,而不是将所有的如AccountDao配置其中,因为我们需要iBatis只是它的JDBC模板,实现持久层方便的持久化,仅此而已!

DaoManagerFactory为我们生产了DaoManager实例,那么如何赋值到BaseSqlMapDao中,我们设计一个创建BaseSqlMapDao工厂如下:

 

public class SqlMapDaoTemplateFactory {

 

    private DaoManagerFactory daoManagerFactory;

 

    public SqlMapDaoTemplateFactory(DaoManagerFactory daoManagerFactory) {

        this.daoManagerFactory = daoManagerFactory;

    }

   

    public SqlMapExecutor getSqlMapDaoTemp(){

        DaoManager daoManager = daoManagerFactory.getDaomanager();

        return (SqlMapExecutor)daoManager.getDao(SqlMapExecutor.class);

    }

 

}

通过getSqlMapDaoTemp方法,由DaoManager.getDao方法获得BaseSqlMapDao实例,BaseSqlMapDao的接口是SqlMapExecutor,这样我们通过DaoManager获得一个JDBC模板SqlMapExecutor的实例。

这样,在AccountDao各个子类AccountSqlMapDao中,我们只要通过SqlMapDaoTemplateFactory获得SqlMapExecutor实例,委托SqlMapExecutor实现JDBC操作,如下:

public class AccountSqlMapDao implements AccountDao {

  //iBatis数据库操作模板

  private SqlMapExecutor sqlMapDaoTemplate;

    //构造方法

  public AccountSqlMapDao(SqlMapDaoTemplateFactory sqlMapDaoTemplateFactory) {

      sqlMapDaoTemplate = sqlMapDaoTemplateFactory.getSqlMapDaoTemp();

  }

 //查询数据库

  public Account getAccount(String username) throws SQLException{

    return (Account)sqlMapDaoTemplate.queryForObject("getAccountByUsername", username);

  }

 

 

部署调试
使用Jdon框架开发Jpetstore, 一次性调试通过率高,一般问题都是存在数据库访问是否正常,一旦正常,主要页面就出来了,其中常见问题是jsp页面和ActionForm的字段不对应,如jsp页面显示如下错误:

No getter method available for property creditCardTypes for bean under name orderForm

表示在OrderForm中没有字段creditCardTypes,或者有此字段,但是大小写错误等粗心问题。

如果jsp页面或后台log记录显示:

System error! please call system Admin.java.lang.Exception

一般这是由于前面出错导致,根据记录向前搜索,搜索到第一个出错记录:

2005-07-07 11:55:16,671 [http-8080-Processor25] DEBUG com.jdon.container.pico.PicoContainerWrapper - getComponentClass: name=orderService

2005-07-07 11:55:16,671 [http-8080-Processor25] ERROR com.jdon.aop.reflection.MethodConstructor -  no this method name:insertOrder

第一个出错是在MethodConstructor报错,没有insertOrder方法,根据上面一行是orderService,那么检查orderService代码看看有无insertOrder:

public interface OrderService {

       void insertOrder(Order order);

       Order getOrder(int orderId);

       List getOrdersByUsername(String username);

}

OrderService接口中是有insertOrder方法,那么为什么报错没有呢?仔细检查一下,是不是insertOrder的方法参数有问题,哦, 因为orderService的调用是通过jdonframework.xml下面配置进行的:

         <model key="orderId"

                            class="com.jdon.framework.samples.jpetstore.domain.Order">

                            <actionForm name="orderForm"/>

                            <handler>

                                     <service ref="orderService">

                                               <createMethod name="insertOrder"/>

                                     </service>

                            </handler>

                   </model>

而根据Jdon框架要求,使用配置实现ModelHandler,则要求OrderService的insertOrder方法参数必须是EventModel,更改OrderService的insertOrder方法如下:

public interface OrderService {

    void insertOrder(EventModel em);

}

同时,修改OrderService的子类代码OrderServiceImp:

  public void insertOrder(EventModel em) {

        Order order = (Order)em.getModel();

        try{

            orderDao.insertOrder(order);

        }catch(Exception daoe){

                Debug.logError(" Dao error : " + daoe, module);

                em.setErrors("db.error");

        }

    }

 

注意em.setErrors方法,该方法可向struts页面显示出错信息,db.error是在本项目的application.properties中配置的。

关于本次页面出错问题,还有更深缘由,因为我们在jdonframework.xml中中定义了createMethod,而根据前面已经有ModelHandler子类代码OrderHndler实现,所以,这里不用配置实现,jdonframework.xml的配置应该如下:

                   <model key="orderId"

                            class="com.jdon.framework.samples.jpetstore.domain.Order">

                            <actionForm name="orderForm"/>

                            <handler class="com.jdon.framework.samples.jpetstore.presentation.action.OrderHandler"/>

                   </model>

直接定义了hanlder的class是OrderHandler,在OrderHandler中的serviceAction我们使用代码调用了OrderService的insertOrder方法,如果使用这样代码调用,无需要求OrderService的insertOrder的参数是EventModel了。

 

 

总结
Jpetstore整个开发大部分基于Jdon框架开发,特别是表现层,很少直接接触使用struts原来功能,Jdon框架的表现层架构基本让程序员远离了struts的烦琐开发过程,又保证了struts的MVC实现。

Jpetstore中只有SignonAction这个类是直接继承struts的DispatchAction,这个功能如果使用基于J2EE容器的安全认证实现(见JdonNews),那么Jpetstore全部没有用到struts的Action,无需编写Action代码;ActionForm又都是Model的拷贝,Action和ActionForm是struts编码的两个主要部分,这两个部分被Jdon框架节省后,整个J2EE的Web层开发方便快速,而且容易得多。

这说明Jdon框架确实是一款快速开发J2EE工具,而且是非常轻量的。

- 作者: xiaoning_2005 2005年10月12日, 星期三 15:44  回复(0) |  引用(0) 加入博采

基于struts+spring+ibatis的轻量级J2EE开发

摘要:
本文是在JpetStore 基础上,采用Spring对其中间层(业务层)进行改造。使开发量进一步减少,同时又拥有了Spring的一些好处…


转载:转载请保留本信息,本文来自http://www.matrix.org.cn/resource/article/1/1183.html
大多数IT 组织都必须解决三个主要问题:1.帮助组织减少成本 2.增加并且保持客户 3.加快业务效率。完成这些问题一般都需要实现对多个业务系统的数据和业务逻辑的无缝访问,也就是说,要实施系统集成工程,以便联结业务流程、实现数据的访问与共享。
JpetStore 4.0是ibatis的最新示例程序,基于Struts MVC框架(注:非传统Struts开发模式),以ibatis作为持久化层。该示例程序设计优雅,层次清晰,可以学习以及作为一个高效率的编程模型参考。本文是在其基础上,采用Spring对其中间层(业务层)进行改造。使开发量进一步减少,同时又拥有了Spring的一些好处…

1. 前言
JpetStore 4.0是ibatis的最新示例程序。ibatis是开源的持久层产品,包含SQL Maps 2.0 和 Data Access Objects 2.0 框架。JpetStore示例程序很好的展示了如何利用ibatis来开发一个典型的J2EE web应用程序。JpetStore有如下特点:

ibatis数据层
POJO业务层
POJO领域类
Struts MVC
JSP 表示层
以下是本文用到的关键技术介绍,本文假设您已经对Struts,SpringFramewok,ibatis有一定的了解,如果不是,请首先查阅附录中的参考资料。

Struts 是目前Java Web MVC框架中不争的王者。经过长达五年的发展,Struts已经逐渐成长为一个稳定、成熟的框架,并且占有了MVC框架中最大的市场份额。但是Struts某些技术特性上已经落后于新兴的MVC框架。面对Spring MVC、Webwork2 这些设计更精密,扩展性更强的框架,Struts受到了前所未有的挑战。但站在产品开发的角度而言,Struts仍然是最稳妥的选择。本文的原型例子JpetStore 4.0就是基于Struts开发的,但是不拘泥于Struts的传统固定用法,例如只用了一个自定义Action类,并且在form bean类的定义上也是开创性的,令人耳目一新,稍后将具体剖析一下。
Spring Framework 实际上是Expert One-on-One J2EE Design and Development 一书中所阐述的设计思想的具体实现。Spring Framework的功能非常多。包含AOP、ORM、DAO、Context、Web、MVC等几个部分组成。Web、MVC暂不用考虑,JpetStore 4.0用的是更成熟的Struts和JSP;DAO由于目前Hibernate、JDO、ibatis的流行,也不考虑,JpetStore 4.0用的就是ibatis。因此最需要用的是AOP、ORM、Context。Context中,最重要的是Beanfactory,它能将接口与实现分开,非常强大。目前AOP应用最成熟的还是在事务管理上。
ibatis 是一个功能强大实用的SQL Map工具,不同于其他ORM工具(如hibernate),它是将SQL语句映射成Java对象,而对于ORM工具,它的SQL语句是根据映射定义生成的。ibatis 以SQL开发的工作量和数据库移植性上的让步,为系统设计提供了更大的自由空间。有ibatis代码生成的工具,可以根据DDL自动生成ibatis代码,能减少很多工作量。
2. JpetStore简述


2.1. 背景
最初是Sun公司的J2EE petstore,其最主要目的是用于学习J2EE,但是其缺点也很明显,就是过度设计了。接着Oracle用J2EE petstore来比较各应用服务器的性能。微软推出了基于.Net平台的 Pet shop,用于竞争J2EE petstore。而JpetStore则是经过改良的基于struts的轻便框架J2EE web应用程序,相比来说,JpetStore设计和架构更优良,各层定义清晰,使用了很多最佳实践和模式,避免了很多"反模式",如使用存储过程,在java代码中嵌入SQL语句,把HTML存储在数据库中等等。最新版本是JpetStore 4.0。

2.2. JpetStore开发运行环境的建立
1、开发环境

Java SDK 1.4.2
Apache Tomcat 4.1.31
Eclipse-SDK-3.0.1-win32
HSQLDB 1.7.2
2、Eclipse插件

EMF SDK 2.0.1:Eclipse建模框架,lomboz插件需要,可以使用runtime版本。
lomboz 3.0:J2EE插件,用来在Eclipse中开发J2EE应用程序
Spring IDE 1.0.3:Spring Bean配置管理插件
xmlbuddy_2.0.10:编辑XML,用免费版功能即可
tomcatPluginV3:tomcat管理插件
Properties Editor:编辑java的属性文件,并可以预览以及自动存盘为Unicode格式。免去了手工或者ANT调用native2ascii的麻烦。
3、示例源程序

ibatis示例程序JpetStore 4.0 http://www.ibatis.com/jpetstore/jpetstore.html
改造后的源程序(+spring)(源码链接)
2.3. 架构


图1 JpetStore架构图


图1 是JPetStore架构图,更详细的内容请参见JPetStore的白皮书。参照这个架构图,让我们稍微剖析一下源代码,得出JpetStore 4.0的具体实现图(见图2),思路一下子就豁然开朗了。前言中提到的非传统的struts开发模式,关键就在struts Action类和form bean类上。

struts Action类只有一个:BeanAction。没错,确实是一个!与传统的struts编程方式很不同。再仔细研究BeanAction类,发现它其实是一个通用类,利用反射原理,根据URL来决定调用formbean的哪个方法。BeanAction大大简化了struts的编程模式,降低了对struts的依赖(与struts以及WEB容器有关的几个类都放在com.ibatis.struts包下,其它的类都可以直接复用)。利用这种模式,我们会很容易的把它移植到新的框架如JSF,spring。

这样重心就转移到form bean上了,它已经不是普通意义上的form bean了。查看源代码,可以看到它不仅仅有数据和校验/重置方法,而且已经具有了行为,从这个意义上来说,它更像一个BO(Business Object)。这就是前文讲到的,BeanAction类利用反射原,根据URL来决定调用form bean的哪个方法(行为)。form bean的这些方法的签名很简单,例如:

public String myActionMethod() {
//..work
return "success";
}

方法的返回值直接就是字符串,对应的是forward的名称,而不再是ActionForward对象,创建ActionForward对象的任务已经由BeanAction类代劳了。

另外,程序还提供了ActionContext工具类,该工具类封装了request 、response、form parameters、request attributes、session attributes和 application attributes中的数据存取操作,简单而线程安全,form bean类使用该工具类可以进一步从表现层框架解耦。

在这里需要特别指出的是,BeanAction类是对struts扩展的一个有益尝试,虽然提供了非常好的应用开发模式,但是它还非常新,一直在发展中。

图2 JpetStore 4.0具体实现


2.4. 代码剖析
下面就让我们开始进一步分析JpetStore4.0的源代码,为下面的改造铺路。

BeanAction.java是唯一一个Struts action类,位于com.ibatis.struts包下。正如上文所言,它是一个通用的控制类,利用反射机制,把控制转移到form bean的某个方法来处理。详细处理过程参考其源代码,简单明晰。
Form bean类位于com.ibatis.jpetstore.presentation包下,命名规则为***Bean。Form bean类全部继承于BaseBean类,而BaseBean类实际继承于ActionForm,因此,Form bean类就是Struts的 ActionForm,Form bean类的属性数据就由struts框架自动填充。而实际上,JpetStore4.0扩展了struts中ActionForm的应用: Form bean类还具有行为,更像一个BO,其行为(方法)由BeanAction根据配置(struts-config.xml)的URL来调用。虽然如此,我们还是把Form bean类定位于表现层。

Struts-config.xml的配置里有3种映射方式,来告诉BeanAction把控制转到哪个form bean对象的哪个方法来处理。

以这个请求连接为例http://localhost/jpetstore4/shop/viewOrder.do

1. URL Pattern


name="orderBean" scope="session"
validate="false">

此种方式表示,控制将被转发到"orderBean"这个form bean对象 的"viewOrder"方法(行为)来处理。方法名取"path"参数的以"/"分隔的最后一部分。

2. Method Parameter


name="orderBean" parameter="viewOrder" scope="session"
validate="false">

此种方式表示,控制将被转发到"orderBean"这个form bean对象的"viewOrder"方法(行为)来处理。配置中的"parameter"参数表示form bean类上的方法。"parameter"参数优先于"path"参数。

3. No Method call


name="orderBean" parameter="*" scope="session"
validate="false">

此种方式表示,form bean上没有任何方法被调用。如果存在"name"属性,则struts把表单参数等数据填充到form bean对象后,把控制转发到"success"。否则,如果name为空,则直接转发控制到"success"。

这就相当于struts内置的org.apache.struts.actions.ForwardAction的功能


parameter="/order/ViewOrder.jsp " scope="session" validate="false">

Service类位于com.ibatis.jpetstore.service包下,属于业务层。这些类封装了业务以及相应的事务控制。Service类由form bean类来调用。
com.ibatis.jpetstore.persistence.iface包下的类是DAO接口,属于业务层,其屏蔽了底层的数据库操作,供具体的Service类来调用。DaoConfig类是工具类(DAO工厂类),Service类通过DaoConfig类来获得相应的DAO接口,而不用关心底层的具体数据库操作,实现了如图2中{耦合2}的解耦。
com.ibatis.jpetstore.persistence.sqlmapdao包下的类是对应DAO接口的具体实现,在JpetStore4.0中采用了ibatis来实现ORM。这些实现类继承BaseSqlMapDao类,而BaseSqlMapDao类则继承ibatis DAO 框架中的SqlMapDaoTemplate类。ibatis的配置文件存放在com.ibatis.jpetstore.persistence.sqlmapdao.sql目录下。这些类和配置文件位于数据层
Domain类位于com.ibatis.jpetstore.domain包下,是普通的javabean。在这里用作数据传输对象(DTO),贯穿视图层、业务层和数据层,用于在不同层之间传输数据。
剩下的部分就比较简单了,请看具体的源代码,非常清晰。

2.5. 需要改造的地方
JpetStore4.0的关键就在struts Action类和form bean类上,这也是其精华之一(虽然该实现方式是试验性,待扩充和验证),在此次改造中我们要保留下来,即控制层一点不变,表现层获取相应业务类的方式变了(要加载spring环境),其它保持不变。要特别关注的改动是业务层和持久层,幸运的是JpetStore4.0设计非常好,需要改动的地方非常少,而且由模式可循,如下:

1. 业务层和数据层用Spring BeanFactory机制管理。

2. 业务层的事务由spring 的aop通过声明来完成。

3. 表现层(form bean)获取业务类的方法改由自定义工厂类来实现(加载spring环境)。

3. JPetStore的改造


3.1. 改造后的架构

其中红色部分是要增加的部分,蓝色部分是要修改的部分。下面就让我们逐一剖析。

3.2. Spring Context的加载
为了在Struts中加载Spring Context,一般会在struts-config.xml的最后添加如下部分:


value="/WEB-INF/applicationContext.xml" />

Spring在设计时就充分考虑到了与Struts的协同工作,通过内置的Struts Plug-in在两者之间提供了良好的结合点。但是,因为在这里我们一点也不改动JPetStore的控制层(这是JpetStore4.0的精华之一),所以本文不准备采用此方式来加载ApplicationContext。我们利用的是spring framework 的BeanFactory机制,采用自定义的工具类(bean工厂类)来加载spring的配置文件,从中可以看出Spring有多灵活,它提供了各种不同的方式来使用其不同的部分/层次,您只需要用你想用的,不需要的部分可以不用。

具体的来说,就是在com.ibatis.spring包下创建CustomBeanFactory类,spring的配置文件applicationContext.xml也放在这个目录下。以下就是该类的全部代码,很简单:

public final class CustomBeanFactory {
static XmlBeanFactory factory = null;
static {
Resource is = new
InputStreamResource( CustomBeanFactory.class.getResourceAsStream("applicationContext.xml"));
factory = new XmlBeanFactory(is);
}
public static Object getBean(String beanName){
return factory.getBean(beanName);
}
}

实际上就是封装了Spring 的XMLBeanFactory而已,并且Spring的配置文件只需要加载一次,以后就可以直接用CustomBeanFactory.getBean("someBean")来获得需要的对象了(例如someBean),而不需要知道具体的类。CustomBeanFactory类用于{耦合1}的解耦。

CustomBeanFactory类在本文中只用于表现层的form bean对象获得service类的对象,因为我们没有把form bean对象配置在applicationContext.xml中。但是,为什么不把表现层的form bean类也配置起来呢,这样就用不着这CustomBeanFactory个类了,Spring会帮助我们创建需要的一切?问题的答案就在于form bean类是struts的ActionForm类!如果大家熟悉struts,就会知道ActionForm类是struts自动创建的:在一次请求中,struts判断,如果ActionForm实例不存在,就创建一个ActionForm对象,把客户提交的表单数据保存到ActionForm对象中。因此formbean类的对象就不能由spring来创建,但是service类以及数据层的DAO类可以,所以只有他们在spring中配置。

所以,很自然的,我们就创建了CustomBeanFactory类,在表现层来衔接struts和spring。就这么简单,实现了另一种方式的{耦合一}的解耦。

3.3. 表现层
面分析到,struts和spring是在表现层衔接起来的,那么表现层就要做稍微的更改,即所需要的service类的对象创建上。以表现层的AccountBean类为例:


原来的源代码如下

private static final AccountService accountService = AccountService.getInstance();
private static final CatalogService catalogService = CatalogService.getInstance();

改造后的源代码如下

private static final AccountService accountService = (AccountService)CustomBeanFactory.getBean("AccountService");
private static final CatalogService catalogService = (CatalogService)CustomBeanFactory.getBean("CatalogService");

其他的几个presentation类以同样方式改造。这样,表现层就完成了。于表现层的其它部分如JSP等一概不动。也许您会说,没有看出什么特别之处的好处啊?你还是额外实现了一个工厂类。别着急,帷幕刚刚开启,spring是在表现层引入,但您发没发现:

presentation类仅仅面向service类的接口编程,具体"AccountService"是哪个实现类,presentation类不知道,是在spring的配置文件里配置。(本例中,为了最大限度的保持原来的代码不作变化,没有抽象出接口)。Spring鼓励面向接口编程,因为是如此的方便和自然,当然您也可以不这么做。
CustomBeanFactory这个工厂类为什么会如此简单,因为其直接使用了Spring的BeanFactory。Spring从其核心而言,是一个DI容器,其设计哲学是提供一种无侵入式的高扩展性的框架。为了实现这个目标,Spring 大量引入了Java 的Reflection机制,通过动态调用的方式避免硬编码方式的约束,并在此基础上建立了其核心组件BeanFactory,以此作为其依赖注入机制的实现基础。org.springframework.beans包中包括了这些核心组件的实现类,核心中的核心为BeanWrapper和BeanFactory类。
3.4. 持久层
在讨论业务层之前,我们先看一下持久层,如下图所示:


在上文中,我们把iface包下的DAO接口归为业务层,在这里不需要做修改。ibatis的sql配置文件也不需要改。要改的是DAO实现类,并在spring的配置文件中配置起来。

1、修改基类

所有的DAO实现类都继承于BaseSqlMapDao类。修改BaseSqlMapDao类如下:

public class BaseSqlMapDao extends SqlMapClientDaoSupport {
protected static final int PAGE_SIZE = 4;
protected SqlMapClientTemplate smcTemplate = this.getSqlMapClientTemplate();
public BaseSqlMapDao() {
}
}

使BaseSqlMapDao类改为继承于Spring提供的SqlMapClientDaoSupport类,并定义了一个保护属性smcTemplate,其类型为SqlMapClientTemplate。关于SqlMapClientTemplate类的详细说明请参照附录中的"Spring中文参考手册"

2、修改DAO实现类

所有的DAO实现类还是继承于BaseSqlMapDao类,实现相应的DAO接口,但其相应的DAO操作委托SqlMapClientTemplate来执行,以AccountSqlMapDao类为例,部分代码如下:

public List getUsernameList() {
return smcTemplate.queryForList("getUsernameList", null);
}
public Account getAccount(String username, String password) {
Account account = new Account();
account.setUsername(username);
account.setPassword(password);
return (Account) smcTemplate.queryForObject("getAccountByUsernameAndPassword", account);
}
public void insertAccount(Account account) {
smcTemplate.update("insertAccount", account);
smcTemplate.update("insertProfile", account);
smcTemplate.update("insertSignon", account);
}

就这么简单,所有函数的签名都是一样的,只需要查找替换就可以了!

3、除去工厂类以及相应的配置文件

除去DaoConfig.java这个DAO工厂类和相应的配置文件dao.xml,因为DAO的获取现在要用spring来管理。

4、DAO在Spring中的配置(applicationContext.xml)

class="org.springframework.jdbc.datasource.DriverManagerDataSource">

org.hsqldb.jdbcDriver


jdbc:hsqldb:hsql://localhost/xdb


sa






class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">


classpath:com\ibatis\jpetstore\persistence\sqlmapdao\sql\sql-map-config.xml







class="org.springframework.jdbc.datasource.DataSourceTransactionManager">





class="com.ibatis.jpetstore.persistence.sqlmapdao.AccountSqlMapDao">



具体的语法请参照附录中的"Spring中文参考手册"。在这里只简单解释一下:

1. 我们首先创建一个数据源dataSource,在这里配置的是hsqldb数据库。如果是ORACLE数据库,driverClassName的值是"oracle.jdbc.driver.OracleDriver",URL的值类似于"jdbc:oracle:thin:@wugfMobile:1521:cdcf"。数据源现在由spring来管理,那么现在我们就可以去掉properties目录下database.properties这个配置文件了;还有不要忘记修改sql-map-config.xml,去掉对它的引用。

2. sqlMapClient节点。这个是针对ibatis SqlMap的SqlMapClientFactoryBean配置。实际上配置了一个sqlMapClient的创建工厂类。configLocation属性配置了ibatis映射文件的名称。dataSource属性指向了使用的数据源,这样所有使用sqlMapClient的DAO都默认使用了该数据源,除非在DAO的配置中另外显式指定。

3. TransactionManager节点。定义了事务,使用的是DataSourceTransactionManager。

4. 下面就可以定义DAO节点了,如AccountDao,它的实现类是com.ibatis.jpetstore.persistence.sqlmapdao.AccountSqlMapDao,使用的SQL配置从sqlMapClient中读取,数据库连接没有特别列出,那么就是默认使用sqlMapClient配置的数据源datasource。

这样,我们就把持久层改造完了,其他的DAO配置类似于AccountDao。怎么样?简单吧。这次有接口了:) AccountDao接口->AccountSqlMapDao实现。

3.5. 业务层
业务层的位置以及相关类,如下图所示:


在这个例子中只有3个业务类,我们以OrderService类为例来改造,这个类是最复杂的,其中涉及了事务。

1、在ApplicationContext配置文件中增加bean的配置:

class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">


















PROPAGATION_REQUIRED


定义了一个OrderService,还是很容易懂的。为了简单起见,使用了嵌套bean,其实现类是com.ibatis.jpetstore.service.OrderService,分别引用了ItemDao,OrderDao,SequenceDao。该bean的insert*实现了事务管理(AOP方式)。TransactionProxyFactoryBean自动创建一个事务advisor, 该advisor包括一个基于事务属性的pointcut,因此只有事务性的方法被拦截。

2、业务类的修改

以OrderService为例:

public class OrderService {

/* Private Fields */
private ItemDao itemDao;
private OrderDao orderDao;
private SequenceDao sequenceDao;

/* Constructors */

public OrderService() {
}

/**
* @param itemDao 要设置的 itemDao。
*/
public final void setItemDao(ItemDao itemDao) {
this.itemDao = itemDao;
}
/**
* @param orderDao 要设置的 orderDao。
*/
public final void setOrderDao(OrderDao orderDao) {
this.orderDao = orderDao;
}
/**
* @param sequenceDao 要设置的 sequenceDao。
*/
public final void setSequenceDao(SequenceDao sequenceDao) {
this.sequenceDao = sequenceDao;
}
//剩下的部分
…….
}

红色部分为修改部分。Spring采用的是Type2的设置依赖注入,所以我们只需要定义属性和相应的设值函数就可以了,ItemDao,OrderDao,SequenceDao的值由spring在运行期间注入。构造函数就可以为空了,另外也不需要自己编写代码处理事务了(事务在配置中声明),daoManager.startTransaction();等与事务相关的语句也可以去掉了。和原来的代码比较一下,是不是处理精简了很多!可以更关注业务的实现。

4. 结束语
ibatis是一个功能强大实用的SQL Map工具,可以直接控制SQL,为系统设计提供了更大的自由空间。其提供的最新示例程序JpetStore 4.0,设计优雅,应用了迄今为止很多最佳实践和设计模式,非常适于学习以及在此基础上创建轻量级J2EE WEB应用程序。JpetStore 4.0是基于struts的,本文在此基础上,最大程度保持了原有设计的精华以及最小的代码改动量,在业务层和持久化层引入了Spring。在您阅读了本文以及改造后的源代码后,会深切的感受到Spring带来的种种好处:自然的面向接口的编程,业务对象的依赖注入,一致的数据存取框架和声明式的事务处理,统一的配置文件…更重要的是Spring既是全面的又是模块化的,Spring有分层的体系结构,这意味着您能选择仅仅使用它任何一个独立的部分,就像本文,而它的架构又是内部一致。

参考资料

jpetstore相关各种资料和源程序 http://www.ibatis.com/jpetstore/jpetstore.html


Spring中文参考手册http://www.jactiongroup.net/reference/html/index.html


Spring 开发指南 夏昕


Struts http://struts.apache.org/


- 作者: china dragon 2005年10月12日, 星期三 11:07  回复(0) |  引用(0) 加入博采

人生必读的60本书

1《圣经》
2《论语》
3《物种起源》
4《全球通史》
5《君王论》
6《史记》
7《孙子兵法》
8《三国演义》
9《战争论》
10《水浒传》
11《曾国藩家书》
12《毛泽东传》
13《汤姆叔叔的小屋》
14《红与黑》
15《红楼梦》 
16《悲惨世界》
17《百年孤独》
18《老人与海》
19《国富论》
20《胡雪岩全传》
21《飘》
22《钢铁是怎样练成的》
23《西游记》
24《呐喊》
25《小王子》
26《本草纲目》
27《昆虫记》
28《倾城之恋》
29《一千零一夜》
30《堂吉诃德》 
31《变形记》
32《吉檀迦利》
33《欧也妮.葛朗台》
34《哈姆莱特》
35《简.爱》
36《雷雨》
37《茶花女》
38《神曲》
39《梦的解析》
40《雪国》
41《边城》
42《喧哗与骚动》
43《复活》
44《罪与罚》
45《四世同堂》
46《生命中不能承受之轻》
47《围城》
48《情人》
49《西厢记》
50《苏菲的世界》
51《瓦尔登湖》
52《穆斯林的葬礼》
53《冰心散文选》
54《日瓦戈医生》
55《美的历程》
56《麦田里的守望者》
57《伊索寓言》
58《活着》
59《我的精神家园》
60《我与地坛》 

- 作者: xiaoning_2005 2005年10月8日, 星期六 13:21  回复(4) |  引用(0) 加入博采

约定
雄与熊间,总是会有一些约定.约定体现了一种此雄的魅力与深度,彼熊的欣赏与崇拜,是何等豪爽,是何等洒脱.我本简单,崇尚着简单是真;我本平凡,喜欢着雄熊相惜的感觉。一本书,一门技术,一个约定(熊的请求),未来岂不一切美哉。公月2005年12月24日,再回来。

- 作者: xiaoning_2005 2005年09月24日, 星期六 17:44  回复(0) |  引用(0) 加入博采

0921
1.He is in conference.
他正在开会。
2.How can I get in touch with you?
我怎样能跟你联络上?
3.How do I look?
我看上去怎么样?
4.How is it going?
情况怎么样?
5.How long did it last?
持续了多久?
6.I bet you can.
我确信你能做到。
7.I can't resist the temptation.
我不能抵挡诱惑。
8.I couldn't agree more.
我完全同意。
9.I couldn't get through.
我打不通电话。
10.I feel the same way.
我也有同感。
11.I have nothing to do with it.
那与我无关。
12.I enjoy your company.
我喜欢有你做伴。
13.I'd like a refund.
我想要退款。
14.I wouldn't worry about it, if I were you.
如果我是你,我就不会担心。
15.I wasn't born yesterday.
我又不是三岁小孩。
16.I'm not feling well.
我感觉不舒服。
17.How late are you open?
你们营业到几点?
18.How often do you eat out?
你个多就在外面吃一次饭?
19.I can't stand it.
我受不了。
20.I didn't mean to.
我不是故意的
21.I mean what I say.
我说话算数。
22.I was just about to call you.
我正准备打电话给你。
23.I'll get it.
我去接电话。
24.I'll treat you to diner.
我想请你吃晚饭。
25.I'm easy to please.
我很随和。
26.I'm just having a look.
我只是随便看看。
27.I'm not myself today.
我今天心神不宁。
28.I'm pressed for time.
我赶时间。
29.I'm working on it.
我正在努力。
30.I've got my hands full.
我手头正忙。
31.Is this seat taken?
这位子有人坐吗?
32.It's a deal.
一言为定。
33.It's a once in a lifetime chance.
这是一生难得的机会。

- 作者: xiaoning_2005 2005年09月21日, 星期三 08:58  回复(0) |  引用(0) 加入博采

企业营销篇(一)
WOW!
争论中才能前行。
我很喜欢版主说“做广告是一门艺术”。
广告应归于市场部的管理范畴。市场部对产品的布局是重中之重。没有一个好的市场规划,只是充满了爱国情怀的满腔热血,只是随意在网上发几个贴子,那是对自己的产品的不尊重,对用户也不是一种尊重。
一个创业者,心底深处充满了爱国情怀,值得敬佩。但一个企业却无法担负国家昌盛的重任,毕竟企业只是一个利益的实体,生存,发展才是其责任中的正道。
至于IT产品营销,我个人有一些愚见:
1、与有影响力的网站合作。
2、与经销商合作。
3、与媒体合作。
这看似简单的营销渠道,只要多投点钱,也就OK了。但不要忘了,你手中的资源是有限的,你要将你的可周转的资金用到刀刃上,而将资金如何用到刀刃上,这就不能不说是一种艺术了,其中充满了无数的玄机与奥妙。比如,你要与网站合作推销你的产品,你首先就要分析你能给你的合作伙伴带来什么,如果你的产品足够有吸引力,可以给你的合作伙伴带来实在的好处,可能零投资也给你带来很好的宣传效果。天下熙攘无非是利来往,平衡好你与合作方的利益,这样才能出现双赢的局面。当然这一切考验着公司的运筹与公关的能力了。

纸上谈兵,道听途说,不妨一笑而过。

- 作者: xiaoning_2005 2005年08月14日, 星期日 11:23  回复(0) |  引用(0) 加入博采

《老友记》笔记 104

【104】The One With George Stephanopoulos

1.Phoebe: Okay, okay. If I were omnipotent for a day, I would want, um, world peace, no more hunger, good things for the rain-forest...And bigger boobs! 几个老友一起聊天,Phoebe说如果她能做一天无所不能的上帝,她就会希望世界和平、没有饥饿、不会滥伐森林以及有大的boobs。Phoebe心地很善良,从上一集她把意外之财送给无家可归的流浪人就可窥见一斑,所以她的愿望都是关于人类发展生存的,当然咯,她也没忘说需要大的boobs,呵呵,想必是小小的恶作剧,挖苦一下Monica吧。Boob常见的意思是蠢材、笨蛋,不过在俚语中也有胸脯、乳房的意思。这之后更精彩的是Joey,他也没听清楚别人到底讨论什么,就来了一句,说如果是他的话,不如死掉算了,大家莫名其妙,还好Ross最了解他。原来,Joey听成如果他阳痿(impotent)了……,呵呵,搞笑吧,看来这部电视剧中很多有关Sex的笑话哦。

 

2. Ross: Today's the day Carol and I first.. consummated our physical relationship. (Joey is puzzled.) Sex. ..You know what, I-I'd better pass on the game. I think I'm just gonna go home and think about my ex-wife and her lesbian lover.这话也只有考古学家(paleontologist)的Ross能说得出来了,居然会用consummate physical relationship来形容他和Carol的第一次。另外一个词组是pass on sth,相当于let sth pass,它还有去世(相当于pass away)和传递的意思,有时候我们也可以说pass on my best wish to sb,请代为转达我最美好的祝愿。

 

3. Ross: Alright, alright, maybe it'll take my mind off it. Do you promise to buy me a big thumb finger? 这里的big thumb finger可能有些费解,不过联系上集中Phoebe喝汽水喝出7000美刀,就可明白了。整句意思是Ross最后同意去看曲棍球比赛,因为可能可以让他不去想和Carol一起的事情,不过要chandler请喝汽水。

 

4.Rachel: God, isn't this exciting? I earned this. I wiped tables for it, I steamed milk for it, and it was totally—(opens envelope)—not worth it. Who's FICA? Why's he getting all my money? I mean, what- Chandler, look at that.Rach非常兴奋,因为终于发工资了,对于一个以前一直在父母温床下生活的人来说,抹桌子、热牛奶还真不容易(虽然总是把别人要的东西搞错),她乐坏了,可是马上发现她的钱都被FICA拿走了,FICA是Federal Insurance Contributions Act的缩写,是联邦社会保险捐款法的意思。

 

5. Chandler: Excuse me, look, we've been here for over an hour, and a lot of people less sick than my friend have gone in. I mean, that guy with the toe thing? Who's he sleeping with? (She slides the gladd panel over and Chandler talks through it in a loud voice.) Oh, c'mon Dora, don't be mad... I know we both said some things we didn't mean, but that doesn't mean we still don't love each other. (To the waiting room.) Y'know, I feel like I've lost her.. (She slides the panel back, he turns, and it takes him by surprise.) Ba-!三个好友一起去看冰球赛,结果Ross光荣负伤,于是紧急送到医院,不料那个值班员正在煲电话粥,态度不好,Chandler好言好语却毫无效果,于是发了点小脾气,没料到吃了闭门羹,所以他阴阳怪气的来了一段台词….

 

6. Ross: I remember the moonlight coming through the window- and her face had the most incredible glow.温柔的月光透过窗户照在她的脸上,显得神采奕奕、容光焕发。真是情人眼里出西施啊。

 

7. Kid: I found it. Finders keepers, losers weepers. (Ross looks at Chandler for help.) 小时候大家都玩过的赖皮童谣,东西掉了,谁拣到的就是谁的。

 

8. Rss: (to the kid) Oh yeah? Well, I'm rubber, you're glue, whatever—(to Chandler)—can't do it. (to the kid) Listen, uh- gimme back my puck.Ross用重创换来的puck居然被小孩拿去了,还阵阵有词的说谁拣到就归谁,Chandler怂恿他也来一段儿童谣,他鼓足勇气说了一半,终究还是捺不下面子。“I’m rubber,you’re glue,whatever (you say bounces off me and stick to you)”在Friends中出现了好几次,这里显然也是小孩子们拌嘴皮子时候用的。意思是我是橡皮你是胶水,你说的所有坏话都会反弹回去粘在你身上,引申过来也就是:你说我坏话实际就是骂你自己。

puck除了冰球的意思之外,还有恶作剧的小精灵的意思(来自一个英格兰民间传说)。

- 作者: xiaoning_2005 2005年07月31日, 星期日 15:59  回复(0) |  引用(0) 加入博采