1.1 異常處理
異常的處理應當符合 盡早拋出,延遲捕獲 的原則。
在進行 MVC 分層調用時,Service 與 Dao 層觸發的異常應統一向上拋出,交給 Controller 層處理。因為只有 Controller 層可以更好的決策發生異常時應當給用戶何種反饋。
但這樣會造成我們的 Controller 被龐大的 try-catch 塊包裹,異常處理與業務邏輯糅雜在一起極大的降低了代碼的可讀性。
比如:
我們實際的業務邏輯僅僅占總代碼量的一半甚至更少。
在項目中使用了 Spring3.0 提供的 RestControllerAdvice 機制,定義統一的異常處理方法。
不管在Controller/Service 還是 Dao 中,我們都不需要捕獲異常,而只需要在恰當的時候拋出異常。
最終堆棧會callBack到Spring DispatchServlet 的try部分觸發我們的異常處理器。當然這要求我們拋出的異常對應的情況顆粒度更細,以便異常處理器做出合適的反應。我們可以在 inspur-common-exception 包中自定義異常,以“文件長度超限異常”為例:
com.inspur.framework.web.exception.DefaultExceptionHandler 類中定義該異常的處理器:
這樣我們的業務代碼中不需要有一行異常處理的動作,只需要在恰當的時候將異常拋出。這使我們的業務代碼可以僅關注業務邏輯:
同時無論在調用棧的何處發生該異常,Spring都會幫助我們 longjump到異常處理器中的方法,返回給前台一個“文件長度超過限制!”的警示信息。
1.2 多線程任務
如果遇到需要進行多線程協作的情況,禁止使用 new Thread().start() 等原始的方式創建線程。如果邏輯出現錯誤(比如過多的循環或層級過深的遞歸調用)/或者開啟新線程的代碼部分承載了較大的並發量,我們可能會創建超過系統承受量的線程,威脅系統安全。同時線程的頻繁創建銷毀需要頻繁的進行系統調用,造成處理器在用戶態與核心態間的切換,帶來不必要的性能開銷。
工程中應對線程池進行封裝,並以單例的方式注冊到了 IOC 容器中,在項目啟動時會對線程進行統一的創建,運行過程中線程創建銷毀的頻率很低,將該部分性能開銷由運行時轉嫁到項目啟動時。通過 IOC 容器拿到全局唯一的線程池對象,向線程池中提交多線程/異步任務。做到線程資源的復用,同時對系統可創建的總線程數進行控制,保證系統安全。
1.3 防抵賴措施
工程中應封裝統一的調用情況記錄工具,用於防抵賴或記錄操作日志。使用動態代理配合自定義注解是個不錯的選擇。
比如自定義@Log 注解修飾期望記錄操作人信息的方法,並標明操作類型與日志名稱,在操作完成后織入后置動作將操作信息記錄到數據庫中。
1.4 Excel導出
為每一種 pojo 實現一次 excel 導出費時費力,應當考慮抽取統一的方法。可以自定義注解修飾 pojo 中的屬性,標示它們在 excel 中對應的列名/列寬等信息。借助反射統一通過該注解生成 excel。
但值得注意是,這類功能調用非常方便,但是十分消耗性能。因為不僅使用了反射技術,包含反射技術的代碼還存在於 for 循環中。 JVM 無法對該部分代碼進行性能優化。當數據量過大或對響應時間有特殊需求時建議針對Pojo重新定制導出Excel 的方法。
1.5 文件的上傳與下載
工程中為文件的上傳/下載應封裝統一的工具類和接口。
文件的上傳與下載都需要在 文件存儲地址-臨時緩存地址-內存 之間拷貝多次,同時每次拷貝都需要經歷 硬盤-內核輸入緩沖區-進程空間-內核輸出緩沖區-目標地址 的拷貝,十分消耗性能但難以避免。應當在適當的時候采用了NIO 支持的zero-copy 技術,比如:
將 過程:硬盤-內核輸入緩沖區-進程空間-內核輸出緩沖區-目標地址 簡化為:硬盤-內核輸入緩沖區-內核輸出緩沖區-目標地址,提升一些性能。性能提升測試:
1.6 接口調用
在調用異構系統接口尤其是請求方式為 POST 方式的接口時,調用參數往往非常復雜,比如:
需要非常多的代碼量來構建請求參數,並且交接人員想要完全掌握這段代碼需要按照接口文檔進行細致的比對,並進行多次測試。
我們應當優化參數拼裝的代碼結構,增強其可讀性於擴展性。我們將接口調用參數封裝為 DTO,並使用 @ParamAnnotation 注解修飾其屬性。
@ParamAnnotation 目前包含兩個屬性:name:參數名稱,isComplete:是否必傳參數。
這樣可以清晰的描述請求參數的含義。在構建一個 DTO 實例后,調用接口前,調用 DTO 的 isComplete 方法(不需顯示聲明,繼承自BaseDto),@ParamAnnotation注解中isComplete屬性為true的成員變量是否為null,如果這些調用接口的必傳參數存在null值,則會拋出一個自定義的ParamterImperfectException 異常,供上層決策。
這樣調用接口被抽象簡化為了三步,如果想要了解接口入參,去查看dto 結構和注解即可。同時如果接口參數發生改變,只需要修改 dto 結構,調用接口代碼部分不需要修改,做到了參數構建與接口調用邏輯的解耦,方便在接口頻繁變動的情況下對原有代碼的擴展。
1.7 調用層級間的通信
不要向上層返回魔法值讓上層決策,如果出現問題就拋出異常。可以幫助我們更清晰的規划代碼邏輯,魔法值的傳遞隨着層級的加深只會越來越復雜。