自定义注解-aop实现日志记录
關于注解,平時接觸的可不少,像是 @Controller、@Service、@Autowried 等等,不知道你是否有過這種疑惑,使用 @Service 注解的類成為我們的業務類,使用 @Controller 注解的類就成了請求的控制器,使用 @Autowried 注解的類就會幫我們實現自動注入…
以前,我們只知道使用注解,今天我們要手寫一個注解。
一、以日志記錄為例
在沒有使用注解實現記錄日志之前,我們往往自己去調用日志記錄的 Service,然后寫入數據庫表。
今天我們將從方法上添加自定義注解實現日志自動記錄,如下:
52e77c79b07d49c6554ff2a0185d7f02.png二、了解關于注解知識
JDK 提供了 meta-annotation 用于自定義注解的時候使用,這四個注解為:@Target,@Retention,@Documented 和 @Inherited。
以 @Controller 為例,其源碼也是如此:
fc6b30adb15c78f562e0de8e503e6881.png我們來看一下上邊提到的四個注解:
| @Target | 用于描述注解的使用范圍,即:被描述的注解可以用在什么地方 |
| @Retention | 指定被描述的注解在什么范圍內有效 |
| @Documented | 是一個標記注解,木有成員,用于描述其它類型的annotation 應該被作為被標注的程序成員的公共 API,因此可以被例如javadoc此類的工具文檔化 |
| @Inherited | 元注解是一個標記注解,@Inherited 闡述了某個被標注的類型是被繼承的。如果一個使用了 @Inherited 修飾的 annotation 類型被用于一個 class,則這個 annotation 將被用于該class的子類 |
三、開始我們的自定義注解
兩個類:
SystemLog:自定義注解類,用于標記到方法、類上,如@SystemLog
SystemLogAspect:AOP實現切點攔截。
關于AOP的補充:
關于AOP面向切面編程概念啥的就不啰嗦了,還不了解的可以自定百度了
描述AOP常用的一些術語有:
通知(Adivce)、連接點(Join point)、切點(Pointcut)、切面(Aspect)、引入(Introduction)、織入(Weaving)
關于術語的部分可參考:https://www.cnblogs.com/niceyoo/p/10162077.html
需要明確的核心概念:切面 = 切點 + 通知。
@Aspect 注解形式是 AOP 的一種實現,如下看一下我們要寫的兩個類吧。
1、@SystemLog
定義我們的自定義注解類
/**?*?系統日志自定義注解
?*/
@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實現日志管理
?*?@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()?{
????}
????/**
?????*?前置通知?(在方法執行之前返回)用于攔截Controller層記錄用戶的操作的開始時間
?????*?@param?joinPoint?切點
?????*?@throws?InterruptedException
?????*/
????@Before("controllerAspect()")
????public?void?doBefore(JoinPoint?joinPoint)?throws?InterruptedException{
????????
????????Date?beginTime=new?Date();
????????beginTimeThreadLocal.set(beginTime);
????}
????/**
?????*?后置通知(在方法執行之后并返回數據)?用于攔截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"));
????????????
????????????log.setRequestUrl(request.getRequestURI());
????????????
????????????log.setRequestType(request.getMethod());
????????????
????????????log.setMapToParams(logParams);
????????????
????????????Date?logStartTime?=?beginTimeThreadLocal.get();
????????????long?beginTime?=?beginTimeThreadLocal.get().getTime();
????????????long?endTime?=?System.currentTimeMillis();
????????????
????????????Long?logElapsedTime?=?endTime?-?beginTime;
????????????log.setCostTime(logElapsedTime.intValue());
????????????
????????????ThreadPoolUtil.getPool().execute(new?SaveSystemLogThread(log,?logService));
????????}?catch?(Exception?e)?{
????????????log.error("AOP后置通知異常",?e);
????????}
????}
????/**
?????*?保存日志至數據庫
?????*/
????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();
????????
????????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)?{
????????????????
????????????????continue;
????????????}
????????????description?=?method.getAnnotation(SystemLog.class).description();
????????????type?=?method.getAnnotation(SystemLog.class).type().ordinal();
????????????map.put("description",?description);
????????????map.put("type",?type);
????????}
????????return?map;
????}
}
流程補充:
額外補充:
關于 SecurityContextHolder 的使用為 Spring Security 用于獲取用戶,實現記錄請求用戶的需求,可根據自己框架情況選擇,如使用 shiro 獲取當前用戶為 SecurityUtils.getSubject().getPrincipal(); 等等。
如果文章有錯的地方歡迎指正,大家互相留言交流。習慣在微信看技術文章,想要獲取更多的Java資源的同學,可以關注微信公眾號:niceyoo
轉載于:https://www.cnblogs.com/niceyoo/p/10907203.html
總結
以上是生活随笔為你收集整理的自定义注解-aop实现日志记录的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python利用unittest进行测试
- 下一篇: 全网最新 Skywalking 6.1.