工作中,少不了要定義各種接口,系統集成要定義接口,前後台掉調用也要定義接口。接口定義一定程度上能反應程序員的編程功底。列舉一下工作中我發現大家容易出現的問題:同一個接口,有時候返回數組,有時候返回單個;成功的時候返回對象,失敗的時候返回錯誤信息字符串。工作中有個系統集成就是這樣定義的接口,真是辣眼睛。這個對應代碼上,返回的類型是map,json,object,都是不應該的。實際工作中,我們會定義一個統一的格式,就是ResultBean,分頁的有另外一個PageResultBean。//返回map可讀性不好,儘量不要 @PostMapping("/delete")publicMap<String,Object>delete(long id,Stringlang) {}// 成功返回boolean,失敗返回string,大忌@PostMapping("/delete")publicObjectdelete(long id,Stringlang) { try{ booleanresult = configService.delete(id, local); returnresult; }catch(Exception e) { log.error(e); returne.toString(); }}
一開始只考慮成功場景,等後面測試發現有錯誤情況,怎麼辦,改接口唄,前後台都改,勞民傷財無用功。//不返回任何數據,沒有考慮失敗場景,容易返工 @PostMapping("/update")publicvoidupdate(longid, xxx){}
如lang語言,當前用戶信息 都不應該出現參數裡面,應該從當前會話裡面獲取。後面講ThreadLocal會說到怎麼樣去掉。除了代碼可讀性不好問題外,尤其是參數出現當前用戶信息的,這是個嚴重問題。// (當前用戶刪除數據)參數出現lang和userid,尤其是userid,大忌 @PostMapping("/delete")publicMap<String,Object>delete(long id,Stringlang,StringuserId) {}
一般情況下,不允許出現例如json字符串這樣的參數,這種參數可讀性極差。應該定義對應的bean。// 參數出現json格式,可讀性不好,代碼也難看 @PostMapping("/update")publicMap<String,Object> update(long id,StringjsonStr) {}5. 沒有返回應該返回的數據
例如,新增接口一般情況下應該返回新對象的id標識,這需要編程經驗。新手定義的時候因為前台沒有用就不返回數據或者只返回true,這都是不恰當的。別人要不要是別人的事情,你該返回的還是應該返回。
錯誤範例:
// 約定俗成,新建應該返回新對象的信息,只返回boolean容易導致返工 @PostMapping("/add")publicbooleanadd(xxx){ //xxx returnconfigService.add();}很多人都覺得技術也很簡單,沒有什麼特別的地方,但是,實現這個代碼框架之前,就是要你的接口的統一的格式ResultBean,aop才好做。有些人誤解了,上周末那篇文章說的都不是技術,重點說的是編碼習慣工作方式,如果你重點還是放在什麼技術上,那我也幫不了你了。同樣,如果我後面的關於習慣和規範的帖子,你重點還是放在技術上的話,那是丟了西瓜撿芝麻,有很多貼還是沒有任何技術點呢。
附上ResultBean,沒有任何技術含量:
@DatapublicclassResultBean<T>implementsSerializable{privatestaticfinallongserialVersionUID =1L;publicstaticfinalintSUCCESS =0;publicstaticfinalintFAIL =1;publicstaticfinalintNO_PERMISSION =2;privateString msg ="success";privateintcode = SUCCESS;privateT data;publicResultBean(){ super();}publicResultBean(T data){ super(); this.data = data;}publicResultBean(Throwable e){ super(); this.msg = e.toString(); this.code = FAIL ;}}
統一的接口規範,能幫忙規避很多無用的返工修改和可能出現的問題。能使代碼可讀性更加好,利於進行aop和自動化測試這些額外工作。大家一定要重視。
二. Controller規範上面2段代碼,第一個是原生態的,第2段是我指定了接口定義規範,使用AOP技術之後最終交付的代碼,從15行到1行,自己感受一下。接下來說說大家關注的AOP如何實現。
先說說Controller規範,主要的內容是就是接口定義裡面的內容,你只要遵循裡面的規範,controller就問題不大,除了這些,還有另外的幾點:
1.所有函數返回統一的ResultBean/PageResultBean格式原因見我的接口定義這個貼。沒有統一格式,AOP無法玩。2.ResultBean/PageResultBean是controller專用的,不允許往後傳!3.Controller做參數格式的轉換,不允許把json,map這類對象傳到services去,也不允許services返回json、map。一般情況下!寫過代碼都知道,map,json這種格式靈活,但是可讀性差,如果放業務數據,每次閱讀起來都比較困難。定義一個bean看着工作量多了,但代碼清晰多了。4.參數中一般情況不允許出現Request,Response這些對象日誌在AOP裡面會打印,而且我的建議是大部分日誌在Services這層打印。規範裡面大部分是 不要做的項多,要做的比較少,落地比較容易。ResultBean定義帶泛型,使用了lombok。@DatapublicclassResultBean<T>implementsSerializable{privatestaticfinallongserialVersionUID =1L;publicstaticfinalintNO_LOGIN = -1;publicstaticfinalintSUCCESS =0;publicstaticfinalintFAIL =1;publicstaticfinalintNO_PERMISSION =2;privateString msg ="success";privateintcode = SUCCESS;privateT data;publicResultBean(){ super();}publicResultBean(T data){ super(); this.data = data;}publicResultBean(Throwable e){ super(); this.msg = e.toString(); this.code = FAIL;}}AOP代碼,主要就是打印日誌和捕獲異常,異常要區分已知異常和未知異常,其中未知的異常是我們重點關注的,可以做一些郵件通知啥的,已知異常可以再細分一下,可以不同的異常返回不同的返回碼:/*** 處理和包裝異常*/publicclassControllerAOP{privatestaticfinalLogger logger = LoggerFactory.getLogger(ControllerAOP.class);publicObject handlerControllerMethod(ProceedingJoinPoint pjp) { long startTime = System.currentTimeMillis(); ResultBean<?> result; try{ result = (ResultBean<?>) pjp.proceed(); logger.info(pjp.getSignature() +"use time:"+ (System.currentTimeMillis() - startTime)); }catch(Throwable e) { result = handlerException(pjp, e); } returnresult;}privateResultBean<?> handlerException(ProceedingJoinPoint pjp, Throwable e) { ResultBean<?> result =newResultBean(); // 已知異常 if(einstanceofCheckException) { result.setMsg(e.getLocalizedMessage()); result.setCode(ResultBean.FAIL); }elseif(einstanceofUnloginException) { result.setMsg("Unlogin"); result.setCode(ResultBean.NO_LOGIN); }else{ logger.error(pjp.getSignature() +" error ", e); //TODO 未知的異常,應該格外注意,可以發送郵件通知等 result.setMsg(e.toString()); result.setCode(ResultBean.FAIL); } returnresult;}}AOP配置:(關於用java代碼還是xml配置,這裡我傾向於xml配置,因為這個會不定期改動)<aop:aspectj-autoproxy/><beans:beanid="controllerAop"class="xxx.common.aop.ControllerAOP"/><aop:config> <aop:aspectid="myAop"ref="controllerAop"> <aop:pointcutid="target" expression="execution(public xxx.common.beans.ResultBean *(..))"/> <aop:aroundmethod="handlerControllerMethod"pointcut-ref="target"/> </aop:aspect></aop:config>現在知道為什麼要返回統一的一個ResultBean了:1.為了統一格式 ;2.為了應用AOP ;3.為了包裝異常信息。
分頁的PageResultBean大同小異,大家自己依葫蘆畫瓢自己完成就好了。
貼一個簡單的controller(左邊的箭頭表示AOP攔截了)。請對比吐槽我見過的最爛的java代碼裡面原來的代碼查看,沒有對比就沒有傷害。
最後說一句,先有統一的接口定義規範,然後有AOP實現。先有思想再有技術。技術不是關鍵,AOP技術也很簡單,這個帖子的關鍵點不是技術,而是習慣和思想,不要撿了芝麻丟了西瓜。網絡上講技術的貼多,講習慣、風格的少,這些都是我工作多年的行之有效的經驗之談。