日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

自定义注解-aop实现日志记录

發(fā)布時間:2025/5/22 编程问答 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 自定义注解-aop实现日志记录 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

關于注解,平時接觸的可不少,像是 @Controller、@Service、@Autowried 等等,不知道你是否有過這種疑惑,使用 @Service 注解的類成為我們的業(yè)務類,使用 @Controller 注解的類就成了請求的控制器,使用 @Autowried 注解的類就會幫我們實現(xiàn)自動注入…

以前,我們只知道使用注解,今天我們要手寫一個注解。

一、以日志記錄為例

在沒有使用注解實現(xiàn)記錄日志之前,我們往往自己去調(diào)用日志記錄的 Service,然后寫入數(shù)據(jù)庫表。

今天我們將從方法上添加自定義注解實現(xiàn)日志自動記錄,如下:

52e77c79b07d49c6554ff2a0185d7f02.png

二、了解關于注解知識

JDK 提供了 meta-annotation 用于自定義注解的時候使用,這四個注解為:@Target,@Retention,@Documented 和 @Inherited。

以 @Controller 為例,其源碼也是如此:

fc6b30adb15c78f562e0de8e503e6881.png

我們來看一下上邊提到的四個注解:

注解說明
@Target用于描述注解的使用范圍,即:被描述的注解可以用在什么地方
@Retention指定被描述的注解在什么范圍內(nèi)有效
@Documented是一個標記注解,木有成員,用于描述其它類型的annotation 應該被作為被標注的程序成員的公共 API,因此可以被例如javadoc此類的工具文檔化
@Inherited元注解是一個標記注解,@Inherited 闡述了某個被標注的類型是被繼承的。如果一個使用了 @Inherited 修飾的 annotation 類型被用于一個 class,則這個 annotation 將被用于該class的子類

三、開始我們的自定義注解

兩個類:
SystemLog:自定義注解類,用于標記到方法、類上,如@SystemLog
SystemLogAspect:AOP實現(xiàn)切點攔截。

關于AOP的補充:
關于AOP面向切面編程概念啥的就不啰嗦了,還不了解的可以自定百度了

描述AOP常用的一些術語有:
通知(Adivce)、連接點(Join point)、切點(Pointcut)、切面(Aspect)、引入(Introduction)、織入(Weaving)

關于術語的部分可參考:https://www.cnblogs.com/niceyoo/p/10162077.html

需要明確的核心概念:切面 = 切點 + 通知。

@Aspect 注解形式是 AOP 的一種實現(xiàn),如下看一下我們要寫的兩個類吧。

1、@SystemLog

定義我們的自定義注解類

/**
?*?系統(tǒng)日志自定義注解
?*/

@Target({ElementType.PARAMETER,?ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public?@interface?SystemLog?{

????????/**
?????????*?日志名稱
?????????*?@return
?????????*/

????????String?description()?default?"";

????????/**
?????????*?日志類型
?????????*?@return
?????????*/

????????LogType?type()?default?LogType.OPERATION;
}
2、@SystemLogAspect

AOP攔截@SystemLog注解

/**
?*?Spring?AOP實現(xiàn)日志管理
?*?@author?Exrickx
?*/

@Aspect
@Component
@Slf4j
public?class?SystemLogAspect?{

????private?static?final?ThreadLocal<Date>?beginTimeThreadLocal?=?new?NamedThreadLocal<Date>("ThreadLocal?beginTime");

????@Autowired
????private?LogService?logService;

????@Autowired
????private?UserService?userService;

????@Autowired(required?=?false)
????private?HttpServletRequest?request;

????/**
?????*?定義切面,只置入帶?@SystemLog?注解的方法或類?
?????*?Controller層切點,注解方式
?????*?@Pointcut("execution(*?*..controller..*Controller*.*(..))")
?????*/

????@Pointcut("@annotation(club.sscai.common.annotation.SystemLog)")
????public?void?controllerAspect()?{

????}

????/**
?????*?前置通知?(在方法執(zhí)行之前返回)用于攔截Controller層記錄用戶的操作的開始時間
?????*?@param?joinPoint?切點
?????*?@throws?InterruptedException
?????*/

????@Before("controllerAspect()")
????public?void?doBefore(JoinPoint?joinPoint)?throws?InterruptedException{

????????##線程綁定變量(該數(shù)據(jù)只有當前請求的線程可見)
????????Date?beginTime=new?Date();
????????beginTimeThreadLocal.set(beginTime);
????}


????/**
?????*?后置通知(在方法執(zhí)行之后并返回數(shù)據(jù))?用于攔截Controller層無異常的操作
?????*?@param?joinPoint?切點
?????*/

????@AfterReturning("controllerAspect()")
????public?void?after(JoinPoint?joinPoint){
????????try?{
????????????String?username?=?"";
????????????String?description?=?getControllerMethodInfo(joinPoint).get("description").toString();
????????????Map<String,?String[]>?logParams?=?request.getParameterMap();
????????????String?principal?=?SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString();
????????????##?判斷允許不用登錄的注解
????????????if("anonymousUser".equals(principal)&&!description.contains("短信登錄")){
????????????????return;
????????????}
????????????if(!"anonymousUser".equals(principal)){
????????????????UserDetails?user?=?(UserDetails)?SecurityContextHolder.getContext().getAuthentication().getPrincipal();
????????????????username?=?user.getUsername();
????????????}
????????????if(description.contains("短信登錄")){
????????????????if(logParams.get("mobile")!=null){
????????????????????String?mobile?=?logParams.get("mobile")[0];
????????????????????username?=?userService.findByMobile(mobile).getUsername()+"("+mobile+")";
????????????????}
????????????}

????????????Log?log?=?new?Log();

????????????##請求用戶
????????????log.setUsername(username);
????????????##日志標題
????????????log.setName(description);
????????????##日志類型
????????????log.setLogType((int)getControllerMethodInfo(joinPoint).get("type"));
????????????##日志請求url
????????????log.setRequestUrl(request.getRequestURI());
????????????##請求方式
????????????log.setRequestType(request.getMethod());
????????????##請求參數(shù)
????????????log.setMapToParams(logParams);
????????????##請求開始時間
????????????Date?logStartTime?=?beginTimeThreadLocal.get();

????????????long?beginTime?=?beginTimeThreadLocal.get().getTime();
????????????long?endTime?=?System.currentTimeMillis();
????????????##請求耗時
????????????Long?logElapsedTime?=?endTime?-?beginTime;
????????????log.setCostTime(logElapsedTime.intValue());

????????????##調(diào)用線程保存至log表
????????????ThreadPoolUtil.getPool().execute(new?SaveSystemLogThread(log,?logService));

????????}?catch?(Exception?e)?{
????????????log.error("AOP后置通知異常",?e);
????????}
????}


????/**
?????*?保存日志至數(shù)據(jù)庫
?????*/

????private?static?class?SaveSystemLogThread?implements?Runnable?{

????????private?Log?log;
????????private?LogService?logService;

????????public?SaveSystemLogThread(Log?esLog,?LogService?logService)?{
????????????this.log?=?esLog;
????????????this.logService?=?logService;
????????}

????????@Override
????????public?void?run()?
{

????????????logService.save(log);
????????}
????}

????/**
?????*?獲取注解中對方法的描述信息?用于Controller層注解
?????*?@param?joinPoint?切點
?????*?@return?方法描述
?????*?@throws?Exception
?????*/

????public?static?Map<String,?Object>?getControllerMethodInfo(JoinPoint?joinPoint)?throws?Exception{

????????Map<String,?Object>?map?=?new?HashMap<String,?Object>(16);
????????##?獲取目標類名
????????String?targetName?=?joinPoint.getTarget().getClass().getName();
????????##?獲取方法名
????????String?methodName?=?joinPoint.getSignature().getName();
????????##?獲取相關參數(shù)
????????Object[]?arguments?=?joinPoint.getArgs();
????????##?生成類對象
????????Class?targetClass?=?Class.forName(targetName);
????????##?獲取該類中的方法
????????Method[]?methods?=?targetClass.getMethods();

????????String?description?=?"";
????????Integer?type?=?null;

????????for(Method?method?:?methods)?{
????????????if(!method.getName().equals(methodName))?{
????????????????continue;
????????????}
????????????Class[]?clazzs?=?method.getParameterTypes();
????????????if(clazzs.length?!=?arguments.length)?{
????????????????##?比較方法中參數(shù)個數(shù)與從切點中獲取的參數(shù)個數(shù)是否相同,原因是方法可以重載
????????????????continue;
????????????}
????????????description?=?method.getAnnotation(SystemLog.class).description();
????????????type?=?method.getAnnotation(SystemLog.class).type().ordinal();
????????????map.put("description",?description);
????????????map.put("type",?type);
????????}
????????return?map;
????}

}

流程補充:

  • 通過 @Pointcut 定義帶有 @SystemLog 注解的方法或類為切入點,可以理解成,攔截所有帶該注解的方法。
  • @Before 前置通知用于記錄請求時的時間
  • @AfterReturning 用于獲取返回值,主要使用 getControllerMethodInfo() 方法,采用類反射機制獲取請求參數(shù),最后調(diào)用 LogService 保存至數(shù)據(jù)庫。
  • 額外補充:

    關于 SecurityContextHolder 的使用為 Spring Security 用于獲取用戶,實現(xiàn)記錄請求用戶的需求,可根據(jù)自己框架情況選擇,如使用 shiro 獲取當前用戶為 SecurityUtils.getSubject().getPrincipal(); 等等。

    如果文章有錯的地方歡迎指正,大家互相留言交流。習慣在微信看技術文章,想要獲取更多的Java資源的同學,可以關注微信公眾號:niceyoo

    轉(zhuǎn)載于:https://www.cnblogs.com/niceyoo/p/10907203.html

    總結

    以上是生活随笔為你收集整理的自定义注解-aop实现日志记录的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。