無論是Dubbo還是JSF等RPC框架,一般都會把接口分為2部分:
1,服務端(provider)
2,客戶端(consumer)
由於,客戶端與服務端可能不在同一個應用中,所以客戶端一般在調用服務端的接口時,通常會返回一個結果實體,來標明這一次請求操作是否成功。
例如:
public class BaseResultDto<T> { /** * 是否操作成功 */ private boolean success; /** * 提示信息 */ private String msg; /** * 操作結果 */ private T result; }
客戶端在拿到這個實體后,可以明確得知,這一次操作是否成功。
但是防御式編程中,我們應該對一切未知的接口都持有懷疑態度,況且不怕一萬就怕萬一:“如果服務端出現異常怎么辦?”
網上有2中答案:
1,直接將異常拋出去,經過RPC序列化后,客戶端進行展示。
2,不拋異常出去,服務端進行全方位攔截,攔截到后,通過BaseResultDto,告訴客戶端現在服務端出現異常了。
但是各自的缺點很明顯:
1,服務端與客戶端,很可能不在同一個應用中,所以各自會依賴不同的jar包,比方說:服務端拋出了個spring的duplicateKeyException,但是客戶端並沒用引用spring的相關jar包,這樣就會導致:拋出異常后,由於客戶端沒有依賴這個類,最終拋出個ClassNotDefError,注意是Error不是Exception。如果客戶端只對Exception進行捕獲的話,會導致直接拋到最頂層。可能日志、重試等都沒了。
2,全方位攔截后,可能返回的結果中只會告訴客戶端:“系統出現異常”,無法准確通過日志去定位問題。
最終解決方案:
將2者折中處理,服務端全方位進行攔截,如果出現異常后,把異常信息轉換成字符串,然后把異常信息返回到客戶端中:
public class BaseResultDto<T> { /** * 是否操作成功 */ private boolean success; /** * 提示信息 */ private String msg; /** * 操作結果 */ private T result; /** * 異常堆棧信息 */ private String errorTrace; }
errorTrace就是存儲異常對戰信息的屬性,這樣如果客戶端檢測到success為false,這樣就可以直接把errorTrace打到log中,方便定位問題。