最佳实践.docx
《最佳实践.docx》由会员分享,可在线阅读,更多相关《最佳实践.docx(49页珍藏版)》请在冰豆网上搜索。
最佳实践
44
第三章
Servlet最佳实践
JasonHunter
自1996年引入以来,Servlet已经占据了服务器端Java领地,并且成为Java介入
Web的一种标准方法。
作为一种基本技术,Java开发人员以此为基础来构建Web
应用以及(更进一步的)Web服务。
这一章将讨论基于Servlet的开发和部署的最
佳实践。
有效使用Servlet
首先来介绍Servlet框架。
框架(如,ApacheStrut)越来越普及,这是因为通过
提供一种“骨架”(skeleton,应用可在此基础上构建),框架能够提高程序员的
效率。
在第一节中,我们将分析Servlet框架提供了什么并对最流行的框架作一个
简要概述;然后,将讨论使用预编码字符可以如何优化Servlet的性能。
接下来,
我们将处理加载配置文件这一棘手的问题,并提供一些代码从而使这个任务更容
易完成;其后,我将提供一些有关何时应当使用(以及何时不应当使用)
HttpSession和SingleThreadModel特性的技巧。
本章结尾部分,我将解释如何
可靠地控制缓存,以提高用户的体验。
然后将解决一个常见问题:
“如何将文件下
载到客户,从而让客户看到一个‘保存为’(SaveAs)弹出对话框?
”,你将看到,
答案就在于设置合适的HTTP首部。
Servlet最佳实践45
选择合适的Servlet框架
编写Web应用时,应当记住Servlet是一种功能强大的技术。
这一点很容易被遗
忘,因为最初ServletAPI即为服务器端JavaWeb编程的全部。
至于ServletAPI
不包括的某些内容,就必须由我们自行构建。
这有点类似于西部片中的情景,当
时情况相当恶劣,真正的程序员需要自己动手来编写Servlet,而且有关规范尚未
编写。
仅仅有out.println()就令我们颇感欣慰了。
时光荏苒,斗转星移。
正如西部原野上出现了大批牛羊,在这一领域中也涌现了
许多新的内容,我们看到,大量基于Servlet的技术被设计出来,从而使Web应
用开发更加容易而且更为有效。
发生变革的第一个领域为表示层。
较之于以前应
用很广的out.println(),诸如JavaServerPages(JSP)、WebMacro和Velocity
等技术则提供了输出更为丰富的方法。
与以往相比,利用这些技术使得快速地开
发、部署和维护动态Web内容更加容易。
有关这些技术以及其他模板技术的全面
讨论,请参见我所著的《JavaServletProgramming》第二版(O'Reilly出版)。
今天,我们发现在表示层之下出现了一个产生变革的新领域,即框架层(如图
3-1所示)。
这些新的框架提供了一个可靠的支架,基于这个支架可以构建新的
Web应用,从而由快速构建页面转移到快速构建完整的应用。
框架采纳了专家们
最优秀的设计,使你能够重用这些设计。
好的框架有助于改善应用的模块性和可
维护性。
框架还将多种单独的技术集合在一个捆绑的包中,并提供了基于这些技
术构建的组件以解决常见的任务。
如果你选择了合适的Servlet框架,不但可以大
大地提高你的工作量,并且可以利用他人的工作。
因此,我建议你考虑使用一个
框架,而且我在这一节中还将提供一些关于选择合适的框架的有用技巧。
选择框架的技巧
选择一个Servlet框架时,考虑其特性表相当重要。
下面列出了框架提供的一些特
性。
并非所有框架都支持以下特性,另外这一简短的列表也并非详尽的(注1)。
注1:
这实际上是一个正在进行的研究项目,其目标是跟踪Servlet框架特性,并且在各个框
架上实现同样的演示Web应用。
更多的信息请参见http;//www.waferproject.org。
46第三章
Servlet框架
JSPWebMacroVelocity
MVC类
Servlets
等等
安全性表单验证
数据库
错误处理集成
图3-1:
Servlet、模板技术和框架
与模板语言的集成
有些框架集成了一个特定模板语言,另外一些框架则有一个“可插拔”的模
型以支持多个模板,不过它们通常都针对一种模板语言加以优化。
如果你偏
好某种特定的模板语言,请确保所选框架能够很好地提供相应的支持。
支持(理想情况下则可增强)设计人员/开发人员的分离
Web应用开发的一个通常目标是将开发人员的责任从设计人员的责任中有效
地分离出来。
对于这一目标,选择适当的模板语言可以提供帮助,但是选择
合适的框架则会有更大的影响。
有一些框架可以支持这种分离,还有一些甚
至可能增强。
安全集成
默认的Servlet访问控制和安全模型可以用于简单任务,但未能针对更为高
级的需求做相应扩展。
有些框架提供了另外的安全模型,而且许多框架还支
持“可插拔”的安全模型。
如果你希望获得更为高级的安全控制,那么选择
合适的框架将有所帮助。
表单验证
框架通常提供了验证表单数据的工具,例如,在Servlet看到数据之前允许
框架对参数进行全面检查。
有些框架使得我们可以很容易地开发“表单范
例”(带有“向后”/“向前”按钮和所维护的状态)。
Servlet最佳实践47
错误处理
有些框架包括高级或定制的错误处理,如发送警告电子邮件、将错误记录在
一个特殊数据存储中,或者是自动将错误格式化并提交给用户和(或)管理
人员。
持久/数据库集成
框架的一个最强大的特性可能是它们与后台数据存储(如数据库)的紧密而
良好的集成。
这些框架使用户能够以对象而不是SQL的方式来考虑问题。
国际化
国际化(Internationalization,i18n)往往是一个难题,但是有些框架拥有一
些特性和习惯用法,可以简化这个过程。
IDE集成
有些框架为开发提供了IDE,而且(或者)具有允许插入到第三方IDE的特
性。
支持Web服务的机制
由于人们对Web服务的兴趣日渐增长,若发现有关Web服务的新框架,或
者原有框架引入了新的Web服务特性,这都是很常见的。
除了特性以外,分析框架时的第二条重要原则是其授权许可。
我的建议是要坚持
采用开放源代码的项目,或者由多个开发商实现的标准。
这样可以保护你的投入。
开放源代码和通用标准都能够避免单一开发商问题,而且,可以确保任何一方都
不能终止对你的应用所基于的框架的支持。
第三个需要考虑的是框架所面向的目标(例如,新闻网站、门户网站、商业网站
等等)。
不同的网站有不同的需求,而一个框架往往针对某一市场环节加以优化。
你可能会发现,调查其他人在实现类似应用时采用了哪种框架将很有帮助。
声名鹊起的框架
尽管在此对框架做全面的比较将很有意思,但这并不是本书的主旨。
我们将简要
地讨论4种目前最为流行的Servlet框架,它们分别是:
J2EEBluePrints、Apache
Struts、JavaServerFaces和ApacheTurbine。
48第三章
你可能会暗自思量:
“直截了当地告诉我哪一种最好吧!
”。
遗憾的是,并没有一
个包罗万象的答案;这完全取决于你的应用和你的个人喜好。
使用服务器端Java
时,正好可以沿用Perl的名言“条条大路通罗马”。
J2EEBluePrints
J2EEBluePrints(
框架,不如说是一个指南更为确切。
这本书由Sun工程人员编著,提供了准
则、模式和代码示例,从而展示了如何最佳地使用J2EE及其组成技术。
例
如,这本书说明了如何实现一个MVC框架,该框架将后台Web操作封装为
3部分,分别为:
表示核心数据的模型(Model)、处理数据显示的视图(View)
以及处理数据调整的控制器(Controller)。
为了支持此MVC模型,BluePrints
建议以“命令”(Command)模式风格使用Action类:
示例应用定义了一个抽象类Action,它表示一个应用模型操作。
控制器
可以通过名字查找具体的Action子类,并将请求委托给这些子类。
这本书对于如何实现一个Action提供了代码示例,但是没有提供任何可供
发布的代码。
若需要发布代码,J2EEBluePrints建议读者参考Apache
Struts。
ApacheStruts
ApacheStruts(http:
//Jakarta.apache.org/struts)可算是最流行的Servlet框
架。
它与BluePrints所述的MVC模式非常接近(在我看来,这两种想法有
着同样的思路):
Struts具有高可配置性,而且有相当多的特性(其特性仍在增加),其中
包括FrontController(前端控制器)、动作类及映射、面向XML的实用
类、服务器端JavaBean的自动填充、带有效性验证的Web表单以及一
些国际化支持。
它还支持一组定制标记以访问服务器端状态、创建
HTML、完成表示逻辑以及模板化。
有些开发商已经开始采用并宣传
Struts。
Struts得到了大量的关注,可以认为它是适于大型应用的具有工业强度的框
架。
在Struts中,请求通过一个控制器Servlet传输。
Action对象控制请求
Servlet最佳实践49
的处理,而且这些动作使用诸如JavaBeans等组件来完成业务逻辑。
对于有
外部配置的Servlet,Struts在其之上极好地创建了一个完全分派机制,从而
消除了URL和在线行为间的人为联系。
几乎所有请求都通过同一个Servlet
进入,客户请求作为请求的一部分,指示了其希望采取的动作(即,登录、
加入购物车、结账等),Struts控制器将把请求分派给一个Action处理。
JSP
被用作表示层,尽管也可以使用ApacheVelocity和其他技术。
Struts是一
个开放源代码项目,是在Apache的开放协作开发模型下开发的。
JavaServerFaces
JavaServerFaces(JSF)是Sun领导的一个JavaCommunityProcess(JCP)
项目(JSR-127),目前仍处在早期开发阶段。
在写这本书时,它刚刚达到
CommunityReview的最早阶段,但是已经得到了相当的关注。
JSF立项文
档中原计划定义一个标准Web应用框架,但是目前提交的文档所关注的目标
却较为受限,仅仅为请求定义一个包括多个阶段请求处理生命期(即一个表
单向导)。
与Struts很好地集成也是JSF的一个目标。
ApacheTurbine
ApacheTurbine可能是最古老的Servlet框架之一,它自1999年就已经存在
了。
此框架拥有处理参数解析和验证、连接池、作业调度、缓存、数据库抽
象以及XML-PRC等服务。
其许多组件都可以独立使用,如对数据库抽象的
Torque工具。
Turbine将这些组件“捆绑”在一起,从而为构建Web应用提
供了一个可靠的平台,这与J2EE为企业应用所采用的方式完全一致。
Turbine与其他框架类似,也基于MVC模型和动作事件抽象。
不过,不同于
其他框架,Turbine在视图(View)层提供了额外的支持,而且自称是“Model
2+1”,即优于标准的“Model2”MVC。
TurbineViews支持许多模板引擎,
不过ApacheVelocity为其首选。
如果篇幅允许,我们还可以讨论更多的框架。
如果你有兴趣学习更多的内容,可
以用以下关键字在Google上查找:
TeaServlet、ApacheCocoon、Enhydra
Barracuda、JCorporateExpresso和Japple。
50第三章
使用预编码字符
编写Servlet时你了解到的第一件事就是要用PrintWriter写字符,而用OutputStream
来写字节。
尽管这在形式上是一个很好的建议,但稍有些简化。
需要
全面地看待这一事实,因为输出字符并不意味着必须使用一个PrintWriter!
PrintWriter有其缺点,具体地说,它必须在内部将char中的各个字符编码为一
个byte序列。
如果有已知编码的内容(如,文件、URL或数据库,甚至是内存
中保存的一个String中的内容),那么通常最好采用流。
这样就可以完成一个直
接的字节-字节传输。
除了所保存编码和所需编码之间偶尔会存在字符集不匹配
这种少有的情况外,没有必要先将内容解码至一个String,然后再在发送至客户
时将其编码为字节。
使用预编码字符则可节省大量开销。
作为演示,例3-1中的Servlet使用了一个读取器(reader)来读取一个文本文件,
并使用了一个书写器(writer)向客户输出文本。
尽管它遵循了对于文本使用
Reader/Writer类的原则,但是却存在非常浪费而且毫无必要的转换。
例3-1:
读入字符,输出字符
importjava.io.*;
importjava.util.prefs.*;
importjavax.servlet.*;
importjavax.servlet.http.*;
publicclassWastedConversionsextendsHttpServlet{
//随机文件,仅用于演示
Stringname="content.txt";
publicvoiddoGet(HttpServletRequestreq,HttpServletResponseres)
throwsServletException,IOException{
Stringfile=getServletContext().getRealPath(name);
res.setContentType("text/plain");
PrintWriterout=res.getWriter();
returnFile(file,out);
}
Servlet最佳实践51
publicstaticvoidreturnFile(Stringfilename,Writerout)
throwsFileNotFoundException,IOException{
Readerin=null;
try{
in=newBufferedReader(newFileReader(filename));
char[]buf=newchar[4*1024];//4K字符缓冲区
intcharsRead;
while((charsRead=in.read(buf))!
=-1){
out.write(buf,0,charsRead);
}
}
finally{
if(in!
=null)in.close();
}
}
}
例3-2中的Servlet更适于返回一个文本文件。
此Servlet识别出文件内容是由字
节开始的,而且只要其编码与客户所需的编码相匹配,就可以直接作为字节发送。
例3-2:
读入字节,输出字节
importjava.io.*;
importjava.util.prefs.*;
importjavax.servlet.*;
importjavax.servlet.http.*;
publicclassNoConversionsextendsHttpServlet{
Stringname="content.txt";//要发送的演示文件
publicvoiddoGet(HttpServletRequestreq,HttpServletResponseres)
throwsServletException,IOException{
Stringfile=getServletContext().getRealPath(name);
res.setContentType("text/plain");
OutputStreamout=res.getOutputStream();
returnFile(file,out);
}
publicstaticvoidreturnFile(Stringfilename,OutputStreamout)
throwsFileNotFoundException,IOException{
InputStreamin=null;
try{
in=newBufferedInputStream(newFileInputStream(filename));
52第三章
byte[]buf=newbyte[4*1024];//4K缓冲区
intbytesRead;
while((bytesRead=in.read(buf))!
=-1){
out.write(buf,0,bytesRead);
}
}
finally{
if(in!
=null)in.close();
}
}
}
通过使用预编码字符对于性能有多大的改善呢?
这取决于服务器。
对一个2MB文
件进行本地访问,以此来测试这两个Servlet,在Tomcat3.x下性能有20%的提
高;Tomcat4.x下则有50%的大幅提高。
尽管这些数字让人印象非常深刻,但它
们当然有一个假设,即应用除了传输文本文件之外不做其他工作。
实际的性能提
高则取决于Servlet的业务逻辑。
此技术(如图3-2所示)对于带宽受限或服务器
CPU受限的应用尤其有用。
字节
字节字符
浏览器服务器文件
字符字节
字节
图3-2:
充分利用预编码字符
只要大多数源内容都是预编码的(如,文件、URL,甚至数据库内容),那么“使
用预编码字符”这个原则就适用。
例如,使用ResultSetgetAsciiStream()
方法而不是getCharacterStream()可以避免对于ASCII字符串的转换开销,无
论在读取数据库还是写至客户时都适用。
还有可能将服务器和数据库之间的带宽
减至一半,因为ASCII流的规模大致为UCS-2流的一半。
具体能够得到多大收益,
当然还要取决于数据库以及它在内部如何保存和传输数据。
实际上,一些Servlet开发人员用String.getBytes()对其静态String内容进行
预编码,这样只需对它们进行一次编码。
采用这种极端做法所得到的性能收益是
Servlet最佳实践53
否值得,这仍取决于个人喜好。
我建议,只有当性能是一个很重要的问题,而且
再没有更简单的解决方案时才采用这样的做法。
输出混合字节和字符实际上比想象得要容易。
例3-3展示了如何使用Servlet-
OutputStream及其组合方法write(byte[])和println(String)来混合输出类
型。
例3-3:
ValueObjectProxy.java
importjava.io.*;
importjava.sql.*;
importjava.util.Date;
importjavax.servlet.*;
importjavax.servlet.http.*;
publicclassAsciiResultextendsHttpServlet{
publicvoiddoGet(HttpServletRequestreq,HttpServletResponseres)
throwsServletException,IOException{
res.setContentType("text/html");
ServletOutputStreamout=res.getOutputStream();
//ServletOutputStream中包含println()方法用以写字符串
//println()调用仅适用于单字节字符编码
//如果需要多字节,请确保在Content-Type(内容类型)中设置了字符集
//而且使用了相应的方法,例如,对于日语使用out.write.(str.getBytes("Shift_JIS"))
out.println("Contentcurrentasof");
out.println(newDate().toString());
//在此获取一个数据库结果集(ResultSet)
try{
InputStreamascii=resultSet.getAsciiStream
(1);
returnStream(ascii,out);
}
catch(SQLExceptione){
thrownewServletException(e);
}
}
publicstaticvoidreturnStream(InputStreamin,OutputStreamout)
throwsFileNotFoundException,IOException{
byte[]buf=newbyte[4*1024];//4K缓冲区
intbytesRead;
54第三章
while((bytesRead=in.read(buf))!
=-1){
out.write(buf,0,bytesRead);
}
}
}
尽管混合字节与字符将带来性能的大幅提升,因为字节可直接传输,我仍建议你
少用这种技术,因为这不仅会让读者感到混乱,而且如果你对字符集如何工作并
没有搞得滚瓜烂熟,那么就很容易导致错误。
如果还需要ASCII字符集以外的字
符,就要确保你明白自己要做什么。
初学者不要尝试向输出流写非ASCII字符。
从类路径加载配置文件
从ServletAPI1.0到ServletAPI2.3,Servlet显然一直缺少一种获取外部配置文
件的标准机制。
尽管许多服务器端库需要配置文件,但Servlet却没有一种大家公
认的定位配置文件的方法。
Servlet在J2EE下运行时,可得到对JNDI的支持,而
后者可以提供一定的配置信息。
不过,常见的Web服务器配置文件问题仍然存在。
最好的解决方案(我也许应当称之为“危害最小的”解决方案)是利用查找类路
径和(或)资源路径定位文件。
这要求服务器管理人员将服务器端配置文件置于
Web服务器的类路径中,或者将每个应用的配置文件置于从资源路径找到的
WEB-INF/classes中。
对于定位置于WAR文件中的配置文件以及(或者)定位已
部署在多个后台Servlet容器上的配置文件,此方法同样有效。
实际上,即便可以
使用JNDI,使用用于配置的文件仍有诸多好处。
一个配置文件可以在整个服务器
上都有效。
而且最后一点,配置文件对于开发和部署人员都很容易理解。
例3-4展示了利用一个名为Resource的类的查找技术。
给定一个资源名,
Resource构造函数将搜索类路径和资源路径以定位资源。
一旦找到资源,则资源
内容以及其目录位置和最后修改时间均可用(如果可以得到这些信息的话