2017-04-01 105 views
2

我使用ASM更新的類棧地圖,但是當ASM getMergedType,會出現以下異常:問題與ASM getMergedType和getCommonSuperClass

了java.lang.RuntimeException:
產生java.io.IOException:資源沒有找到IntefaceImplA。

如果沒有asm修改類方法,它確實工作正常。
我已經定義了兩個接口A和B:IntefaceImplAIntefaceImplB

我的環境的源代碼:

IntefaceA.java

public interface IntefaceA { 
    void inteface(); 
} 

IntefaceImplA.java

public class IntefaceImplA implements IntefaceA { 
@Override 
public void inteface() { 

} 
} 

IntefaceImplB.java

public class IntefaceImplB implements IntefaceA { 
@Override 
public void inteface() { 

} 
} 

Test.java

public class Test { 
public IntefaceA getImpl(boolean b) { 
    IntefaceA a = b ? new IntefaceImplA() : new IntefaceImplB(); 
    return a; 
} 

}

Main.java

public class Main { 
public static void main(String args[]) { 
.... 
if (a instance of Test) { 
.. 
... 
} 
} 
} 

我編一個亞軍罐子後,並刪除IntefaceImplA.class和IntefaceA從jar中手動.class。爲什麼我想刪除這些類文件,因爲春天總是喜歡做這個東西。

runner jar可以在沒有ASM的情況下正常運行,但使用asm會發生異常。因爲asm想要爲IntefaceImplA和IntefaceImplB獲取MergeType,但IntefaceImplA被我刪除。 調查後,ASM ClassWriter源代碼,我發現下面的代碼:

protected String getCommonSuperClass(String type1, String type2) 
{ 
    ClassLoader classLoader = this.getClass().getClassLoader(); 
    Class c; 
    Class d; 
    try { 
     c = Class.forName(type1.replace('/', '.'), false, classLoader); 
     d = Class.forName(type2.replace('/', '.'), false, classLoader); 
    } catch (Exception var7) { 
     throw new RuntimeException(var7.toString()); 
    } 

    if(c.isAssignableFrom(d)) { 
     return type1; 
    } else if(d.isAssignableFrom(c)) { 
     return type2; 
    } else if(!c.isInterface() && !d.isInterface()) { 
     do { 
      c = c.getSuperclass(); 
     } while(!c.isAssignableFrom(d)); 

     return c.getName().replace('.', '/'); 
    } else { 
     return "java/lang/Object"; 
    } 
} 

其實,我刪除了相關的類文件,類加載器無法找到類。但程序沒有正常工作。
我應該增強對getCommonSuperClass方法的覆蓋,如果發生異常,那麼返回java/lang/Object?這很有趣

+0

1.)爲什麼「使用ASM更新類堆棧映射」?沒有理由更新堆棧映射,除非您執行了其他代碼轉換。但是如果您執行其他代碼轉換,則這些轉換是ASM的主要用途,而不是更新堆棧映射。你應該說一些關於實際的目標。 2)爲什麼你會刪除仍然被代碼引用的類? 「因爲春天總是喜歡做這個東西」並不是一個有意義的解釋。 – Holger

+0

因爲我需要在運行時插入一些代碼。 –

+0

由於在使用tomcat作爲嵌入容器時,彈簧引導刪除了代碼以減小jar大小。 爲什麼「使用ASM更新類堆棧映射」?---我需要在運行時插入一些代碼。 –

回答

2

通常,重寫getCommonSuperClass以使用不同的策略,例如,不加載類,是一個有效的用例。作爲it’s documentation狀態:

此方法的默認實現負載兩個給定類和使用方法java.lang.Class類找到共同的超類。可以通過其他方式重寫計算這種常見的超類型,特別是不需要實際加載任何類,或者考慮當前由該ClassWriter生成的類,當然由於它正在構建中而不能被加載。

除了這兩個參數都是您正在構建(或實質上正在改變)的類的可能性之外,代碼轉換工具的上下文可能不是類最終將運行的上下文,所以在這種情況下,他們不必通過Class.forName來訪問。由於Class.forName使用調用者的上下文(即ASM的ClassWriter),即使在使用ASM的代碼的上下文中(如果涉及不同的類加載器),ASM甚至有可能無法訪問該類。

另一個有效的方案是通過使用已有的元信息來解析請求,而無需實際加載類,從而獲得更高效的方法。

但是,當然,這不是一個有效的決議,只是返回"java/lang/Object"。雖然這確實是每個參數的常見超類型,但它並不一定是代碼的正確類型。留在你的榜樣,

public IntefaceA getImpl(boolean b) { 
    IntefaceA a = b ? new IntefaceImplA() : new IntefaceImplB(); 
    return a; 
} 

IntefaceImplAIntefaceImplB公用超類型,不僅需要覈實分配任一類型以它的有效性,這也是條件表達式的結果類型,它必須是可分配給方法的返回類型。如果您使用java/lang/Object作爲常用超級類型,驗證程序將拒絕該代碼,因爲它不能指定給IntefaceA

由於該類型與方法的返回類型相同,所以原始堆棧圖很可能會報告IntefaceA作爲常見的超級類型,因此即使不加載類型也可以被認爲是可分配的。無論是IntefaceImplA還是IntefaceImplB,測試都可以分配給該指定的通用類型,可能會推遲到實際加載這些類型的點,並且由於您說過,您將刪除IntefaceA,這種情況永遠不會發生。

其聲明的返回類型不存在的方法根本無法工作。你的觀察的唯一解釋是「沒有程序正常工作」,就是說,在你的測試過程中,這個方法從來沒有被調用過。您最有可能通過刪除正在使用的課程在您的軟件中創建了一個定時炸彈。

目前尚不清楚你爲什麼這麼做。你的解釋「春天總是喜歡做這個東西」遠遠不能理解。


但是,您可以使用重寫方法獲得與未修改代碼相同的行爲。它只是不返回java/lang/Object。你可以使用

@Override 
protected String getCommonSuperClass(String type1, String type2) { 
    if(type1.matches("IntefaceImpl[AB]") && type2.matches("IntefaceImpl[AB]")) 
     return "IntefaceA"; 
    return super.getCommonSuperClass(type1, type2); 
} 

當然,如果你刪除了更多的類文件,你必須添加更多的特殊情況。

完全不同的方法是不使用COMPUTE_FRAMES選項。這個選項意味着ASM將從頭開始重新計算所有的堆棧映射框架,這對於懶惰的程序員來說非常有用,但是如果你只是對現有的類進行很少的代碼轉換,就意味着很多不必要的工作,當然,這就需要有一個工作getCommonSuperClass方法。

如果沒有這個選項,ClassWriter只會複製ClassReader報告的幀,所以所有未更改的方法也將具有不變的堆棧映射。您將不得不關心您更改代碼的方法,但對於很多典型的代碼轉換任務,您仍然可以保留原始框架。例如。如果您只是將方法調用重定向到與簽名兼容的目標或注入日誌語句,而這些語句將堆棧保持在它們之前的相同狀態,則仍然可以保留自動發生的原始幀。請注意0​​的存在,它允許更高效地轉移不改變的方法。

只有當您更改分支結構或插入帶分支的代碼時,您必須關心框架。但即使如此,通常值得學習如何做到這一點,因爲自動計算安靜而昂貴,而在進行代碼轉換時通常已經有了必要的信息。