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方法外,其他方法調用都會產生異常,該方法常用作離線註冊碼校驗,當破解者試圖暴力破解時,由於主執行者不是期望的值,因此會返回一個經過包裝和混淆的異常信息,大大增加了破解的難度。