讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議115:使用Throwable獲得棧信息 >

建議115:使用Throwable獲得棧信息

AOP編程可以很輕鬆地控制一個方法調用哪些類,也能夠控制哪些方法允許被調用,一般來說切面編程(比如AspectJ)只能控制到方法級別,不能實現代碼級別的植入(Weave),比如一個方法被類A的m1方法調用時返回1,在類B的m2方法調用時返回0(同參數情況下),這就要求被調用者具有識別調用者的能力。在這種情況下,可以使用Throwable獲得棧信息,然後鑒別調用者並分別輸出,代碼如下:


class Foo{

public static boolean m(){

//取得當前棧信息

StackTraceElementsts=new Throwable().getStackTrace();

//檢查是否是m1方法調用

for(StackTraceElement st:sts){

if(st.getMethodName().equals("m1")){

return true;

}

}

return false;

}

}

//調用者

class Invoker{

//該方法打印出true

public static void m1(){

System.out.println(Foo.m());

}

//該方法打印出false

public static void m2(){

System.out.println(Foo.m());

}

}


注意看Invoker類,兩個方法m1和m2都調用了Foo的m方法,都是無參調用,返回值卻不同,這是我們的Throwable類發揮效能了。JVM在創建一個Throwable類及其子類時會把當前線程的棧信息記錄下來,以便在輸出異常時準確定位異常原因,我們來看Throwable源代碼:


public class Throwable implements Serializable{

//出現異常的棧記錄

private StackTraceElementstackTrace;

//默認構造函數

public Throwable(){

//記錄棧幀

fllInStackTrace();

}

//本地方法,抓取執行時的棧信息

public synchronized native Throwable fillInStackTrace();

}


在出現異常時(或主動聲明一個Throwable對像時),JVM會通過fillInStackTrace方法記錄下棧幀信息,然後生成一個Throwable對象,這樣我們就可以知道類間的調用順序、方法名稱及當前行號等了。

獲得棧信息可以對調用者進行判斷,然後決定不同的輸出,比如我們的m1和m2方法,同樣是輸入參數,同樣的調用方法,但是輸出卻不同,這看起來很像是一個Bug:方法m1調用m方法是正常顯示,而方法m2調用卻會返回「錯誤」數據。因此我們雖然可以依據調用者不同產生不同的邏輯,但這僅局限在對此方法的廣泛認知上。更多的時候我們使用m方法的變形體,代碼如下:


class Foo{

public static boolean m(){

//取得當前棧信息

StackTraceElementsts=new Throwable().getStackTrace();

//檢查是否是m1方法調用

for(StackTraceElement st:sts){

if(st.getMethodName().equals("m1")){

return true;

}

}

throw new RuntimeException("除m1方法外,該方法不允許其他方法調用");

}

}


只是把"return false"替換成一個運行期異常,除了m1方法外,其他方法調用都會產生異常,該方法常用作離線註冊碼校驗,當破解者試圖暴力破解時,由於主執行者不是期望的值,因此會返回一個經過包裝和混淆的異常信息,大大增加了破解的難度。