使用Aop的方式實(shí)現(xiàn)自動(dòng)日志記錄的方式詳細(xì)介紹
34、使用Aop的方式實(shí)現(xiàn)自動(dòng)日志記錄
自動(dòng)日志記錄的實(shí)現(xiàn)的兩種方式:
①通過(guò)監(jiān)聽(tīng)器去監(jiān)聽(tīng),當(dāng)訪問(wèn)到具體的類(lèi)方法,通過(guò)aop切面去獲取訪問(wèn)的方法,然后將日志記錄下來(lái)
②通過(guò)攔截器,編寫(xiě)一個(gè)類(lèi)去繼承HandlerInterceptorAdapter,重寫(xiě)preHandle,postHandle,然后在里面進(jìn)行日志記錄,編寫(xiě)的類(lèi)加到spring容器里
采用第一種方式:
1、第一步、定義一個(gè)注解:
Annotation 注解的作用:
@interface 表示這是一個(gè)注解類(lèi), 不是interface,是注解類(lèi) 定義注解用的,是jdk1.5之后加入的,java沒(méi)有給它新的關(guān)鍵字,所以就用@interface 這么個(gè)東西表示了
@Inherited //這個(gè)Annotation 可以被繼承
@Documented //這個(gè)Annotation可以被寫(xiě)入javadoc
@Target:注解的作用目標(biāo)
@Target(ElementType.TYPE) //接口、類(lèi)、枚舉、注解
@Target(ElementType.FIELD) //字段、枚舉的常量
@Target(ElementType.METHOD) //方法
@Target(ElementType.PARAMETER) //方法參數(shù)
@Target(ElementType.CONSTRUCTOR) //構(gòu)造函數(shù)
@Target(ElementType.LOCAL_VARIABLE)//局部變量
@Target(ElementType.ANNOTATION_TYPE)//注解
@Target(ElementType.PACKAGE) ///包
@Retention(RetentionPolicy.RUNTIME) //可以用來(lái)修飾注解,是注解的注解,稱(chēng)為元注解。
public enum RetentionPolicy { SOURCE, // 編譯器處理完Annotation后不存儲(chǔ)在class中 CLASS, // 編譯器把Annotation存儲(chǔ)在class中,這是默認(rèn)值 RUNTIME // 編譯器把Annotation存儲(chǔ)在class中,可以由虛擬機(jī)讀取,反射需要 }
創(chuàng)建一個(gè)注解:
default 0 相當(dāng)于set和get方法,添加一個(gè)默認(rèn)值
/** * 系統(tǒng)日志注解 * */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface AutoLog { /** * 日志內(nèi)容 * * @return */ String value() default ""; * 日志類(lèi)型 * @return 1:登錄日志;2:操作日志;3:訪問(wèn)日志;4:異常日志;5:定時(shí)任務(wù); int logType() default CommonConstant.LOG_TYPE_2; * 操作日志類(lèi)型 * @return (1查詢,2添加,3修改,4刪除) int operateType() default 0; }
CommonConstant 相關(guān)的配置
public interface CommonConstant { /** * 正常狀態(tài) */ public static final Integer STATUS_NORMAL = 0; * 禁用狀態(tài) public static final Integer STATUS_DISABLE = -1; * 刪除標(biāo)志 public static final Integer DEL_FLAG_DELETED = 1; * 未刪除 public static final Integer DEL_FLAG_UNDELETED = 0; * 系統(tǒng)日志類(lèi)型: 登錄 public static final int LOG_TYPE_1 = 1; * 系統(tǒng)日志類(lèi)型: 操作 public static final int LOG_TYPE_2 = 2; /** * 系統(tǒng)日志類(lèi)型: 訪問(wèn) */ public static final int LOG_TYPE_3 = 3; * 系統(tǒng)日志類(lèi)型: 異常 public static final int LOG_TYPE_4 = 4; * 系統(tǒng)日志類(lèi)型: 定時(shí)任務(wù) public static final int LOG_TYPE_5 = 5; * 系統(tǒng)日志類(lèi)型: 用戶管理 public static final int LOG_TYPE_6 = 6; * 系統(tǒng)登陸日志:正常賬戶密碼登錄 public static final int OPERATE_TYPE_LT1_1 = 1; * 系統(tǒng)登陸日志:二維碼登陸 public static final int OPERATE_TYPE_LT1_2 = 2; * 系統(tǒng)登陸日志:?jiǎn)吸c(diǎn)登陸 public static final int OPERATE_TYPE_LT1_3 = 3; * 系統(tǒng)登陸日志:登出 public static final int OPERATE_TYPE_LT1_4 = 4; * 系統(tǒng)登陸日志:模擬登陸 public static final int OPERATE_TYPE_LT1_5 = 5; * 操作日志類(lèi)型: 查詢 public static final int OPERATE_TYPE_LT2_1 = 1; * 操作日志類(lèi)型: 添加 public static final int OPERATE_TYPE_LT2_2 = 2; * 操作日志類(lèi)型: 更新 public static final int OPERATE_TYPE_LT2_3 = 3; * 操作日志類(lèi)型: 刪除 public static final int OPERATE_TYPE_LT2_4 = 4; * 操作日志類(lèi)型: 導(dǎo)入 public static final int OPERATE_TYPE_LT2_5 = 5; * 操作日志類(lèi)型: 導(dǎo)出 public static final int OPERATE_TYPE_LT2_6 = 6; * 訪問(wèn)日志類(lèi)型: 進(jìn)入 public static final int OPERATE_TYPE_LT3_1 = 1; * 異常日志類(lèi)型: 普通操作即代碼錯(cuò)誤 public static final int OPERATE_TYPE_LT4_1 = 1; * 異常日志類(lèi)型: 非法操作即越權(quán)操作 public static final int OPERATE_TYPE_LT4_2 = 2; public static final String CLIENT_TYPE_PC="0"; public static final String CLIENT_TYPE_MOBILE="1"; /** {@code 500 Server Error} (HTTP/1.0 - RFC 1945) */ public static final Integer SC_INTERNAL_SERVER_ERROR_500 = 500; /** {@code 200 OK} (HTTP/1.0 - RFC 1945) */ public static final Integer SC_OK_200 = 200; /**訪問(wèn)權(quán)限認(rèn)證未通過(guò) 510*/ public static final Integer SC_JEECG_NO_AUTHZ=510; /** 登錄用戶Shiro權(quán)限緩存KEY前綴 */ public static String PREFIX_USER_SHIRO_CACHE = "shiro:cache:org.jeecg.modules.shiro.authc.ShiroRealm.authorizationCache:"; /** 登錄用戶Token令牌緩存KEY前綴 */ public static final String PREFIX_USER_TOKEN = "prefix_user_token_"; /** Token緩存時(shí)間:3600秒即一小時(shí) */ public static final int TOKEN_EXPIRE_TIME = 3600; * 0:一級(jí)菜單 public static final Integer MENU_TYPE_0 = 0; /** * 1:子菜單 */ public static final Integer MENU_TYPE_1 = 1; * 2:按鈕權(quán)限 public static final Integer MENU_TYPE_2 = 2; /**通告對(duì)象類(lèi)型(USER:指定用戶,ALL:全體用戶)*/ public static final String MSG_TYPE_UESR = "USER"; public static final String MSG_TYPE_ALL = "ALL"; /**發(fā)布狀態(tài)(0未發(fā)布,1已發(fā)布,2已撤銷(xiāo))*/ public static final String NO_SEND = "0"; public static final String HAS_SEND = "1"; public static final String HAS_CANCLE = "2"; /**閱讀狀態(tài)(0未讀,1已讀)*/ public static final String HAS_READ_FLAG = "1"; public static final String NO_READ_FLAG = "0"; /**優(yōu)先級(jí)(L低,M中,H高)*/ public static final String PRIORITY_L = "L"; public static final String PRIORITY_M = "M"; public static final String PRIORITY_H = "H"; * 短信模板方式 0 .登錄模板、1.注冊(cè)模板、2.忘記密碼模板 public static final String SMS_TPL_TYPE_0 = "0"; public static final String SMS_TPL_TYPE_1 = "1"; public static final String SMS_TPL_TYPE_2 = "2"; * 狀態(tài)(0無(wú)效1有效) public static final String STATUS_0 = "0"; public static final String STATUS_1 = "1"; * 同步工作流引擎1同步0不同步 public static final String ACT_SYNC_0 = "0"; public static final String ACT_SYNC_1 = "1"; * 消息類(lèi)型1:通知公告2:系統(tǒng)消息 public static final String MSG_CATEGORY_1 = "1"; public static final String MSG_CATEGORY_2 = "2"; * 是否配置菜單的數(shù)據(jù)權(quán)限 1是0否 public static final Integer RULE_FLAG_0 = 0; public static final Integer RULE_FLAG_1 = 1; * 用戶狀態(tài) 0凍結(jié) 1正常 2待定 public static final Integer USER_FREEZE = 0; public static final Integer USER_NORMAL = 1; * 用戶刪除標(biāo)志位 0未刪 1已刪 public static final Integer USER_DELETE_NO=0; public static final Integer USER_DELETE_YES=1; /**字典翻譯文本后綴*/ public static final String DICT_TEXT_SUFFIX = "_dictText"; public static final String ITEM_DISPLAY = "_display"; * 表單設(shè)計(jì)器主表類(lèi)型 public static final Integer DESIGN_FORM_TYPE_MAIN = 1; * 表單設(shè)計(jì)器子表表類(lèi)型 public static final Integer DESIGN_FORM_TYPE_SUB = 2; * 表單設(shè)計(jì)器URL授權(quán)通過(guò) public static final Integer DESIGN_FORM_URL_STATUS_PASSED = 1; * 表單設(shè)計(jì)器URL授權(quán)未通過(guò) public static final Integer DESIGN_FORM_URL_STATUS_NOT_PASSED = 2; * 表單設(shè)計(jì)器新增 Flag public static final String DESIGN_FORM_URL_TYPE_ADD = "add"; * 表單設(shè)計(jì)器修改 Flag public static final String DESIGN_FORM_URL_TYPE_EDIT = "edit"; * 表單設(shè)計(jì)器詳情 Flag public static final String DESIGN_FORM_URL_TYPE_DETAIL = "detail"; * 表單設(shè)計(jì)器復(fù)用數(shù)據(jù) Flag public static final String DESIGN_FORM_URL_TYPE_REUSE = "reuse"; * 表單設(shè)計(jì)器編輯 Flag (已棄用) public static final String DESIGN_FORM_URL_TYPE_VIEW = "view"; * online參數(shù)值設(shè)置(是:Y, 否:N) public static final String ONLINE_PARAM_VAL_IS_TURE = "Y"; public static final String ONLINE_PARAM_VAL_IS_FALSE = "N"; * 文件上傳類(lèi)型(本地:local,Minio:minio,阿里云:alioss) public static final String UPLOAD_TYPE_LOCAL = "local"; public static final String UPLOAD_TYPE_MINIO = "minio"; public static final String UPLOAD_TYPE_OSS = "alioss"; * 員工身份 (1:普通員工 2:上級(jí)) public static final Integer USER_IDENTITY_1 = 1; public static final Integer USER_IDENTITY_2 = 2; * 日期格式 public static final String TIME_FORMAT_YMD = "yyyy-MM-dd"; public static final String TIME_FORMAT_YMDHMS = "yyyy-MM-dd HH:mm:ss"; public static final String TIME_FORMAT_YMDHMSSZ = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; }
2、第二步、編寫(xiě)一個(gè)切面
@Aspect 表示這是一個(gè)切面
@Component 告訴spring 這是一個(gè)bean ,注入
@annotation 獲取定義的注解
@Pointcut 切點(diǎn),
@Pointcut("@annotation(xx.AutoLog)") 表示,使用了這個(gè)注解的,就是切入點(diǎn)
@Around的作用
既可以在目標(biāo)方法之前織入增強(qiáng)動(dòng)作,也可以在執(zhí)行目標(biāo)方法之后織入增強(qiáng)動(dòng)作;
可以決定目標(biāo)方法在什么時(shí)候執(zhí)行,如何執(zhí)行,甚至可以完全阻止目標(biāo)目標(biāo)方法的執(zhí)行;
可以改變執(zhí)行目標(biāo)方法的參數(shù)值,也可以改變執(zhí)行目標(biāo)方法之后的返回值; 當(dāng)需要改變目標(biāo)方法的返回值時(shí),只能使用Around方法;
雖然Around功能強(qiáng)大,但通常需要在線程安全的環(huán)境下使用。因此,如果使用普通的Before、AfterReturing增強(qiáng)方法就可以解決的事情,就沒(méi)有必要使用Around增強(qiáng)處理了。
ProceedingJoinPoint 環(huán)繞通知,主要作用找到程序執(zhí)行中的可識(shí)別的點(diǎn),當(dāng)aop的切入點(diǎn)
- 環(huán)繞通知 ProceedingJoinPoint 執(zhí)行proceed方法的作用是讓目標(biāo)方法執(zhí)行,這也是環(huán)繞通知和前置、后置通知方法的一個(gè)最大區(qū)別。
- 簡(jiǎn)單理解,環(huán)繞通知=前置+目標(biāo)方法執(zhí)行+后置通知,proceed方法就是用于啟動(dòng)目標(biāo)方法執(zhí)行的.
/** * 系統(tǒng)日志,切面處理類(lèi) * */ @Aspect @Component public class AutoLogAspect { @Autowired private ISysLogService sysLogService; @Pointcut("@annotation(xx.AutoLog)") public void logPointCut() { } @Around("logPointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { long beginTime = System.currentTimeMillis(); //執(zhí)行方法 Object result = point.proceed(); //執(zhí)行時(shí)長(zhǎng)(毫秒) long time = System.currentTimeMillis() - beginTime; //保存日志 saveSysLog(point, time); return result; } private void saveSysLog(ProceedingJoinPoint joinPoint, long time) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); SysLog sysLog = new SysLog(); AutoLog syslog = method.getAnnotation(AutoLog.class); if (syslog != null) { //注解上的描述,操作日志內(nèi)容 sysLog.setLogContent(syslog.value()); sysLog.setLogType(syslog.logType()); } //請(qǐng)求的方法名 String className = joinPoint.getTarget().getClass().getName(); String methodName = signature.getName(); sysLog.setMethod(className + "." + methodName + "()"); //設(shè)置操作類(lèi)型 if (sysLog.getLogType() == CommonConstant.LOG_TYPE_2) { sysLog.setOperateType(getOperateType(methodName, syslog.operateType())); } //請(qǐng)求的參數(shù) Object[] args = joinPoint.getArgs(); try { String params = JSONObject.toJSONString(args); sysLog.setRequestParam(params); } catch (Exception e) { } try { //獲取request HttpServletRequest request = SpringContextUtils.getHttpServletRequest(); //設(shè)置IP地址 sysLog.setIp(IPUtils.getIpAddr(request)); } catch (Exception e) { } //獲取登錄用戶信息 LoginUser sysUser = null; try { sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal(); } catch (Exception e) { } if (sysUser != null) { sysLog.setUserId(sysUser.getId()); sysLog.setUserName(sysUser.getUserName()); sysLog.setRealName(sysUser.getRealName()); sysLog.setOrgId(sysUser.getNowOrgId()); sysLog.setOrgName(sysUser.getNowOrgName()); } //耗時(shí) sysLog.setCostTime(time); sysLog.setCreateTime(new Date()); //保存系統(tǒng)日志 sysLogService.save(sysLog); } /** * 獲取操作類(lèi)型 */ private int getOperateType(String methodName, int operateType) { if (operateType > 0) { return operateType; } if (methodName.startsWith("list")) { return CommonConstant.OPERATE_TYPE_LT2_1; } if (methodName.startsWith("add")) { return CommonConstant.OPERATE_TYPE_LT2_2; } if (methodName.startsWith("edit")) { return CommonConstant.OPERATE_TYPE_LT2_3; } if (methodName.startsWith("delete")) { return CommonConstant.OPERATE_TYPE_LT2_4; } if (methodName.startsWith("import")) { return CommonConstant.OPERATE_TYPE_LT2_5; } if (methodName.startsWith("export")) { return CommonConstant.OPERATE_TYPE_LT2_6; } return CommonConstant.OPERATE_TYPE_LT2_1; } @AfterThrowing(pointcut = "logPointCut()", throwing = "ex") public void afterThrowing(JoinPoint joinPoint, Throwable ex) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); SysLog sysLog = new SysLog(); StackTraceElement[] stackTraceElements = ex.getStackTrace(); String rootExceptionName = ex.getClass().getName(); StringBuilder resultContent = new StringBuilder("異常類(lèi):" + rootExceptionName); int count = 0; int maxTrace = 3; for (StackTraceElement stackTraceElement : stackTraceElements) { if (stackTraceElement.getClassName().contains("com.lingxu") && count < maxTrace) { resultContent.append("\n出現(xiàn)于").append(stackTraceElement.getClassName()) .append("類(lèi)中的").append(stackTraceElement.getMethodName()) .append("方法中 位于該類(lèi)文件的第").append(stackTraceElement.getLineNumber()) .append("行)"); count++; if (count == maxTrace) { break; } } } sysLog.setExceptionContent(resultContent.toString()); AutoLog syslog = method.getAnnotation(AutoLog.class); if (syslog != null) { //注解上的描述,操作日志內(nèi)容 sysLog.setLogContent(syslog.value() + "出現(xiàn)異常"); sysLog.setLogType(CommonConstant.LOG_TYPE_4); } //請(qǐng)求的方法名 String className = joinPoint.getTarget().getClass().getName(); String methodName = signature.getName(); sysLog.setMethod(className + "." + methodName + "()"); //設(shè)置操作類(lèi)型 sysLog.setOperateType(CommonConstant.OPERATE_TYPE_LT4_1); //請(qǐng)求的參數(shù) Object[] args = joinPoint.getArgs(); try { String params = JSONObject.toJSONString(args); sysLog.setRequestParam(params); } catch (Exception e) { } try { //獲取request HttpServletRequest request = SpringContextUtils.getHttpServletRequest(); //設(shè)置IP地址 sysLog.setIp(IPUtils.getIpAddr(request)); } catch (Exception e) { } try { //獲取登錄用戶信息 LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal(); if (sysUser != null) { sysLog.setUserId(sysUser.getId()); sysLog.setUserName(sysUser.getUserName()); sysLog.setRealName(sysUser.getRealName()); sysLog.setOrgId(sysUser.getNowOrgId()); sysLog.setOrgName(sysUser.getNowOrgName()); } } catch (Exception e) { } //保存系統(tǒng)日志 sysLogService.save(sysLog); } }
3、使用自定義注解
可以在controller或者實(shí)現(xiàn)類(lèi)上進(jìn)行注解的加入
@AutoLog(value = "sss",logType = CommonConstant.LOG_TYPE_3, operateType = 2) @ApiOperation(value="記錄查詢?nèi)罩?分頁(yè)列表查詢", notes="記錄查詢?nèi)罩?分頁(yè)列表查詢") @PostMapping(value = "/queryPage") public Result<?> queryPage(@RequestBody SysSelectLog sysSelectLog){ Page<SysSelectLog> page = new Page<>(sysSelectLog.getPageNo(),sysSelectLog.getPageSize()); IPage<SysSelectLog> sysSelectLogIPage = sysSelectLogService.queryPage(page,sysSelectLog); return Result.ok(sysSelectLogIPage); }
到此這篇關(guān)于使用Aop的方式實(shí)現(xiàn)自動(dòng)日志記錄的文章就介紹到這了,更多相關(guān)Aop自動(dòng)日志記錄內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java設(shè)置Excel數(shù)據(jù)驗(yàn)證的示例代碼
數(shù)據(jù)驗(yàn)證是Excel 2013版本中,數(shù)據(jù)功能組下面的一個(gè)功能。本文將通過(guò)Java程序代碼演示數(shù)據(jù)驗(yàn)證的設(shè)置方法及結(jié)果,感興趣的可以了解一下2022-05-05Java前后端的JSON傳輸方式(前后端JSON格式轉(zhuǎn)換)
這篇文章主要介紹了Java前后端的JSON傳輸方式(前后端JSON格式轉(zhuǎn)換),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04java實(shí)現(xiàn)簡(jiǎn)單猜數(shù)字游戲
這篇文章主要介紹了java實(shí)現(xiàn)簡(jiǎn)單猜數(shù)字游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-12-12SpringCloud學(xué)習(xí)筆記之SpringCloud搭建父工程的過(guò)程圖解
SpringCloud是分布式微服務(wù)架構(gòu)的一站式解決方案,十多種微服務(wù)架構(gòu)落地技術(shù)的集合體,俗稱(chēng)微服務(wù)全家桶,這篇文章主要介紹了SpringCloud學(xué)習(xí)筆記(一)搭建父工程,需要的朋友可以參考下2021-10-10Java 信號(hào)量Semaphore的實(shí)現(xiàn)
這篇文章主要介紹了Java 信號(hào)量Semaphore的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09Java多線程CountDownLatch的實(shí)現(xiàn)
本文主要介紹了Java多線程CountDownLatch的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02java中Hutool工具類(lèi)的常見(jiàn)使用場(chǎng)景詳解
在日常開(kāi)發(fā)中,我們會(huì)使用很多工具類(lèi)來(lái)提升項(xiàng)目開(kāi)發(fā)的速度,而國(guó)內(nèi)用的比較多的 Hutool 框架,就是其中之一,本文我們就來(lái)介紹一下Hutool的具體使用吧2023-12-12SpringBoot使用Guava實(shí)現(xiàn)日志脫敏的示例代碼
本文主要介紹了SpringBoot使用Guava實(shí)現(xiàn)日志脫敏的示例代碼,使用Guava中的Strings、Maps和CharMatcher類(lèi)來(lái)進(jìn)行日志脫敏,保護(hù)敏感數(shù)據(jù)的安全,感興趣的可以了解一下2024-01-01