Spring Boot AOP 應用場景

1. 前言

Spring 最重要的兩個功能,就是依賴注入(DI)和面向切面編程 (AOP)。

AOP 為我們提供了處理問題的全局化視角,使用得當可以極大提高編程效率。

Spring Boot 中使用 AOP 與 Spring 中使用 AOP 幾乎沒有什么區別,只是建議盡量使用 Java 配置代替 XML 配置。

本節就來演示下 Spring Boot 中使用 AOP 的常見應用場景。

2. 構建項目

首先我們需要構建一個 Spring Boot 項目并引入 AOP 依賴,后續場景演示均是在這個項目上實現的。

2.1 使用 Spring Initializr 創建項目

Spring Boot 版本選擇 2.2.5 ,Group 為 com.5axxw , Artifact 為 spring-boot-aop,生成項目后導入 Eclipse 開發環境。

2.2 引入項目依賴

我們引入 Web 項目依賴與 AOP 依賴。

實例:

		<!-- Web項目依賴 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!-- AOP -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>

2.3 新建控制層、服務層、數據訪問層

為了便于后續的演示,我們依次新建控制類、服務類、數據訪問類,并將其放入對應的包中,項目結構如下:

圖片描述

項目結構

各個類代碼如下,注意此處僅僅是為了演示 AOP 的使用,并未真實訪問數據庫,而是直接返回了測試數據。

實例:

/**
 * 商品控制器類
 */
@RestController
public class GoodsController {
	@Autowired
	private GoodsService goodsService;

	/**
	 * 獲取商品列表
	 */
	@GetMapping("/goods")
	public List getList() {
		return goodsService.getList();
	}
}

實例:

/**
 * 商品服務類
 */
@Service
public class GoodsService {
	@Autowired
	private GoodsDao goodsDao;

	/**
	 * 獲取商品信息列表
	 */
	public List getList() {
		return goodsDao.getList();
	}
}

實例:

/**
 * 商品數據庫訪問類
 */
@Repository // 標注數據訪問類
public class GoodsDao {
	/**
	 * 查詢商品列表
	 */
	public List getList() {
		return new ArrayList();
	}
}

3. 使用 AOP 記錄日志

如果要記錄對控制器接口的訪問日志,可以定義一個切面,切入點即為控制器中的接口方法,然后通過前置通知來打印日志。

實例:

/**
 * 日志切面
 */
@Component
@Aspect // 標注為切面
public class LogAspect {
	private Logger logger = LoggerFactory.getLogger(this.getClass());

	// 切入點表達式,表示切入點為控制器包中的所有方法
	@Pointcut("within(com.5axxw.springbootaop.controller..*)")
	public void LogAspect() {
	}

	// 切入點之前執行
	@Before("LogAspect()")
	public void doBefore(JoinPoint joinPoint) {
		logger.info("訪問時間:{}--訪問接口:{}", new Date(), joinPoint.getSignature());
	}
}

啟動項目后,訪問控制器中的方法之前會先執行 doBefore 方法??刂婆_打印如下:

2020-05-25 22:14:12.317  INFO 9992 --- [nio-8080-exec-2] com.5axxw.springbootaop.LogAspect        : 
訪問時間:Mon May 25 22:14:12 CST 2020--訪問接口:List com.5axxw.springbootaop.controller.GoodsController.getList()

4. 使用 AOP 監控性能

在研發項目的性能測試階段,或者項目部署后,我們會希望查看服務層方法執行的時間。以便精準的了解項目中哪些服務方法執行速度慢,后續可以針對性的進行性能優化。

此時我們就可以使用 AOP 的環繞通知,監控服務方法的執行時間。

實例:

/**
 * 服務層方法切面
 */
@Component
@Aspect // 標注為切面
public class ServiceAspect {
	private Logger logger = LoggerFactory.getLogger(this.getClass());

	// 切入點表達式,表示切入點為服務層包中的所有方法
	@Pointcut("within(com.5axxw.springbootaop.service..*)")
	public void ServiceAspect() {
	}

	@Around("ServiceAspect()") // 環繞通知
	public Object deAround(ProceedingJoinPoint joinPoint) throws Throwable {
		long startTime = System.currentTimeMillis();// 記錄開始時間
		Object result = joinPoint.proceed();
		logger.info("服務層方法:{}--執行時間:{}毫秒", joinPoint.getSignature(), System.currentTimeMillis() - startTime);
		return result;
	}
}

當服務層方法被調用時,控制臺輸入日志如下:

2020-05-25 22:25:56.830  INFO 4800 --- [nio-8080-exec-1] com.5axxw.springbootaop.ServiceAspect    : 
服務層方法:List com.5axxw.springbootaop.service.GoodsService.getList()--執行時間:3毫秒

Tips:正常情況下,用戶查看頁面或進行更新操作時,耗時超過 1.5 秒,就會感覺到明顯的遲滯感。由于前后端交互也需要耗時,按正態分布的話,大部分交互耗時在 0.4 秒 左右。所以在我參與的項目中,會對耗時超過 1.1 秒的服務層方法進行跟蹤分析,通過優化 SQL 語句、優化算法、添加緩存等方式縮短方法執行時間。上面的數值均為我個人的經驗參考值,還要視乎具體的服務器、網絡、應用場景來確定合理的監控臨界值。

5. 使用 AOP 統一后端返回值格式

前后端分離的項目結構中,前端通過 Ajax 請求后端接口,此時最好使用統一的返回值格式供前端處理。此處就可以借助 AOP 來實現正常情況、異常情況返回值的格式統一。

5.1 定義返回值類

首先定義返回值類,它屬于業務邏輯對象 (Bussiness Object),所以此處命名為 ResultBo ,代碼如下:

實例:

public class ResultBo<T> {
	/**
	 * 錯誤碼 0表示沒有錯誤(異常) 其他數字代表具體錯誤碼
	 */
	private int code;
	/**
	 * 后端返回消息
	 */
	private String msg;
	/**
	 * 后端返回的數據
	 */
	private T data;
	/**
	 * 無參數構造函數
	 */
	public ResultBo() {
		this.code = 0;
		this.msg = "操作成功";
	}
	/**
	 * 帶數據data構造函數
	 */
	public ResultBo(T data) {
		this();
		this.data = data;
	}
	/**
	 * 存在異常的構造函數
	 */
	public ResultBo(Exception ex) {
		this.code = 99999;// 其他未定義異常
		this.msg = ex.getMessage();
	}
	// 省略 get set
}

5.2 修改控制層返回值類型

對所有的控制層方法進行修改,保證返回值均通過 ResultBo 包裝,另外我們再定義一個方法,模擬拋出異常的控制層方法。

實例:

	/**
	 * 獲取商品列表
	 */
	@GetMapping("/goods")
	public ResultBo getList() {
		return new ResultBo(goodsService.getList());
	}
	/**
	 * 模擬拋出異常的方法
	 */
	@GetMapping("/test")
	public ResultBo test() {
		int a = 1 / 0;
		return new ResultBo(goodsService.getList());
	}

5.3 定義切面處理異常返回值

正??刂茖臃椒ǘ挤祷?ResultBo 類型對象,然后我們需要定義切面,處理控制層拋出的異常。當發生異常時,同樣返回 ResultBo 類型的對象,并且對象中包含異常信息。

實例:

/**
 * 返回值切面
 */
@Component
@Aspect
public class ResultAspect {
	// 切入點表達式,表示切入點為返回類型ResultBo的所有方法
	@Pointcut("execution(public com.5axxw.springbootaop.ResultBo *(..))")
	public void ResultAspect() {
	}

	// 環繞通知
	@Around("ResultAspect()")
	public Object deAround(ProceedingJoinPoint joinPoint) throws Throwable {
		try {
			return joinPoint.proceed();// 返回正常結果
		} catch (Exception ex) {
			return new ResultBo<>(ex);// 被切入的方法執行異常時,返回ResultBo
		}
	}
}

5.4 測試

啟動項目,訪問 http://127.0.0.1:8080/goods 返回數據如下:

實例:

{"code":0,"msg":"操作成功","data":[]}

然后訪問 http://127.0.0.1:8080/goods ,返回數據如下:

實例:

{"code":99999,"msg":"/ by zero","data":null}

這樣,前端可以根據返回值的 code, 來判斷后端是否正常響應。如果 code 為 0 ,則進行正常業務邏輯操作;如果 code 非 0 ,則可以彈窗顯示 msg 提示信息。

6. 小結

AOP 之所以如此重要,在于它提供了解決問題的新視角。通過將業務邏輯抽象出切面,功能代碼可以切入指定位置,從而消除重復的模板代碼。

使用 AOP 有一種掌握全局的快感,發現業務邏輯中的切面頗有一番趣味,希望大家都能多多體會,編程且快樂著應該是我輩的追求。

主站蜘蛛池模板: 一区二区三区福利视频| 日本福利一区二区| 成人久久精品一区二区三区| 亚洲无人区一区二区三区| 国产一区二区三区高清视频| 中文字幕一区二区三区精华液| 亚洲一区二区三区高清视频| 亚洲熟妇无码一区二区三区| 亚洲综合一区二区国产精品| 痴汉中文字幕视频一区| 国产一区在线电影| 午夜DV内射一区区| 亚洲熟妇av一区二区三区| 国内精品一区二区三区最新| 亚洲av成人一区二区三区| 一区二区高清在线| 韩国美女vip福利一区| 国产情侣一区二区三区| 国产精品成人免费一区二区 | 国产日韩一区二区三区在线观看| 国产成人无码精品一区在线观看| 无码人妻一区二区三区av| 精品一区二区三区四区在线| 农村乱人伦一区二区| 无码人妻精品一区二区三区久久久| 亚洲日本久久一区二区va| 秋霞鲁丝片一区二区三区| 无码午夜人妻一区二区三区不卡视频 | 中文无码精品一区二区三区 | 女女同性一区二区三区四区| 国产一区二区高清在线播放| 色噜噜狠狠一区二区三区| 亚洲国产精品一区二区成人片国内 | 亚洲av日韩综合一区二区三区| 成人精品一区二区三区中文字幕| 精品一区二区三区在线观看视频| 亚洲Av高清一区二区三区| 久久久久国产一区二区| 国产精品成人99一区无码| 欲色影视天天一区二区三区色香欲| 国产91精品一区|