Spring Boot 2.X(八):Spring AOP 實現簡單的日誌切面

AOP

1.什麼是 AOP ?

AOP 的全稱爲 Aspect Oriented Programming,譯爲面向切面編程,是經過預編譯方式和運行期動態代理實現核心業務邏輯以外的橫切行爲的統一維護的一種技術。AOP 是面向對象編程(OOP)的補充和擴展。 利用 AOP 能夠對業務邏輯各部分進行隔離,從而達到下降模塊之間的耦合度,並將那些影響多個類的公共行爲封裝到一個可重用模塊,從而到達提升程序的複用性,同時提升了開發效率,提升了系統的可操做性和可維護性。java

2.爲何要用 AOP ?

在實際的 Web 項目開發中,咱們經常須要對各個層面實現日誌記錄,性能統計,安全控制,事務處理,異常處理等等功能。若是咱們對每一個層面的每一個類都獨立編寫這部分代碼,那長此以往代碼將變得很難維護,因此咱們把這些功能從業務邏輯代碼中分離出來,聚合在一塊兒維護,並且咱們能靈活地選擇何處須要使用這些代碼。git

3.AOP 的核心概念

名詞 概念 理解
通知(Advice) 攔截到鏈接點以後所要執行的代碼,通知分爲前置、後置、異常、最終、環繞通知五類 咱們要實現的功能,如日誌記錄,性能統計,安全控制,事務處理,異常處理等等,說明何時要幹什麼
鏈接點(Joint Point) 被攔截到的點,如被攔截的方法、對類成員的訪問以及異常處理程序塊的執行等等,自身還能嵌套其餘的 Joint Point Spring 容許你用通知的地方,方法有關的前先後後(包括拋出異常)
切入點(Pointcut) 對鏈接點進行攔截的定義 指定通知到哪一個方法,說明在哪幹
切面(Aspect) 切面類的定義,裏面包含了切入點(Pointcut)和通知(Advice)的定義 切面就是通知和切入點的結合
目標對象(Target Object) 切入點選擇的對象,也就是須要被通知的對象;因爲 Spring AOP 經過代理模式實現,因此該對象永遠是被代理對象 業務邏輯自己
織入(Weaving) 把切面應用到目標對象從而建立出 AOP 代理對象的過程。織入能夠在編譯期、類裝載期、運行期進行,而 Spring 採用在運行期完成 切點定義了哪些鏈接點會獲得通知
引入(Introduction ) 能夠在運行期爲類動態添加方法和字段,Spring 容許引入新的接口到全部目標對象 引入就是在一個接口/類的基礎上引入新的接口加強功能
AOP 代理(AOP Proxy ) Spring AOP 可使用 JDK 動態代理或者 CGLIB 代理,前者基於接口,後者基於類 經過代理來對目標對象應用切面

Spring AOP

1.簡介

AOP 是 Spring 框架中的一個核心內容。在 Spring 中,AOP 代理能夠用 JDK 動態代理或者 CGLIB 代理 CglibAopProxy 實現。Spring 中 AOP 代理由 Spring 的 IOC 容器負責生成和管理,其依賴關係也由 IOC 容器負責管理。github

2.相關注解

註解 說明
@Aspect 將一個 java 類定義爲切面類
@Pointcut 定義一個切入點,能夠是一個規則表達式,好比下例中某個 package 下的全部函數,也能夠是一個註解等
@Before 在切入點開始處切入內容
@After 在切入點結尾處切入內容
@AfterReturning 在切入點 return 內容以後處理邏輯
@Around 在切入點先後切入內容,並本身控制什麼時候執行切入點自身的內容
@AfterThrowing 用來處理當切入內容部分拋出異常以後的處理邏輯
@Order(100) AOP 切面執行順序, @Before 數值越小越先執行,@After 和 @AfterReturning 數值越大越先執行

其中 @Before、@After、@AfterReturning、@Around、@AfterThrowing 都屬於通知(Advice)。web

利用 AOP 實現 Web 日誌處理

1.構建項目

2.添加依賴

<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<!-- 熱部署模塊 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<optional>true</optional> <!-- 這個須要爲 true 熱部署纔有效 -->
		</dependency>
		<!-- Spring AOP -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
	</dependencies>
複製代碼

3.Web 日誌註解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ControllerWebLog {
	 String name();//所調用接口的名稱
     boolean intoDb() default false;//標識該條操做日誌是否須要持久化存儲
}

複製代碼

4.實現切面邏輯

@Aspect
@Component
@Order(100)
public class WebLogAspect {

	private static final Logger logger = LoggerFactory.getLogger(WebLogAspect.class);

	private ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal<Map<String, Object>>();

	/** * 橫切點 */
	@Pointcut("execution(public * cn.zwqh.springboot.controller..*.*(..))")
	public void webLog() {
	}
	/** * 接收請求,並記錄數據 * @param joinPoint * @param controllerWebLog */
	@Before(value = "webLog()&& @annotation(controllerWebLog)")
	public void doBefore(JoinPoint joinPoint, ControllerWebLog controllerWebLog) {
		// 接收到請求
		RequestAttributes ra = RequestContextHolder.getRequestAttributes();
		ServletRequestAttributes sra = (ServletRequestAttributes) ra;
		HttpServletRequest request = sra.getRequest();
		// 記錄請求內容,threadInfo存儲全部內容
		Map<String, Object> threadInfo = new HashMap<>();
		logger.info("URL : " + request.getRequestURL());
		threadInfo.put("url", request.getRequestURL());
		logger.info("URI : " + request.getRequestURI());
		threadInfo.put("uri", request.getRequestURI());
		logger.info("HTTP_METHOD : " + request.getMethod());
		threadInfo.put("httpMethod", request.getMethod());
		logger.info("REMOTE_ADDR : " + request.getRemoteAddr());
		threadInfo.put("ip", request.getRemoteAddr());
		logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "."
				+ joinPoint.getSignature().getName());
		threadInfo.put("classMethod",
				joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
		logger.info("ARGS : " + Arrays.toString(joinPoint.getArgs()));
		threadInfo.put("args", Arrays.toString(joinPoint.getArgs()));
		logger.info("USER_AGENT"+request.getHeader("User-Agent"));
		threadInfo.put("userAgent", request.getHeader("User-Agent"));
		logger.info("執行方法:" + controllerWebLog.name());
		threadInfo.put("methodName", controllerWebLog.name());
		threadLocal.set(threadInfo);
	}
	/** * 執行成功後處理 * @param controllerWebLog * @param ret * @throws Throwable */
	@AfterReturning(value = "webLog()&& @annotation(controllerWebLog)", returning = "ret")
	public void doAfterReturning(ControllerWebLog controllerWebLog, Object ret) throws Throwable {
		Map<String, Object> threadInfo = threadLocal.get();
		threadInfo.put("result", ret);
		if (controllerWebLog.intoDb()) {
			//插入數據庫操做
			//insertResult(threadInfo);
		}
		// 處理完請求,返回內容
		logger.info("RESPONSE : " + ret);
	}
	/** * 獲取執行時間 * @param proceedingJoinPoint * @return * @throws Throwable */
	@Around(value = "webLog()")
	public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
		long startTime = System.currentTimeMillis();
		Object ob = proceedingJoinPoint.proceed();
		Map<String, Object> threadInfo = threadLocal.get();
		Long takeTime = System.currentTimeMillis() - startTime;
		threadInfo.put("takeTime", takeTime);
		logger.info("耗時:" + takeTime);
		threadLocal.set(threadInfo);
		return ob;
	}
	/** * 異常處理 * @param throwable */
	@AfterThrowing(value = "webLog()", throwing = "throwable")
	public void doAfterThrowing(Throwable throwable) {
		RequestAttributes ra = RequestContextHolder.getRequestAttributes();

		ServletRequestAttributes sra = (ServletRequestAttributes) ra;

		HttpServletRequest request = sra.getRequest();
		// 異常信息
		logger.error("{}接口調用異常,異常信息{}", request.getRequestURI(), throwable);
	}

}

複製代碼

5.測試接口

@RestController
@RequestMapping("/user")
public class UserController {

	@GetMapping("/getOne")
	@ControllerWebLog(name = "查詢", intoDb = true)
	public String getOne(Long id, String name) {

		return "1234";
	}
}
複製代碼

6.運行測試

瀏覽器請求:http://127.0.0.1:8080/user/getOne?id=1&name=zwqh ,能夠看到後臺日誌輸出: spring

小結

日誌記錄只是一個簡單的示例,而 Spring AOP 的應用讓整個系統變的更加有條不紊,在其餘場景應用也很強大。 它幫助咱們下降模塊間耦合度,提升程序複用性,提升開發效率,提升系統可作性和可維護性。數據庫

示例代碼

github編程

碼雲瀏覽器

非特殊說明,本文版權歸 朝霧輕寒 全部,轉載請註明出處.安全

原文標題:Spring Boot 2.X(八):Spring AOP 實現簡單的日誌切面springboot

原文地址: https://www.zwqh.top/article/info/14

若是文章對您有幫助,請掃碼關注下個人公衆號,文章持續更新中...

相關文章
相關標籤/搜索