零、系列
歡迎來嫖從零開始SpringCloud Alibaba電商系列:
- 從零開始SpringCloud Alibaba電商系統(一)——Alibaba與Nacos服務註冊與發現
- 從零開始SpringCloud Alibaba電商系統(二)——Nacos配置中心
- 從零開始SpringCloud Alibaba電商系統(三)——Sentinel流量防衛兵介紹、流量控制demo
- 從零開始SpringCloud Alibaba電商系統(四)——Sentinel的fallback和blockHandler
- 從零開始SpringCloud Alibaba電商系統(五)——Feign Demo,Sentinel+Feign實現多節點間熔斷/服務降級
- 從零開始SpringCloud Alibaba電商系統(六)——Sentinel規則持久化到Nacos配置中心
- 從零開始SpringCloud Alibaba電商系統(七)——Spring Security實現登錄認證、權限控制
- 從零開始SpringCloud Alibaba電商系統(八)——用一個好看的Swagger接口文檔
- 從零開始SpringCloud Alibaba電商系統(九)——基於Spring Security OAuth2實現SSO-認證服務器(非JWT)
- 從零開始SpringCloud Alibaba電商系統(十)——基於Redis Session的認證鑑權
- 從零開始SpringCloud Alibaba電商系統(十一)——spring security完善之動態url控制
一、需求簡述
日誌在任何一個系統中都是必不可少的,用戶訪問日誌/操作日誌無論在前臺還是後臺管理都很重要,比如前臺的用戶點擊行爲作爲特徵去做推薦系統,後臺管理的找兇手。
一般來說,用戶訪問日誌可以在網關(nginx或其他)這一層就可以記錄下,但是如果想更多的記錄一些業務相關的,還是需要放到我們的邏輯層來。
今天我們就將這些Controller的訪問日誌借用AOP統一記錄起來。
二、Spring AOP
AOP相信大家多多少少都瞭解,俗稱面向切面編程的化身、OOP的好伴侶、動態代理的踐行者、Spring的大褲衩子等等。 但是這個東西究竟有多少用用過,這又是一個神奇的話題。
閒話少說,Spring AOP是對AOP概念的一個實現,並非一模一樣的實現,所以我們今天只關注Spring AOP怎麼實現業務。
- @Aspect
aspectj組織提供的註解,用於標識這是一個切面。Spring中將其實現爲一個切面類
,在這個類裏面可以去定義PointCut、Advice等。 - @PointCut
切點的規則,即哪些方法是我們要切入的。
PointCut原意是可以切入方法、字段等任何東西
的前後,但是在Spring中,它只能切入一個方法的執行前、後。
我們在這裏,也只需要它來切入Controller所有的方法。 - Advice、@After、@Before
通知是對PointCut的具體處理邏輯,即我們用PointCut規定了一些要被攔截處理的方法
,Advice是對他具體的處理邏輯。
@Befor、@After、@Around等就是Advice,分別代表在PointCut前處理的邏輯、在PointCut之後處理的邏輯、前後都處理的邏輯。 - JointPoint
JointPoint爲不以註解的方式出現,而是在Advice中作爲參數,即當前被Advice攔截處理的是哪一個具體的方法。
三、實現
-
在上一章節demo(或一個完善的springboot/cloud項目)的基礎上,增加一個aspect包,增加一個SystemLogAspect類。
這個類需要加@Aspect註解。 -
聲明一個pointcut。
我在這裏直接攔截了所有的controller下面的方法,將它們作爲攔截點。
@pointCut的攔截規則建議直接到spring官網去查:
https://docs.spring.io/spring/docs/5.3.0-SNAPSHOT/spring-framework-reference/core.html#spring-core
@Pointcut("within(org.lele.*.controller..*)")
private void logPointCut(){}
- 根據pointcut定義Advice,我這裏分別定義了@Befer、@AfterReturn(帶返回值的after)、@AfterThrowing(拋異常會調用到的after)。
注意,jointPoint在這裏就是這些Advice的參數,它實質上代表的就是被訪問的那個方法。
@Before("logPointCut()")
public void before(JoinPoint jp) {
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
UserDTO userDTO = sessionUtils.getCurrentUser();
System.out.println( (jp.getTarget().getClass().getName() + "." + jp.getSignature().getName() + "()——請求信息:") );
System.out.println("請求地址:" + request.getRemoteAddr());
System.out.println("請求人:" + userDTO.toString() );
System.out.println("請求方法:" + (jp.getTarget().getClass().getName() + "." + jp.getSignature().getName() + "()"));
System.out.println("請求參數:" + (jp.getArgs().toString()));
}
@AfterReturning(value = "logPointCut()",returning = "result")
public void afterReturning(JoinPoint jp,Object result) {
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
UserDTO userDTO = sessionUtils.getCurrentUser();
System.out.println( (jp.getTarget().getClass().getName() + "." + jp.getSignature().getName() + "()——返回結果:") );
System.out.println("請求地址:" + request.getRemoteAddr());
System.out.println("請求人:" + userDTO.toString() );
System.out.println("返回數據:" + JSONObject.toJSONString(result) );
}
@AfterThrowing(value = "logPointCut()",throwing = "e")
public void afterThrowing(JoinPoint jp,Throwable e) {
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
UserDTO userDTO = sessionUtils.getCurrentUser();
System.out.println( (jp.getTarget().getClass().getName() + "." + jp.getSignature().getName() + "()——異常結果:") );
System.out.println("請求地址:" + request.getRemoteAddr());
System.out.println("請求人:" + userDTO.toString() );
System.out.println("異常信息:" + e.getMessage() );
}
sessionUtils是通過spring security容器獲取當前session的工具類,需要的話可以到下面demo中拿。
- 啓動項目,隨意訪問controller下面的一個方法,可以看到控制檯打印日誌。
四、demo地址
這一次並沒有將日誌保存起來,是因爲筆者想要將這些信息寫入到es中,同時其他日誌也寫入es中,配合kibana,做一個方便的可視化,這個下期在寫。
完整代碼:
https://github.com/flyChineseBoy/lel-mall/tree/master/mall12