java - Java - 擴展final類或替換它

140 5

它已經被多次討論了,唯一的答案是"不,不可能",但是我們是寫代碼的人,從理論上講,一切都有可能。

問題

我試圖擴展一個final類來改變一個行為,這不能用傳統方式完成-在編譯時使用extend關鍵字,我已經找了一些其他的方法:

  • 創建一個只適用於介面的代理;
  • 編輯類的Modifiers,移除final修飾符,並且在運行時擴展類,但是可以對欄位執行這些操作:
  • 使用自定義ClassLoader載入類的新版本-,因為要擴展的類是java包的一部分,所以它不能工作,幸運的是,ClassLoader無法載入這個包的類,因為它有SecurityException ,否則會造成很多混亂,

static class TestClassLoader extends ClassLoader {


 @Override


 public Class<?> loadClass(String name) throws ClassNotFoundException {


 if (name.equals("java.io.StringReader")) {


 try {


 InputStream is = Test.class.getClassLoader().getResourceAsStream("java/io/StringReader.class");


 byte[] buf = new byte[10000];


 int len = is.read(buf);


 return defineClass(name, buf, 0, len); // This line throws the SecurityException :(


 } catch (IOException e) {


 throw new ClassNotFoundException("", e);


 }


 }


 return getParent().loadClass(name);


 }


}



有什麼方法可以用來替換或擴展final類?

Context

我必須為一個無法改變的類編寫單元測試,除了catch塊不能訪問外,此部分非常容易:


Properties props = new Properties();


try


{


 props.load(new StringReader(rules));


} catch (IOException e)


{


 e.printStackTrace();


 throw e;


}




我可以訪問的唯一變數是Stringrules ,然後,我應該對它進行處理,使load調用拋出IOException ,這由ensureOpen方法檢查:


/** Check to make sure that the stream has not been closed */


private void ensureOpen() throws IOException {


 if (str == null)


 throw new IOException("Stream closed");


}



因此,只有空String才能使此方法引發IOException ,然後,將空String注入rules很容易,壞消息是StringReader構造函數:


public StringReader(String s) {


 this.str = s;


 this.length = s.length();


}



當傳遞nullString時它拋出一個NPE ,

我的最後一個想法是改變String的值使它內部value欄位null -在使用該欄位的Modifiers時很容易,因為引用本身不是null,所以,這不能工作; 因為在StringReader上調用read方法,這隻會引發NPE :


public int read() throws IOException {


 synchronized (lock) {


 ensureOpen();


 if (next >= length)


 return -1;


 return str.charAt(next++);


 }


}



有什麼辦法嗎?

时间: 原作者:

145 1

有一個InstrumentationAPI,它允許Java軟體(稱為Java代理)在運行時修改類,把它用於單元測試是不合理的。

但是請注意,代碼注入的特定目標是錯誤的。 在代碼中


Properties props = new Properties();


try


{


 props.load(new StringReader(rules));


} catch (IOException e)


{


 e.printStackTrace();


 throw e;


}



IOException從不被拋出,你已經注意到,StringReader的構造函數並不拋出它,同樣也適用於它的read方法,Propertiesload方法聲明它,因為它必須處理任意的Reader實現,但是,它不會自己生成這樣的異常。

所以,在這個場景中,從String讀取一個新的StringReader,它將不會被拋出,並且注入一個不存在的方案。

正如你自己注意到的那樣,它可以拋出IOException的唯一方法是ensureOpen(),如名稱所示,因此,你可以調用close(),這與測試中的代碼的實際行為不匹配。

原作者:
59 1

可能的解決方案之一是使用Aspect。

你可以在final類方法的執行過程中創建一個Aspect和pointcut,你可以在aspect實現你的行為。

原作者:
80 0

你說,修改或替換JRE不是你的選擇,那麼唯一的解決方法就是修改位元組碼,

1)使用PowerMock它的一個重要部分是修改位元組代碼MockClassLoader的類載入器,

2)在載入位元組代碼之前修改它,有不同的工具可以使用: AspectJ (當你擁有源代碼,並且編譯它時),ASM (你還可以修改現有的位元組代碼),其他方法的一個重要區別是在編譯期間使用這種修改類,編譯器不知道修改類的原始位元組代碼。

3)在載入時修改位元組代碼,你可以使用AsppectJ,spring AOP,ASM ,最強大的是ASM,但是,它也需要更多的努力,AspectJ是強大的,並且擁有與ASM相比的高級API ,和AspectJ相比,spring AOP相當有限,但是在某些情況下它的用法更簡單。

4)你可以結合不同的方法,然後你可以使用CGLIB修改類的行為,如你所需,但是使用CGLIB你可以給你的類添加新的方法。

原作者:
...