1.簡單回顧AOP
- @Before 前置通知(Before advice) :在某連接點(JoinPoint)——核心代碼(類或者方法)之前執行的通知,但這個通知不能阻止連接點前的執行。爲啥不能阻止線程進入核心代碼呢?因爲@Before註解的方法入參不能傳ProceedingJoinPoint,而只能傳入JoinPoint。要知道從aop走到核心代碼就是通過調用ProceedingJionPoint的proceed()方法。而JoinPoint沒有這個方法。
- 這裏牽扯區別這兩個類:Proceedingjoinpoint 繼承了 JoinPoint 。是在JoinPoint的基礎上暴露出 proceed 這個方法。proceed很重要,這個是aop代理鏈執行的方法。暴露出這個方法,就能支持 aop:around 這種切面(而其他的幾種切面只需要用到JoinPoint,這跟切面類型有關), 能決定是否走代理鏈還是走自己攔截的其他邏輯。建議看一下 JdkDynamicAopProxy的invoke方法,瞭解一下代理鏈的執行原理。這樣你就能明白 proceed方法的重要性。
- @After 後通知(After advice) :當某連接點退出的時候執行的通知(不論是正常返回還是異常退出)。
- @AfterReturning 返回後通知(After return advice) :在某連接點正常完成後執行的通知,不包括拋出異常的情況。
- @Around 環繞通知(Around advice) :包圍一個連接點的通知,類似Web中Servlet規範中的Filter的doFilter方法。可以在方法的調用前後完成自定義的行爲,也可以選擇不執行。這時aop的最重要的,最常用的註解。用這個註解的方法入參傳的是ProceedingJionPoint pjp,可以決定當前線程能否進入核心方法中——通過調用pjp.proceed();
- @AfterThrowing 拋出異常後通知(After throwing advice) : 在方法拋出異常退出時執行的通知。
2.spring boot AOP使用流程
- 首先將一個類聲明成切面類
@Aspect
@Component
public class LogAop {}
- 接着定義切點,在此使用註解切點
@Pointcut(value = "@annotation(cn.stylefeng.guns.core.common.annotion.BussinessLog)")
public void cutService() { }
- 接着定義建言
@Around("cutService()")
public Object recordSysLog(ProceedingJoinPoint point) throws Throwable {
//先執行業務
Object result = point.proceed();
try {
handle(point);
} catch (Exception e) {
log.error("日誌記錄出錯!", e);
}
return result;
}
- 最後在需要織入建言的方法上加入註解即可
@RequestMapping(value = "/add")
@BussinessLog(value = "添加角色", key = "name", dict = RoleDict.class)
public ResponseData add(@Valid Role role, BindingResult result) {
if (result.hasErrors()) {
throw new ServiceException(BizExceptionEnum.REQUEST_NULL);
}
role.setId(null);
this.roleService.insert(role);
return SUCCESS_TIP;
}
3.日誌框架的基本功能
- 對用戶的登陸狀態進行記錄(成功、失敗、退出)------直接在相應登陸控制器上記錄日誌
- 對異常操作進行記錄(自定義異常和運行時異常)------直接在全局異常攔截控制器上記錄日誌
- 對業務操作進行記錄(增刪改查)------使用AOP方式記錄日誌
4.日誌框架實現
注意: 在實際記錄日誌時候,需要開啓線程記錄,因爲記錄操作會非常多防止堵塞主線程
- 第一種方法如guns一樣自定義線程池
//異步操作記錄日誌的線程池
private ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(10);
- 第二種方法使用spring的線程池,需要配置相關的配置類,之後在需要執行的方法打上@Async上打上註解即可異步調用
@Async
public void async(){}
5.分析AOP的主要邏輯
- 可以看到主要方法在handle(),並且是在執行具體業務邏輯之後纔開始執行aop的邏輯
@Around("cutService()")
public Object recordSysLog(ProceedingJoinPoint point) throws Throwable {
//先執行業務
Object result = point.proceed();
try {
handle(point);
} catch (Exception e) {
log.error("日誌記錄出錯!", e);
}
return result;
}
- 首先獲取的是攔截方法名
private void handle(ProceedingJoinPoint point) throws Exception {
//獲取攔截的方法名
Signature sig = point.getSignature();
MethodSignature msig = null;
if (!(sig instanceof MethodSignature)) {
throw new IllegalArgumentException("該註解只能用於方法");
}
msig = (MethodSignature) sig;
Object target = point.getTarget();
Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
String methodName = currentMethod.getName();
- 下方是一些常用操作
//攔截的實體類
Object target = point.getTarget();
//攔截的方法名稱
String methodName = point.getSignature().getName();
//攔截的方法參數
Object[] args = point.getArgs();
//攔截的放參數類型
Class[] parameterTypes = ((MethodSignature)point.getSignature()).getMethod().getParameterTypes();
Method m = null;
try {
//通過反射獲得攔截的method
m = target.getClass().getMethod(methodName, parameterTypes);
//如果是橋則要獲得實際攔截的method
if(m.isBridge()){
for(int i = 0; i < args.length; i++){
//獲得泛型類型
Class genClazz = GenericsUtils.getSuperClassGenricType(target.getClass());
//根據實際參數類型替換parameterType中的類型
if(args[i].getClass().isAssignableFrom(genClazz)){
parameterTypes[i] = genClazz;
}
}
//獲得parameterType參數類型的方法
m = target.getClass().getMethod(methodName, parameterTypes);
- 獲取註解上的值寫入日誌 ,包括業務內容value、主鍵key、字典類dict
//獲取操作名稱
BussinessLog annotation = currentMethod.getAnnotation(BussinessLog.class);
String bussinessName = annotation.value();
String key = annotation.key();
Class dictClass = annotation.dict();
@RequestMapping(value = "/add")
@BussinessLog(value = "添加角色", key = "name", dict = RoleDict.class)
@Permission(Const.ADMIN_NAME)
@ResponseBody
public ResponseData add(@Valid Role role, BindingResult result) {
if (result.hasErrors()) {
throw new ServiceException(BizExceptionEnum.REQUEST_NULL);
}
role.setId(null);
this.roleService.insert(role);
return SUCCESS_TIP;
}
---------------------
參考原文:https://blog.csdn.net/zhanglf02/article/details/78132304