|
摘要:JavaServer Faces作为一种新兴的Web表现层框架,正在受到越来越多的关注。本文描述了JSF的几大优势,以及这些优势所带来的Web开发的重大变革,从而试图说明JSF将会在众多竞争者中脱颖而出,成为Web表现层框架的主流。
1. 引子:我与JSF的第一次亲密接触
2004年3月,当我着手开发我的第一个Web程序时,我选择了JSP。作为一个传统的桌面程序员,而且是老程序员,向Web程序员的转变是异常痛苦的。3个月的时间,程序总算完成了,但从此对JSP恨之入骨。凌乱的书写格式,数据和界面的混杂,尤其是嵌入到页面里的Scriptlet,让我搞不清自己是编程序的还是写网页的。
因此,当接到第二版的开发任务时,我毫不犹豫地放弃了JSP,寻找一种替代技术。上网一搜,却发现框架多如牛毛,评论文章各执一词,莫衷一是,让我彻底迷失了。犹豫摇摆不定之际,sun的J2EE Tutorial文档中关于JavaServer Faces技术的介绍吸引了我:UI、component、event、listener这些在桌面程序中熟悉的字眼,让我在Web开发中找到了桌面程序员的一些感觉。
象开发桌面程序那样开发web程序,这是我选择JSF的初衷。基于这样肤浅的认识,跌跌撞撞上路了,在工期和新技术的双重压力之下,超负荷的工作令人透不过气来,但每每从JSF中发掘出令人惊喜的新特性,又给我带来极大的满足感。第二版终于完成时,日历恰好翻过一个整月。JSF带来的效率提升是显著的。
事实上,到现在为止,我对于JSF还只能说是初步了解,远未达到掌握,更谈不上精通,但这并不妨碍我视JSF为Web开发的首选框架。尤其是对于新手,如果还没有在Struts、Tiles、Spring、Tapestry等框架中走得太远,那么,集中你有限的精力踏上JSF之路吧。
2. JSF优势之一:UI组件(UI-component)
UI组件(UI-component)一直是桌面程序的专利,web程序中,虽然HTML定义了基本的UI标签,但要使这些UI标签像UI组件那样工作,还需要很多代码片断来处理数据及其表现形式,而且有效地组织这些代码片断使其协调一致也是一件繁琐的工作。JSF的UI组件是真正意义上的UI组件,能极大地简化程序员的工作,例如,在页面上放置一个文本输入框,这个输入框立即具备了数据填充、界面更新、事件侦听、动作触发、有效性检查和类型转换的功能。更为重要的是,程序员只需根据业务逻辑编写核心业务代码,JSF会保证代码在合适的时候被执行,完全不用考虑代码与代码之间该如何来配合。
3. JSF优势之二:事件驱动模式
事件是面向对象方法的重要组成部分,对象之间通过事件进行沟通和交流,使得一个或多个对象能够对另一个对象的行为作出响应,共同合作去完成一项业务逻辑。通常,编写Web程序时,程序员要为对象之间的沟通设计机制,编写代码。虽然沟通的内容属于业务逻辑,但沟通的机制显然与业务没有太大关系,程序员因此为业务逻辑之外的功能浪费了时间。JSF改变了这种状况。JSF的事件和侦听模式与大家熟悉的Javabean的事件模式类似,有Java基础的程序员并不需要学习任何新的东西。JSF的UI组件可以产生事件,例如,当页面上一个文本输入框的内容被修改时,会发出一个“值改变事件”。另一个对象如果对“值改变事件”感兴趣,只需注册为该对象的侦听者,并编写处理例程,即可命令JSF在事件发生时自动调用处理例程。JSF做了所有该做的事,留给程序员的只有业务逻辑代码的编写。
4. JSF优势之三:用户界面到业务逻辑的直接映射
举个例子,表单提交是Web编程最常见的任务,也是最复杂的任务之一。当用户在网页上点击“确定”按钮时,浏览器将生成一个HTTP请求,发往服务器端的某个Servlet,执行该Servlet的service方法。在service方法中,HTTP请求需要经历解码、类型转换、有效性验证、状态保存、数据更新等环节,处理这些环节的所有细节,对程序员来说是沉重的负担。在JSF下,这些工作的很大一部分都由框架承担了,在程序员看来,这个过程是透明的,用户界面端的HTTP请求可以直接映射到后端的一个事件处理例程,JSF起到了承前启后的作用。
5. JSF优势之四:程序员和网页设计人员的分工
在JSP中,程序员和网页设计人员的工作有时候是互相交织、无法区分的。这是因为JSP页面中掺入了网页设计人员所不熟悉的一些JSP标签,甚至是晦涩的Java代码。要求网页设计人员理解这些标签和代码是不现实的,不符合分工合作的原则。在JSF中,框架为网页设计人员提供了一套标准的UI组件,在工具的支持下,可以通过拖放简单地添加到网页上,然后设置某些显示属性来满足视觉要求。网页设计人员不需要知道UI组件背后的复杂代码,那是程序员的事,而程序员也不需要再处理任何与视觉相关的细节,程序员所做的只是给UI组件绑定类的属性或方法。虽然程序员和网页设计人员需要修改同一份文件,但他们各司其职,各得其所,互不干扰。程序员和网页设计人员工作的明确划分,是JSF在易用性方面迈出的一大步。
6. JSF优势之五:请求处理生命周期的多阶段划分
虽然都是建立在Servlet基础之上,但JSF的生命周期要比JSP复杂得多。JSP的生命周期非常简单,页面被执行时,HTML标记立即被生成了,生命周期随即结束。而一个完整的JSF请求-处理生命周期被精心规划为6个阶段,典型的JSF请求需要经历所有阶段,某些特殊的请求也可以跳过一些阶段。阶段的细分,显然引入了更多的处理,但JSF框架会管理这一切,所以,程序员在获得更多控制能力的同时,工作量并没有增加。
7. JSF优势之六:伴随工具而生存
JSF带来了Web编程的巨大变革,变革的强烈程度超出了很多工具厂商的预料,以至于现在可供JSF使用的工具非常缺乏。缺乏工具支持的JSF只会令人敬而远之,因此,JSF在设计之初就为工具厂商预留了用武之地。在为数不多的JSF工具中,sun的Java Studio Creator是一个优秀的开发环境;Borland的Jbuilder在JSF1.1时曾经是非常好用的开发工具,可惜现在对JSF1.2的支持没有跟上;Eclipse下JSF的插件很多,但真正支持所见即所得的JSF插件都是收费的,例如Bea的Workshop for JSF、Exadel的JSF Studio等等;此外,Oracle和IBM也有JSF的开发工具。随着工具的不断涌现,用JSF开发Web程序将会越来越方便和快速。
8. JSF优势之七:全面的用户自定义支持
前面提到,JSF将极大地简化Web程序的开发,作为一个相对复杂的框架,JSF是如何做到这点的呢?原来JSF为程序员提供了很多默认的组件和类,通常情况下,JSF的这些默认组件和类足以满足Web开发的需要了。但是,考虑到在某些应用场合,框架的默认行为也许不符合业务的要求,JSF特别允许程序员编写自己的组件和类,来满足客户的特殊需求。例如,程序员可以编写自己的UI组件,甚至可以创建自己的EL解释器,来支持非标准的EL表达语言。
9. JSF优势之八:Web开发的官方标准之一
JSF的1.0版本发布于2004年2月份,当时是作为一项独立的Web技术推出的。经过1.1版到现在最新的1.2版,短短的两年多时间,JSF终于在2006年年中成为Java EE 5的组成部分,上升为Web开发的官方标准之一。Java EE 5最重要的使命就是简化Java的开发,而JSF无疑为这一使命立下了汗马功劳。在Web框架层出不穷甚至有些泛滥成灾的今天,Sun以JSF来树立标准,对Java的发展是有益的。Sun在Java领域的领袖地位不容动摇,对于Java程序员来说,始终追随业界领袖的步伐,也许是避免技术落伍的最好方法。
10. 结束语:该你了,JSF!
考察某项技术的流行程度,google的关键字搜索不失为一种简便易行的方法。为了便于说明,我们选择目前最热门的Struts与JSF进行比较。在google中分别输入关键字“Struts”和“JSF”,看看google返回的网页数量。令我们感兴趣的不是网页的绝对数量,而是JSF网页数量与Struts网页数量的比值。我们看到,对于英文网页,这个比值是0.6,日文网页是1.0,繁体中文网页是0.8,而简体中文网页只有0.4。表1列出了具体的数据和比值。
英文网页数量(万) 日文网页数量(万) 繁体网页数量(万) 简体网页数量(万)
JSF Struts JSF Struts JSF Struts JSF Struts
719 1170 145 140 10 13 59 138
JSF / Struts = 0.6JSF / Struts = 1.0JSF / Struts = 0.8JSF / Struts = 0.4
虽然这样的比较方法不够严谨,但0.4的比例从一个侧面说明JSF在国内还没有流行起来,用“方兴未艾”四个字来描述JSF的这种状况,是再合适不过了。由于历史的原因,国内的软件技术一直亦步亦趋地跟着国外跑,这不是我们希望的,但我们不得不承认,因此,从国外的情况来推论,我们有理由相信,JSF必将成为国内程序员追捧的对象。正如某位哲人说的那样,JSF是早晨八、九点钟的太阳,希望寄托在JSF身上。
11. 后记:不同的声音
客观地说,JSF并非完美,业界对JSF的评价也褒贬不一。例如,由于JSF过于复杂,其学习曲线明显长于其他框架如Struts等,这在一定程度上妨碍了JSF的推广;此外,JSF的推出略显仓促,1.0版在使用中发现很多bug,以至于sun匆忙发布的1.1版主要是为了修正这些bug;还有,在JSF1.2版之前,JSP和JSF的融合有严重的缺陷,这主要是由于二者不同的生命周期造成的,不过,1.2版在这方面作出了改进,现在,JSP和JSF可以在一个项目中相安无事了。
JSF的不足之处还有很多,比如UI组件不够丰富、具体实现的可选择余地过窄、使用JSF开发的实际项目不多、sun的参考实现还存在诸多bug、短期内缺乏工具支持等等,尤其是在国内,JSF的中文文档和书籍相当缺乏。但是,不管怎样,这些都是JSF成长道路上必须经历的磨难,我相信,Sun会努力的。
EJB的理想 | yongbing 转贴 更新:2007-01-03 16:50:45 版本: 1.0 |
| 摘要: EJB是一种企业应用技术,旨在建立一个企业应用开发框架,但从其诞生之日起,质疑之声一直不断。EJB是企业应用框架的先驱,在企业应用框架的方法论上有独到的见解,虽然存在不少缺陷,但仍不失为企业应用框架的理想。
1. 备受争议的EJB
EJB也许是Java领域里中最受争议的技术了。有人说EJB是最伟大的发明,也有人说EJB完全是多此一举;当一些人陶醉于EJB的深奥理论时,另外一些人却正被EJB的繁琐复杂所折磨;尝到EJB甜头的人,在EJB的每个新版本中,都能发现盼望已久的惊喜,而被EJB拒之门外的人,则随着EJB的升级,愈发对EJB敬而远之了。
EJB的全称是Enterprise JavaBeans,JavaBeans很普通,不过Enterprise就不那么简单了。什么技术,一旦被冠以Enterprise的名头,就像男人走入婚姻殿堂一样,身上的责任与单身汉不可同日而语了。从定义上看,JavaBeans只是J2SE平台上的一个组件架构,包含一些业务逻辑,并且可以被重用。
EJB不同,作为企业级的JavaBeans,Sun对EJB的定位要远远高于JavaBeans,所以EJB的目标也比JavaBeans要远大得多,除了作为一个包含业务逻辑的可重用组件外,EJB更被赋予了诸如“可移植”、“安全”、“可伸缩”、“交易性”等特征。
所有这些EJB必须具备的特征,其实正是企业应用所要求的。这也是Enterprise一词所代表的技术上的含义。企业应用不同于普通应用,企业应用是大规模的、高复杂度的和关键的,它所面临的挑战,要比普通应用艰巨得多。比如,企业应用对可移植性的要求非常高,这是因为,企业都不愿意将自己的未来绑定到某个供应商的身上,除非是不得已而为之;又比如,安全性对企业应用至关重要,谁能使用什么功能、哪些数据哪些人可以看到,都有严格的限制;更不用说的是企业应用的可伸缩性了,当业务规模变大时,你希望全盘推翻旧系统,采购一批崭新的软件和硬件,对IT系统来个彻底的革命吗?增加一台服务器就能应付更多的客户,我想这是头脑正常的企业家都希望的。
企业应用的需求,就是EJB的目标。用EJB开发的应用,完全符合企业应用的特征。EJB是一个规范,只要符合这个规范,EJB可以在不同的操作系统、不同的应用服务器中无缝地移植;EJB允许开发者在EJB部署描述文件中进行方法级的、基于角色的安全性配置,以统一的方式保护企业应用和数据的安全性;只要你愿意,EJB应用可以全部部署在一台单独的服务器上,也可以任何组合方式分布在一组服务器群中,满足你扩大规模和均衡负载的要求;如果你想保持事务的完整性,那么,EJB的事务管理是一个可靠的、稳健的解决方案。
这就是EJB,一个企业应用的集大成者,多种技术的浓缩精华,全能的框架和基础结构。可就是这样一个将企业应用的开发简化到了前所未有之程度的技术,却成为许多人口诛笔伐的对象。复杂、难以使用、性能低下、繁琐等等,从1998年EJB诞生之日起,各种各样的恶名就伴随左右,直到八年后的今天,当EJB迎来它的第三次大变脸时,质疑之声依然不绝于耳。EJB真的那么糟糕吗?
2. EJB是企业应用的先驱
笔者接触第一个企业应用,是在1997年。那时PowerBuilder风头正劲,不过,多数人使用PowerBuilder,是因为它的数据窗口。当时笔者在一个项目中遇到一个难题,那就是如何把一台服务器上的应用一分为二,跑在两台服务器上,以提高性能。这是典型的分布式应用,虽然不是一个完整意义上的企业应用,不过,因为应用中需要用到分布式的概念,多少也算和企业应用沾上边了。
PowerBuilder其实是个非常不错的开发工具,在1997年的时候,已经提出了分布式应用的概念,并且付诸实施了。在PowerBuilder中,一个组件可以有一个称为代理的对象,这个对象可以运行在与组件不同的机器上,其他组件通过代理可以访问该组件的功能。
这是一个很初级的分布式应用框架,不过,那时已经给了笔者很大的震动。我试着编了一个实验性质的程序,当我在一台机器上按下一个按钮时,另外一台机器上赫然弹出一个预期中的对话框,着实让我大吃一惊。没有任何Socket编程,也不需要关心实际的应用跑在哪台机器上,PowerBuilder让我首次见识了分布式应用框架的巨大威力。
PowerBuilder解决了分布的问题,但安全性和事务控制,仍然需要程序员自己想办法。十个程序员可以有十种解决方案,每种都不同,而每种都可能含有未经发现的缺陷。在EJB之前,企业应用的开发没有规范可循,每个公司都有自己的一套方案,尽管每个公司都对自己的方案充满信心,但其实这些未经大量应用考验的方案,都有着这样那样的不足或局限。
J2EE是第一个为业界所广为接受的完整的企业应用框架,而EJB在其中扮演重要角色。在J2EE框架的支持下,运行在EJB容器中的EJB,完全符合企业应用关于分布、移植、安全和交易的要求。这对于企业应用的开发者来说,意义非同寻常。首先,现在大家可以在一个公共的平台技术上构建自己的企业应用,不必绞尽脑汁“发明”自己的“轮子”,从而节省大量无谓的、重复性的技术和时间投入;其次,一个公开的平台,让大量的企业应用开发者有了共同语言,可以相互交流平台的使用经验和教训,这样,随着平台之上企业应用的不断增加,平台的优劣得失一览无遗,有利于平台的改进和发展。
这就是EJB为企业应用作出的贡献。在EJB之前,多数人不知企业应用为何物,或者虽然有企业应用的模糊概念,但要编写一个企业应用,谈何容易。不同的操作系统、不同的开发语言、不同的网络环境、不同的应用终端,开发一个企业应用,程序员面临着两难的抉择:要么限定应用的软硬件平台,或者牺牲应用的安全性、分布性或交易性,开发一个“伪”企业应用;要么下决心开发一个真正的企业应用,然后累死自己。
3. EJB是一种思想
EJB让程序员编写真正意义上的企业应用而不必累死,应该说,EJB已经是企业应用开发领域的一大进步,让企业应用和普通应用的开发差距缩短到了前所未有的程度,可是,为什么还有很多人抱怨EJB过于复杂呢?EJB的复杂性其实是个伪命题。所谓复杂,一定是相对的。如果和普通应用相比,EJB当然要复杂很多,因为人们对于普通应用没有企业应用那么苛刻的要求。但是,如果将EJB之前和EJB之后的企业应用开发的难度相比,相信人们就会对EJB赞誉有加了。举个不准确的例子,假如EJB之前,企业应用的难度是普通应用的10倍,那么,EJB之后,这个比例缩小到了5倍,批评EJB复杂者,只看到了还剩下的5倍复杂度,却没有看到EJB所减去的5倍复杂度。
关于EJB过于复杂的论断,还来自于与其竞争技术的比较。例如,Spring就是一个声称比EJB更简单的、轻量级的企业应用框架。Spring确实简单,很多人喜欢Spring,也正是因为Spring简单。可是,Spring的支持者们,忽略了一个基本的事实,那就是Spring的功能要比EJB弱,也就是说,Spring是通过放弃某些不常用的功能来达到简化目的的。Spring好比一辆没有安全气囊的车,尽管依然可以拉客跑运输,但一旦发生碰撞事故,也许司机会陪上性命。没有安全气囊的Spring,在制造上当然要比有安全气囊的EJB简单,而且轻便,跑得快,不过,Spring始终不适合投入正式营运。这就是为什么不推荐在大型企业应用上采用Spring的原因。没有完善的事务处理,不能提供7X24小时的服务,Spring迈不过关键企业应用的门槛。
与Spring形影不离的是Java对象持久化的“红人”Hibernate。Hibernate的矛头直指EJB的Entity Bean。Entity Bean,尤其是它的持久化技术,是最为程序员所诟病的,成为EJB挥之不去的阴影,并最终促成了Hibernate的辉煌。Hibernate其实并不精深,在技术上也没有太多值得称道的创新,但它的文档非常优秀。我知道很多程序员就是被Hibernate的文档所吸引的,他们只学过一些SQL初步,没有系统的关系数据库理论知识,Hibernate关于数据库表间关系的论述,深入浅出,十分精彩,让他们在对关系数据库的理解上有了迅猛突破的同时,Hibernate轻易的俘虏了他们的心。
Hibernate的成功,反衬了EJB在持久化方面的失败,但在我看来,这并不影响EJB的伟大。与其说EJB是一种技术,不如说EJB的是一种思想更恰当,而不论Hibernate还是Spring,只不过是一种工具,他们只是跟在EJB后面,发现了EJB的某些不足,然后有针对性地加以改进,以迎合普通程序员对于“技术快餐”的需求。
他们既没有从形形色色的企业应用中,抽象出隐藏在不同表现后面的本质特征,也没有创造性地用Stateless Session Bean和Stateful Session Bean来描述千变万化的现实世界。工具只是工具,不出两年就会有新的后起之秀,取而代之,但思想的光辉将长久地照亮技术的未来。EJB是一种思想,更是一种理想,尽管理想和现实总是存在差距,但这不能成为我们放弃EJB的理由。一种满足企业应用分布性、扩展性、安全性和交易性要求的、方便使用的框架技术,既是EJB的理想,也是广大程序员的理想。
|
|
|
当前流行的J2EE WEB应用架构分析
|
xuyy_cn 原创 更新:2006-09-14 13:04:12 版本: 1.0 |
|
1. 架构概述
J2EE体系包括java server pages(JSP) ,java SERVLET, enterprise bean,WEB service等技术。这些技术的出现给电子商务时代的WEB应用程序的开发提供了一个非常有竞争力的选择。怎样把这些技术组合起来形成一个适应项目需要的稳定架构是项目开发过程中一个非常重要的步骤。完成这个步骤可以形成一个主要里程碑基线。形成这个基线有很多好处:
各种因数初步确定
为了形成架构基线,架构设计师要对平台(体系)中的技术进行筛选,各种利弊的权衡。往往架构设计师在这个过程中要阅读大量的技术资料,听取项目组成员的建议,考虑领域专家的需求,考虑赞助商成本(包括开发成本和运行维护成本)限额。一旦架构设计经过评审,这些因数初步地就有了在整个项目过程中的对项目起多大作用的定位。
定向技术培训
一旦架构师设计的架构得到了批准形成了基线,项目开发和运行所采用的技术基本确定下来了。众多的项目经理都会对预备项目组成员的技术功底感到担心;他们需要培训部门提供培训,但就架构师面对的技术海洋,项目经理根本就提不出明确的技术培训需求。怎不能够对体系中所有技术都进行培训吧!有了架构里程碑基线,项目经理能确定这个项目开发会采用什么技术,这是提出培训需求应该是最精确的。不过在实际项目开发中,技术培训可以在基线确定之前与架构设计并发进行。
角色分工
有了一个好的架构蓝图,我们就能准确划分工作。如网页设计,JSP 标签处理类设计,SERVLET 设计,session bean设计,还有各种实现。这些任务在架构蓝图上都可以清晰地标出位置,使得项目组成员能很好地定位自己的任务。一个好的架构蓝图同时也能规范化任务,能很好地把任务划分为几类,在同一类中的任务的工作量和性质相同或相似。这样工作量估计起来有一个非常好的基础。
运行维护
前面说过各个任务在架构图上都有比较好的定位。任何人能借助它很快地熟悉整个项目的运行情况,错误出现时能比较快速地定位错误点。另外,有了清晰的架构图,项目版本管理也有很好的版本树躯干。
扩展性
架构犹如一颗参天大树的躯干,只要躯干根系牢,树干粗,长一些旁支,加一些树叶轻而易举无疑。同样,有一个稳定的经得起考验的架构,增加一两个业务组件是非常快速和容易的。
大家都知道这些好处,一心想形成一个这样的J2EE应用程序架构(就像在windows平台中的MFC)。在这个路程中经历了两个大的阶段:
1.1. 模型1
模型1其实不是一个什么稳定架构,甚至谈不上形成了架构。模型1的基础是JSP文件。它从HTTP的请求中提取参数,调用相应的业务逻辑,处理HTTP会话,最后生成HTTP文档。一系列这样的JSP文件形成一个完整的模型1应用,当然可能会有其他辅助类或文件。早期的ASP 和 PHP 技术就属于这个情况。
总的看来,这个模型的好处是简单,但是它把业务逻辑和表现混在一块,对大应用来说,这个缺点是令人容忍不了的。
1.2. 模型2
在经过一番实践,并广泛借鉴和总结经验教训之后,J2EE应用程序终于迎来了MVC(模型-视图-控制)模式。MVC模式并不是J2EE行业人士标新立异的,所以前面我谈到广发借鉴。MVC的核心就是做到三层甚至多层的松散耦合。这对基于组件的,所覆盖的技术不断膨胀的J2EE体系来说真是福音和救星。
它在浏览器(本文对客户代理都称浏览器)和JSP或SERVLET之间插入一个控制组件。这个控制组件集中了处理浏览器发过来的HTTP请求的分发逻辑,也就是说,它会根据HTTP请求的URL,输入参数,和目前应用的内部状态,把请求分发给相应的WEB 层的JSP 或SERVLET。另外它也负责选择下一个视图(在J2EE中,JSP,SERVLET会生成回给浏览器的html从而形成视图)。集中的控制组件也有利于安全验证,日志纪录,有时也封装请求数据给下面的WEB tier层。这一套逻辑的实现形成了一个像MFC的应用框架,位置如图:
1.3. 多层应用
下图为J2EE体系中典型的多层应用模型。
Client tier客户层
一般为浏览器或其他应用。客户层普遍地支持HTTP协议,也称客户代理。
WEB tier WEB应用层
在J2EE中,这一层由WEB 容器运行,它包括JSP, SERVLET等WEB部件。
EJB tier 企业组件层
企业组件层由EJB容器运行,支持EJB, JMS, JTA 等服务和技术。
EIS tier 企业信息系统层
企业信息系统包含企业内传统信息系统如财务,CRM等,特点是有数据库系统的支持。
应用框架目前主要集中在WEB层,旨在规范这一层软件的开发。其实企业组件层也可以实现这个模型,但目前主要以设计模式的形式存在。而且有些框架可以扩充,有了企业组件层组件的参与,框架会显得更紧凑,更自然,效率会更高。
2. 候选方案
目前,实现模型2的框架也在不断的涌现,下面列出比较有名的框架。
2.1. Apache Struts
Struts是一个免费的开源的WEB层的应用框架,apache软件基金致力于struts的开发。Struts具是高可配置的性,和有一个不断增长的特性列表。一个前端控制组件,一系列动作类,动作映射,处理XML的实用工具类,服务器端java bean 的自动填充,支持验证的WEB 表单,国际化支持,生成HTML,实现表现逻辑和模版组成了struts的灵魂。
2.1.1. Struts和MVC
模型2的目的和MVC的目的是一样的,所以模型2基本可以和MVC等同起来。下图体现了Struts的运作机理:
2.1.1.1. 控制
如图所示,它的主要部件是一个通用的控制组件。这个控制组件提供了处理所有发送到Struts 的HTTP请求的入口点。它截取和分发这些请求到相应的动作类(这些动作类都是Action类的子类)。另外控制组件也负责用相应的请求参数填充 From bean,并传给动作类。动作类实现核心商业逻辑,它可以通过访问java bean 或调用EJB。最后动作类把控制权传给后续的JSP 文件,后者生成视图。所有这些控制逻辑利用一个叫struts-config.xml文件来配置。
2.1.1.2. 模型
模型以一个或几个java bean的形式存在。这些bean分为三种:
Form beans(表单Beans)
它保存了HTTP post请求传来的数据,在Struts里,所有的Form beans都是 ActionFrom 类的子类。
业务逻辑beans
专门用来处理业务逻辑。
系统状态beans
它保存了跨越多个HTTP 请求的单个客户的会话信息,还有系统状态。
2.1.1.3. 视图
控制组件续传HTTP请求给实现了视图的JSP文件。JSP能访问beans 并生成结果文档反馈到客户。Struts提供JSP 标签库: Html,Bean,Logic,Template等来达到这个目的,并有利于分开表现逻辑和程序逻辑。
2.1.2. Struts的细节分析
2.1.2.1. 视图-控制-模型
用户发出一个*.do的HTTP请求,控制组件接收到这个请求后,查找针对这个请求的动作映射,再检查是否曾创建过相应的动作对象(action实例),如果没有则调用actionmapping生成一个动作对象,控制组件会保存这个动作对象供以后使用。接着调用actionmapping的方法得到actionForm对象。之后把actionForm作为参数传给动作对象的perform方法,这个方法结束之后会返回给控制组件一个 actionforward对象。控制组件接着从这个对象中获取下一个视图的路径和重定向属性。如果为重定向则调用HTTPSERVLETREPONSE的方法来显示下一个视图,否则相继调用requestdispatcher, SERVLETcontext的方法续传HTTP请求到下一个视图。
当动作对象运行perform方法时,可能出现错误信息。动作对象可以保存这些错误信息到一个error对象中,接着调用自身的saveerrors方法把这个错误保存到request对象的属性中。接着动作对象调用actionmapping对象的getInput方法从动作映射中获取input参数,也就是产生输入的视图,并以这个input为参数生成一个actionforward对象返回。这个input参数的JSP中一般有HTTP:errors定制标签读取这些错误信息并显示在页面上。
2.1.2.2. 模型到视图
模型到视图指视图在显示之前装载系统数据到视图的过程。系统数据一般为模型内java bean的信息。示意图表现了由控制组件forward过来的有html:form定制标签的JSP 的处理逻辑。
html:form定制标签处理对象从application scope(通过查询SERVLETCONTEXT对象的属性来实现)获取先前由控制组件actionSERVLET放在那里的动作映射等对象,由html:form 的action属性查得actionform名字、类型和范围等信息,在相应的范围内查找actionform,如果有则利用它的信息填充html form表单[实际填充动作在嵌套的html:text等定制标签的处理对象中]。否则在相应范围内创建一个actionform 对象。
2.1.3. 优缺点
优点:
一些开发商开始采用并推广这个框架
作为开源项目,有很多先进的实现思想
对大型的应用支持的较好
有集中的网页导航定义
缺点:
不是业届标准
对开发工具的支持不够
复杂的taglib,需要比较长的时间来掌握
html form 和 actionform的搭配比较封闭,但这也是它的精华所在。
修改建议
把actionform属性的设置器和访问器修改成读取或生成xml文档的方法,然后 html form和actionform之间用xml文档进行数据交换,使之松散耦合,适应数据结构易变化的应用。
2.2. JATO
JATO应用程序框架是iPlanet 应用程序框架的旧名。它是一个成熟的、强大的,基于J2EE标准的面向于开发WEB应用程序的应用框架。结合了显示字段、应用程序事件、组件层次和以页面为中心的开发方法、以及MVC和服务到工作者service-to-workers的设计模式等概念。JATO可适用于中、大、超大规模的WEB应用。但是它也不是一个企业层的应用框架,也就是说它不会直接提供创建EJB, WEB services等企业层组件的方法,但用它可以构造出访问企业层组件的客户应用。
这个框架功能主要有三部分组成:
iPlanet应用框架核心;
iPlanet应用框架组件;
iPlanet应用框架扩展。
应用框架核心定义了基本接口、对象协议、简单组件,以及iPlanet应用框架程序的最小核心。包括视图简单组件、模型简单组件、请求分发组件和可重用命令对象。iPlanet应用框架组件利用框架核心定义的基本接口、协议和组件向开发者提供高层的重用组件,这些组件既有与特定视觉效果无关的水平组件,同时也有适应特定实用环境、提高可用性而特意提供的垂直型组件。框架扩展实现了用框架相容的方法访问非J2EE环境的方法。通常情况下,扩展被框架应用程序用来无缝访问J2EE容器特定功能。JATO平台栈图很清楚地表达了这个情况。
JATO最大的威力在:对于快速开发用户,你能利用框架组件和扩展提高生产率,对于要求更大灵活性的用户,你能实现框架核心提供的接口来保持应用的框架兼容性。
此图表示实现一个JATO应用程序,可以简单地实现控制组件module1Servlet,视图组件ListCustomersViewBean和模型组件CustomersModuleImpl,以及一个给客户代理显示界面的ListCustomers.jsp文件。并清楚地表明这些组件与JATO框架组件的继承关系。
JATO标签库提供了VIEW对象与JSP文件的接口。库中标签处理程序负责实现VIEW对象和JSP产生地客户端文档的信息同步和交换。这个图清楚地表达了这种对应关系
2.2.1. MVC分析
前端控制组件接收用户发来的任何请求,这个可在WEB.xml中指定请求分发组件负责视图管理和导航,和前端控制组件封装在ApplicationSERVLETBase一起实现。应用程序开发者需要为每一个子系统(人力资源,财务,CRM等)实现一个此类的继承。
请求分发组件分发请求给工作者,工作者实现了command接口。应用开发者可以实现这个接口。JATO提供了一个缺省实现:DefaultRequestHandingCommand,这个实现会把请求传给视图组件的特定事件。
组合视图是指视图组件在显示给用户时的层次关系:根视图是一个ViewBean类的对象字段是一个DisplayField类的对象,容器视图是一个ContainerView类的对象。视图组件类的层次关系如下图:
2.2.2. 优缺点分析
优点:
这种框架的适应范围大,即提供了底层接口,也有立即可用的组件
具有与客户端RAD开发工具相似的开发概念如页为中心(等同于VB的FORM),事件处理等.
对大型的应用支持较好
缺点:
不是业届标准
目前还没有开发工具的支持(然JATO已经为工具支持做好了准备)
没有定义网页导航,开发者在视图中自己指定具体的导航URL
修改建议
把众多的VIEW/MODEL对应修改成xml文档传递数据,加上集中的网页导航定义
2.3. JSF(JavaServer Faces)
JSF是一个包括SUN在内的专家组正在定义的开发WEB应用用户界面的框架,JSF 技术包括:
一组API,它实现UI了组件,管理组件的状态,处理事件,输入校验,定义页面导航,支持国际化和访问;
一个JSP定制标签库实现与JSP的接口。
JSF非常简单,是一个定义良好的编程模型。利用这个技术,开发者通过在页面内组合可重用的UI组件,在把这些组件和应用的数据源相连,路由客户产生的事件到服务器端的事件处理器进行编程。JSP处理了所有幕后的复杂工作,使得开发者把关注重点放在应用代码上。
2.3.1. STRUTS、JATO和JSF比较
它们之间有部分重叠,但重点不一样。
STRUTS和JATO都提供了一个MVC式的应用模型,而JSF只在用户界面上提供编程接口。这意味着前两者涉及的范围比后者广。JSF可以成为前两者在UI开发的部分。
JSF的规范的发布版将在 2002年底发布,实现可能要比这个时间晚些。另外将会有工具支持这个框架的应用开发。
2.4. WAF
WAF是WEB APPLICATION FRAMWORK的简称,是SUN蓝皮书例子程序中提出的应用框架。它实现了 MVC和其他良好的设计模式。
2.4.1. 细节分析
2.4.2. 视图-控制-模型
如图所示,开发人员编写的两个xml配置文件定义了WAF的运作参数。Screendefinition.xml定义了一系列的屏幕(screen)。Mapping.xml则定义了某个动作之后应该显示的屏幕,但没有指定屏幕到哪里拿数据。
用户发出一个HTTP请求(*.screen),由TemplateSERVLET屏幕前端控制组件接收,它提取请求信息,设置request对象CurrentScreen属性,再把请求发到模版JSP。模版JSP收到请求后,JSP中的Template标签察看这个当前屏幕,并从屏幕定义文件(Screendefinition.xml)中获取这个屏幕的具体参数,再生成html返回给客户。
假设返回给客户的html中包括了html表单,用户在输入一定数据之后提交,发出一个HTTP请求(*.do)。这个请求被MainSERVLET接收,它提取请求信息,察看动作映射文件(mapping.xml),设置处理这个请求的动作对象(HTTPAction对象),交给requestprosessor对象处理。Requestprosessor对象调用动作对象完成任务,如果需要进一步处理,requestprosessor对象会调用WEBclientcontroler对象的事件处理机制。MainSERVLET在处理完请求之后,从屏幕流管理对象那里得到下一个屏幕,并把请求传给这个屏幕的JSP文件。
值得一提的是WEBclientcontroler事件处理机制最终把HTTP请求的数据传到了EJBAction对象那里处理。这样HTTPAction对象和EJBAction对象形成了两级处理机制,前一级与request对象紧密相关,把数据封装起来形成一个Event对象,再传给了EJBAction对象,后者与Request对象无关。这个方式可以形成一个session级别的数据处理机制。下图显示了这个方法。HTTPAction1对象处理一个请求,并把数据放到一个状态SessionBean内,HTTPAction2也如此,当HTTPAction3接收到HTTP请求之后,把控制传给EJBAction, 后者获取状态SessionBean数据,处理请求,成功后清控状态SessionBean的内容。这个机制非常适应多个输入页面才能满足一个业务的输入数据的情况(比如购物车)。
2.4.3. 优缺点分析
优点
屏幕导航定义明确
为框架的扩展提供了一个空间
缺点
源码比较乱,稳定性和可靠性没人验证。
只是一个框架躯干,没有正式的model层,视图的概念不强
没有模型到视图的定义
修改意见
只有一个框架躯干,正为实现自己的应用框架提供了灵活性。没有僵化的视图概念,提供了在网页输入到模型的扩充接口,比如插入XML数据交换。 |
|
|
Java EE 常见性能问题解决手册
|
xuyy_cn 转贴 更新:2006-09-14 13:25:57 版本: 1.0 |
|
这篇文章,是PRO JAVA EE 5 Performance Management and Optimization 的一个章节,作者Steven Haines分享了他在调优企业级JAVA应用时所遇到的常见问题。
Java EE(Java企业开发平台)应用程序,无论应用程序服务器如何部署,所面对的一系列问题大致相同。作为一个JAVAEE问题解决专家,我曾经面对过众多的环境同时也写了不少常见问题的观察报告。在这方面,我觉得我很象一个汽车修理工人:你告诉修理工人发动机有声音,他就会询问你一系列的问题,帮你回忆发动机运行的情形。从这些信息中,他寻找到可能引起问题的原因。
众多解决问题的方法思路基本相同,第一天我同要解决问题的客户接触,接触的时候,我会寻找已经出现的问题以及造成的负面的影响。了解应用程序的体系结构和问题表现出的症状,这些工作很够很大程度上提高我解决问题的几率。在这一节,我分享我在这个领域遇过的常见问题和他们的症状。希望这篇文章能成为你JAVAEE的故障检测手册。
内存溢出错误
最常见的折磨着企业级应用程序的错误是让人恐惧的outofmemoryError(内存溢出错误)
这个错误引起下面这些典型的症状:
----应用服务器崩溃 ----性能下降 ----一个看起来好像无法结束的死循环在重复不断的执行垃圾收集,它会导致程序停止运行,并且经常导致应用服务器崩溃 不管症状是什么,如果你想让程序恢复正常运行,你一般都需要重新启动应用服务器。
引发out-of-memory 错误的原因
在你打算解决out-of-memory 错误之前,首先了解为什么会引发这个错误对你有很大的帮助。如果JVM里运行的程序, 它的内存堆和持久存储区域的都满了,这个时候程序还想创建对象实例的话,垃圾收集器就会启动,试图释放足够的内存来创建这个对象。这个时候如果垃圾收集器没有能力释放出足够的内存,它就会抛出OutOfMemoryError内存溢出错误。
Out-of-memory错误一般是JAVA内存泄漏引起的。回忆上面所讨论的内容,内存泄漏的原因是一个对象虽然不被使用了,但是依然还有对象引用他。当一个对象不再被使用时,但是依然有一个或多个对象引用这个对象,因此垃圾收集器就不会释放它所占据的内存。这块内存就被占用了,堆中也就少了块可用的空间。在WEB REQUESTS中这种类型的的内存泄漏很典型,一两个内存对象的泄漏可能不会导致程序服务器的崩溃,但是10000或者20000个就可能会导致这个恶果。而且,大多数这些泄漏的对象并不是象DOUBLE或者INTEGER这样的简单对象,而可能是存在于堆中一系列相关的对象。例如,你可能在不经意间引用了一个Person对象,但是这个对象包含一个Profile对象,此对象还包含了许多拥有一系列数据的PerformanceReview对象。这样不只是丢失了那个Person对象所占据的100 bytes的内存,你丢失了这一系列相关对象所占据的内存空间,可能是高达500KB甚至更多。
为了寻找这个问题的真正根源,你需要判断是内存泄漏还是以OutOfMemoryError形式出现的其他一些故障。我使用以下2种方法来判断:
----深入分析内存数据 ----观察堆的增长方式 不同JVM(JAVA虚拟机)的调整程序的运作方式是不相同的,例如SUN和IBM的JVM,但都有相同的的地方。
SUN JVM的内存管理方式
SUN的JVM是类似人类家族,也就是在一个地方创建对象,在它长期占据空间之前给它多次死亡的机会。
SUN JVM会划分为:
1 年轻的一代(Young generation),包括EDEN和2个幸存者空间(出发地和目的地the From space and the To space) 2 老一代(Old generation) 3 永久的一代(Permanent generation) 图1 解释了SUN 堆的家族和空间的详细分类
对象在EDEN出生就是被创建,当EDEN满了的时候,垃圾收集器就把所有在EDEN中的对象扫描一次,把所有有效的对象拷贝到第一个幸存者空间,同时把无效的对象所占用的空间释放。当EDEN再次变满了的时候,就启动移动程序把EDEN中有效的对象拷贝到第二个幸存者空间,同时,也将第一个幸存者空间中的有效对象拷贝到第二个幸存者空间。如果填充到第二个生存者空间中的有效对象被第一个生存者空间或EDEN中的对象引用,那么这些对象就是长期存在的(也就是说,他们被拷贝到老一代)。若垃圾收集器依据这种小幅度的调整收集(minor collection)不能找出足够的空间,就是象这样的拷贝收集(copy collection),就运行大幅度的收集,就是让所有的东西停止(stop-the-world collection)。运行这个大幅度的调整收集时,垃圾收集器就停止所有在堆中运行的线程并执行清除动作(mark-and-sweep collection),把新一代空间释放空并准备重启程序。
图2和图3展示的是了小幅度收集如何运行
图2。对象在EDEN被创建一直到这个空间变满。
图3。处理的顺序十分重要:垃圾收集器首先扫描EDEN和生存者空间,这就保证了占据空间的对象有足够的机会证明自己是有效的。 图4展示了一个小幅度调整是如何运行的
图4:当垃圾收集器释放所有的无效的对象并把有效的对象移动到一个更紧凑整齐的新空间,它将EDEN和生存者空间清空。
以上就是SUN实现的垃圾收集器机制,你可以看出在老一代中的对象会被大幅度调整器收集清除。长生命周期的对象的清除花费的代价很高,因此如果你希望生命周期短的对象在占据空间前及时的死亡,就需要一个主垃圾收集器去回收他们的内存。
上面所讲解的东西是为了更好的帮助我们识别出内存泄漏。当JAVA中的一个对象包含了一个并不想要的一个指向其他对象的引用的时候,内存就会泄漏,这个引用阻止了垃圾收集器去回收它所占据的内存。采用这种机制的SUN 虚拟机,对象不会被丢弃,而是利用自己特有的方法把他们从乐园和幸存者空间移动到老一代地区。因此,在一个基于多用户的WEB环境,如果许多请求造成了泄漏,你就会发现老一代的增长。
图5显示了那些潜在可能造成泄漏的对象:主收集器收集后遗留下来占据空间的对象会越来越多。不是所有的占据空间的对象都造成内存泄漏,但是造成内存泄漏的对象最终都占据者空间。如果内存泄漏的确存在,这些造成泄漏的对象就会不断的占据空间,直至造成内存溢出。
因此,我们需要去跟踪垃圾收集器在处理老一代中的运行:每次垃圾收集器大幅度收集运行时,有多少内存被释放?老一代内容是不是按一定的原理来增长?
图5。阴影表示在经过大幅度的收集后幸存下来的对象,这些对象是潜在可能引发内存泄漏的对象
一部分这些相关的信息是可以通过跟踪API得到,更详细的信息通过详细的垃圾收集器的日志也可以看到。和所有的跟踪技术一样,日值记录详细的程度影响着JVM的性能,你想得到的信息越详细,付出的代价也就越高。为了能够判断内存是否泄漏,我使用了能够显示辈分之间所有的不同的较权威的技术来显示他们的区别,并以此来得到结果。SUN 的日志报告提供的信息比这个详细的程度超过5%,我的很多客户都一直使用那些设置来保证他们管理和调整垃圾收集器。下面的这个设置能够给你提供足够的分析数据:
?verbose:gc ?xloggc:gc.log ?XX:+PrintGCDetails ?XX:+PrintGCTimeStamps
明确发现在整个堆中存在有潜在可能泄漏内存的情况,用老一代增长的速率才比较有说服力。切记调查不能决定些什么:为了能够最终确定你内存泄漏,你需要离线在内存模拟器中运行你的应用程序。
IBM JVM内存管理模式
IBM的JVM的机制有一点不同。它不是运行在一个巨大的继承HEAP中,它仅在一个单一的地区维护了所有的对象同时随着堆的增长来释放内存。这个堆是这样运行的:在一开始运行的时候,它会很小,随着对象实例不断的填充,在需要执行垃圾收集的地方清除掉无效的对象同时把所有有效的对象紧凑的放置到堆的底部。因此你可能猜测到了,如果想寻找可能发生的内存泄漏应该观察堆中所有的动作,堆的使用率是在提高?
如何分析内存泄漏
内存泄漏非常难确定,如果你能够确定是请求导致的,那你的工作就非常简单了。把你的程序放入到运行环境中,并在内存模拟器中运行,按下面的步骤来:
1. 在内存模拟器中运行你的应用程序 2. 执行使用方案(制造请求)以便让程序在内存中装载请求所需要的所有的对象,这可以为以后详细的分析排除不必要的干扰 3. 在执行使用方案前对堆进行拍照以便捕获其中所有运行的对象。 4. 再次运行使用方案。 5. 再次拍照,来捕获使用方案运行之后堆中所有对象的状态。 6. 比较这2个快照,找出执行使用方案后本不应该出现在堆中的对象。 这个时候,你需要去和开发者交流,告诉他你所碰到的棘手的请求,他们可以判断究竟是对象泄漏还是为了某个目的特地让对象保留下来的。如果执行完后并没有发现内存泄漏的情况,我一般会转到步骤4再进行多次类似的跟踪。比如,我可能会将我的请求反复运行17次,希望我的泄漏分析能够得到17个情况(或更多)。这个方法不一定总有用,但如果是因为请求引起的对象泄漏的话,就会有很大的帮助。
如果你无法明确的判断泄漏是因为请求引发的,你有2个选择:
1. 模拟每一个被怀疑的请求直至发现内存泄漏 2. 存配置一个内存性能跟踪工具 第一个选项在小应用程序中是确实可用的或者你非常走运的解决了问题,但对大型应用程序不太有用。如果你有跟踪工具的话第二个选择是比较有用的。这些工具利用字节流工具跟踪对象的创建和销毁的数量,他们可以报告特定类中的对象的数量状态,例如把Collections类作为特定的请求。例如,一个跟踪工具可以跟踪/action/login.do请求,并在它完成后将其中的100个对象放入HASHMAP中。这个报告并不能告诉你造成泄漏的是代码还是某个对象,而是告诉你在内存模拟器中应该留意那些类型的请求。把程序服务器放到产品环境中并不会使他们变敏感,而是跟踪性能的工具可以使你的工作变的更简单化。
虚假内存泄漏
少数的一些问题看起来是内存泄漏实际上并非如此。
我将这些情况称为假泄漏,表现在下面几种情况:
1. 分析过早 2. Session泄漏 3. 异常的持久区域 这章节对这些假泄漏都进行了调查,描述了如何去判断这些情况以及如何处理.
不要过早分析
为了在寻找内存泄漏的时候尽量减少出现判断错误的可能性,你应当在适当的时候分析堆。危险是:一些生命周期长的对象需要装载到堆中,因此在堆达到稳定状态且包含了核心对象之前具有很大的欺骗性。在分析堆之前,应该让应用程序达到稳定状态。
为了判断是否过早的对堆进行分析,持续2个小时对跟踪到的分析快照进行分析,看堆的使用率是上升还是下降。如果是下降,保存这个时候的内存记录。如果是上升,这个时候就需要分析内存中的SESSION了。 发生泄漏的session
WEB请求经常导致内存泄漏,在一个WEB请求中,对象会被限制存储在有限的几个区域。这些区域就是:
1. 页面区域 2. 请求区域 3. 上下文区域 4. 应用程序区域 5. 静态变量 6. 长生命周期的变量,例如SERVLET 当实现一些JSP(JAVASERVER页面)时,在页面上声明的变量在页面结束的时候就被释放,这些变量仅仅在这个单独的页面存在时存在。WEB服务器会向应用程序服务器传送一系列参数和属性,也就是在SERVLET和JSP之间传输HttpServletRequest中的对象。你的动态页面依靠HttpServletRequest在不同的组件之间传输信息,但当请求完成或者socket结束的时候,SERVLET控制器会释放所有在HttpServletRequest 中的对象。这些对象仅在他们的请求的生命周期内存在。
HTTP是无状态的,这意味着客户向服务器发送一个请求,服务器回应这个请求,这个传递就完成了,就是会话结束了。我们应该感激WEB页面帮我们做的日志,这样我们就能向购物车放置东西,并去检查它,服务器能够定义一个跨越多请求的扩展对话。属性和参数被放在各自用户的HttpSession对象中,并通过它让程序的SERVLET和JSP交流。利用这种办法,页面存储你的信息并把他们添加到HttpSession中,因此你可以用购物车购买东西,并检查商品和使用信用卡付帐。作为一个无状态的协议,它总是客户端发起连接请求,服务器需要知道一个会话存在多长时间,到时候就应该释放这个用户的数据。超过这个会话的最长时间就是会话超时,他们在程序服务器中设置。除非明确的要求释放对象或者这个会话失效,否则在会话超时之前会话中的对象会一直存在。
正如session是为每个用户管理对象一样,ServletContext为整个程序管理对象。ServletContext的有效范围是整个程序,因此你可以利用Servlet中的ServletContext或者JSP应用程序对象在所有的Servlet和JSP之间让在这个程序中的所有用户共享数据。ServletContext是最主要的存放程序配置信息和缓存程序数据的地方,例如JNDI的信息。
如果数据不是存储这个四个地方(页面范围,请求范围,会话范围,程序范围)那就可能存储在下面的对象中:
1. 静态变量 2. 长生命周期的类变量 每个类的静态变量被JVM(JAVA虚拟机)所控制,他们存在与否和类是否已经被初始化无关。一个类的所有实例共用一个存储静态变量的地方,因此在任何一个实例中修改静态变量会影响这个类的其他实例。因此,如果一个程序在静态变量中存放了一个对象,如果这个变量生命周期没有到,那么这个对象就不会被JVM释放。这些静态对象是造成内存泄漏的主要原因。
最后,对象能够被放到内部数据类型或者长生命周期类中的成员变量中,例如SERVLET。当一个SERVLET被创建并且被装载到内存,它在内存中仅有一个实例,采用多线程去访问这个SERVLET实例。如果在INIT()方法中装载配置信息,将他存储于类变量中,那么当需要维护的时候就可以随时读出这些信息,这样所有的对象就用相同的配置。我常碰到的一个问题就是利用SERVLET类变量去存储象页面缓存这样的信息。在他们自己内部本身存贮这些缓存配置是个不错的选择,但存贮在SERVLET中是最糟糕的情况。如果你需要使用缓存,你最好使用第三方控制插件,例如 TANGOSOL的COHERENCE。
当在页面或者请求范围中利用变量存放对象的时候,在他们结束的时候这些对象会自动释放。同样,在SESSION中存放对象的时候,当程序明确说明此SESSION失效的或者会话执行超时的时候,这些对象才会自动被释放。
很多看起来象内存泄漏的情况都是上面的那些会话中的泄漏。一个造成泄漏的会话并不是泄漏了内存而是类似于泄漏,它消耗了内存,但最终这些内存都会被释放的。如果程序服务器发生内存溢出,判断是内存泄漏还是内存缺乏的最好的方法就是:停止所有向这个服务器所发的请求的对象,等待会话超时,看内存时候会被释放出来。这虽然不会一定能够达到你要的目的,但是这是最好的分段处理方法,当你装载测试器的时候,你应该先挂断你内容巨大的会话而不是先去寻找内存泄漏。
通常来说,如果你执行了一个很大的会话,你应该尽量去减少它所占用的内存空间,如果可以的话最好能重构程序,以减少session所占据的内存空间。下面2种方法可以降低大会话和内存的冲突:
1. 增大堆的空间以支持你的大会话 2. 缩短会话的超时时间,让它能够快速的失效 一个巨大的堆会导致垃圾回收花费更多的时间,因此这不是一个好解决方法,但总比发生OutofMemoryError强。增加足够的堆空间以使它能够存储所有应该保存的有效值,也意味着你必须有足够的内存去存储所有访问你站点的用户的有效会话。如果商业规则允许的话最好能缩短会话超时的时间,以减少堆占用空间的冲突。
总结下,你应该依据合理性和重要性按下面的步骤依次去执行:
1. 重构程序,尽量减少拥有session范围的变量所存储的信息量 2. 鼓励你的客户在他们使用完后,明确的释放会话 3. 缩短超时的时间,以便于让你内存尽快的得到回收 4. 增加你堆空间的大小 无论如何,不要让程序范围级的变量,静态变量,长生命周期的类存储对象,事实上,你需要在内存模拟器中去分析泄漏。
异常的持久空间
容易误解JVM为持久空间分配内存的目的。堆仅仅存储类的实例,但JVM在堆中创建类实例之前,它必须把字节流文件(.class文件)装载到程序内存中。它利用内存中的字节流在堆中创建类的实例。JVM利用程序的内存来装载字节流文件,这个内存空间称为持久空间。图6显示了持久空间和堆的关系:它存在于JVM程序中,并不是堆的一部分。
Figure 6. The relationship between the permanent space and the heap
通常,你可能想让你的持久空间足够大以便于它能够装载你程序所有的类,因为很明显,从文件系统中读取类文件比从内存中装载代价高很多。JVM提供了一个参数让你不的程序不卸载已经装载到持久空间中的类文件:
?noclassgc
这个参数选项告诉JVM不要跑到持久空间去执行垃圾收集释放其中已经装载的类文件。这个参数选项很聪明,但是会引起一个问题:当持久空间满了以后依然需要装载新文件的时候JVM会怎么处理呢?我观测到的资料说明:如果JVM检测到持久空间还需要内存,就会调用主垃圾收集程序。垃圾收集器清除堆,但它并不会对持久空间进行任何操作,因此它的努力是白费的。于是JVM就再重新检测持久空间,看它是否满,然后再次执行程序,一遍的一遍重复。 我第一次碰到这种问题的时候,用户抱怨说程序性能很差劲,并且在运行了几次后就出现了问题,可能是内存溢出问题。在我调查了详细的关于堆和程序内存利用的收集器的记录后,我迅速发觉堆的状态非常正常,但程序确发生了内存溢出。这个用户维持了数千的JSP页面,在装载到内存前把他们都编译成了字节流文件放入持久空间。他的环境已经造成了持久空间溢出,但是在堆中由于用了 -noclassgc 选项,于是JVM并不去释放类文件来装载新的类文件。于是就导致了内存溢出错误,我把他的持久空间改为512M大小,并去掉了 -noclassgc 参数。
正像图7显示的,当持久空间变满了的时候,就引发垃圾收集,清理了乐园和幸存者空间,但是并不释放持久空间中的一点内存。
Figure 7. Garbage collection behavior when the permanent space becomes full. Click on thumbnail to view full-sized image.
注意
当设置持久空间大小时候,一般考虑128M,除非你的程序有很多的类文件,这个时候,你就可以考虑使用256M大小。如果你想让他能够装载所有的类的时候,就会导致一个典型的结构错误。设置成512M就足够了,它仅仅是暂时的时间的花费。把持久空间设置成512M大小就象给一个脚痛的人吃止痛药,虽然暂时缓解了痛,但是脚还是没有好,依然需要医生把痛治疗好,否则只是把问题延迟了而已。
线程池
外界同WEB或程序服务器连接的主要方法就是向他们发送请求,这些请求被放置到程序的执行次序队列中。和内存最大的冲突就是程序服务器所设置的线程池的大小。线程池的大小就是程序可以同时处理的请求的数量。如果池太小,请求就需要在队列中等待程序处理,如果太大,CPU就需要花费太多的时间在这些众多的线程之间来回的切换。
每个服务器都有一个SOCKET负责监听。程序把接受到的请求放到待执行队列中,然后将这个请求从队列移动到线程中被程序处理。
图8显示了服务器的处理程序。
Figure 8. 服务器处理请求的次序结构 线程池太小
每当我碰到有人抱怨装载速度的性能随着装载的数量的增加变的越来越糟糕的时候,我会首先检查线程池。特别是,我在看到下面这些信息的时候:
1.线程池的使用 2.很多请求等待处理(在队列中等待处理) 当一个线程池被待处理的请求装满的时候,响应的时间就变的极其糟糕,因为这些在队列中等待处理的请求会消耗很多的额外时间。这个时候,CPU的利用率会非常低,因为程序服务器没有时间去指挥CPU工作。这个时候,我会按一定幅度增加调节池的大小,并在未处理请求的数量减少前一直监视程序的吞吐量,你需要一个合理甚至更好的负载量者,一个精确的负载量测试工具可以准确的帮你测试出结果。当你观测吞吐量的时候,如果你发现吞吐量降低了,你就应该把池的大小下调一个幅度,一直到找到让它保持最大吞吐量的大小为止。
图9显示了连接池太小的情况
Figure 9. 所有的线程都被占用了,请求就只能在队列中等待
每当我阅读性能调整手册的时候,最让我头疼的就是他们从来不告诉你特殊情况下线程池应该是多大。由于这些值非常依赖程序的行为,他们只告诉你大普通情况下正确的大小,但是他们给了你一个范围内的值,这对用户很有利的。例如考虑下面2种情况::
1. 一个程序从内存中读出一个字符串,把它传给JSP页面,让JSP页面去显示 2. 另一个程序从数据库中读出1000个数值,为这些不规则的数值求平均。第一个程序对请求的回应会很块,大概仅需要不足0.25秒的时间,且不怎么占据CPU。第二个程序可能需要3秒去回应,同时会占据CPU。因此,为第一个程序配置的池大小是100就有点太小了,因为程序能够同时处理200个;但为第二个程序配置的池是100,就有点太大了,因为CPU可能就能应付50个线程。 但是,很多程序并没有在这种情况下动态的去调整的功能。多数情况下是做相同的事,但是应该为他们划分范围。因此,我建议你为一个CPU分配50到75个左右的线程。对一些程序来说,这个数量可能太少,对另一个些来说可能太多,我刚开始为每个CPU分配50到75个线程,然后根据吞吐量和CPU的性能,并做适当的调整。
线程池太大
除了线程池数量太小之外的情况外,环境也可能把线程数量配置的过大。当这些环境中的负载量不断增大的时候,CPU的使用率会持续无法降低,就没有什么响应请求的时间了,因为CPU只顾的在众多的线程之间来回的切换跳动,没时间让线程去做他们应该做的事了。
连接池过大的最主要的迹象就是CPU的使用率一直很高。有些时候,垃圾收集也可能导致CPU使用率很高,但是垃圾收集导致的CPU使用率很高和池过大导致的使用率有一个主要的区别就是:垃圾收集引起的只是短时间的高使用率就象个钉子,而池过大导致的就是一直持续很高呈线性。
这个情况发生的时候,请求会被放在队列中不被处理,但是不会始终如此,因为请求占用CPU的情况和程序占用的情况造成的后果不同。降低线程池的大小可能会让请求等待,但是让请求等待总比为了处理请求而让CPU忙不过来的好。让CPU保持持续的高使用率,同时性能不降低,新请求到来的时候放入到队列中,这是最理想的程序。考虑下面这个很类似的情况:很多高速公里有交通灯来保证车辆进入到拥挤的公里中。在我看来,这些交通灯根本没用,道理很充分。比如你来了,在交通灯后面的安全线上等待进入到高速公路上。如果所有的车辆都同时涌向公里,我们就动弹不得,但是只要减缓涌向高速公路车辆的速度,交通迟早会畅通。事实上,很多的大城市都有这样功能,但根本没用,他们真正需要的是一些更多的小路(CPU),涌向高速公路的速度真的降低了,那么交通会变的正常起来。
设置一个饱和的池,然后逐步减少连接池大小,一直到CPU占用率为75%到85%之间,同时用户负载正常。如果等待队列大小实在无法控制,考虑下面2中建议:
1.把你的程序放入代码模拟器运行,调整程序代码 2.增加额外的硬件 如果你的用户负载超过了环境能承受的范围,你应该考虑修正代码减少和CPU的冲突或者增加CPU。
JDBC连接池
很多JAVA EE 程序连接到一个后台数据源,大多数是通过JDBC(JAVA DATABASE CONNECTIVITY)将程序和后台连接起来。由于创建数据库连接的代价很高,程序服务器让在同一个程序服务器实例下的所有程序共享特定数量的一些连接。如果一个请求需要连接到数据库,但是数据库的连接池无法为这个请求创建一个新连接,这个时候请求就会停下来等待连接池完成自己的操作再给她分配一个连接。反过来,如果数据库连接池太大程序服务器就会浪费资源,并且程序有可能强迫数据库承受过量的负荷。我们调试的目的就是尽量减少请求的等待时间和饱和的资源之间之间的冲突,让一个请求在数据库外等待要比强迫数据库好的多。
一个程序服务器如果设置连接的数量不合理就会有下面这些特征:
1.程序运行速度缓慢 2.CPU使用率低 3.数据库连接池使用率非常高 4.线程等待数据库的连接 5.线程使用率很高 6.请求队列中有待处理的请求(潜在的) 7.数据库CPU使用率很低(因为没有足够的请求能够让他繁忙起来) JDBC prepared statements
和JDBC相关的另一个重要的设置就是:为JDBC使用的statement 所预设的缓存的大小。当你的程序在数据库中运行SQL statement 的时候三下面3个步骤进行:
1.准备 2.执行 3.返回数值 在准备阶段,数据库驱动器让数据库完成队列中的执行计划。执行的时候,数据库执行语句并返回指向结果的引用。在返回的时候,程序重新描述这些结果并描述出这些被请求的信息。
数据库驱动会这样优化程序:首先,你需要去准备一个statement ,这个statement 它会让数据库做好执行和缓存结果的准备。在此同时,数据库驱动会从缓存中装载已经准备好的statement ,而不用直接连接到数据库。
如果prepared statement 设置太小,数据库驱动器会被迫去查询没有装载进缓存区的statement ,这就会增加额外的连接到数据库的时间。prepared statement 缓存区设置不恰当最主要的症状就是花费大量的时间去连接相同的statement。这段被浪费的时间本来是为了让它去装载后面的调用的。
事情变的稍微复杂了点,缓存prepared statement 是每个statement的基础,就是说在一个statement连接之前都应当缓存起来。这个增加的复杂性就产生了一个冲突:如果你有100个prepared statement需要去缓存,但你的连接池中有50个数据库连接,这个时候你就需要有存放5000条预备语句的内存。
通过跟踪性能,确定出你程序所执行的不重复的statement 的数量,并从这些statement 中找出哪些条是频繁执行的。
Entity bean(实体BEAN)和stateful session bean的缓冲
无状态(stateless)对象可以被放入到池中共享,但象Entity beans和 stateful session bean这样的有状态的对象就需要被缓存,因为这些bean的每个实例都是不相同的。当你需要一个有状态对象时,你需要明确创建这个对象的特定实例,普通的实例是不能满足的。类似的,你考虑一个超市类似的情况,你需要个售货员但他叫什么并不重要,任何售货员都可以满足你。也就是,售货员被放入池中共享,因为你只需要是售货员就可以,而不是一个叫做史缔夫的这个售货员。当你离开超市的时候,你需要带上你的孩子,不是其他人的孩子,而是你自己的。这个时候,孩子就需要被缓存。
Figure 10. The application requests an object from the cache that is in the cache, so a reference to that object is returned without making a network trip to the database 当你的缓存区太小的时候,缓存的性能就会明显的受到影响。特别是,当一个请求去一个已经满了的缓存区域去请求一个对象的时候,下面的步骤就会执行,这些步骤会在图11中显示:
1. 程序请求一个对象 2. 缓存检测这个对象是否已经存在于缓存中 3. 缓存决定把一个对象开除出缓存(一般采用的算法是遗弃最近使用次数最少的对象) 4. 把这个对象扔出缓存(称为passivated) 5. 把从数据库中装载这个新对象并放入到缓存(称为activated) 6. 把指向这个对象的引用返回给程序
Figure 11. Because the requested object is not in the cache, an object must be selected for removal from the cache and removed from it.
如果多数的请求都需要执行这些步骤的话,那你采用缓存技术就不是好的选择了!如果这些处理步骤频繁发生的话,你就需要重新推敲下你的缓存了。回忆一下:从缓存中去除一个对象称为passivation,从持久存储区取出一个对象放入缓存称为activation。能在缓存中找到的请求(缓存中有此请求的对象)的百分率称为hit ratio,相反找不到的请求的百分率称为miss ratio。
缓存刚被初始化的时候,hit ratio是0,它的activation数量非常高,因此在初始化后你需要去观察缓存的性能。初始化以后,你应该跟踪passivation的数量并把它和与向缓存请求对象的请求的总量相比较,因为passivations只会发生在缓存被初始化以后。但一般来说,我们更需要关心缓存的miss ratio。如果miss ratio超过25%,那么缓存可能是太小了。因此,如果missratio的数量超过75%,那么不是你的缓存设置的太小就是你不需要缓存这个技术。
一旦你觉得你的缓存太小,就去尝试着增大大小,并测试增加的性能。如果miss ration下降到20%以下,那你的缓存的大小就非常棒了,如果没有什么效果,那么你就需要和这个程序的技术员联系,看是这个对象是不是需要缓存或者是否应该修正程序中这个对象的代码。
Staless session bean和message-driven bean池
Stateless session bean 和message-driven bean 在商业应用方面很重要,不要期望它们会保持自己特有的状态信息。当你的程序需要使用这些BEAN的商业功能的时候,它就从一个池中取出一个BEAN实例,用这个实例来调用一个个方法,用完后再将BEAN的实例再放回到池中。如果你的程序过了一会又需要这个一摸一样的BEAN,就从池中再得到一个实例,但不能保证你得到的就是上一个实例。池能够让程序共享资源,但是会让你的程序付出潜在的等待时间。如果你无法从池中得到想要的BEAN,请求就会等待,一直到这个BEAN被放入到池中。很多程序服务器都会把这些池调整的很好,但是我碰到过因为在环境中把他们设置的太小而引发的不少麻烦。Stateless bean池的大小应该和可执行线程池的大小一般大,因为一个线程同时只能使用一个对象,再多了就造成浪费的。因此,一些程序服务器把池的大小和线程的数量设置成同样的数量。为了保险起见,你应该亲自把它设置成这个数。 事务
使用Enterprise Java的一个好处就是它天生就支持事务。通过JAVAEE 5 EJB(Enterprise javaBeans)的注释,你可以控制事务中方法的使用。事务会以下面2中方式结束:
1. 事务提交 2. 事务回滚 当一个事务被提交的时候,说明它已经完全成功了,但是当它回滚的时候,就说明发生了一些错误。回滚会是下面2种情况:
1. 程序造成的回滚(程序回滚) 2. 非程序造成的回滚(非程序回滚) 通常,程序回滚是因为商业的规定。比如一个WEB程序做一个素描画的价格的调查,程序可能让用户输入年龄,并且商业规定18岁以上才可以进入。如果一个16岁的提交了信息,那么程序就会抛出一个错误,打开一个网页告诉他,他年龄还不能参与到这个信息的调查。因为程序抛出了异常,因此包含在程序中的事务的就会发生回滚。这只是普通的程序回滚,只有当发生大量的程序回滚才值得我们注意。
另一方面,非程序回滚是非常糟糕的。有三种情形的非程序回滚:
1. 系统回滚 2. 超时回滚 3. 资源回滚 系统回滚意味着程序服务器中的一些东西非常的糟糕,恢复的几率很渺茫。超时回滚就是当程序服务器中的程序处理请求时超时;除非你把超时设置的很短才会出现这种错误。资源回滚就是当一个程序服务器管理内部的资源的时候发生错误。例如,如果你设置你的程序服务器通过一个简单的SQL语句去测试数据库的连接,但数据库对于程序服务器来说是无法连接的,这个时候任何和这个资源相关的事情都会发生资源回滚。
如果发生非程序回滚,我们应该立刻注意,这个是不小的问题,但是你也需要留意程序回滚发生的频率。很多时候人们对发生的异常很敏感,因此你需要哪些异常对你程序来说才是重要的。
总结
尽管各个程序和他们的环境都各不相同,但是有一些共同的问题困扰着他们。这篇文章的注意力并不是放在程序代码的问题上,因为把注意力放在因为环境的问题而导致的低性能的问题上:
1.内存溢出 2.线程池大小 3.JDBC连接池大小 4.JDBC预先声明语句缓存大小 5.缓存大小 6.池大小 7.执行事务时候的回滚 为了有效的诊断性能的问题,你应该了解什么问题会导致什么样的症状。如果主要是程序的代码导致的恶果那你应该带着问题去寻求负责代码的人寻求帮助,但是如果问题是由环境引起的,那么就要依靠你的操作来解决了。
问题的根源依赖于很多要素,但是一些指示器可以增加一些你处理问题时候的一些信心,依靠他们可以完全排除一些其他的原因。我希望这个文章能对你排解JAVAEE环境问题起到帮助。 |
|
posted on 2007-03-31 21:13
MEYE 阅读(2051)
评论(0) 编辑 收藏