ASM 是一個(gè) Java 字節(jié)碼操控框架。它能被用來動(dòng)態(tài)生成類或者增強(qiáng)既有類的功能。ASM 可以直接產(chǎn)生二進(jìn)制 class 文件,也可以在類被加載入 Java 虛擬機(jī)之前動(dòng)態(tài)改變類行為。Java class 被存儲(chǔ)在嚴(yán)格格式定義的 .class 文件里,這些類文件擁有足夠的元數(shù)據(jù)來解析類中的所有元素:類名稱、方法、屬性以及 Java 字節(jié)碼(指令)。ASM 從類文件中讀入信息后,能夠改變類行為,分析類信息,甚至能夠根據(jù)用戶要求生成新類。
與 BCEL 和 SERL 不同,ASM 提供了更為現(xiàn)代的編程模型。對(duì)于 ASM 來說,Java class 被描述為一棵樹;使用 “Visitor” 模式遍歷整個(gè)二進(jìn)制結(jié)構(gòu);事件驅(qū)動(dòng)的處理方式使得用戶只需要關(guān)注于對(duì)其編程有意義的部分,而不必了解 Java 類文件格式的所有細(xì)節(jié):ASM 框架提供了默認(rèn)的 “response taker”處理這一切。
動(dòng)態(tài)生成 Java 類與 AOP 密切相關(guān)的。AOP 的初衷在于軟件設(shè)計(jì)世界中存在這么一類代碼,零散而又耦合:零散是由于一些公有的功能(諸如著名的 log 例子)分散在所有模塊之中;同時(shí)改變 log 功能又會(huì)影響到所有的模塊。出現(xiàn)這樣的缺陷,很大程度上是由于傳統(tǒng)的 面向?qū)ο缶幊套⒅匾岳^承關(guān)系為代表的“縱向”關(guān)系,而對(duì)于擁有相同功能或者說方面 (Aspect)的模塊之間的“橫向”關(guān)系不能很好地表達(dá)。例如,目前有一個(gè)既有的銀行管理系統(tǒng),包括 Bank、Customer、Account、Invoice 等對(duì)象,現(xiàn)在要加入一個(gè)安全檢查模塊, 對(duì)已有類的所有操作之前都必須進(jìn)行一次安全檢查。
然而 Bank、Customer、Account、Invoice 是代表不同的事務(wù),派生自不同的父類,很難在高層上加入關(guān)于 Security Checker 的共有功能。對(duì)于沒有多繼承的 Java 來說,更是如此。傳統(tǒng)的解決方案是使用 Decorator 模式,它可以在一定程度上改善耦合,而功能仍舊是分散的 —— 每個(gè)需要 Security Checker 的類都必須要派生一個(gè) Decorator,每個(gè)需要 Security Checker 的方法都要被包裝(wrap)。下面我們以 Account
類為例看一下 Decorator:
首先,我們有一個(gè) SecurityChecker
類,其靜態(tài)方法 checkSecurity
執(zhí)行安全檢查功能:
public class SecurityChecker { public static void checkSecurity() { System.out.println("SecurityChecker.checkSecurity ..."); //TODO real security check } } |
另一個(gè)是 Account
類:
public class Account { public void operation() { System.out.println("operation..."); //TODO real operation } } |
若想對(duì) operation
加入對(duì) SecurityCheck.checkSecurity()
調(diào)用,標(biāo)準(zhǔn)的 Decorator 需要先定義一個(gè) Account
類的接口:
public interface Account { void operation(); } |
然后把原來的 Account
類定義為一個(gè)實(shí)現(xiàn)類:
public class AccountImpl extends Account{ public void operation() { System.out.println("operation..."); //TODO real operation } } |
定義一個(gè) Account
類的 Decorator,并包裝 operation
方法:
public class AccountWithSecurityCheck implements Account { private Account account; public AccountWithSecurityCheck (Account account) { this.account = account; } public void operation() { SecurityChecker.checkSecurity(); account.operation(); } } |
在這個(gè)簡單的例子里,改造一個(gè)類的一個(gè)方法還好,如果是變動(dòng)整個(gè)模塊,Decorator 很快就會(huì)演化成另一個(gè)噩夢(mèng)。動(dòng)態(tài)改變 Java 類就是要解決 AOP 的問題,提供一種得到系統(tǒng)支持的可編程的方法,自動(dòng)化地生成或者增強(qiáng) Java 代碼。這種技術(shù)已經(jīng)廣泛應(yīng)用于最新的 Java 框架內(nèi),如 Hibernate,Spring 等。
最直接的改造 Java 類的方法莫過于直接改寫 class 文件。Java 規(guī)范詳細(xì)說明了class 文件的格式,直接編輯字節(jié)碼確實(shí)可以改變 Java 類的行為。直到今天,還有一些 Java 高手們使用最原始的工具,如 UltraEdit 這樣的編輯器對(duì) class 文件動(dòng)手術(shù)。是的,這是最直接的方法,但是要求使用者對(duì) Java class 文件的格式了熟于心:小心地推算出想改造的函數(shù)相對(duì)文件首部的偏移量,同時(shí)重新計(jì)算 class 文件的校驗(yàn)碼以通過 Java 虛擬機(jī)的安全機(jī)制。
Java 5 中提供的 Instrument 包也可以提供類似的功能:啟動(dòng)時(shí)往 Java 虛擬機(jī)中掛上一個(gè)用戶定義的 hook 程序,可以在裝入特定類的時(shí)候改變特定類的字節(jié)碼,從而改變?cè)擃惖男袨椤5瞧淙秉c(diǎn)也是明顯的:
ClassFileTransformer. transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer)
,還是 Instrument.redefineClasses(ClassDefinition[] definitions)
,都必須提供新 Java 類的字節(jié)碼。也就是說,同直接改寫 class 文件一樣,使用 Instrument 也必須了解想改造的方法相對(duì)類首部的偏移量,才能在適當(dāng)?shù)奈恢蒙喜迦胄碌拇a。 盡管 Instrument 可以改造類,但事實(shí)上,Instrument 更適用于監(jiān)控和控制虛擬機(jī)的行為。
一種比較理想且流行的方法是使用 java.lang.ref.proxy
。我們?nèi)耘f使用上面的例子,給 Account
類加上 checkSecurity 功能:
首先,Proxy 編程是面向接口的。下面我們會(huì)看到,Proxy 并不負(fù)責(zé)實(shí)例化對(duì)象,和 Decorator 模式一樣,要把 Account
定義成一個(gè)接口,然后在 AccountImpl
里實(shí)現(xiàn) Account
接口,接著實(shí)現(xiàn)一個(gè) InvocationHandler
Account
方法被調(diào)用的時(shí)候,虛擬機(jī)都會(huì)實(shí)際調(diào)用這個(gè) InvocationHandler
的 invoke
方法:
class SecurityProxyInvocationHandler implements InvocationHandler { private Object proxyedObject; public SecurityProxyInvocationHandler(Object o) { proxyedObject = o; } public Object invoke(Object object, Method method, Object[] arguments) throws Throwable { if (object instanceof Account && method.getName().equals("opertaion")) { SecurityChecker.checkSecurity(); } return method.invoke(proxyedObject, arguments); } } |
最后,在應(yīng)用程序中指定 InvocationHandler
生成代理對(duì)象:
public static void main(String[] args) { Account account = (Account) Proxy.newProxyInstance( Account.class.getClassLoader(), new Class[] { Account.class }, new SecurityProxyInvocationHandler(new AccountImpl()) ); account.function(); } |
其不足之處在于:
Proxy.newProxyInstance
生成的是實(shí)現(xiàn) Account
接口的對(duì)象而不是 AccountImpl
的子類。這對(duì)于軟件架構(gòu)設(shè)計(jì),尤其對(duì)于既有軟件系統(tǒng)是有一定掣肘的。
ASM 能夠通過改造既有類,直接生成需要的代碼。增強(qiáng)的代碼是硬編碼在新生成的類文件內(nèi)部的,沒有反射帶來性能上的付出。同時(shí),ASM 與 Proxy 編程不同,不需要為增強(qiáng)代碼而新定義一個(gè)接口,生成的代碼可以覆蓋原來的類,或者是原始類的子類。它是一個(gè)普通的 Java 類而不是 proxy 類,甚至可以在應(yīng)用程序的類框架中擁有自己的位置,派生自己的子類。
相比于其他流行的 Java 字節(jié)碼操縱工具,ASM 更小更快。ASM 具有類似于 BCEL 或者 SERP 的功能,而只有 33k 大小,而后者分別有 350k 和 150k。同時(shí),同樣類轉(zhuǎn)換的負(fù)載,如果 ASM 是 60% 的話,BCEL 需要 700%,而 SERP 需要 1100% 或者更多。
ASM 已經(jīng)被廣泛應(yīng)用于一系列 Java 項(xiàng)目:AspectWerkz、AspectJ、BEA WebLogic、IBM AUS、OracleBerkleyDB、Oracle TopLink、Terracotta、RIFE、EclipseME、Proactive、Speedo、Fractal、EasyBeans、BeanShell、Groovy、Jamaica、CGLIB、dynaop、Cobertura、JDBCPersistence、JiP、SonarJ、Substance L&F、Retrotranslator 等。Hibernate 和 Spring 也通過 cglib,另一個(gè)更高層一些的自動(dòng)代碼生成工具使用了 ASM。
![]() ![]() |
![]()
|
所謂 Java 類文件,就是通常用 javac 編譯器產(chǎn)生的 .class 文件。這些文件具有嚴(yán)格定義的格式。為了更好的理解 ASM,首先對(duì) Java 類文件格式作一點(diǎn)簡單的介紹。Java 源文件經(jīng)過 javac 編譯器編譯之后,將會(huì)生成對(duì)應(yīng)的二進(jìn)制文件(如下圖所示)。每個(gè)合法的 Java 類文件都具備精確的定義,而正是這種精確的定義,才使得 Java 虛擬機(jī)得以正確讀取和解釋所有的 Java 類文件。
Java 類文件是 8 位字節(jié)的二進(jìn)制流。數(shù)據(jù)項(xiàng)按順序存儲(chǔ)在 class 文件中,相鄰的項(xiàng)之間沒有間隔,這使得 class 文件變得緊湊,減少存儲(chǔ)空間。在 Java 類文件中包含了許多大小不同的項(xiàng),由于每一項(xiàng)的結(jié)構(gòu)都有嚴(yán)格規(guī)定,這使得 class 文件能夠從頭到尾被順利地解析。下面讓我們來看一下 Java 類文件的內(nèi)部結(jié)構(gòu),以便對(duì)此有個(gè)大致的認(rèn)識(shí)。
例如,一個(gè)最簡單的 Hello World 程序:
public class HelloWorld { public static void main(String[] args) { System.out.println("Hello world"); } } |
經(jīng)過 javac 編譯后,得到的類文件大致是:
從上圖中可以看到,一個(gè) Java 類文件大致可以歸為 10 個(gè)項(xiàng):
事實(shí)上,使用 ASM 動(dòng)態(tài)生成類,不需要像早年的 class hacker 一樣,熟知 class 文件的每一段,以及它們的功能、長度、偏移量以及編碼方式。ASM 會(huì)給我們照顧好這一切的,我們只要告訴 ASM 要改動(dòng)什么就可以了 —— 當(dāng)然,我們首先得知道要改什么:對(duì)類文件格式了解的越多,我們就能更好地使用 ASM 這個(gè)利器。
![]() ![]() |
![]()
|
ASM 通過樹這種數(shù)據(jù)結(jié)構(gòu)來表示復(fù)雜的字節(jié)碼結(jié)構(gòu),并利用 Push 模型來對(duì)樹進(jìn)行遍歷,在遍歷過程中對(duì)字節(jié)碼進(jìn)行修改。所謂的 Push 模型類似于簡單的 Visitor 設(shè)計(jì)模式,因?yàn)樾枰幚碜止?jié)碼結(jié)構(gòu)是固定的,所以不需要專門抽象出一種 Vistable 接口,而只需要提供 Visitor 接口。所謂 Visitor 模式和 Iterator 模式有點(diǎn)類似,它們都被用來遍歷一些復(fù)雜的數(shù)據(jù)結(jié)構(gòu)。Visitor 相當(dāng)于用戶派出的代表,深入到算法內(nèi)部,由算法安排訪問行程。Visitor 代表可以更換,但對(duì)算法流程無法干涉,因此是被動(dòng)的,這也是它和 Iterator 模式由用戶主動(dòng)調(diào)遣算法方式的最大的區(qū)別。
在 ASM 中,提供了一個(gè) ClassReader
類,這個(gè)類可以直接由字節(jié)數(shù)組或由 class 文件間接的獲得字節(jié)碼數(shù)據(jù),它能正確的分析字節(jié)碼,構(gòu)建出抽象的樹在內(nèi)存中表示字節(jié)碼。它會(huì)調(diào)用 accept
方法,這個(gè)方法接受一個(gè)實(shí)現(xiàn)了 ClassVisitor
接口的對(duì)象實(shí)例作為參數(shù),然后依次調(diào)用 ClassVisitor
接口的各個(gè)方法。字節(jié)碼空間上的偏移被轉(zhuǎn)換成 visit 事件時(shí)間上調(diào)用的先后,所謂 visit 事件是指對(duì)各種不同 visit 函數(shù)的調(diào)用,ClassReader
知道如何調(diào)用各種 visit 函數(shù)。在這個(gè)過程中用戶無法對(duì)操作進(jìn)行干涉,所以遍歷的算法是確定的,用戶可以做的是提供不同的 Visitor 來對(duì)字節(jié)碼樹進(jìn)行不同的修改。ClassVisitor
會(huì)產(chǎn)生一些子過程,比如 visitMethod
會(huì)返回一個(gè)實(shí)現(xiàn) MethordVisitor
接口的實(shí)例,visitField
會(huì)返回一個(gè)實(shí)現(xiàn) FieldVisitor
接口的實(shí)例,完成子過程后控制返回到父過程,繼續(xù)訪問下一節(jié)點(diǎn)。因此對(duì)于 ClassReader
來說,其內(nèi)部順序訪問是有一定要求的。實(shí)際上用戶還可以不通過 ClassReader
類,自行手工控制這個(gè)流程,只要按照一定的順序,各個(gè) visit 事件被先后正確的調(diào)用,最后就能生成可以被正確加載的字節(jié)碼。當(dāng)然獲得更大靈活性的同時(shí)也加大了調(diào)整字節(jié)碼的復(fù)雜度。
各個(gè) ClassVisitor
通過職責(zé)鏈 (Chain-of-responsibility) 模式,可以非常簡單的封裝對(duì)字節(jié)碼的各種修改,而無須關(guān)注字節(jié)碼的字節(jié)偏移,因?yàn)檫@些實(shí)現(xiàn)細(xì)節(jié)對(duì)于用戶都被隱藏了,用戶要做的只是覆寫相應(yīng)的 visit 函數(shù)。
ClassAdaptor
類實(shí)現(xiàn)了 ClassVisitor
接口所定義的所有函數(shù),當(dāng)新建一個(gè) ClassAdaptor
對(duì)象的時(shí)候,需要傳入一個(gè)實(shí)現(xiàn)了 ClassVisitor
接口的對(duì)象,作為職責(zé)鏈中的下一個(gè)訪問者 (Visitor),這些函數(shù)的默認(rèn)實(shí)現(xiàn)就是簡單的把調(diào)用委派給這個(gè)對(duì)象,然后依次傳遞下去形成職責(zé)鏈。當(dāng)用戶需要對(duì)字節(jié)碼進(jìn)行調(diào)整時(shí),只需從 ClassAdaptor
類派生出一個(gè)子類,覆寫需要修改的方法,完成相應(yīng)功能后再把調(diào)用傳遞下去。這樣,用戶無需考慮字節(jié)偏移,就可以很方便的控制字節(jié)碼。
每個(gè) ClassAdaptor
類的派生類可以僅封裝單一功能,比如刪除某函數(shù)、修改字段可見性等等,然后再加入到職責(zé)鏈中,這樣耦合更小,重用的概率也更大,但代價(jià)是產(chǎn)生很多小對(duì)象,而且職責(zé)鏈的層次太長的話也會(huì)加大系統(tǒng)調(diào)用的開銷,用戶需要在低耦合和高效率之間作出權(quán)衡。用戶可以通過控制職責(zé)鏈中 visit 事件的過程,對(duì)類文件進(jìn)行如下操作:
刪除類的字段、方法、指令:只需在職責(zé)鏈傳遞過程中中斷委派,不訪問相應(yīng)的 visit 方法即可,比如刪除方法時(shí)只需直接返回 null
,而不是返回由 visitMethod
方法返回的 MethodVisitor
對(duì)象。
class DelLoginClassAdapter extends ClassAdapter { public DelLoginClassAdapter(ClassVisitor cv) { super(cv); } public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { if (name.equals("login")) { return null; } return cv.visitMethod(access, name, desc, signature, exceptions); } } |
修改類、字段、方法的名字或修飾符:在職責(zé)鏈傳遞過程中替換調(diào)用參數(shù)。
class AccessClassAdapter extends ClassAdapter { public AccessClassAdapter(ClassVisitor cv) { super(cv); } public FieldVisitor visitField(final int access, final String name, final String desc, final String signature, final Object value) { int privateAccess = Opcodes.ACC_PRIVATE; return cv.visitField(privateAccess, name, desc, signature, value); } } |
增加新的類、方法、字段
ASM 的最終的目的是生成可以被正常裝載的 class 文件,因此其框架結(jié)構(gòu)為客戶提供了一個(gè)生成字節(jié)碼的工具類 —— ClassWriter
。它實(shí)現(xiàn)了 ClassVisitor
接口,而且含有一個(gè) toByteArray()
函數(shù),返回生成的字節(jié)碼的字節(jié)流,將字節(jié)流寫回文件即可生產(chǎn)調(diào)整后的 class 文件。一般它都作為職責(zé)鏈的終點(diǎn),把所有 visit 事件的先后調(diào)用(時(shí)間上的先后),最終轉(zhuǎn)換成字節(jié)碼的位置的調(diào)整(空間上的前后),如下例:
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassAdaptor delLoginClassAdaptor = new DelLoginClassAdapter(classWriter); ClassAdaptor accessClassAdaptor = new AccessClassAdaptor(delLoginClassAdaptor); ClassReader classReader = new ClassReader(strFileName); classReader.accept(classAdapter, ClassReader.SKIP_DEBUG); |
綜上所述,ASM 的時(shí)序圖如下:
![]() ![]() |
![]()
|
我們還是用上面的例子,給 Account
類加上 security check 的功能。與 proxy 編程不同,ASM 不需要將 Account
聲明成接口,Account
可以仍舊是一個(gè)實(shí)現(xiàn)類。ASM 將直接在 Account
類上動(dòng)手術(shù),給 Account
類的 operation
方法首部加上對(duì) SecurityChecker.checkSecurity
的調(diào)用。
首先,我們將從 ClassAdapter
繼承一個(gè)類。ClassAdapter
是 ASM 框架提供的一個(gè)默認(rèn)類,負(fù)責(zé)溝通 ClassReader
和 ClassWriter
。如果想要改變 ClassReader
處讀入的類,然后從 ClassWriter
處輸出,可以重寫相應(yīng)的 ClassAdapter
函數(shù)。這里,為了改變 Account
類的 operation
方法,我們將重寫 visitMethdod
方法。
class AddSecurityCheckClassAdapter extends ClassAdapter{ public AddSecurityCheckClassAdapter(ClassVisitor cv) { //Responsechain 的下一個(gè) ClassVisitor,這里我們將傳入 ClassWriter, //負(fù)責(zé)改寫后代碼的輸出 super(cv); } //重寫 visitMethod,訪問到 "operation" 方法時(shí), //給出自定義 MethodVisitor,實(shí)際改寫方法內(nèi)容 public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { MethodVisitor mv = cv.visitMethod(access, name, desc, signature,exceptions); MethodVisitor wrappedMv = mv; if (mv != null) { //對(duì)于 "operation" 方法 if (name.equals("operation")) { //使用自定義 MethodVisitor,實(shí)際改寫方法內(nèi)容 wrappedMv = new AddSecurityCheckMethodAdapter(mv); } } return wrappedMv; } } |
下一步就是定義一個(gè)繼承自 MethodAdapter
的 AddSecurityCheckMethodAdapter
,在“operation
”方法首部插入對(duì) SecurityChecker.checkSecurity()
的調(diào)用。
class AddSecurityCheckMethodAdapter extends MethodAdapter { public AddSecurityCheckMethodAdapter(MethodVisitor mv) { super(mv); } public void visitCode() { visitMethodInsn(Opcodes.INVOKESTATIC, "SecurityChecker", "checkSecurity", "()V"); } } |
其中,ClassReader
讀到每個(gè)方法的首部時(shí)調(diào)用 visitCode()
,在這個(gè)重寫方法里,我們用visitMethodInsn(Opcodes.INVOKESTATIC, "SecurityChecker","checkSecurity", "()V");
插入了安全檢查功能。
最后,我們將集成上面定義的 ClassAdapter
,ClassReader
和ClassWriter
產(chǎn)生修改后的 Account
類文件:
import java.io.File; import java.io.FileOutputStream; import org.objectweb.asm.*; public class Generator{ public static void main() throws Exception { ClassReader cr = new ClassReader("Account"); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassAdapter classAdapter = new AddSecurityCheckClassAdapter(cw); cr.accept(classAdapter, ClassReader.SKIP_DEBUG); byte[] data = cw.toByteArray(); File file = new File("Account.class"); FileOutputStream fout = new FileOutputStream(file); fout.write(data); fout.close(); } } |
執(zhí)行完這段程序后,我們會(huì)得到一個(gè)新的 Account.class 文件,如果我們使用下面代碼:
public class Main { public static void main(String[] args) { Account account = new Account(); account.operation(); } } |
使用這個(gè) Account,我們會(huì)得到下面的輸出:
SecurityChecker.checkSecurity ... operation... |
也就是說,在 Account
原來的 operation
內(nèi)容執(zhí)行之前,進(jìn)行了 SecurityChecker.checkSecurity()
檢查。
將動(dòng)態(tài)生成類改造成原始類 Account 的子類
上面給出的例子是直接改造 Account
類本身的,從此 Account
類的 operation
方法必須進(jìn)行 checkSecurity 檢查。但事實(shí)上,我們有時(shí)仍希望保留原來的 Account
類,因此把生成類定義為原始類的子類是更符合 AOP 原則的做法。下面介紹如何將改造后的類定義為 Account
的子類 Account$EnhancedByASM
。其中主要有兩項(xiàng)工作:
Account$EnhancedByASM
,將其父類指定為 Account
。
Account
構(gòu)造函數(shù)的調(diào)用。 在 AddSecurityCheckClassAdapter
類中,將重寫 visit
方法:
public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) { String enhancedName = name + "$EnhancedByASM"; //改變類命名 enhancedSuperName = name; //改變父類,這里是”Account” super.visit(version, access, enhancedName, signature, enhancedSuperName, interfaces); } |
改進(jìn) visitMethod
方法,增加對(duì)構(gòu)造函數(shù)的處理:
public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); MethodVisitor wrappedMv = mv; if (mv != null) { if (name.equals("operation")) { wrappedMv = new AddSecurityCheckMethodAdapter(mv); } else if (name.equals("<init>")) { wrappedMv = new ChangeToChildConstructorMethodAdapter(mv, enhancedSuperName); } } return wrappedMv; } |
這里 ChangeToChildConstructorMethodAdapter
將負(fù)責(zé)把 Account
的構(gòu)造函數(shù)改造成其子類 Account$EnhancedByASM
的構(gòu)造函數(shù):
class ChangeToChildConstructorMethodAdapter extends MethodAdapter { private String superClassName; public ChangeToChildConstructorMethodAdapter(MethodVisitor mv, String superClassName) { super(mv); this.superClassName = superClassName; } public void visitMethodInsn(int opcode, String owner, String name, String desc) { //調(diào)用父類的構(gòu)造函數(shù)時(shí) if (opcode == Opcodes.INVOKESPECIAL && name.equals("<init>")) { owner = superClassName; } super.visitMethodInsn(opcode, owner, name, desc);//改寫父類為superClassName } } |
最后演示一下如何在運(yùn)行時(shí)產(chǎn)生并裝入產(chǎn)生的 Account$EnhancedByASM
。 我們定義一個(gè) Util
類,作為一個(gè)類工廠負(fù)責(zé)產(chǎn)生有安全檢查的 Account
類:
public class SecureAccountGenerator { private static AccountGeneratorClassLoader classLoader = new AccountGeneratorClassLoade(); private static Class secureAccountClass; public Account generateSecureAccount() throws ClassFormatError, InstantiationException, IllegalAccessException { if (null == secureAccountClass) { ClassReader cr = new ClassReader("Account"); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassAdapter classAdapter = new AddSecurityCheckClassAdapter(cw); cr.accept(classAdapter, ClassReader.SKIP_DEBUG); byte[] data = cw.toByteArray(); secureAccountClass = classLoader.defineClassFromClassFile( "Account$EnhancedByASM",data); } return (Account) secureAccountClass.newInstance(); } private static class AccountGeneratorClassLoader extends ClassLoader { public Class defineClassFromClassFile(String className, byte[] classFile) throws ClassFormatError { return defineClass("Account$EnhancedByASM", classFile, 0, classFile.length()); } } } |
靜態(tài)方法 SecureAccountGenerator.generateSecureAccount()
在運(yùn)行時(shí)動(dòng)態(tài)生成一個(gè)加上了安全檢查的 Account
子類。著名的 Hibernate 和 Spring 框架,就是使用這種技術(shù)實(shí)現(xiàn)了 AOP 的“無損注入”。
![]() ![]() |
![]()
|
最后,我們比較一下 ASM 和其他實(shí)現(xiàn) AOP 的底層技術(shù):
以下是一份完整的struts-config.xml文件,配置元素的說明詳見注釋.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config.dtd">
<!-- struts-config.xml中的元素必須按照上述doc指令中的dtd文檔定義順序書寫,本例即遵從了dtd定義順序 -->
<!-- struts-config是整個(gè)xml的根元素,其他元素必須被包含其內(nèi) -->
<struts-config>
<!--
名稱:data-sources
描述:data-sources元素定義了web App所需要使用的數(shù)據(jù)源
數(shù)量:最多一個(gè)
子元素:data-source
-->
<data-sources>
<!--
名稱:data-source
描述:data-source元素定義了具體的數(shù)據(jù)源
數(shù)量:任意多個(gè)
屬性:
@key:當(dāng)需要配置多個(gè)數(shù)據(jù)源時(shí),相當(dāng)于數(shù)據(jù)源的名稱,用來數(shù)據(jù)源彼此間進(jìn)行區(qū)別
@type:可以使用的數(shù)據(jù)源實(shí)現(xiàn)的類,一般來自如下四個(gè)庫
Poolman,開放源代碼軟件
Expresso,Jcorporate
JDBC Pool,開放源代碼軟件
DBCP,Jakarta
-->
<data-source key="firstOne" type="org.apache.commons.dbcp.BasicDataSource">
<!--
名稱:set-property
描述:用來設(shè)定數(shù)據(jù)源的屬性
屬性:
@autoCommit:是否自動(dòng)提交 可選值:true/false
@description:數(shù)據(jù)源描述
@driverClass:數(shù)據(jù)源使用的類
@maxCount:最大數(shù)據(jù)源連接數(shù)
@minCount:最小數(shù)據(jù)源連接數(shù)
@user:數(shù)據(jù)庫用戶
@password:數(shù)據(jù)庫密碼
@url:數(shù)據(jù)庫url
-->
<set-property property="autoCommit" value="true"/>
<set-property property="description" value="Hello!"/>
<set-property property="driverClass" value="com.mysql.jdbc.Driver"/>
<set-property property="maxCount" value="10"/>
<set-property property="minCount" value="2"/>
<set-property property="user" value="root"/>
<set-property property="password" value=""/>
<set-property property="url" value="jdbc:mysql://localhost:3306/helloAdmin"/>
</data-source>
</data-sources>
<!--
名稱:form-beans
描述:用來配置多個(gè)ActionForm Bean
數(shù)量:最多一個(gè)
子元素:form-bean
-->
<form-beans>
<!--
名稱:form-bean
描述:用來配置ActionForm Bean
數(shù)量:任意多個(gè)
子元素:form-property
屬性:
@className:指定與form-bean元素相對(duì)應(yīng)的配置類,一般默認(rèn)使用org.apaceh.struts.config.FormBeanConfig,如果自定義,則必須繼承 FormBeanConfig
@name:必備屬性!為當(dāng)前form-bean制定一個(gè)全局唯一的標(biāo)識(shí)符,使得在整個(gè)Struts框架內(nèi),可以通過該標(biāo)識(shí)符來引用這個(gè)ActionForm Bean。
@type:必備屬性!指明實(shí)現(xiàn)當(dāng)前ActionForm Bean的完整類名。
-->
<form-bean name="Hello" type="myPack.Hello">
<!--
名稱:form-property
描述:用來設(shè)定ActionForm Bean的屬性
數(shù)量:根據(jù)實(shí)際需求而定,例如,ActionForm Bean對(duì)應(yīng)的一個(gè)登陸Form中有兩個(gè)文本框,name和password,ActionForm Bean中也有這兩個(gè)字段,則此處編寫兩個(gè)form-property來設(shè)定屬性
屬性:
@className:指定與form-property相對(duì)應(yīng)的配置類,默認(rèn)是org.apache.struts.config.FormPropertyConfig,如果自定義,則必須繼承FormPropertyConfig類
@name:所要設(shè)定的ActionForm Bean的屬性名稱
@type:所要設(shè)定的ActionForm Bean的屬性值的類
@initial:當(dāng)前屬性的初值
-->
<form-property name="name" type="java.lang.String"/>
<form-property name="number" type="java.lang.Iteger" initial="18"/>
</form-bean>
</form-beans>
<!--
名稱:global-exceptions
描述:處理異常
數(shù)量:最多一個(gè)
子元素:exception
-->
<global-exceptions>
<!--
名稱:exception
描述:具體定義一個(gè)異常及其處理
數(shù)量:任意多個(gè)
屬性:
@className:指定對(duì)應(yīng)exception的配置類,默認(rèn)為org.apache.struts.config.ExceptionConfig
@handler:指定異常處理類,默認(rèn)為org.apache.struts.action.ExceptionHandler
@key:指定在Resource Bundle種描述該異常的消息key
@path:指定當(dāng)發(fā)生異常時(shí),進(jìn)行轉(zhuǎn)發(fā)的路徑
@scope:指定ActionMessage實(shí)例存放的范圍,默認(rèn)為request,另外一個(gè)可選值是session
@type:必須要有!指定所需要處理異常類的名字。
@bundle:指定資源綁定
-->
<exception
key=""hello.error
path="/error.jsp"
scope="session"
type="hello.HandleError"/>
</global-exceptions>
<!--
名稱:global-forwards
描述:定義全局轉(zhuǎn)發(fā)
數(shù)量:最多一個(gè)
子元素:forward
-->
<global-forwards>
<!--
名稱:forward
描述:定義一個(gè)具體的轉(zhuǎn)發(fā)
數(shù)量:任意多個(gè)
屬性:
@className:指定和forward元素對(duì)應(yīng)的配置類,默認(rèn)為org.apache.struts.action.ActionForward
@contextRelative:如果為true,則指明使用當(dāng)前上下文,路徑以“/”開頭,默認(rèn)為false
@name:必須配有!指明轉(zhuǎn)發(fā)路徑的唯一標(biāo)識(shí)符
@path:必須配有!指明轉(zhuǎn)發(fā)或者重定向的URI。必須以"/"開頭。具體配置要與contextRelative相應(yīng)。
@redirect:為true時(shí),執(zhí)行重定向操作,否則執(zhí)行請(qǐng)求轉(zhuǎn)發(fā)。默認(rèn)為false
-->
<forward name="A" path="/a.jsp"/>
<forward name="B" path="/hello/b.do"/>
</global-forwards>
<!--
名稱:action-mappings
描述:定義action集合
數(shù)量:最多一個(gè)
子元素:action
-->
<action-mappings>
<!--
名稱:action
描述:定義了從特定的請(qǐng)求路徑到相應(yīng)的Action類的映射
數(shù)量:任意多個(gè)
子元素:exception,forward(二者均為局部量)
屬性:
@attribute:制定與當(dāng)前Action相關(guān)聯(lián)的ActionForm Bean在request和session范圍內(nèi)的名稱(key)
@className:與Action元素對(duì)應(yīng)的配置類。默認(rèn)為org.apache.struts.action.ActionMapping
@forward:指名轉(zhuǎn)發(fā)的URL路徑
@include:指名包含的URL路徑
@input:指名包含輸入表單的URL路徑,表單驗(yàn)證失敗時(shí),請(qǐng)求會(huì)被轉(zhuǎn)發(fā)到該URL中
@name:指定和當(dāng)前Acion關(guān)聯(lián)的ActionForm Bean的名字。該名稱必須在form-bean元素中定義過。
@path:指定訪問Action的路徑,以"/"開頭,沒有擴(kuò)展名
@parameter:為當(dāng)前的Action配置參數(shù),可以在Action的execute()方法中,通過調(diào)用ActionMapping的getParameter()方法來獲取參數(shù)
@roles:指定允許調(diào)用該Aciton的安全角色。多個(gè)角色之間用逗號(hào)分割。處理請(qǐng)求時(shí),RequestProcessor會(huì)根據(jù)該配置項(xiàng)來決定用戶是否有調(diào)用該Action的權(quán)限
@scope:指定ActionForm Bean的存在范圍,可選值為request和session。默認(rèn)為session
@type:指定Action類的完整類名
@unknown:值為true時(shí),表示可以處理用戶發(fā)出的所有無效的Action URL。默認(rèn)為false
@validate:指定是否要先調(diào)用ActionForm Bean的validate()方法。默認(rèn)為true
注意:如上屬性中,forward/include/type三者相斥,即三者在同一Action配置中只能存在一個(gè)。
-->
<action path="/search"
type="addressbook.actions.SearchAction"
name="searchForm"
scope="request"
validate="true"
input="/search.jsp">
<forward name="success" path="/display.jsp"/>
</action>
</action-mappings>
<!--
名稱:controller
描述:用于配置ActionServlet
數(shù)量:最多一個(gè)
屬性:
@bufferSize:指定上傳文件的輸入緩沖的大小.默認(rèn)為4096
@className:指定當(dāng)前控制器的配置類.默認(rèn)為org.apache.struts.config.ControllerConfig
@contentType:指定相應(yīng)結(jié)果的內(nèi)容類型和字符編碼
@locale:指定是否把Locale對(duì)象保存到當(dāng)前用戶的session中,默認(rèn)為false
@processorClass:指定負(fù)責(zé)處理請(qǐng)求的Java類的完整類名.默認(rèn)org.apache.struts.action.RequestProcessor
@tempDir:指定文件上傳時(shí)的臨時(shí)工作目錄.如果沒有設(shè)置,將才用Servlet容器為web應(yīng)用分配的臨時(shí)工作目錄.
@nochache:true時(shí),在相應(yīng)結(jié)果中加入特定的頭參數(shù):Pragma ,Cache-Control,Expires防止頁面被存儲(chǔ)在可數(shù)瀏覽器的緩存中,默認(rèn)為false
-->
<controller
contentType="text/html;charset=UTF-8"
locale="true"
processorClass="CustomRequestProcessor">
</controller>
<!--
名稱:message-resources
描述:配置Resource Bundle.
數(shù)量:任意多個(gè)
屬性:
@className:指定和message-resources對(duì)應(yīng)的配置類.默認(rèn)為org.apache.struts.config.MessageResourcesConfig
@factory:指定資源的工廠類,默認(rèn)為org.apache.struts.util.PropertyMessageResourcesFactory
@key:
@null:
@parameter:
-->
<message-resources
null="false"
parameter="defaultResource"/>
<message-resources
key="images"
null="false"
parameter="ImageResources"/>
<!--
名稱:plug-in
描述:用于配置Struts的插件
數(shù)量:任意多個(gè)
子元素:set-property
屬性:
@className:指定Struts插件類.此類必須實(shí)現(xiàn)org.apache.struts.action.PlugIn接口
-->
<plug-in
className="org.apache.struts.validator.ValidatorPlugIn">
<!--
名稱:set-property
描述:配置插件的屬性
數(shù)量:任意多個(gè)
屬性:
@property:插件的屬性名稱
@value:該名稱所配置的值
-->
<set-property
property="pathnames"
value="/WEB-INF/validator-rules.xml,/WEB-INF/vlaidation.xml"/>
</plug-in>
</struts-config>
1. 以常規(guī)的方式調(diào)用資源(即,調(diào)用servlet或JSP頁面)。個(gè)人理解為請(qǐng)求通過過濾執(zhí)行其他的操作
2.利用修改過的請(qǐng)求信息調(diào)用資源。對(duì)請(qǐng)求的信息加以修改,然后繼續(xù)執(zhí)行
3. 調(diào)用資源,但在發(fā)送響應(yīng)到客戶機(jī)前對(duì)其進(jìn)行修改
4. 阻止該資源調(diào)用,代之以轉(zhuǎn)到其他的資源,返回一個(gè)特定的狀態(tài)代碼或生成替換輸出。個(gè)人理解為請(qǐng)求被攔截時(shí)強(qiáng)制執(zhí)行(跳轉(zhuǎn))的操作
過濾器提供了幾個(gè)重要好處 :
首先,它以一種模塊化的或可重用的方式封裝公共的行為。你有30個(gè)不同的serlvet或JSP頁面,需要壓縮它們的內(nèi)容以減少下載時(shí)間嗎?沒問題:構(gòu)造一個(gè)壓縮過濾器,然后將它應(yīng)用到30個(gè)資源上即可。
其次,利用它能夠?qū)⒏呒?jí)訪問決策與表現(xiàn)代碼相分離。這對(duì)于JSP特別有價(jià)值,其中一般希望將幾乎整個(gè)頁面集中在表現(xiàn)上,而不是集中在業(yè)務(wù)邏輯上。例如,希 望阻塞來自某些站點(diǎn)的訪問而不用修改各頁面(這些頁面受到訪問限制)嗎?沒問題:建立一個(gè)訪問限制過濾器并把它應(yīng)用到想要限制訪問的頁面上即可。
最后,過濾器使你能夠?qū)υS多不同的資源進(jìn)行批量性的更改。你有許多現(xiàn)存資源,這些資源除了公司名要更改外其他的保持不變,能辦到么?沒問題:構(gòu)造一個(gè)串替換過濾器,只要合適就使用它。
但要注意,過濾器只在與servlet規(guī)范2.3版兼容的服務(wù)器上有作用。如果你的Web應(yīng)用需要支持舊版服務(wù)器,就不能使用過濾器。
1. 建立基本過濾器
建立一個(gè)過濾器涉及下列五個(gè)步驟:
1)建立一個(gè)實(shí)現(xiàn)Filter接口的類。這個(gè)類需要三個(gè)方法,分別是:doFilter、init和destroy。
doFilter方法包含主要的過濾代碼(見第2步),init方法建立設(shè)置操作,而destroy方法進(jìn)行清楚。
2)在doFilter方法中放入過濾行為。doFilter方法的第一個(gè)參數(shù)為ServletRequest對(duì)象。此對(duì)象給過濾器提供了對(duì)進(jìn)入的信息 (包括表單數(shù)據(jù)、cookie和HTTP請(qǐng)求頭)的完全訪問。第二個(gè)參數(shù)為ServletResponse,通常在簡單的過濾器中忽略此參數(shù)。最后一個(gè)參 數(shù)為FilterChain,如下一步所述,此參數(shù)用來調(diào)用servlet或JSP頁。
3)調(diào)用FilterChain對(duì)象的doFilter方法。Filter接口的doFilter方法取一個(gè)FilterChain對(duì)象作為它的一個(gè)參 數(shù)。在調(diào)用此對(duì)象的doFilter方法時(shí),激活下一個(gè)相關(guān)的過濾器。如果沒有另一個(gè)過濾器與servlet或JSP頁面關(guān)聯(lián),則servlet或JSP 頁面被激活。
4)對(duì)相應(yīng)的servlet和JSP頁面注冊(cè)過濾器。在部署描述符文件(web.xml)中使用filter和filter-mapping元素。
5)禁用激活器servlet。防止用戶利用缺省servlet URL繞過過濾器設(shè)置。
1.1 建立一個(gè)實(shí)現(xiàn)Filter接口的類
所有過濾器都必須實(shí)現(xiàn)javax.servlet.Filter。這個(gè)接口包含三個(gè)方法,分別為doFilter、init和destroy。
public void doFilter(ServletRequset request,
ServletResponse response,
FilterChain chain)
thows ServletException, IOException
每當(dāng)調(diào)用一個(gè)過濾器(即,每次請(qǐng)求與此過濾器相關(guān)的servlet或JSP頁面)時(shí),就執(zhí)行其doFilter方法。正是這個(gè)方法包含了大部分過濾邏輯。 第一個(gè)參數(shù)為與傳入請(qǐng)求有關(guān)的ServletRequest。對(duì)于簡單的過濾器,大多數(shù)過濾邏輯是基于這個(gè)對(duì)象的。如果處理HTTP請(qǐng)求,并且需要訪問諸 如getHeader或getCookies等在ServletRequest中無法得到的方法,就要把此對(duì)象構(gòu)造成 HttpServletRequest。
第二個(gè)參數(shù)為ServletResponse。除了在兩個(gè)情形下要使用它以外,通常忽略這個(gè)參數(shù)。首先,如果希望完全阻塞對(duì)相關(guān)servlet或JSP頁 面的訪問。可調(diào)用response.getWriter并直接發(fā)送一個(gè)響應(yīng)到客戶機(jī)。其次,如果希望修改相關(guān)的servlet或JSP頁面的輸出,可把響 應(yīng)包含在一個(gè)收集所有發(fā)送到它的輸出的對(duì)象中。然后,在調(diào)用serlvet或JSP頁面后,過濾器可檢查輸出,如果合適就修改它,之后發(fā)送到客戶機(jī)。
DoFilter的最后一個(gè)參數(shù)為FilterChain對(duì)象。對(duì)此對(duì)象調(diào)用doFilter以激活與servlet或JSP頁面相關(guān)的下一個(gè)過濾器。如果沒有另一個(gè)相關(guān)的過濾器,則對(duì)doFilter的調(diào)用激活servlet或JSP本身。
public void init(FilterConfig config) thows ServletException
init方法只在此過濾器第一次初始化時(shí)執(zhí)行,不是每次調(diào)用過濾器都執(zhí)行它。對(duì)于簡單的過濾器,可提供此方法的一個(gè)空體,但有兩個(gè)原因需要使用init。 首先,F(xiàn)ilterConfig對(duì)象提供對(duì)servlet環(huán)境及web.xml文件中指派的過濾器名的訪問。因此,普遍的辦法是利用init將 FilterConfig對(duì)象存放在一個(gè)字段中,以便doFilter方法能夠訪問servlet環(huán)境或過濾器名.其次,F(xiàn)ilterConfig對(duì)象具 有一個(gè)getInitParameter方法,它能夠訪問部署描述符文件(web.xml)中分配的過濾器初始化參數(shù)。
public void destroy( )
大多數(shù)過濾器簡單地為此方法提供一個(gè)空體,不過,可利用它來完成諸如關(guān)閉過濾器使用的文件或數(shù)據(jù)庫連接池等清除任務(wù)。
1.2 將過濾行為放入doFilter方法
doFilter方法為大多數(shù)過濾器地關(guān)鍵部分。每當(dāng)調(diào)用一個(gè)過濾器時(shí),都要執(zhí)行doFilter。對(duì)于大多數(shù)過濾器來說,doFilter執(zhí)行的步驟是 基于傳入的信息的。因此,可能要利用作為doFilter的第一個(gè)參數(shù)提供的ServletRequest。這個(gè)對(duì)象常常構(gòu)造為 HttpServletRequest類型,以提供對(duì)該類的更特殊方法的訪問。
1.3 調(diào)用FilterChain對(duì)象的doFilter方法
Filter接口的doFilter方法以一個(gè)FilterChain對(duì)象作為它的第三個(gè)參數(shù)。在調(diào)用該對(duì)象的doFilter方法時(shí),激活下一個(gè)相關(guān)的 過濾器。這個(gè)過程一般持續(xù)到鏈中最后一個(gè)過濾器為止。在最后一個(gè)過濾器調(diào)用其FilterChain對(duì)象的doFilter方法時(shí),激活servlet或 頁面自身。
但是,鏈中的任意過濾器都可以通過不調(diào)用其FilterChain的doFilter方法中斷這個(gè)過程。在這樣的情況下,不再調(diào)用JSP頁面的serlvet,并且中斷此調(diào)用過程的過濾器負(fù)責(zé)將輸出提供給客戶機(jī)。
1.4 對(duì)適當(dāng)?shù)膕ervlet和JSP頁面注冊(cè)過濾器
部署描述符文件的2.3版本引入了兩個(gè)用于過濾器的元素,分別是:filter和filter-mapping。filter元素向系統(tǒng)注冊(cè)一個(gè)過濾對(duì)象,filter-mapping元素指定該過濾對(duì)象所應(yīng)用的URL。
1.filter元素
filter元素位于部署描述符文件(web.xml)的前部,所有filter-mapping、servlet或servlet-mapping元素之前。filter元素具有如下六個(gè)可能的子元素:
1、 icon 這是一個(gè)可選的元素,它聲明IDE能夠使用的一個(gè)圖象文件。
2、filter-name 這是一個(gè)必需的元素,它給過濾器分配一個(gè)選定的名字。
3、display-name 這是一個(gè)可選的元素,它給出IDE使用的短名稱。
4、 description 這也是一個(gè)可選的元素,它給出IDE的信息,提供文本文檔。
5、 filter-class 這是一個(gè)必需的元素,它指定過濾器實(shí)現(xiàn)類的完全限定名。
6、 init-param 這是一個(gè)可選的元素,它定義可利用FilterConfig的getInitParameter方法讀取的初始化參數(shù)。單個(gè)過濾器元素可包含多個(gè)init-param元素。
請(qǐng)注意,過濾是在serlvet規(guī)范2.3版中初次引入的。因此,web.xml文件必須使用DTD的2.3版本。下面介紹一個(gè)簡單的例子:
<xml version="1.0" encoding="ISO-8859-1"?>
DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "
<web-app>
<filter>
<filter-name>MyFilterfilter-name>
<filter-class>myPackage.FilterClassfilter-class>
filter>
<filter-mapping>...filter-mapping>
<web-app>
2.filter-mapping元素
filter-mapping元素位于web.xml文件中filter元素之后serlvet元素之前。它包含如下三個(gè)可能的子元素:
1、 filter-name 這個(gè)必需的元素必須與用filter元素聲明時(shí)給予過濾器的名稱相匹配。
2、 url-pattern 此元素聲明一個(gè)以斜杠(/)開始的模式,它指定過濾器應(yīng)用的URL。所有filter-mapping元素中必須提供url-pattern或 servlet-name。但不能對(duì)單個(gè)filter-mapping元素提供多個(gè)url-pattern元素項(xiàng)。如果希望過濾器適用于多個(gè)模式,可重復(fù) 整個(gè)filter-mapping元素。
3、 servlet-name 此元素給出一個(gè)名稱,此名稱必須與利用servlet元素給予servlet或JSP頁面的名稱相匹配。不能給單個(gè)filter-mapping元素提供 多個(gè)servlet-name元素項(xiàng)。如果希望過濾器適合于多個(gè)servlet名,可重復(fù)這個(gè)filter-mapping元素。
下面舉一個(gè)例子:
xml version="1.0" encoding="ISO-8859-1"?>
DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"
<web-app>
<filter>
<filter-name>MyFilterfilter-name>
<filter-class>myPackage.FilterClassfilter-class>
filter>
<filter-mapping>
<filter-name>MyFilterfilter-name>
<url-pattern>/someDirectory/SomePage.jspurl-pattern>
filter-mapping>
web-app>
1.5 禁用激活器servlet
在對(duì)資源應(yīng)用過濾器時(shí),可通過指定要應(yīng)用過濾器的URL模式或servlet名來完成。如果提供servlet名,則此名稱必須與web.xml的 servlet元素中給出的名稱相匹配。如果使用應(yīng)用到一個(gè)serlvet的URL模式,則此模式必須與利用web.xml的元素servlet- mapping指定的模式相匹配。但是,多數(shù)服務(wù)器使用“激活器servlet”為servlet體統(tǒng)一個(gè)缺省的URL:http: //host/WebAppPrefix/servlet/ServletName。需要保證用戶不利用這個(gè)URL訪問servlet(這樣會(huì)繞過過濾器 設(shè)置)。
例如,假如利用filter和filter-mapping指示名為SomeFilter的過濾器應(yīng)用到名為SomeServlet的servlet,則如下:
<filter>
<filter-name>SomeFilterfilter-name>
<filter-class>somePackage.SomeFilterClassfilter-class>
<filter>
<filter-mapping>
<filter-name>SomeFilterfilter-name>
<servlet-name>SomeServletservlet-name>
<filter-mapping>
接著,用servlet和servlet-mapping規(guī)定URL http://host/webAppPrefix/Blah 應(yīng)該調(diào)用SomeSerlvet,如下所示:
<filter>
<filter-name>SomeFilterfilter-name>
<filter-class>somePackage.SomeFilterClassfilter-class>
filter>
<filter-mapping>
<filter-name>SomeFilterfilter-name>
<servlet-name>/Blahservlet-name>
<filter-mapping>
現(xiàn)在,在客戶機(jī)使用URL http://host/webAppPrefix/Blah 時(shí)就會(huì)調(diào)用過濾器。過濾器不應(yīng)用到
http://host/webAppPrefix/servlet/SomePackage.SomeServletClass。
盡管有關(guān)閉激活器的服務(wù)器專用方法。但是,可移植最強(qiáng)的方法時(shí)重新映射Web應(yīng)用鐘的/servlet模式,這樣使所有包含此模式的請(qǐng)求被送到相同的 servlet中。為了重新映射此模式,首先應(yīng)該建立一個(gè)簡單的servlet,它打印一條錯(cuò)誤消息,或重定向用戶到頂層頁。然后,使用servlet和 servlet-mapping元素發(fā)送包含/servlet模式的請(qǐng)求到該servlet。程序清單9-1給出了一個(gè)簡短的例子。
程序清單9-1 web.xml(重定向缺省servlet URL的摘錄)
xml version="1.0" encoding="ISO-8859-1"?>
DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"
<web-app>
<servlet>
<servlet-name>Errorservlet-name>
<servlet-class>somePackage.ErrorServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>Errorservlet-name>
<url-pattern>/servlet/*url-pattern>
servlet-mapping>
<web-app>
本文參考:http://www.javaeye.com/topic/140553
struts的validator的客戶端驗(yàn)證,不能進(jìn)行多表單頁面的驗(yàn)證,原因是由<html:script>標(biāo)簽生成的javascipt是根據(jù)每個(gè)表單,生成一段代碼。例如:
<html:javascript formName="searchSgbySjForm" dynamicJavascript="true" staticJavascript="false"/>
生成 :
var bCancel = false;
function validateSearchSgbySjForm(form)
{
if (bCancel) return true;
else return validateRequired(form) && validateDate(form);
}
function required ()
{
this.aa = new Array("sgfssjq", "事故發(fā)生時(shí)間起 不可為空.", new Function ("varName", "this.datePatternStrict='yyy-MM-dd'; return thisvarName];"));
this.ab = new Array("sgfssjz", "事故發(fā)生時(shí)間止 不可為空.", new Function ("varName", "this.datePatternStrict='yyy-MM-dd'; return this[varName];"));
}
function DateValidations ()
{
this.aa = new Array("sgfssjq", "事故發(fā)生時(shí)間起 不是有效的日期類型.", new Function ("varName", "this.datePatternStrict='yyy-MM-dd'; return this [varName];"));
this.ab = new Array("sgfssjz", "事故發(fā)生時(shí)間止 不是有效的日期類型.", new Function ("varName", "this.datePatternStrict='yyy-MM-dd'; return this[varName];"));
}
如果有多個(gè)的話required和DateValidations 都會(huì)重復(fù)的,而javascript是只認(rèn)最后一個(gè)函數(shù)的。所以,會(huì)導(dǎo)致驗(yàn)證出錯(cuò)。
再寫一個(gè)標(biāo)簽 ,主要根據(jù)原來的代碼修改,代碼如下:
package com.tmri.acd.tag;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.BodyTagSupport;
import org.apache.commons.validator.Field;
import org.apache.commons.validator.Form;
import org.apache.commons.validator.ValidatorAction;
import org.apache.commons.validator.ValidatorResources;
import org.apache.commons.validator.util.ValidatorUtils;
import org.apache.commons.validator.Var;
import org.apache.struts.Globals;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.config.ModuleConfig;
import com.tmri.acd.tag.TagUtils;
import org.apache.struts.util.MessageResources;
import org.apache.struts.validator.Resources;
import org.apache.struts.validator.ValidatorPlugIn;
import java.util.StringTokenizer;
public class JavascriptValidatorTag extends BodyTagSupport
{
private static final Comparator actionComparator = new Comparator()
{
public int compare(Object o1, Object o2)
{
ValidatorAction va1 = (ValidatorAction) o1;
ValidatorAction va2 = (ValidatorAction) o2;
if ((va1.getDepends() == null || va1.getDepends().length() == 0) && (va2.getDepends() == null || va2.getDepends().length() == 0))
{
return 0;
}
else if ( (va1.getDepends() != null && va1.getDepends().length() > 0) && (va2.getDepends() == null || va2.getDepends().length() == 0))
{
return 1;
}
}
HTML 注釋
1 引子
try…catch…finally恐怕是大家再熟悉不過的語句了,而且感覺用起來也是很簡單,邏輯上似乎也是很容易理解。不過,我親自體驗(yàn)的“教訓(xùn)”告訴我,這個(gè)東西可不是想象中的那么簡單、聽話。不信?那你看看下面的代碼,“猜猜”它執(zhí)行后的結(jié)果會(huì)是什么?不要往后看答案、也不許執(zhí)行代碼看真正答案哦。如果你的答案是正確,那么這篇文章你就不用浪費(fèi)時(shí)間看啦。
public class TestException
{
public TestException()
{
}
boolean testEx() throws Exception
{
boolean ret = true;
try
{
ret = testEx1();
}
catch (Exception e)
{
System.out.println("testEx, catch exception");
ret = false;
throw e;
}
finally
{
System.out.println("testEx, finally; return value=" + ret);
return ret;
}
}
boolean testEx1() throws Exception
{
boolean ret = true;
try
{
ret = testEx2();
if (!ret)
{
return false;
}
System.out.println("testEx1, at the end of try");
return ret;
}
catch (Exception e)
{
System.out.println("testEx1, catch exception");
ret = false;
throw e;
}
finally
{
System.out.println("testEx1, finally; return value=" + ret);
return ret;
}
}
boolean testEx2() throws Exception
{
boolean ret = true;
try
{
int b = 12;
int c;
for (int i = 2; i >= -2; i--)
{
c = b / i;
System.out.println("i=" + i);
}
return true;
}
catch (Exception e)
{
System.out.println("testEx2, catch exception");
ret = false;
throw e;
}
finally
{
System.out.println("testEx2, finally; return value=" + ret);
return ret;
}
}
public static void main(String[] args)
{
TestException testException1 = new TestException();
try
{
testException1.testEx();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
你的答案是什么?是下面的答案嗎?
i=2
i=1
testEx2, catch exception
testEx2, finally; return value=false
testEx1, catch exception
testEx1, finally; return value=false
testEx, catch exception
testEx, finally; return value=false
如果你的答案真的如上面所說,那么你錯(cuò)啦。^_^,那就建議你仔細(xì)看一看這篇文章或者拿上面的代碼按各種不同的情況修改、執(zhí)行、測(cè)試,你會(huì)發(fā)現(xiàn)有很多事情不是原來想象中的那么簡單的。
現(xiàn)在公布正確答案:
i=2
i=1
testEx2, catch exception
testEx2, finally; return value=false
testEx1, finally; return value=false
testEx, finally; return value=false
2 基礎(chǔ)知識(shí)
2.1 相關(guān)概念
例外是在程序運(yùn)行過程中發(fā)生的異常事件,比如除0溢出、數(shù)組越界、文件找不到等,這些事件的發(fā)生將阻止程序的正常運(yùn)行。為了加強(qiáng)程序的魯棒性,程序設(shè)計(jì)時(shí),必須考慮到可能發(fā)生的異常事件并做出相應(yīng)的處理。C語言中,通過使用if語句來判斷是否出現(xiàn)了例外,同時(shí),調(diào)用函數(shù)通過被調(diào)用函數(shù)的返回值感知在被調(diào)用函數(shù)中產(chǎn)生的例外事件并進(jìn)行處理。全程變量ErroNo常常用來反映一個(gè)異常事件的類型。但是,這種錯(cuò)誤處理機(jī)制會(huì)導(dǎo)致不少問題。
Java通過面向?qū)ο蟮姆椒▉硖幚砝狻T谝粋€(gè)方法的運(yùn)行過程中,如果發(fā)生了例外,則這個(gè)方法生成代表該例外的一個(gè)對(duì)象,并把它交給運(yùn)行時(shí)系統(tǒng),運(yùn)行時(shí)系統(tǒng)尋找相應(yīng)的代碼來處理這一例外。我們把生成例外對(duì)象并把它提交給運(yùn)行時(shí)系統(tǒng)的過程稱為拋棄(throw)一個(gè)例外。運(yùn)行時(shí)系統(tǒng)在方法的調(diào)用棧中查找,從生成例外的方法開始進(jìn)行回朔,直到找到包含相應(yīng)例外處理的方法為止,這一個(gè)過程稱為捕獲(catch)一個(gè)例外。
2.2 Throwable類及其子類
用面向?qū)ο蟮姆椒ㄌ幚砝猓捅仨毥㈩惖膶哟巍n?Throwable位于這一類層次的最頂層,只有它的后代才可以做為一個(gè)例外被拋棄。圖1表示了例外處理的類層次。
從圖中可以看出,類Throwable有兩個(gè)直接子類:Error和Exception。Error類對(duì)象(如動(dòng)態(tài)連接錯(cuò)誤等),由Java虛擬機(jī)生成并拋棄(通常,Java程序不對(duì)這類例外進(jìn)行處理);Exception類對(duì)象是Java程序處理或拋棄的對(duì)象。它有各種不同的子類分別對(duì)應(yīng)于不同類型的例外。其中類RuntimeException代表運(yùn)行時(shí)由Java虛擬機(jī)生成的例外,如算術(shù)運(yùn)算例外ArithmeticException(由除0錯(cuò)等導(dǎo)致)、數(shù)組越界例外ArrayIndexOutOfBoundsException等;其它則為非運(yùn)行時(shí)例外,如輸入輸出例外IOException等。Java編譯器要求Java程序必須捕獲或聲明所有的非運(yùn)行時(shí)例外,但對(duì)運(yùn)行時(shí)例外可以不做處理。
2.3 異常處理關(guān)鍵字
Java的異常處理是通過5個(gè)關(guān)鍵字來實(shí)現(xiàn)的:try,catch,throw,throws,finally。JB的在線幫助中對(duì)這幾個(gè)關(guān)鍵字是這樣解釋的:
Throws: Lists the exceptions a method could throw.
Throw: Transfers control of the method to the exception handler.
Try: Opening exception-handling statement.
Catch: Captures the exception.
Finally: Runs its code before terminating the program.
2.3.1 try語句
try語句用大括號(hào){}指定了一段代碼,該段代碼可能會(huì)拋棄一個(gè)或多個(gè)例外。
2.3.2 catch語句
catch語句的參數(shù)類似于方法的聲明,包括一個(gè)例外類型和一個(gè)例外對(duì)象。例外類型必須為Throwable類的子類,它指明了catch語句所處理的例外類型,例外對(duì)象則由運(yùn)行時(shí)系統(tǒng)在try所指定的代碼塊中生成并被捕獲,大括號(hào)中包含對(duì)象的處理,其中可以調(diào)用對(duì)象的方法。
catch語句可以有多個(gè),分別處理不同類的例外。Java運(yùn)行時(shí)系統(tǒng)從上到下分別對(duì)每個(gè)catch語句處理的例外類型進(jìn)行檢測(cè),直到找到類型相匹配的catch語句為止。這里,類型匹配指catch所處理的例外類型與生成的例外對(duì)象的類型完全一致或者是它的父類,因此,catch語句的排列順序應(yīng)該是從特殊到一般。
也可以用一個(gè)catch語句處理多個(gè)例外類型,這時(shí)它的例外類型參數(shù)應(yīng)該是這多個(gè)例外類型的父類,程序設(shè)計(jì)中要根據(jù)具體的情況來選擇catch語句的例外處理類型。
2.3.3 finally語句
try所限定的代碼中,當(dāng)拋棄一個(gè)例外時(shí),其后的代碼不會(huì)被執(zhí)行。通過finally語句可以指定一塊代碼。無論try所指定的程序塊中拋棄或不拋棄例外,也無論catch語句的例外類型是否與所拋棄的例外的類型一致,finally所指定的代碼都要被執(zhí)行,它提供了統(tǒng)一的出口。通常在finally語句中可以進(jìn)行資源的清除工作。如關(guān)閉打開的文件等。
2.3.4 throws語句
throws總是出現(xiàn)在一個(gè)函數(shù)頭中,用來標(biāo)明該成員函數(shù)可能拋出的各種異常。對(duì)大多數(shù)Exception子類來說,Java 編譯器會(huì)強(qiáng)迫你聲明在一個(gè)成員函數(shù)中拋出的異常的類型。如果異常的類型是Error或 RuntimeException, 或它們的子類,這個(gè)規(guī)則不起作用, 因?yàn)檫@在程序的正常部分中是不期待出現(xiàn)的。 如果你想明確地拋出一個(gè)RuntimeException,你必須用throws語句來聲明它的類型。
2.3.5 throw語句
throw總是出現(xiàn)在函數(shù)體中,用來拋出一個(gè)異常。程序會(huì)在throw語句后立即終止,它后面的語句執(zhí)行不到,然后在包含它的所有try塊中(可能在上層調(diào)用函數(shù)中)從里向外尋找含有與其匹配的catch子句的try塊。
3 關(guān)鍵字及其中語句流程詳解
3.1 try的嵌套
你可以在一個(gè)成員函數(shù)調(diào)用的外面寫一個(gè)try語句,在這個(gè)成員函數(shù)內(nèi)部,寫另一個(gè)try語句保護(hù)其他代碼。每當(dāng)遇到一個(gè)try語句,異常的框架就放到堆棧上面,直到所有的try語句都完成。如果下一級(jí)的try語句沒有對(duì)某種異常進(jìn)行處理,堆棧就會(huì)展開,直到遇到有處理這種異常的try語句。下面是一個(gè)try語句嵌套的例子。
class MultiNest {
static void procedure() {
try {
int a = 0;
int b = 42/a;
} catch(java.lang.ArithmeticException e) {
System.out.println("in procedure, catch ArithmeticException: " + e);
}
}
public static void main(String args[]) {
try {
procedure();
} catch(java.lang. Exception e) {
System.out.println("in main, catch Exception: " + e);
}
}
}
這個(gè)例子執(zhí)行的結(jié)果為:
in procedure, catch ArithmeticException: java.lang.ArithmeticException: / by zero
成員函數(shù)procedure里有自己的try/catch控制,所以main不用去處理 ArrayIndexOutOfBoundsException;當(dāng)然如果如同最開始我們做測(cè)試的例子一樣,在procedure中catch到異常時(shí)使用throw e;語句將異常拋出,那么main當(dāng)然還是能夠捕捉并處理這個(gè)procedure拋出來的異常。例如在procedure函數(shù)的catch中的System.out語句后面增加throw e;語句之后,執(zhí)行結(jié)果就變?yōu)椋?br />
in procedure, catch ArithmeticException: java.lang.ArithmeticException: / by zero
in main, catch Exception: java.lang.ArithmeticException: / by zero
3.2 try-catch程序塊的執(zhí)行流程以及執(zhí)行結(jié)果
相對(duì)于try-catch-finally程序塊而言,try-catch的執(zhí)行流程以及執(zhí)行結(jié)果還是比較簡單的。
首先執(zhí)行的是try語句塊中的語句,這時(shí)可能會(huì)有以下三種情況:
1.如果try塊中所有語句正常執(zhí)行完畢,那么就不會(huì)有其他的“動(dòng)做”被執(zhí)行,整個(gè)try-catch程序塊正常完成。
2.如果try語句塊在執(zhí)行過程中碰到異常V,這時(shí)又分為兩種情況進(jìn)行處理:
-->如果異常V能夠被與try相應(yīng)的catch塊catch到,那么第一個(gè)catch到這個(gè)異常的catch塊(也是離try最近的一個(gè)與異常V匹配的catch塊)將被執(zhí)行;如果catch塊執(zhí)行正常,那么try-catch程序塊的結(jié)果就是“正常完成”;如果該catch塊由于原因R突然中止,那么try-catch程序塊的結(jié)果就是“由于原因R突然中止(completes abruptly)”。
-->如果異常V沒有catch塊與之匹配,那么這個(gè)try-catch程序塊的結(jié)果就是“由于拋出異常V而突然中止(completes abruptly)”。
3. 如果try由于其他原因R突然中止(completes abruptly),那么這個(gè)try-catch程序塊的結(jié)果就是“由于原因R突然中止(completes abruptly)”。
3.3 try-catch-finally程序塊的執(zhí)行流程以及執(zhí)行結(jié)果
try-catch-finally程序塊的執(zhí)行流程以及執(zhí)行結(jié)果比較復(fù)雜。
首先執(zhí)行的是try語句塊中的語句,這時(shí)可能會(huì)有以下三種情況:
1.如果try塊中所有語句正常執(zhí)行完畢,那么finally塊的居于就會(huì)被執(zhí)行,這時(shí)分為以下兩種情況:
-->如果finally塊執(zhí)行順利,那么整個(gè)try-catch-finally程序塊正常完成。
-->如果finally塊由于原因R突然中止,那么try-catch-finally程序塊的結(jié)局是“由于原因R突然中止(completes abruptly)”
2.如果try語句塊在執(zhí)行過程中碰到異常V,這時(shí)又分為兩種情況進(jìn)行處理:
-->如果異常V能夠被與try相應(yīng)的catch塊catch到,那么第一個(gè)catch到這個(gè)異常的catch塊(也是離try最近的一個(gè)與異常V匹配的catch塊)將被執(zhí)行;這時(shí)就會(huì)有兩種執(zhí)行結(jié)果:
-->如果catch塊執(zhí)行正常,那么finally塊將會(huì)被執(zhí)行,這時(shí)分為兩種情況:
-->如果finally塊執(zhí)行順利,那么整個(gè)try-catch-finally程序塊正常完成。
-->如果finally塊由于原因R突然中止,那么try-catch-finally程序塊的結(jié)局是“由于原因R突然中止(completes abruptly)”
-->如果catch塊由于原因R突然中止,那么finally模塊將被執(zhí)行,分為兩種情況:
-->如果如果finally塊執(zhí)行順利,那么整個(gè)try-catch-finally程序塊的結(jié)局是“由于原因R突然中止(completes abruptly)”。
-->如果finally塊由于原因S突然中止,那么整個(gè)try-catch-finally程序塊的結(jié)局是“由于原因S突然中止(completes abruptly)”,原因R將被拋棄。
(注意,這里就正好和我們的例子相符合,雖然我們?cè)趖estEx2中使用throw e拋出了異常,但是由于testEx2中有finally塊,而finally塊的執(zhí)行結(jié)果是complete abruptly的(別小看這個(gè)用得最多的return,它也是一種導(dǎo)致complete abruptly的原因之一啊——后文中有關(guān)于導(dǎo)致complete abruptly的原因分析),所以整個(gè)try-catch-finally程序塊的結(jié)果是“complete abruptly”,所以在testEx1中調(diào)用testEx2時(shí)是捕捉不到testEx1中拋出的那個(gè)異常的,而只能將finally中的return結(jié)果獲取到。
如果在你的代碼中期望通過捕捉被調(diào)用的下級(jí)函數(shù)的異常來給定返回值,那么一定要注意你所調(diào)用的下級(jí)函數(shù)中的finally語句,它有可能會(huì)使你throw出來的異常并不能真正被上級(jí)調(diào)用函數(shù)可見的。當(dāng)然這種情況是可以避免的,以testEx2為例:如果你一定要使用finally而且又要將catch中throw的e在testEx1中被捕獲到,那么你去掉testEx2中的finally中的return就可以了。
這個(gè)事情已經(jīng)在OMC2.0的MIB中出現(xiàn)過啦:服務(wù)器的異常不能完全被反饋到客戶端。)
-->如果異常V沒有catch塊與之匹配,那么finally模塊將被執(zhí)行,分為兩種情況:
-->如果finally塊執(zhí)行順利,那么整個(gè)try-catch-finally程序塊的結(jié)局就是“由于拋出異常V而突然中止(completes abruptly)”。
-->如果finally塊由于原因S突然中止,那么整個(gè)try-catch-finally程序塊的結(jié)局是“由于原因S突然中止(completes abruptly)”,異常V將被拋棄。
3.如果try由于其他原因R突然中止(completes abruptly),那么finally塊被執(zhí)行,分為兩種情況:
-->如果finally塊執(zhí)行順利,那么整個(gè)try-catch-finally程序塊的結(jié)局是“由于原因R突然中止(completes abruptly)”。
-->如果finally塊由于原因S突然中止,那么整個(gè)try-catch-finally程序塊的結(jié)局是“由于原因S突然中止(completes abruptly)”,原因R將被拋棄。
3.4 try-catch-finally程序塊中的return
從上面的try-catch-finally程序塊的執(zhí)行流程以及執(zhí)行結(jié)果一節(jié)中可以看出無論try或catch中發(fā)生了什么情況,finally都是會(huì)被執(zhí)行的,那么寫在try或者catch中的return語句也就不會(huì)真正的從該函數(shù)中跳出了,它的作用在這種情況下就變成了將控制權(quán)(語句流程)轉(zhuǎn)到finally塊中;這種情況下一定要注意返回值的處理。
例如,在try或者catch中return false了,而在finally中又return true,那么這種情況下不要期待你的try或者catch中的return false的返回值false被上級(jí)調(diào)用函數(shù)獲取到,上級(jí)調(diào)用函數(shù)能夠獲取到的只是finally中的返回值,因?yàn)閠ry或者catch中的return語句只是轉(zhuǎn)移控制權(quán)的作用。
3.5 如何拋出異常
如果你知道你寫的某個(gè)函數(shù)有可能拋出異常,而你又不想在這個(gè)函數(shù)中對(duì)異常進(jìn)行處理,只是想把它拋出去讓調(diào)用這個(gè)函數(shù)的上級(jí)調(diào)用函數(shù)進(jìn)行處理,那么有兩種方式可供選擇:
第一種方式:直接在函數(shù)頭中throws SomeException,函數(shù)體中不需要try/catch。比如將最開始的例子中的testEx2改為下面的方式,那么testEx1就能捕捉到testEx2拋出的異常了。
boolean testEx2() throws Exception{
boolean ret = true;
int b=12;
int c;
for (int i=2;i>=-2;i--){
c=b/i;
System.out.println("i="+i);
}
return true;
}
第二種方式:使用try/catch,在catch中進(jìn)行一定的處理之后(如果有必要的話)拋出某種異常。例如上面的testEx2改為下面的方式,testEx1也能捕獲到它拋出的異常:
boolean testEx2() throws Exception{
boolean ret = true;
try{
int b=12;
int c;
for (int i=2;i>=-2;i--){
c=b/i;
System.out.println("i="+i);
}
return true;
}catch (Exception e){
System.out.println("testEx2, catch exception");
Throw e;
}
}
第三種方法:使用try/catch/finally,在catch中進(jìn)行一定的處理之后(如果有必要的話)拋出某種異常。例如上面的testEx2改為下面的方式,testEx1也能捕獲到它拋出的異常:
boolean testEx2() throws Exception{
boolean ret = true;
try{
int b=12;
int c;
for (int i=2;i>=-2;i--){
c=b/i;
System.out.println("i="+i);
throw new Exception("aaa");
}
return true;
}catch (java.lang.ArithmeticException e){
System.out.println("testEx2, catch exception");
ret = false;
throw new Exception("aaa");
}finally{
System.out.println("testEx2, finally; return value="+ret);
}
}
4 關(guān)于abrupt completion
前面提到了complete abruptly(暫且理解為“突然中止”或者“異常結(jié)束”吧),它主要包含了兩種大的情形:abrupt completion of expressions and statements,下面就分兩種情況進(jìn)行解釋。
4.1 Normal and Abrupt Completion of Evaluation
每一個(gè)表達(dá)式(expression)都有一種使得其包含的計(jì)算得以一步步進(jìn)行的正常模式,如果每一步計(jì)算都被執(zhí)行且沒有異常拋出,那么就稱這個(gè)表達(dá)式“正常結(jié)束(complete normally)”;如果這個(gè)表達(dá)式的計(jì)算拋出了異常,就稱為“異常結(jié)束(complete abruptly)”。異常結(jié)束通常有一個(gè)相關(guān)聯(lián)的原因(associated reason),通常也就是拋出一個(gè)異常V。
與表達(dá)式、操作符相關(guān)的運(yùn)行期異常有:
-->A class instance creation expression, array creation expression , or string concatenation operatior expression throws an OutOfMemoryError if there is insufficient memory available.
-->An array creation expression throws a NegativeArraySizeException if the value of any dimension expression is less than zero.
-->A field access throws a NullPointerException if the value of the object reference expression is null.
-->A method invocation expression that invokes an instance method throws a NullPointerException if the target reference is null.
-->An array access throws a NullPointerException if the value of the array reference expression is null.
-->An array access throws an ArrayIndexOutOfBoundsException if the value of the array index expression is negative or greater than or equal to the length of the array.
-->A cast throws a ClassCastException if a cast is found to be impermissible at run time.
-->An integer division or integer remainder operator throws an ArithmeticException if the value of the right-hand operand expression is zero.
-->An assignment to an array component of reference type throws an ArrayStoreException when the value to be assigned is not compatible with the component type of the array.
4.2 Normal and Abrupt Completion of Statements
正常情況我們就不多說了,在這里主要是列出了abrupt completion的幾種情況:
-->break, continue, and return 語句將導(dǎo)致控制權(quán)的轉(zhuǎn)換,從而使得statements不能正常地、完整地執(zhí)行。
-->某些表達(dá)式的計(jì)算也可能從java虛擬機(jī)拋出異常,這些表達(dá)式在上一小節(jié)中已經(jīng)總結(jié)過了;一個(gè)顯式的的throw語句也將導(dǎo)致異常的拋出。拋出異常也是導(dǎo)致控制權(quán)的轉(zhuǎn)換的原因(或者說是阻止statement正常結(jié)束的原因)。
如果上述事件發(fā)生了,那么這些statement就有可能使得其正常情況下應(yīng)該都執(zhí)行的語句不能完全被執(zhí)行到,那么這些statement也就是被稱為是complete abruptly.
導(dǎo)致abrupt completion的幾種原因:
-->A break with no label
-->A break with a given label
-->A continue with no label
-->A continue with a given label
-->A return with no value
-->A return with a given value A
-->throw with a given value, including exceptions thrown by the Java virtual machine
5 關(guān)于我們的編程的一點(diǎn)建議
弄清楚try-catch-finally的執(zhí)行情況后我們才能正確使用它。
如果我們使用的是try-catch-finally語句塊,而我們又需要保證有異常時(shí)能夠拋出異常,那么在finally語句中就不要使用return語句了(finally語句塊的最重要的作用應(yīng)該是釋放申請(qǐng)的資源),因?yàn)?span id="wmqeeuq" class="hilite1">finally中的return語句會(huì)導(dǎo)致我們的throw e被拋棄,在這個(gè)try-catch-finally的外面將只能看到finally中的返回值(除非在finally中拋出異常)。(我們需要記住:不僅throw語句是abrupt completion 的原因,return、break、continue等這些看起來很正常的語句也是導(dǎo)致abrupt completion的原因。)