??xml version="1.0" encoding="utf-8" standalone="yes"?> 一?异常的概念和Java异常体系l构 异常是程序运行过E中出现的错误。本文主要讲授的是Java语言的异常处理。Java语言的异常处理框Ӟ是Java语言健壮性的一个重要体现?/p>
Java把异常当作对象来处理Qƈ定义一个基cjava.lang.Throwable作ؓ所有异常的类。在Java API中已l定义了许多异常c,q些异常cdZ大类Q错误Error和异常Exception。Java异常体系l构呈树Ӟ其层ơ结构图如图 1所C: ?/span>1Java异常体系l构 ThorwablecL有异常和错误的超c,有两个子cError和ExceptionQ分别表C错误和异常。其中异常类Exception又分行时异常(RuntimeException)和非q行时异常,q两U异常有很大的区别,也称之ؓ不检查异常(Unchecked ExceptionQ和查异常(Checked ExceptionQ。下面将详细讲述q些异常之间的区别与联系Q?/p>
1、Error与Exception Error是程序无法处理的错误Q比如OutOfMemoryError、ThreadDeath{。这些异常发生时QJava虚拟机(JVMQ一般会选择U程l止?/p>
Exception是程序本w可以处理的异常Q这U异常分两大c运行时异常和非q行时异常。程序中应当可能去处理q些异常?/p>
2、运行时异常和非q行时异?/strong> q行时异帔R是RuntimeExceptioncd其子cd常,如NullPointerException、IndexOutOfBoundsException{,q些异常是不查异常,E序中可以选择捕获处理Q也可以不处理。这些异怸般是q序逻辑错误引v的,E序应该从逻辑角度可能避免这cd常的发生?/p>
非运行时异常是RuntimeException以外的异常,cd上都属于Exceptioncd其子cR从E序语法角度讲是必须q行处理的异常,如果不处理,E序׃能编译通过。如IOException、SQLException{以及用戯定义的Exception异常Q一般情况下不自定义查异常?/p>
二?异常的捕获和处理 Java异常的捕获和处理是一个不Ҏ把握的事情,如果处理不当Q不但会让程序代码的可读性大大降低,而且Dpȝ性能低下Q甚臛_发一些难以发现的错误?/p>
Java异常处理涉及C个关键字Q分别是Qtry、catch、finally、throw、throws。下面将骤一介绍Q通过认识q五个关键字Q掌握基本异常处理知识?/p>
1?异常处理的基本语?br />
在java中,异常处理的完整语法是Q?br />
try{
//Q尝试运行的Q程序代?br />
}catch(异常cd 异常的变量名){
//异常处理代码
}finally{
//异常发生Q方法返回之前,L要执行的代码
}
以上语法有三个代码块Q?br /> try语句块,表示要尝试运行代码,try语句块中代码受异常监控,其中代码发生异常Ӟ会抛出异常对象?/p>
catch语句块会捕获try代码块中发生的异常ƈ在其代码块中做异常处理,catch语句带一个Throwablecd的参敎ͼ表示可捕获异常类型。当try中出现异常时Qcatch会捕获到发生的异常,q和自己的异常类型匹配,若匹配,则执行catch块中代码Qƈcatch块参数指向所抛的异常对象。catch语句可以有多个,用来匚w多个中的一个异常,一旦匹配上后,׃再尝试匹配别的catch块了。通过异常对象可以获取异常发生时完整的JVM堆栈信息Q以及异怿息和异常发生的原因等?/p>
finally语句块是紧跟catch语句后的语句块,q个语句块L会在Ҏq回前执行,而不是否try语句块是否发生异常。ƈ且这个语句块L在方法返回前执行。目的是l程序一个补救的Z。这样做也体CJava语言的健壮性?/p>
2?try、catch、finally三个语句块应注意的问?br /> W一、try、catch、finally三个语句块均不能单独使用Q三者可以组?try...catch...finally、try...catch、try...finally三种l构Qcatch语句可以有一个或多个Qfinally语句最多一个?br /> W二、try、catch、finally三个代码块中变量的作用域Z码块内部Q分别独立而不能相互访问。如果要在三个块中都可以讉KQ则需要将变量定义到这些块的外面?br /> W三、多个catch块时候,只会匚w其中一个异常类q执行catch块代码,而不会再执行别的catch块,q且匚wcatch语句的顺序是׃C?/p>
3、throw、throws关键?br /> throw关键字是用于Ҏ体内部,用来抛出一个Throwablecd的异常。如果抛Z查异常,则还应该在方法头部声明方法可能抛出的异常cd。该Ҏ的调用者也必须查处理抛出的异常。如果所有方法都层层上抛获取的异常,最lJVM会进行处理,处理也很单,是打印异常消息和堆栈信息。如果抛出的是Error或RuntimeExceptionQ则该方法的调用者可选择处理该异常。有兛_常的转译会在下面说明?/p>
throws关键字用于方法体外部的方法声明部分,用来声明Ҏ可能会抛出某些异常。仅当抛Z查异常,该方法的调用者才必须处理或者重新抛异常。当Ҏ的调用者无力处理该异常的时候,应该l箋抛出Q而不是囫囵吞枣一般在catch块中打印一下堆栈信息做个勉强处理。下面给Z个简单例子,看看如何使用q两个关键字Q?br />
public static void test3() throws Exception{ //抛出一个检查异?br /> throw new Exception("Ҏtest3中的Exception"); } 3?ThrowablecM的常用方?br /> getCause()Q返回抛出异常的原因。如?cause 不存在或未知Q则q回 null?br /> getMessage()Q返回异常的消息信息?br /> printStackTrace()Q对象的堆栈跟踪输出至错误输出流Q作为字D?System.err 的倹{?/p> |
1?能处理就早处理,抛出不去q不能处理的想法消化掉或者{换ؓRuntimeException处理。因为对于一个应用系l来_抛出大量异常是有问题的,应该从程序开发角度尽可能的控制异常发生的可能?br /> 2?对于查异常,如果不能行之有效的处理,q不如{换ؓRuntimeException抛出。这样也让上层的代码有选择的余地――可处理也可不处理?br /> 3?对于一个应用系l来_应该有自q一套异常处理框Ӟq样当异常发生时Q也能得到统一的处理风|优雅的异常信息反馈l用戗?/p>
四?异常的{译与异常?/font>
1、异常{译的原理
所谓的异常转译是一U异常{换另一U新的异常,也许q种新的异常更能准确表达E序发生异常?/p>
在Java中有个概念就是异常原因,异常原因D当前抛出异常的那个异常对象,几乎所有带异常原因的异常构造方法都使用Throwablecd做参敎ͼq也׃ؓ异常的{译提供了直接的支持,因ؓM形式的异常和错误都是Throwable的子cR比如将SQLException转换为另外一个新的异常DAOExceptionQ可以这么写Q?/p>
先自定义一个异常DAOExceptionQ?/p>
public class DAOException extends RuntimeException { //(省略了部分代? public DAOException(String message, Throwable cause) { super(message, cause); } } |
DAOException daoEx = new DAOException ( "SQL异常", e);
异常转译是针Ҏ有承Throwable类的类而言的,从编E的语法角度Ԍ其子cM间都可以怺转换。但是,从合理性和pȝ设计角度考虑Q可异常分Zc:Error、Exception、RuntimeExceptionQ笔者认为,合理的{译关pd应该如图 2Q?/p>
?/span>2异常转译
Z么要q么做呢Q笔者认为,异常的处理存在着一套哲学思想Q?strong>对于一个应用系l来_pȝ所发生的Q何异常或者错误对操作用户来说都是pȝ"q行?异常Q都是这个应用系l内部的异常。这也是异常转译和应用系l异常框架设计的指导原则?/strong>在系l中大量处理非检查异常的负面影响很多Q最重要的一个方面就是代码可L降低,E序~写复杂Q异常处理的代码也很苍白无力。因此,很有必要这些检查异常Exception和错误Error转换为RuntimeException异常Q让E序员根据情冉|军_是否捕获和处理所发生的异常?/p>
图中的三条线标识转换的方向,分三U情况:
①:Error到ExceptionQ将错误转换为异常,ql抛出。例如Spring WEB框架中,org.springframework.web.servlet.DispatcherServlet的doDispatch()Ҏ中,捕L错误转译Z个NestedServletException异常。这样做的目的是Z最大限度挽回因错误发生带来的负面媄响。因Z个Error常常是很严重的错误,可能会引Ll挂赗?/p>
②:Exception到RuntimeExceptionQ将查异常{换ؓRuntimeException可以让程序代码变得更优雅Q让开发h员集中经理设计更合理的程序代码,反过来也增加了系l发生异常的可能性?/p>
③:Error到RuntimeExceptionQ目的还是一L。把所有的异常和错误{译ؓ不检查异常,q样可以让代码更为简z,q有利于寚w误和异常信息的统一处理?/p>
1?异常?/strong>
异常N名思义是异常发生的原因一个传一个串hQ即把底层的异常信息传给上层Q这样逐层抛出。Java API文档中给Z一个简单的模型Q?/p>
try { lowLevelOp(); } catch (LowLevelException le) { throw (HighLevelException) new HighLevelException().initCause(le); } |
当程序捕获到了一个底层异常leQ在处理部分选择了l抛Z个更高别的新异常给此方法的调用者。这样异常的原因׃逐层传递。这P位于高层的异帔R归调用getCause()ҎQ就可以遍历各层的异常原因。这是Java异常铄原理。异帔R的实际应用很,发生异常时候逐层上抛不是个好注意Q上层拿到这些异常又能奈之何Q而且异常逐层上抛会消耗大量资源,因ؓ要保存一个完整的异常链信息?/p>
五?设计一个高效合理的异常处理框架
对于一个应用系l来_发生所有异常在用户看来都是应用pȝ内部的异常。因此应该设计一套应用系l的异常框架Q以处理pȝq行q程中的所有异常?/p>
Zq种观点Q可以设计一个应用系l的异常比如叫做AppException。ƈ且对用户来说Q这些异帔R是运行应用系l运行时发生的,因此AppException应该l承RuntimeExceptionQ这Ll中所有的其他异常都{译ؓAppExceptionQ当异常发生的时候,前端接收到AppExcetpionq做l一的处理。画出异常处理框架如?3 Q?/p>
?/span>3一个应用系l的异常处理框架
在这个设计图中,AppRuntimeException是系l异常的基类Q对外只抛出q个异常Q这个异常可以由前端Q客LQ接收处理,当异常发生时Q客L的相关组件捕获ƈ处理q些异常Q将"友好"的信息展C给客户?/p>
在AppRuntimeException下层Q有各种各样的异常和错误Q最l都转译为AppRuntimeExceptionQAppRuntimeException下面q可以设计一些别的子cd常,比如AppDAOException、OtherException{,q些都根据实际需要灵zd理。在往下就是如何将捕获的原始异常比如SQLException、HibernateException转换为更高一点AppDAOException?/p>
有关异常框架设计q方面公认比较好的就是SpringQSpring中的所有异帔R可以用org.springframework.core.NestedRuntimeException来表C,q且该基cȝ承的是RuntimeException。Spring框架很庞大,因此设计了很多NestedRuntimeException的子c,q有异常转换的工Pq些都是非常优秀的设计思想?/p>
六?Java异常处理ȝ
回顾全文Qȝ一下Java异常处理的要点:
1?异常是程序运行过E过E出现的错误Q在Java中用cL描述Q用对象来表C具体的异常。Java其区分为Error与ExceptionQError是程序无力处理的错误QException是程序可以处理的错误。异常处理是ZE序的健壮性?br />
2?Java异常cL自于Java API定义和用h展。通过l承Java API异常cd以实现异常的转译?br />
3?异常能处理就处理Q不能处理就抛出Q最l没有处理的异常JVM会进行处理?br />
4?异常可以传播Q也可以怺转译Q但应该Ҏ需要选择合理的异常{译的方向?br />
5?对于一个应用系l,设计一套良好的异常处理体系很重要。这一点在pȝ设计的时候就应该考虑到?/p>
摘要Q?/strong>本文倡导一U对异常条g本质的思考方式,q描qC些有助于设计的模式。最后,本文q将在AOP模型中,作ؓ怺渗透的问题Q来讨论异常的处理。当你能正确使用异常Ӟ它们会有极大的好处。本文将帮助你做到这一炏V?/font>
原作者:Barry Ruzek 译者: 易晓斓,原文Q?a >http://www.yeeyan.com/articles/view/2091/976
2 Z异常是如此重?/strong>
Java应用中的异常处理在很大程度上揭示了其所Z架构的强度。架构是在应用程序各个层ơ上所做出q循的军_。其中最重要的一个就是决定应用程序中的类Q亚pȝQ或层之间沟通的方式。Java异常是JavaҎ另cL行结果交出ȝ方式Q所以值得在应用架构中l予Ҏx?/font>
一个衡量Java设计师水q_开发团队纪律性的好方法就是读M们应用程序里的异常处理代码。首先要注意的是有多代码用于捕获异常,写进日志文gQ决定发生了什么,和在不同的异帔R跌{。干净Q简P兌性强的异常处理通常表明开发团队有着E_的用Java异常的方式。当异常处理代码的数量甚臌过其他代码Ӟ你可以看出团队之间的交流合作有很大的问题Q可能在一开始就不存在)Q每个h都在用他们自q方式来处理异常?/font>
对突发异常的处理l果是可以预见的。如果你问问团队成员Z么异怼被抛出,捕获Q或在特定的一处代码里忽视了异常的发生Q他们的回答通常是,“我没有别的可?#8221;。如果你问当他们~写的异常真的发生了会怎么P他们会皱qQ你得到的回{类gq样Q?#8220;我不知道。我们从没测试过?#8221;
你可以从客户端的代码判断一个java的组件是否有效利用了java的异常。如果它们包含着大堆的逻辑d清楚在何时一W操作失败了Qؓ何失败,是否有I补的余地Q那么原因很有可能要归咎于组件的报错设计。错误的报错pȝ会在客户端生大量的“记录然后忘掉”的代码,q些代码鲜有用途。最差的是弄拧的逻辑Q嵌套的try/catch/finally代码块,和一些其他的混ؕ而导致脆p难于管理的应用E序?/font>
事后再来解决Java异常的问题,或根本就不解冻I是Y仉目生乱ƈD滞后的主要原因。异常处理是一个在设计的各个部分都急需解决的问题。对异常处理建立一个架构性的U定是项目中首要做出的决定。合理用Java异常模型对确保你的应用简单,易维护,和正有着长远的媄响?/font>
3 解析异常
正确使用Java异常模型所包含的内容一直以来有着很大的争议。Java不是W一U支持异常算法语义的Q但是,它却是第一U通过~译器来执行声明和处理某些异常的规则的语a。许多h都认为编译时的异常检查对_的Y件设计颇有帮助。图1昄的Java异常的等U?br />
?QJava异常的等U?/font>
通常QJava~译器强q抛出基于java.lang.Throwable的异常的Ҏ要在它声明中?#8220;throws”部分加上那个异常。而且Q编译器q会证实客户端的Ҏ或者捕获已声明的异常,或者特别声明自׃抛出同样的异常。这些简单的规则对世界范围的JavaE序员都有深q的影响?/font>
~译器放松了对Throwablel承树中两个分支的异常检查。java.long.Error和java.lang.RuntimeException 的子cd于编译时的检查。在q两cMQY件工E师通常对运行中异常更感兴趣?#8220;不检?#8221;的异常指的是q一l,以便和所有其?#8220;?#8221;的异常区别开?/font>
我可以想象那些接?#8220;?#8221;的异常的人,也会很看重Java的数据类型。毕竟,~译器对数据cd施加的限刉׃格的~码和精的思维。编译时的类型检查对减少q行时的严重问题有帮助。编译时的异常检查也能v到类似的作用Q它会提醒开发h员某个方法可能会有预想不到的l果需要处理好?/font>
早期的徏议是可能的使用“察的异常”Q以此来最大限度的利用~译器提供的帮助来写出无错误的Y件。JavacdAPI的设计者们都认同这一点,他们q泛C?#8220;察的异常”来模拟类库方法中几乎所有的紧急应变措施。在J2SE5.1 API规格中,“察的异常”cd??的比率超q了“未检查的异常”cd?/font>
对程序员而言Q看上去在Javacd中大多数的常用方法对每一个可能的p|都声明了“察的异常”。例如,java.io?br /> 对IOExceptionq个“察的异常”有着很大的依赖。至有63个Javacd包,或直接,或通过十几个下面的子类Q抛个异常?/font>
I/O的失败极其稀有,但是却很严重。而且Q一旦发生,从你所写的代码里基本上是无法补救的。JavaE序员意识到他们不得不提供IOException 或类似的不可补救的事Ӟ而一个简单的JavacdҎ的调用就可能让这些事件发生。捕莯些异常给本来单的代码带来了一定的晦ӆQ因为即使在捕获的代码块里也基本上帮不上忙。但是不加以捕获又可能更p糕Q因为编译器要求你的Ҏ必须要抛出那些异常。这样你的实施细则就不得不暴露在外了Q而通常好的面向对象的设计都是要隐藏l节的?/font>
q样一个不可能赢的局面导致了我们今天所警告的绝大多数臭名卓著的异常处理的颠覆性格局。同时也衍生了很多正或错误的补救之道?/font>
一些Java界的知名人物开始质疑Java?#8220;察的异常”的模型是否是一个失败的试验。有一些东西肯定是p|的,但是q和在Java语言里加入对异常的检查是毫无兌的。失败是׃在Java API的设计者们的思维里,大多数失败的情Ş是雷同的Q所以可以通过同一U异怼辑և厅R?/font>
4 故障和应?/strong>
让我们来考虑在一个假想的银行应用中的CheckingAccountcR一个CheckingAcccount属于一个用P记蝲着用户的存ƾ余额,也能接受存款Q接受止兑的通知Q和处理汇入的支。一个CheckingAcccount对象必须协调同步U程的访问,因ؓM一个线E都可能改变它的状态?/font>CheckingAcccountc里processCheck的方法会接受一个Check对象为参敎ͼ通常从帐户余额里减去支票的金额。但是一个管理支清的用户端程序调用processCheckҎӞ必须有两U可能的应变措施。一QCheckingAccount对象里可能对该支已有一个止付的命oQ二Q帐L余额可能不已满x的金额?/font>
所以,processCheck的方法对来自客户端的调用可以?U方式回应。正常的是处理好支票Qƈ把方法签名里声明的结果返回给调用斏V两U应变的回应则是需要与支票清算端沟通的在银行领域实实在在存在的情况。processCheckҎ所?U返回结果都是按照典型的银行支票帐户的行精心设计的?/font>
在Java里,一个自然的Ҏ来表CZq紧急的应变是定义两U异常,比如StopPaymentExceptionQ止付异常)?InsufficientFundsExceptionQ余额不_常)。一个客L如果忽略q些异常是不对的Q因些异常在正常操作的情况下一定会被抛出。他们如同方法的{一样反映了Ҏ的全面行为?/font>
客户端可以很Ҏ的处理好q两U异常。如果对支票的兑付被停止了,客户端把该支交付特别处理。如果是因ؓ资金不Q用L可以从用L储蓄帐户里{UM些资金到支票帐户里,然后再试一ơ?/font>
在用CheckingAccount的APIӞq些应变都是可以预计的和自然的结果。他们ƈ不是意味着软g或运行环境的p|。这些异常和׃CheckingAccountcM一些内部实施细则引L真正p|是不同的?/font>
设想CheckingAccount对象在数据库里保持着一个恒定的状态,q用JDBC API来对之访问。在那个API里,几乎所有的数据库访问方法都有可能因为和CheckingAccount实施无关的原因而失败。比如,有h可能忘了把数据库服务器运行v来,一个未有连上的|络数据U,讉K数据库的密码改变了,{等?/font>
JDBC依靠一U?#8220;查的异常”QSQLExceptionQ来汇报M可能的错误。可能出错的l大多数原由都是数据库的配置Q连接,或其所在的g设施。对processCheckҎ而言Q它对上q错误是无计可施的。这不应该,因ؓprocessCheck臛_了解它自q实施l则。在调用栈里上游的方法能处理q些问题的可能就更小?/font>
CheckingAccountq个例子说明了一个方法不能成功返回它惌的结果的两个基本原因。这里是两个描述性的术语Q?/font>
应变
与实际预料相W,一个方法给出另外一U回应,而这U回应可以表达成该方法所要达到的目的之一。这个方法的调用者预料到q个情况的出玎ͼq有相对的应付之道?/font>
故障
在未l计划的情况下,一个方法不能达到它的初Pq是一个不诉诸该方法的实施l则很难搞清的情况?br />
应用q些术语Q对processCheckҎ而言Q一个止付的命o和一个超额的提取是两U可能的应变。而SQLException反映了可能的故障。processCheckҎ的调用者应该能够提供应变,但却不一定能有效的处理好可能发生的故障?/font>
Java异常的匹?br /> 在徏立应用架构中Java异常的规则时Q以应变和故障的方式仔细考虑?#8220;什么可能会出错”是有长远意义的?/font>
应变情况恰如其分地匹配给了Java查的异常。因为它们是Ҏ的语义算法合同中不可~少的一部分Q在q里借助于编译器的帮助来保它们得到解决是很有道理的。如果你发现~译器坚持应变的异常必须要处理或者在不方便的时候必要声明会给你带来些ȝQ你在设计上几乎肯定要做些重构了。这其实是g好事?/font>
出现故障的情况对开发h员而言是蛮有意思的Q但对Y仉辑而言却ƈ非如此。那些Y?#8221;消化问题“的专家们需要关于故障的信息以便来解决问题。因此,未检查的异常是表C故障的很好方式。他们让故障的通知原封不动C调用栈上所有的Ҏ滤过Q到达一个专门来捕获它们的地方,q得到它们自w包含的有利于诊断的信息Q对整个事g提供一个有节制的优雅的l论。生故障的Ҏ不需要来声明Q异常)Q上游的调用Ҏ不需要捕获它们,Ҏ的实施细则被正确的隐藏v来- 以最低的代码复杂度?/font>
C些的Java APIQ比如像Spring架构和Java Data OjectscdҎ查的异常几乎没有依赖。Hibernate ORM架构?.0版本里重新定义了一些关键功能来去除Ҏ查的异常的用。这意味着在这些架构D报的l大部分异常都是不可恢复的,归咎于错误的Ҏ调用代码Q或是类g数据库服务器之类的底层部件的p|。特别的Q强q一个调用方来捕h声明q些异常几乎没有M好处?br /> 设计里的故障处理
在你的计划里Q承认你需要去做就q好了有效处理好故障的第一步。对那些坚信自己能写出无懈可ȝ软g的工E师们来_承认q一Ҏ不容易的。这里是一些有帮助的思考方式。首先,如果错误俯拾xQ应用的开发时间将很长Q当然前提是E序员自qbug自己修理。第二,在Javacd中,q度使用查的异常来处理故障情形将q你的代码要应对好故障Q即使你的调用次序完全正。如果没有一个故障处理的架构Q凑合的异常处理导致应用中的信息丢失?/font>
一个成功的故障处理架构一定要辑ֈ下面的目标:
• 减少代码的复杂?
• 捕获和保存诊断性信?
• 对合适的人提醒注?
• 优雅地退?
故障是应用的真实意图的干扰。因此,用来处理它们的代码应量的少Q理想上Q把它们和应用的语义法部分隔离开。故障的处理必须满那些负责Ҏ它们的h的需要。开发h员需要知道故障发生了Qƈ得到能帮助他们搞清ؓ何发生的信息。即使一个故障,在定义上而言Q是不可补救的,好的故障处理会试着优雅地结束引h障的zd?br />
Ҏ障情况用未查的异常
在做框架上的军_Ӟ用未查的异常来代表故障情冉|有很多原因的。Java的运行环境对代码的错误会抛出“q行时异?#8221;的子c,比如Q?ArithmeticException或ClassCastException。这Z的框架设了一个先例。未查的异常让上游的调用Ҏ不需要ؓ和它们目的不相关的情况而添加代码,从而减了混ؕ?/font>
你的故障处理{略应该认识到Javacd的方法和其他API可能会用检查的异常来代表对你的应用而言只可能是故障的情c在q种情Ş下,采用设计U定来捕获API异常Q将其以故障来看待,抛出一个未查的异常来指C故障的情况和捕莯断的信息?/font>
在这U情况下抛出的特定异常类型应该由你的框架来定义。不要忘C个故障异常的主要目的是传递记录下来的诊断信息Q以便让Z来想出出错的原因。用多个故障异常类型可能有些过Q因Z的架构对它们都一视同仁。多数情况下Q一条好的,描述性强的信息将单一的故障类型嵌入就够用了。用Java基本?RuntimeException来代表故障情冉|很容易的。截止到Java1.4QRuntimeExceptionQ和其他的抛出类型一P都支持异常的嵌套Q这样你可以捕获和报出导向故障的检查的异常?/font>
你也怼Z故障报告的目的而定义你自己的未查的异常。这样做可能是必要的Q如果你使用Java1.3或更早的版本Q它们都不支持异常的嵌套。实施一个类似的嵌套功能来捕获和转换你应用中构成故障的检查的异常是很单的。你的应用在报错时可能需要一个特D的行ؓ。这可能是你在架构中创徏 RuntimeException子类的另一个原因?/font>
建立一个故障的屏障
对你的故障处理架构而言Q决定抛Z么样的异常,何时抛出是重要的军_。同样重要的是,何时来捕获一个故障异常,之后再怎么办。这里的目的是让你应用中的功能性部分不需要处理故障。把问题分开来处理通常都是一件好事情Q有一个中央故障处理机刉q来看是很有裨益的?/font>
在故障屏障的模式里,M应用lg都可以抛出故障异常,但是只有作ؓ“故障屏障”的组件才捕获异常。采用此U模式去除了大多数程序员Z在本地处理故障而插入的复杂的代码。故障屏障逻辑上位于调用栈的上层,q样在一个默认的行动被激发前Q一个异常向上D报的行ؓpL了。根据不同的应用cdQ默认的行动所指也不同。对一个独立的Java应用而言Q这个行动指zȝ的线E被停止。对一个位于应用服务器上的Web应用而言Q这个行动指应用服务器向览器送出不友好的Q甚至o人尴的Q回应?/font>
一个故障屏障组件的W一要务是记录下故障异怸包含的信息以为将来所用。到现在为止Q一个应用日志是做成此事的首选。异常的嵌套的信息,栈日志,{等Q都是对诊断有h值的信息。传递故障信息最差的地方是通过用户界面。把应用的用者卷q查错的q程对你Q对你的用户而言都不好。如果你真的很想把诊断信息放上用L面,那可能意味着你的日志{略需要改q?/font>
故障屏障的下一个要务是以一U可控的方式来结束操作。这具体的意义要取决于你应用的设计,但通常包括产生一个可通用的回应给可能正在{待的客L。如果你的应用是一个Web serviceQ这意味着在回应中用soap:Server?lt;faultcode>和通用的失败信?lt; faultstring>来徏立一个SOAP故障元素<fault>。如果你的应用于览器交,q个屏障׃安排好一个通用?HTML回应来表明需求是不能被处理的?/font>
在一个Struts的应用里Q你的故障屏障会以一U全局异常处理器的形式出现Qƈ被配|成处理RuntimeException的Q何子cR你的故障屏障类g伸org.apache.struts.action.ExceptionHandlerc,必要的话Q重写它的方法来实施用户自己的特别处理。这样就会处理好不小心生的故障情况和在处理一个Struts动作时发现的故障。图2昄的就是应变和故障异常?br />
? 应变和故障异?/font>
如果你用的是Spring MVC架构Q你可以l承SimpleMappingExceptionResolverc,q|成处理RuntimeException和它的子cMQ这样很Ҏ的就v了故障屏障。通过重写resolveException的方法,你可以在使用父类的方法来把需求导引到一个发出通用错误提示的view lg之前Q加入你需要的用户化的处理?/font>
当你的架构包含了故障屏障Q程序员都知晓了后,再写Zơ性的故障异常的冲动就会锐减。结果就是应用中出现更干净Q更易于l护的代码?/font>
5 架构中应变的处理
故障处理交与屏障后Q主要组仉的应变交变得容易多了。一个应变代表着与主要返回结果同{重要的另外一U方法结果。因此,查的异常cd是一个能够很好地传递应变情늚存在q提供必要的信息来与它竞争的工具。这个方式借助于Java~译器的帮助来提醒程序员关于他们所用的API的方斚w面以及提供全套的Ҏ输出的必要性?/font>
仅仅使用Ҏ的返回值类型来传递简单的应变是可能的。比如,q回一个空引用Q而不是一个具体的对象Q可以意味着对象׃一个已定义的原因不能被建立?Java I/O的方法通常q回一个整数|1Q而不是字节的值或字节的数来表C文件的l尾。如果你的方法的语义单到可以允许的地步,另一U返回值的Ҏ是可以用的Q因为它摒弃了异常带来的额外的花销。不之处是Ҏ的调用方要检一下返回的值来判断是主要结果,q是应变l果。但是,~译器没有办法来保证Ҏ调用者会使用q个判断?/font>
如果一个方法有一个void的返回类型,异常是唯一的方法来表示应变发生了。如果一个方法返回的是一个对象的引用Q那么返回值只可能是空或非I(null and non-null)。如果一个方法返回一个整数型Q选择与主要返回g冲突的,可以表示多种应变情况的数值是可能的。但是这L话,我们p入了错误代码查的世界Q而这正式Java异常模式所着力避免的?br /> 提供一些有用的信息
定义不同的故障报告的异常cd是没什么道理的Q因为故障屏障对所有异常类型一视同仁。应变异常就有很大的不同Q因为它们的原意是要向方法调用者传递各U情c你的架构可能会指出q些异常应该l承java.lang.Exception或一个指定的基类?/font>
不要忘记你的异常应该是百分百的JavacdQ你可以用它来存放ؓ你的Ҏ目的服务的特D字D,ҎQ甚x构造器。比如,被假想的 processCheck()Ҏ抛出的InsufficientFundsExceptionq个异常cd应该包含着一?OverdraftProtection的对象,它能够从另外一个帐户里把短~的资金转过来?/font>
日志q是不要日志
记录下故障异常是有用处的Q因为日志的目的是在一些需要改正的情况下,日志可以吸引Z的注意力。但对应变异常而言却ƈ非如此。应变异常可能代表的只是极少数情况,但是在你的应用里Q每一个情况还是会发生的。它们意味着你的应用正在如最初的设计般正常工作着。经常把日志代码加进应变的捕获块里会使你的代码晦涩难懂,而又没有实际的好处。如果一个应变代表了一重要的事Ӟ在抛Z个异常应变来警醒调用者之前,产生一W日志,记录下这个事件可能会让这个方法更好些?/font>
6 异常的各个方?/strong>
在Aspect Oriented ProgrammingQAOPQ的术语里,故障和应变的处理是互相渗透的问题。比如,要实施故障屏障的模式Q所有参与的cd遵循通用规格Q?/font>
• 故障屏障Ҏ必须存活在遍历参与类的方法调用图的最前端
• 参与cdM用未查的异常来表C故障情?
• 参与cdM用故障屏障期望得到的有针Ҏ的未检查的异常cd
• 参与cdL获ƈ从低端方法中把在执行情境下注定的故障转换成检查的异常
• 参与cM能干扰故障异常被传递到故障屏障的过E?/font>
q些问题越了那些本不相q的cȝ边界。结果就是少数零散的故障处理代码Q以及屏障类和参与类间暗含的耦合Q这已经比不使用模式q步多了Q)。AOP让故障处理的问题被封装在通用的可以作用到参与cȝ层面上。如AspectJ和Spring AOPq样的Java AOP架构认ؓ异常的处理是d故障处理行ؓ的切入点。这P把参与者绑定在故障屏障的模式可以放松些。故障的处理可以存活在一个独立的Q不相干的方面里Q从而摒弃了屏障Ҏ需要放在方法激zL序的最前头的要求?/font>
如果在你的架构里利用了AOPQ故障和应变的处理是理想的在应用里用到的在方面上的候选。对故障和应变的处理在AOP架构下的使用做一个完整的勘探是来论文里一个很有意思的题目?/font>
7 l论
虽然Java异常模型自它出现以来激发了热烈的讨论,如果使用正确的话Q它的hD是很大的。作Z个设计师Q你的Q务是建立好规格来最大限度地利用好这个模型。以故障和应变的方式来考量异常可以帮助你做出正的军_。合理用好Java异常模型可以让你的应用简单,易维护,和正。AOP技术将故障和应变定位ؓ怺渗透的问题Q这个方法可能会对你的架构提供一些帮助?/font>
8 引用
• Sun's Exception Tutorial Java异常的基本知?br />
• Does Java Need Checked Exception? Bruce Eckel对Java中检查的异常的异?
• Exceptional Java 关于异常的很好的讨论Q有架构式的异常规则来模?br />
• The Exceptions Debate 来自于developerWorks的关于异常的来龙去脉
• The Apache Struts Web Application Framework Struts的信息源
• The Spring Framework Spring框架的信息源
• Wikipedia: Aspect Oriented Programming 一个很好的对AOP概念的介l?br />
• The AspectJ Project AspectJ的信息源
|