2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
記錄一下Spring AOP切入DAO層,進(jìn)行數(shù)據(jù)監(jiān)控。
在寫這個需求時(shí),從網(wǎng)上找了很多的資料,大部分都是沒有解決相關(guān)問題的。當(dāng)然也有少數(shù)部分解決,但用的還是SSM架構(gòu)的xml配置。一開始我的出發(fā)點(diǎn)是通過注解@annotation的方式來切入DAO需要監(jiān)控的方法,但是并沒有用。接下來剖析下個人的實(shí)現(xiàn)和思路。
相關(guān)依賴
- spring-boot-starter-web
- mysql-connector-java
- lombok
- spring-boot-starter-aop
- mybatis-spring-boot-starter
Spring AOP兩種代理
- jdk代理
使用Java動態(tài)代理來創(chuàng)建AOP代理,在程序運(yùn)行期間由JVM根據(jù)反射等機(jī)制動態(tài)的生成(當(dāng)然此接口要有實(shí)現(xiàn)類)。 - cglib代理
代理類不是接口時(shí),Spring會切換為使用CGLIB代理,它的工作原理是:直接在class字節(jié)碼文件添加增強(qiáng)的代碼。
思路
在這里只針對ADD、UPDATE、DELETE做相關(guān)數(shù)據(jù)處理。
直接使用注解@annotation的方式是不能實(shí)現(xiàn),所以我先通過execution的方式切到DAO層,再通過一個自定義注解區(qū)分?jǐn)?shù)據(jù)操作的類型以及區(qū)分所操作的是哪一張表,具體詳情如下:
- 記錄ADD操作:使用@After注解,由于新增一條數(shù)據(jù)中如果有自增的值,也需要把自增對應(yīng)字段的值也要記錄,所有在執(zhí)行完之后記錄
- 記錄DELETE操作:使用@Before注解,在執(zhí)行刪除之前,記錄原數(shù)據(jù)。如果在刪除之后再記錄的話,那條數(shù)據(jù)已經(jīng)沒有了
- 記錄UPDATE操作:使用@Around注解,記錄更新前后的數(shù)據(jù),舊數(shù)據(jù)與新數(shù)據(jù)有父子關(guān)系,這樣數(shù)據(jù)才能一一對應(yīng)。
主要核心代碼
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AuditAction {/*** 操作類型*/Action action() default Action.GET;/*** 目標(biāo)table*/String targetTable() default "";
}
public enum Action {ADD,DELETE,UPDATE,GET
}
- 相關(guān)DAO層接口中,在需要監(jiān)控的方法上加上注解
@Mapper
public interface CompanyDao {Company getCompanyByNum(int companyNum);@AuditAction(action = Action.ADD, targetTable = "company")int addCompany(Company company);@AuditAction(action = Action.UPDATE, targetTable = "company")int updateCompany(Company company);@AuditAction(action = Action.DELETE, targetTable = "company")int deleteCompany(int companyNum);
}
@Slf4j
@Aspect
@Component
public class SystemAudioAspect {// 省略相關(guān)業(yè)務(wù)代碼,詳細(xì)代碼請看博客末尾
}
- 切面類: 監(jiān)控DELETE操作
- 對應(yīng)DELETE操作,這里我采用在執(zhí)行之前攔截@Before,@Before(value = "execution(public * com.jtcoding.auditlog.dao...delete(..))")** 攔截所有的delete*方法,通過@AuditAction注解來判定是否需要監(jiān)控,再通過注解中的targetTable來區(qū)分對應(yīng)是對哪個表的操作,具體代碼如下:
/*** 攔截DELETE操作,記錄被刪除的數(shù)據(jù)* @param joinPoint*/
@Before(value = "execution(public * com.jtcoding.auditlog.dao..*.delete*(..))")
public void doBefore(JoinPoint joinPoint) {// 獲取方法中的參數(shù)Object[] args = joinPoint.getArgs();// 獲取該方法上的 @AuditAction注解AuditAction audioAction = this.getAudioActionByJoinPoint(joinPoint);if (audioAction != null && audioAction.action() == Action.DELETE) {Object obj = null;String targetTable = audioAction.targetTable();switch (targetTable) {case "company":int companyNum = (int) args[0];obj = companyService.getCompanyByNum(companyNum);break;case "plan":int planNum = (int) args[0];obj = planService.getPlanByNum(planNum);break;}if (obj != null) {this.addAudioLog(obj, AuditLogDao.DELETE, targetTable, null);}}
}
/*** 攔截ADD操作,記錄新增的數(shù)據(jù)* @param joinPoint*/
@After(value = "execution(public * com.jtcoding.auditlog.dao..*.add*(..))")
public void doAfter(JoinPoint joinPoint) {// 獲取該方法上的 @AuditAction注解AuditAction audioAction = this.getAudioActionByJoinPoint(joinPoint);if (audioAction != null && audioAction.action() == Action.ADD) {Object obj = joinPoint.getArgs()[0];this.addAudioLog(obj, AuditLogDao.ADD, audioAction.targetTable(), null);}
}
/*** 攔截UPDATE操作,記錄更新前后的數(shù)據(jù)* @param pjp* @return* @throws Throwable*/
@Around(value = "execution(public * com.jtcoding.auditlog.dao..*.update*(..))")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {AuditAction audioAction = this.getAudioActionByJoinPoint(pjp);Object proceed = null;if (audioAction != null && audioAction.action() == Action.UPDATE) {String uuid = UUID.randomUUID().toString();Object originalObj = null;Object arg = pjp.getArgs()[0];String targetTable = audioAction.targetTable();switch (targetTable) {case "company":Company company = (Company) arg;originalObj = companyService.getCompanyByNum(company.getCompanyNum());break;case "plan":Plan plan = (Plan) arg;originalObj = planService.getPlanByNum(plan.getPlanNum());break;}AuditLog auditLog = null;if (originalObj != null) {// TODO 在執(zhí)行原方法之前,記錄舊數(shù)據(jù)auditLog = this.addAudioLog(originalObj, AuditLogDao.UPDATE, targetTable, null);}// 執(zhí)行原方法proceed = pjp.proceed();// TODO 在執(zhí)行原方法之后,記錄新數(shù)據(jù)if (auditLog != null) {this.addAudioLog(arg, AuditLogDao.UPDATE, targetTable, auditLog.getLogNum());}}if (proceed == null) {return pjp.proceed();}return proceed;
}
至此,有關(guān)切面核心的邏輯已經(jīng)代碼已經(jīng)完成,相關(guān)Service和Controller代碼,請看這里(源碼)
測試
- 通過Postman進(jìn)行相關(guān)API測試
- Add Company : /companies POST Request
- Delete Company : /companies/{companyNum} DELETE Request
- Update Company : /companies PUT Request
- ............
-
具體請求如下圖
? 新增Company
? 刪除Company
? 獲取某一個Company
? 修改Company
DB結(jié)果
數(shù)據(jù)庫數(shù)據(jù)
- 由上圖可以看出:
- 新增操作:記錄新增的數(shù)據(jù)
- 刪除操作:記錄原來的數(shù)據(jù)
- 更新操作:記錄原數(shù)據(jù)與新數(shù)據(jù),兩條數(shù)據(jù)有一個父子關(guān)系,方便數(shù)據(jù)的關(guān)聯(lián)
- log_type與log_table_name:可以非常清晰的看出操作的是哪張表以及操作類型
- src_num:可以找到UPDATE操作,前后數(shù)據(jù)的關(guān)聯(lián)
遺留問題
- 問題1:監(jiān)控變更的數(shù)據(jù),這里使用的是MySQL數(shù)據(jù)庫,存儲類型是VARCHAR(255),所以很容易超出上限。
- 問題2:如果項(xiàng)目中使用會更改IoC容器加載順序的Jar,導(dǎo)致切入點(diǎn)無效,可以在使用到DAO接口的地方,加上@Lazy 懶加載注解即可。(該問題純屬本人猜測,還需深度研究)
最后
以上是個人的思路實(shí)現(xiàn),有不對或者需要優(yōu)化之處,請指出,謝謝。
?
轉(zhuǎn)載于:https://my.oschina.net/u/4094976/blog/3034519
總結(jié)
以上是生活随笔為你收集整理的SpringBoot + AOP + MySQL监控系统数据变更实战的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。