1. 程式人生 > >Spring Boot 學習之路——4 AOP註解方式實現列印日誌

Spring Boot 學習之路——4 AOP註解方式實現列印日誌

前言:

據XX統計,四分之一的程式碼都是日誌有關,日誌對於定位和解決問題尤為重要,以前公司的編碼規範中要求介面必須在日誌中記錄入參和返回值以及關鍵程式碼,引數部分完全可以用Spring的AOP——面向切面來實現。

什麼叫AOP?

百度:AOP(Aspect Oriented Programming),意為:面向切面程式設計,通過預編譯方式和執行期動態代理實現程式功能的統一維護的一種技術。AOP是OOP的延續,是軟體開發中的一個熱點,也是Spring框架中的一個重要內容,是函數語言程式設計的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。


AspectJ是AOP的一個很悠久的實現,它能夠和 Java 配合起來使用。

Aspect: Aspect 宣告類似於 Java 中的類宣告,在 Aspect 中會包含著一些 Pointcut 以及相應的 Advice。Joint point:表示在程式中明確定義的點,典型的包括方法呼叫,對類成員的訪問以及異常處理程式塊的執行等等,它自身還可以巢狀其它 joint point。Pointcut:表示一組 joint point,這些 joint point 或是通過邏輯關係組合起來,或是通過通配、正則表示式等方式集中起來,它定義了相應的 Advice 將要發生的地方。Advice:Advice 定義了在 pointcut 裡面定義的程式點具體要做的操作,它通過 before、after 和 around 來區別是在每個 joint point 之前、之後還是代替執行的程式碼。

簡單介紹完概念,上程式碼。

1.定義日誌註解類

package com.joanna.annotationdemo.demo.annotation;


import java.lang.annotation.*;

/**
 * 監聽每個方法傳入的引數、返回值,列印日誌
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface LoggerProfile {
    // 方法註釋
String methodNote();
}

2.定義日誌處理的切面類

package com.joanna.annotationdemo.demo.annotation;

import 
com.alibaba.fastjson.JSON; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; /** * 使用AOP對方法進行日誌記錄 */ @Aspect//表示這是一個切面類 @Component//加入IOC容器 public class LoggerProfilerIntercepter { private static final Logger LOGGER = LoggerFactory.getLogger("LoggerProfilerDemo"); // 用@PointCut註解統一宣告,然後在其它通知中引用該統一宣告即可! @Pointcut("@annotation(com.joanna.annotationdemo.demo.annotation.LoggerProfile)") public void loggerProfilePointCut() { } // 環繞通知@Around 修飾的方法一定要將方法的返回值返回!本身相當於代理! @Around("loggerProfilePointCut()") public Object doLoggerProfiler(final ProceedingJoinPoint joinPoint) throws Throwable { Map<String, Object> logMap = new HashMap<String, Object>(); // part 1 獲取目標方法名、引數等 try { if (LOGGER.isInfoEnabled()) { String className = joinPoint.getTarget().getClass().getName(); logMap.put("className", className); String methodName = joinPoint.getSignature().getName(); logMap.put("methodName", methodName); Method method = getMethod(joinPoint); LoggerProfile loggerProfile = method.getAnnotation(LoggerProfile.class); String methodNote = loggerProfile.methodNote(); if (null != methodNote && methodNote.length() > 0) { logMap.put("note", methodNote); } Object[] args = joinPoint.getArgs(); if (null != args && args.length > 0) { for (int i = 0; i < args.length; i++) { logMap.put("arg-" + i, args[i]); } } } } catch (Exception e) { LOGGER.error("LoggerProfile Part 1 Exception ", e); } // part 2 執行目標方法 Object obj = null; Exception error = null; try { obj = joinPoint.proceed(); } catch (Exception e) { error = e; } // part 3 獲取返回值 try { if (LOGGER.isInfoEnabled()) { if (null != error) { logMap.put("error", error); } else { if (null != obj) { logMap.put("result", obj); } } LOGGER.info(JSON.toJSONString(logMap)); } } catch (Exception e) { LOGGER.error("LoggerProfile Part 2 Exception ", e); } return obj; } /** * 獲取當前方法 * * @param joinPoint * @return * @throws NoSuchMethodException */ private Method getMethod(final JoinPoint joinPoint) throws NoSuchMethodException { final Signature sig = joinPoint.getSignature(); if (!(sig instanceof MethodSignature)) { throw new NoSuchMethodException( "This annotation is only valid on a method."); } final MethodSignature msig = (MethodSignature) sig; final Object target = joinPoint.getTarget(); return target.getClass().getMethod(msig.getName(), msig.getParameterTypes()); } }

3.新增依賴:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.0.5.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.8</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.8</version>
</dependency>

4.在controller方法上添加註解

@LoggerProfile(methodNote = "humanSays")
@RequestMapping(value = "/humanSays", method = RequestMethod.GET)
public String humanSays(@RequestParam(name = "name", required = false) String name,
                        @RequestParam(name = "comeFrom", required = false) String comeFrom) {
    return "Hello " + name + ", who comes from " + comeFrom + ", I'm a human , hahaha";
}

5.測試

啟動專案,訪問:http://localhost:8080/sayHello/humanSays?name=Joanna&comeFrom=China

可以看到console中列印: