SpringBoot系列(14): Spring AOP裝逼指南之實現(xiàn)操作日志記錄


作者: 修羅debug
版權聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 by-sa 版權協(xié)議,轉(zhuǎn)載請附上原文出處鏈接和本聲明。

摘要:AOP ,也稱為“面向切面編程”,其大名早已如雷貫耳,是 Spring 框架的核心特性之一,相信各位小伙伴也早已聽聞過,其最普遍的用法是“記錄應用系統(tǒng)業(yè)務模塊的操作日志”,今天我們就來分享介紹一下如何利用Spring AOP實現(xiàn)業(yè)務模塊操作日志的記錄。

內(nèi)容:Spring  AOP,是“面向切面編程”的簡稱,可以起到“解耦業(yè)務模塊”的作用,深層次的作用可以利用網(wǎng)上一位博主所說的一句話進行概括,即:

“AOP 可以實現(xiàn)在不修改源代碼的情況下給程序動態(tài)統(tǒng)一添加功能,而不需要破壞某個操作業(yè)務模塊代碼的完整性”

對于Spring AOP,在這里有必要再啰嗦一番其核心要素,“面向切面編程”,一聽就知道其核心要素是“切面”,而“切面”并非只是單純存在的玩意,它由兩大部分組成,“切點PointCut”+“通知Notice”,下面,讓我用“通俗易懂”的語句表達出來吧:

“切點”:在什么地方觸發(fā)我們自定義的操作(定義的是“何處”)

“通知”:在什么時候執(zhí)行我們自定義的操作,同時定義好“自定義的操作”具體是什么!

“切面”:切點 + 通知;即在什么時候,在哪個地方觸發(fā)執(zhí)行什么樣的操作!

好了,若需要具體的、深層次的關于專業(yè)術語的介紹、剖析,可以上官網(wǎng)或者博客找找相應的介紹吧!下面我們就進入實際的代碼實戰(zhàn)過程!

(1)首先,我們當然需要建立一個“記錄日志的數(shù)據(jù)庫表”,sys_log,其DDL定義如下所示:

CREATE TABLE `sys_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(50) DEFAULT NULL COMMENT '用戶名',
`operation` varchar(50) DEFAULT NULL COMMENT '用戶操作',
`method` varchar(200) DEFAULT NULL COMMENT '請求方法',
`params` varchar(5000) DEFAULT NULL COMMENT '請求參數(shù)',
`time` bigint(20) NOT NULL COMMENT '執(zhí)行時長(毫秒)',
`ip` varchar(64) DEFAULT NULL COMMENT 'IP地址',
`create_date` datetime DEFAULT NULL COMMENT '創(chuàng)建時間',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='系統(tǒng)日志';

然后,采用Mybatis逆向工程生成相應的Entity、Mapper、Mapper.xml,之后便是等待著被調(diào)用!

(2)然后,我們定義一個注解LogAnnotation,這個注解將用于應用系統(tǒng)中“所有需要觸發(fā)記錄日志”的操作模塊對應的方法之上,在后面大家會發(fā)現(xiàn),正是這種方式,才實現(xiàn)了所謂的“服務模塊解耦”、“在不修改源代碼的情況下給程序動態(tài)統(tǒng)一添加功能”的作用。其完整源代碼如下所示:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogAnnotation {
//value將用于存儲 “具體的操作模塊的操作描述”
String value() default "";
}

(3)緊接著,我們需要定義一個重量級的東西,即“切面”,這個切面是基于AspectJ注解實現(xiàn)的類LogAspect,其完整源代碼如下所示:  

/**
* 日志切面 = 切點+通知
* @Author:debug (SteadyJack)
* @Link: weixin-> debug0868 qq-> 1948831260
* @Date: 2019/11/7 15:18
**/
@Aspect
@Component
public class LogAspect {

private static final Logger log= LoggerFactory.getLogger(LogAspect.class);

@Autowired
private SysLogMapper sysLogMapper;

@Autowired
private ObjectMapper objectMapper;

//定義切點:發(fā)生的時機 - 即一旦加了這個注解,將觸發(fā)某些事情
@Pointcut("@annotation(com.debug.springboot.server.annotation.LogAnnotation)")
public void logPointCut(){}

//定義通知:環(huán)繞通知 - 執(zhí)行核心任務之前 + 執(zhí)行完核心任務之后 該做的事情
@Around("logPointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
long start=System.currentTimeMillis();
//TODO:執(zhí)行核心任務
Object object=joinPoint.proceed();
long time=System.currentTimeMillis()-start;

saveLog(joinPoint,time);
return object;
}

//TODO:保存操作日志
private void saveLog(ProceedingJoinPoint point,Long time) throws Exception{
log.info("開始觸發(fā)-保存操作日志");

MethodSignature signature= (MethodSignature) point.getSignature();
Method method=signature.getMethod();

SysLog entity=new SysLog();

//TODO:獲取請求操作的描述信息
LogAnnotation annotation=method.getAnnotation(LogAnnotation.class);
if (annotation!=null){
entity.setOperation(annotation.value());
}

//TODO:獲取操作方法名
String className=point.getTarget().getClass().getName();
String methodName=signature.getName();
entity.setMethod(className + "." + methodName + "()");

//TODO:獲取請求參數(shù)
Object[] args=point.getArgs();
entity.setParams(objectMapper.writeValueAsString(args[0]));

//TODO:獲取剩余參數(shù)
entity.setTime(time);
entity.setCreateDate(DateTime.now().toDate());
entity.setUsername("debug");
entity.setIp("localhost");
sysLogMapper.insertSelective(entity);
}
}

 (3)最后,我們在MultipartSourceController 中寫個請求方法,并在該請求方法上加入日志注解,用于記錄該請求方法在執(zhí)行前、后、執(zhí)行中的軌跡和相應的參數(shù)。完整的源代碼如下所示:  

@LogAnnotation("多數(shù)據(jù)源時-跨事務-日志操作")
@RequestMapping(value = "update",method = RequestMethod.POST,consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
public BaseResponse update(@RequestBody LogUpdateDto dto) throws Exception{
BaseResponse response=new BaseResponse(StatusCode.Success);

User user=new User();
user.setName(dto.getName());
user.setCode(dto.getCode());
userMapper.insertSelective(user);

return response;
}

而LogUpdateDto只是一個簡單的實體類:  

/**
* @Author:debug (SteadyJack)
* @Link: weixin-> debug0868 qq-> 1948831260
* @Date: 2019/11/7 16:14
**/
@Data
public class LogUpdateDto implements Serializable{

@NotBlank
private String name;

@NotBlank
private String code;
}

(4)最后,我們將整個系統(tǒng)跑起來,然后在Postman發(fā)起相應的請求,請求完成后,可以看到數(shù)據(jù)庫表sys_log中記錄了相應的請求軌跡以及相應的請求參數(shù)等等,如下圖所示:


好了,本篇文章我們就介紹到這里了,其他相關的技術,感興趣的小伙伴可以關注底部Debug的技術公眾號,或者加Debug的微信,拉你進“微信版”的真正技術交流群!一起學習、共同成長!

補充

1、本文涉及到的相關的源代碼可以到此地址,check出來進行查看學習:

https://gitee.com/steadyjack/SpringBootTechnology

2、目前Debug已將本文所涉及的內(nèi)容整理錄制成視頻教程,感興趣的小伙伴可以前往觀看學習:https://www.fightjava.com/web/index/course/detail/5

3、關注一下Debug的技術微信公眾號,最新的技術文章、課程以及技術專欄將會第一時間在公眾號發(fā)布哦!