標籤:tar gty 完成 影響 怎麼 Fix win 技術 需求
AOP,中文名稱,切面。在不影響業務代碼情況下,實現想要的功能,是個真炫酷的事。這不,最近來了個需求 。
業務需求:希望記錄多個關鍵業務的請求及操作情況!這本身是ok的,但是這些需求規律性太強,咱們不能硬幹,否則就一個字low。
aop是個好東西,記錄日誌是必須的。
記錄資料也一樣的,那麼也是可以用aop來實現的,這裡藉助註解一起解決問題吧。
因為是關鍵業務,所以意味著不是所有業務,那麼自然地就想到了,可以使用過濾的方式,也就是使用註解,如果有註解那麼就代表要記錄資料,否則不記錄。
記錄過程需要做什麼呢?
首先,因為是記錄業務資料,那麼我們可以抽象出一個方法出來,也就是 xxx.addRec()。也就是就切面裡只需調用記錄方法即可。
那麼還剩下幾個問題,怎樣找到這個方法?參數的處理怎麼辦?需要使用規範限制嗎?
找到這個方法,我們可以使用反射調用,當然需要註解進行調用的類和方法名稱(如果固定死則無需標註),用反射的好處就是自由,想怎麼寫就怎麼寫,要麼失敗要麼成功。但是low!
參數判定,可以根據輸入的類,進行if..else.. 判定,然後直接處理相應參數即可。這樣可以很自由,想怎麼寫參數就怎麼寫參數。但是這樣的話,這個切面就和業務代碼完全耦合在一起了,還多了n多無謂的判斷。很low!
那麼問題來了,怎樣才不low呢?
幾個理論可以借鑒下:
1. 面向介面編程而非面向類編程;
2. 使用枚舉值進行註解統一規劃;
3. 使用泛型,進行參數上下限處理;
4. 使用模板方法模式封裝參數;
5. 使用線程池;
讓我們細看下~
面向介面編程,即規定所有業務處理類都實現一個公用的介面,從而使方法不至於淩亂,定義一個清晰的介面,更易於理解含義。各實作類別只需關注自己的邏輯即可。
使用枚舉值進行註解參數的限定,可以使所有業務操作都在一處進行羅列,且都符合必須的規範。另外,當哪天發現參數無法滿足某些需求時,可方便地在該枚舉中加入相應參數以完成需求。
使用泛型,將參數限定一定範圍內,如要求入庫參數必須繼承某個基類以實現統一參數處理,也防止了傳入任意參數而必須做相應轉換的效能消耗。
使用模板方法模式進行參數封裝,因入參中都要求繼承一個基類,也就是具有共性的參數,所以應具備自動處理公用參數的能力,但是不可越權處理各業務實現的處理,應讓實作類別有能力自行處理個人化參數(實作類別也可以不處理)。交由實作類別處理個人化參數時,應使其具有絕對的能力,不應限制其發揮。
使用線程池技術,將額外的工作交給額外的線程,從而使主業務不愛影響。
具體代碼擼一遍:
1. 註解開啟新篇章,無註解,不工作。
@Componentpublic class UserTestService { // 添加註解,代表需要進行相應的邏輯處理 @BizRecordTrans(bizType = BizRecordTypeEnum.USER_ADD_FLOW) public ResponseEntity<Boolean> addUser(UserlDto terminalReq) { ResponseEntity<Boolean> ret = ResponseBuilder.buildResponse(); // 完成自己的業務... return ret; }}
2. 切面配置,做一個幕後的老兵。
@Component@Aspectpublic class BizRecordAop { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Around("execution(* com.xxx.dubbo.*.*(..))) and @annotation(com.xxx.spring.annotation.BizRecordTrans)") public Object deal(ProceedingJoinPoint pjp) throws Throwable { Object retVal = null; String methodName = pjp.getSignature().getName(); Object[] args = pjp.getArgs(); Class<?> classTarget = pjp.getTarget().getClass(); Class<?>[] argTypes = ((MethodSignature) pjp.getSignature()).getParameterTypes(); Method objMethod = classTarget.getMethod(methodName, argTypes); BizRecordTrans bizRecordTrans = objMethod.getAnnotation(BizRecordTrans.class); BizRecordTypeEnum bizRecordTypeEnum = null; if (BizRecordTrans != null) { bizRecordTypeEnum = BizRecordTrans.bizType(); } try { retVal = pjp.proceed(); } catch (Exception e) { // ignore throw e; } finally { if (bizRecordTrans != null) { Class<? extends SvcBaseEntity> bizRecEntityCls = bizRecordTypeEnum.getBizRecEntityCls(); SvcBaseEntity recEntity = (SvcBaseEntity) bizRecEntityCls.newInstance(); if (args[0] instanceof BaseDto) { BaseDto baseDto = (BaseDto) args[0]; fillBaseFields(recEntity, baseDto); } try { ThreadPoolTaskExecutor executor = (ThreadPoolTaskExecutor) SpringContextsUtil.getBean("threadPoolTaskExecutor"); executor.execute(() -> { BizRecBaseService<BaseEntity> recService = (BizRecBaseService<SvcBaseEntity>) SpringContextsUtil .getBean(bizRecordTypeEnum.getHandlerBeanName()); recService.fitOwnParams(recEntity, args); recService.addRecord(recEntity); }); } catch (Exception e) { logger.error("發生異常", e); } } } return retVal; } /** * 填充公用參數 */ private void fillBaseFields(BaseEntity baseEntity, BaseDto base) { baseEntity.setUserId(base.getUserId()); baseEntity.setSeqId(base.getSeqId()); baseEntity.setAddIp(base.getAddIp()); }}
3. 寫一個基礎介面,讓業務去實現。
public interface BizRecordBaseService<T extends BaseEntity> { /** * 執行業務方法 */ public Integer addRecord(T bizRecord); /** * 個人化參數填充方法,選擇性實現 */ default void fixOwnParams(T bizRecord, Object[] rawData) { System.out.println("可以不實現"); }}
4. 寫一個基礎類,讓所有其他業務實體繼承以實現公用參數的封裝。
@Datapublic class SvcBaseEntity { /** * ID */ private Long id; /** * 使用者ID */ private Long userId; /** * 請求序列id */ private String seqId; /** * 添加ip */ private String addIp; }
5. 打一個組合拳,搞定。
原理淺顯,易懂。效能嘛,也ok,無需太擔心。
嘿! 每一個奇怪的業務,都是一次實踐的機會。
SpringAop實操之記錄關鍵業務請求