【正文】
理在 AOP 架構(gòu)下的使用做一個(gè)完整的勘探將是將來論文里一個(gè)很有意思的題目。如 AspectJ和 Spring AOP這樣的 Java AOP 架構(gòu)認(rèn)為異常的處理是添加故障處理行為的切入點(diǎn)。 異常的各個(gè)方面 在 Aspect Oriented Programming( AOP)的術(shù)語里,故障和應(yīng)變的處理是互相滲透的問題。應(yīng)變異??赡艽淼闹皇菢O少數(shù)情況,但是在你的應(yīng)用里,每一個(gè)情況還是會(huì)發(fā)生的。 不要忘記你的異常應(yīng)該是百分百的 Java 類型,你可以用它來存放為你的特殊目的服務(wù)的特殊字段,方法,甚至是構(gòu)造器。但是這樣的話,我們就進(jìn)入了錯(cuò)誤代碼檢查的世界,而這正式 Java 異常模式所著力避免的。但是,編譯器沒有辦法來保證方法調(diào)用者會(huì)使用這個(gè)判斷。比如,返回一個(gè)空引用,而不是一個(gè)具體的對象,可以意味著對象由于一個(gè)已定義的原因不能被建立。一個(gè)應(yīng)變代表著與主要返回結(jié)果同等重要的另外一種方法結(jié)果。通過重寫 resolveException 的方法,你可以在使用父類的方法來把需求導(dǎo)引到一個(gè)發(fā)出通用錯(cuò)誤提示的 view 組件之前,加入你需要的用戶化的處理。你的故障屏障類將延伸 類,必要的話,重寫它的方法來實(shí)施用戶自己的特別處理。這具體的意義要取決于你應(yīng)用的設(shè)計(jì),但通常包括產(chǎn)生一個(gè)可通用的回應(yīng)給可能正在等待的客戶端。傳遞故障信息最差的地方是通過用戶界面。對一個(gè)位于應(yīng)用服務(wù)器上的 Web 應(yīng)用而言,這個(gè)行動(dòng)指應(yīng)用服務(wù)器向?yàn)g覽器送出不友好的(甚至令人尷尬的)回應(yīng)。采用此種模式去除了大多數(shù)程序員為了在本地處理故障而插入的復(fù)雜的代碼。同樣重要的是,何時(shí)來捕獲一個(gè)故障異常,之后再怎么辦。實(shí)施一個(gè)類似的嵌套功能來捕獲和轉(zhuǎn)換你應(yīng)用中構(gòu)成故障的檢查的異常是很簡單的。使用 Java 基本的 RuntimeException 來代表 故障情況是很容易的。 在這種情況下拋出的特定異常類型應(yīng)該由你的框架來定義。這為你的框架設(shè)了一個(gè)先例。開發(fā)人員需要知道故障發(fā)生了,并得到能 幫助他們搞清為何發(fā)生的信息。 (4) 優(yōu)雅地退出行動(dòng) 。如果沒有一個(gè)故障處理的架構(gòu),湊合的異常處理將導(dǎo)致應(yīng)用中的信息丟失。對那些堅(jiān)信自己能寫出無懈可擊的軟件的工程師們來說,承認(rèn)這一點(diǎn)是不容易的。 Hibernate ORM 架構(gòu)在 版本里重新定義了一些關(guān)鍵功能來去除對檢查的異常的使用。因此,未檢查的異常是表示故障的很好方式。如果你發(fā)現(xiàn)編譯器堅(jiān)持應(yīng)變的異常必須要處理或者在不方便的時(shí)候必須要聲明會(huì)給你帶來些麻煩,你在設(shè)計(jì)上幾乎肯定要做些重構(gòu)了。 processCheck 方法的調(diào)用者應(yīng)該能夠提供應(yīng)變,但卻不一定能有效的處理好可能發(fā)生的故障。這個(gè)方法的調(diào)用者預(yù)料到這個(gè)情況的出現(xiàn),并有相對的應(yīng)付之道。這不應(yīng)該,因?yàn)?processCheck 至少了解它自己的實(shí)施細(xì)則。比如,有人可能忘了把數(shù)據(jù)庫服務(wù)器運(yùn)行起來,一個(gè)未有連上的網(wǎng)絡(luò)數(shù)據(jù)線,訪問數(shù)據(jù)庫的密碼改變了,等等。他們并不是意味著軟件或運(yùn)行環(huán)境的失敗。 客戶端可以很容易的處理好這兩種異常。 processCheck 方法所有 3 種返回結(jié)果都是按照典型的銀行支票帳戶的行為而精心設(shè)計(jì)的。一,CheckingAccount 對象里可能對該支票已有一個(gè)止付的命令;二,帳戶的余額可 能不足已滿足支票的金額。一個(gè)CheckingAcccount 屬于一個(gè)用戶,記載著用戶的存款余額,也能接受存款,接受止兌的通知,和處理匯入的支票。 一些 Java 界的知名人物開始質(zhì)疑 Java 的 “ 檢察的異常 ” 的模型是否是一個(gè)失敗的試驗(yàn)。但是不加以捕獲又可能更糟糕,因?yàn)榫幾g器要求你的方法必須要拋出那些異常。 圖 1: Java 異常等級層次圖 I/O 的失敗極其稀有,但是卻很嚴(yán)重。 對程序員而言,看上去在 Java 類庫中大多數(shù)的常用方法對每一個(gè)可能的失敗都聲明了 “ 檢察的異常 ” 。編譯時(shí)的異常檢查也能起到類似的作用,它會(huì)提醒開發(fā)人員某個(gè)方法可能會(huì)有預(yù)想不到的結(jié)果需要處理好。 “ 不檢查 ” 的異常指的是這一組,以便和所有其它 “ 檢查 ” 的異常區(qū)別開。這些簡單的規(guī)則對世界范圍的 Java 程序員都有深遠(yuǎn)的影響。 Java 不是第一種支持異常算法語義的;但是,它卻是第一種通過編譯器來執(zhí)行聲明和處理某些異常的規(guī)則的語言。異常處理是一個(gè)在設(shè)計(jì)的各個(gè)部分都急需解決的問題。如果它們包含著大堆的邏輯去弄清楚在何時(shí)一筆操作失敗了,為何失敗,是否有彌補(bǔ)的余地,那么原因很有可能要?dú)w咎于組件的報(bào)錯(cuò)設(shè)計(jì)。如果你問問團(tuán)隊(duì)成員為什么異常會(huì)被拋出,捕獲,或在特定的一處代碼里忽視了異常的發(fā)生,他們的回答通常是, “ 我沒有別的可做 ” 。首先要注意的是有多少代碼用于捕獲異常,寫進(jìn)日志文件,決定發(fā)生了什么,和在不同的異常間跳轉(zhuǎn)。架構(gòu)是在應(yīng)用程序各個(gè)層次上所做出并遵循的決定。最后,本文還將在AOP 模型中,作為相互滲透的問題,來討論異常的處理。 Java 異常一直以來就是 人們 爭議的 焦點(diǎn) 。有人爭論到,在 Java 語言中的異常檢查已是一場失敗的試驗(yàn)。當(dāng)你能正確使用異常時(shí),它們會(huì)有極大的好處。其中最重要的一個(gè)就是決定應(yīng) 用程序中的類,亞系統(tǒng),或?qū)又g溝通的方式。干凈,簡捷,關(guān)聯(lián)性強(qiáng)的異常處理通常表明開發(fā)團(tuán)隊(duì)有著穩(wěn)定的使用 Java 異常的方式。如果你問當(dāng)他們編寫的異常真的發(fā)生了會(huì)怎么樣,他們會(huì)皺皺眉,你得到的回答類似于這樣, “ 我不知道。錯(cuò)誤的報(bào)錯(cuò)系統(tǒng)會(huì) 在客戶端產(chǎn)生大量的“ 記錄然后忘掉 ” 的代碼,這些代碼鮮有用途。對異常處理建立一個(gè)架構(gòu)性的約定是項(xiàng)目中首要做出的決定。許多人都認(rèn)為編譯時(shí)的異常檢查對精確的軟件設(shè)計(jì)頗有幫助。 編譯器放松了對 Throwable 繼承樹中兩個(gè)分支的異常檢查。 我可以想象那些接受 “ 檢查 ” 的異常的人,也會(huì)很看重 Java 的數(shù)據(jù)類型。 早期的建議是盡可能的使用 “ 檢察的異常 ” ,以此來最大限度的利用編譯器提供的幫助來寫出無錯(cuò)誤的軟件。例如, 包 。而且,一旦發(fā)生,從你所寫的代碼里基本上是無法補(bǔ)救的。這樣你的實(shí)施細(xì)則就不得不暴露在外了,而通常好的面向?qū)ο蟮脑O(shè)計(jì)都是要隱藏 細(xì)節(jié)的。有一些東西肯定是失敗的,但是這和在 Java 語言里加入對異常的檢查是毫無關(guān)聯(lián)的。一個(gè) CheckingAcccount 對象必須協(xié)調(diào)同步線程的訪問,因?yàn)槿魏我粋€(gè)線程都可能改變它的狀態(tài)。 所以, processCheck 的方法對來自客戶端的調(diào)用可以有 3 種方式回應(yīng)。 在 Java 里,一個(gè)自然的方法來表示上述緊急的應(yīng)變是定義兩種異常,比如StopPaymentException(止付異常)和 InsufficientFundsException(余額不足異常 )。如果對支票的兌付被停止了,客戶端把該支票交付特別處理。這些異常和由于 CheckingAccount 類中一些內(nèi)部實(shí)施細(xì)則引起的真正失敗是 不同的。 JDBC 依靠一種 “ 檢查的異常 ” , SQLException,來匯報(bào)任何可能的錯(cuò)誤。在調(diào)用棧里上游的方法能處理這些問題的可能就更小。 (2) 故障 在未經(jīng)計(jì)劃的情況下,一個(gè)方法不能達(dá)到它的初衷,這是一個(gè)不訴諸該方法的實(shí) 施細(xì)則就很難搞清的情況 。 Java 異常的匹配 在建立應(yīng)用架構(gòu)中 Java 異常的規(guī)則時(shí),以應(yīng)變和故障的方式仔細(xì)考慮好 “ 什么可能會(huì)出錯(cuò) ” 是有長遠(yuǎn)意義的。這其實(shí)是件好事。他們讓故障的通知原封不動(dòng)地從調(diào)用棧上所有的方法濾過,到達(dá)一個(gè)專門來捕獲它們的地方,并得到它們自身包含的有利于診斷的信息,對整個(gè)事件提供一個(gè)有節(jié)制的優(yōu)雅的結(jié)論。這就意味著在這些架構(gòu)舉報(bào)的絕大部分異常都是不可恢復(fù)的,歸咎于錯(cuò)誤的方法調(diào)用代碼,或是類似于數(shù)據(jù)庫服務(wù)器之類的底層部件的失敗。這里是一些有幫助的思考方式。 一個(gè)成功的故障處理架構(gòu)一定要達(dá)到下面的目標(biāo): (1) 減少代碼的復(fù)雜性 。 故障是應(yīng)用的真實(shí)意圖的干擾。即使一個(gè)故障,在定義上而言,是不可補(bǔ)救的,好的故障處理會(huì)試著優(yōu)雅地結(jié)束引起故障的活動(dòng)。未檢查的異常讓上游的調(diào)用方法不需要為和它們目的不相關(guān)的情況而添加代碼,從而減少了混亂。不要忘記一個(gè)故障異常的主要目的是傳遞記錄下來的診斷信息,以便讓人們來想出出錯(cuò)的原因。截止到 , RuntimeException,和其他的拋出類型一樣, 都支持異常的嵌套,這樣你就可以捕獲和報(bào)出導(dǎo)向故障的檢查的異常。你的應(yīng)用在報(bào)錯(cuò)時(shí)可能需要一個(gè)特殊的行為。這里的目的是讓你應(yīng)用中的功能性部分不需要處理故障。故障屏障邏輯上位于調(diào)用棧的上層,這樣在一個(gè)默認(rèn)的行動(dòng)被激發(fā)前,一個(gè)異常向上舉報(bào)的行為就被阻止了。 一個(gè)故障屏障組件的第一要?jiǎng)?wù)就是記錄下故障異常中包含的信息以為將來所用。把應(yīng)用的使用者卷進(jìn)查錯(cuò)的進(jìn)程對你,對你的用戶而言都不好。如果你的應(yīng)用是一個(gè) Web service,這就意味著在回應(yīng)中用 soap:Server 的 faultcode和通用的失敗信息 faultstring來建立一個(gè) SOAP 故障元素 fault。這樣就會(huì)處理好不小心產(chǎn)生的故障情況和在處理一個(gè) Struts 動(dòng)作時(shí)發(fā)現(xiàn)的故障。 當(dāng)你的架構(gòu)包含了故障屏障,程序員都知曉了后,再寫出一次性的故障異常的沖動(dòng)就會(huì)銳減。因此,檢查的異常類型是一個(gè)能夠很好地傳遞應(yīng)變 情況的存在并提供必要的信息來與它競爭的工具。 Java I/O的方法通常返回一個(gè)整數(shù)值- 1,而不是字節(jié)的值或字節(jié)的數(shù)來表示文件的結(jié)尾。 如果一個(gè)方法有一個(gè) void 的返回類型,異常是唯一的方法來表示應(yīng)變發(fā)生了。 提供一些有用的信息 定義不同的故障報(bào)告的異常類型是沒什 么道理的,因?yàn)楣收掀琳蠈λ挟惓n愋鸵灰曂?。比如,被假想?processCheck()方法拋出的InsufficientFundsException 這個(gè)異常類型就應(yīng)該包含著一個(gè) OverdraftProtection的對象,它能夠從另外一個(gè)帳 戶里把短缺的資金轉(zhuǎn)過來。它們意味著你的應(yīng)用正在如最初的設(shè)計(jì)般正常工作著。比如,要實(shí)施故障屏障的模式,所有參與的類必須遵循通用規(guī)格: 這些問題超越了那些本不相干的類的邊界。這樣,把參與者綁定在故障屏障的模式可以放松些。 結(jié)論 雖然 Java 異常模型自它出現(xiàn)以來就激發(fā)了熱烈的討論,如果使用正確的話,它的價(jià)值還是很大的。 AOP 技術(shù)將故障和應(yīng)變定位為相互滲透的問題,這個(gè)方法可能會(huì)對你的架構(gòu)提供一些幫助。t know what else to do. If you ask them what would happen if an exception they are coding for actually occurred, a frown follows, and you get a statement similar to, I don39。s exception model has been the subject of considerable debate. Java was not the first language to support exceptionlike semantics。t be right for a client to ignore these, since they are sure to be thrown in the normal operation of the application. They help express the full behavior of the method just as importantly as the method signature. Clients can easily handle both kinds of exception. If payment on a check is stopped, the client can route the check for special handling. If there are insufficient funds, the client can transfer funds from the customer39。s intended purpose. The caller of the method expects these kinds of conditions and has a strategy for coping with them. Fault An unplanned condition that prevents a method from achieving its intended purpose that cannot be described without reference to the method39。s a sure bet that your design could use some refactoring. That39。s real purpose. Therefore, the amount of code devoted to processing them should be minimal and, ideally, isolated from the semantic parts of the application. Fault processing must serve the needs of the people responsible for correcting them. They need to know that a fault happened and