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