Spring Boot之面向切面編程:Spring AOP

前言

來(lái)啦老鐵乾翔!

筆者學(xué)習(xí)Spring Boot有一段時(shí)間了,附上Spring Boot系列學(xué)習(xí)文章箍镜,歡迎取閱鱼鸠、賜教:

  1. 5分鐘入手Spring Boot;
  2. Spring Boot數(shù)據(jù)庫(kù)交互之Spring Data JPA;
  3. Spring Boot數(shù)據(jù)庫(kù)交互之Mybatis;
  4. Spring Boot視圖技術(shù);
  5. Spring Boot之整合Swagger;
  6. Spring Boot之junit單元測(cè)試踩坑;
  7. 如何在Spring Boot中使用TestNG;
  8. Spring Boot之整合logback日志;
  9. Spring Boot之整合Spring Batch:批處理與任務(wù)調(diào)度;
  10. Spring Boot之整合Spring Security: 訪問(wèn)認(rèn)證;
  11. Spring Boot之整合Spring Security: 授權(quán)管理;
  12. Spring Boot之多數(shù)據(jù)庫(kù)源:極簡(jiǎn)方案;
  13. Spring Boot之使用MongoDB數(shù)據(jù)庫(kù)源;
  14. Spring Boot之多線程、異步:@Async;
  15. Spring Boot之前后端分離(一):Vue前端;
  16. Spring Boot之前后端分離(二):后端稳衬、前后端集成
  17. Spring Boot之前后端分離(三):登錄、登出坐漏、頁(yè)面認(rèn)證

之前在剛學(xué)習(xí)Spring Boot的時(shí)候有看到AOP薄疚,還是挺容易的碧信,但沒有實(shí)踐一下,而近期由于某些原因街夭,幾次被問(wèn)及AOP砰碴,作為系統(tǒng)學(xué)習(xí)Spring Boot的咱們,當(dāng)然不能落下Spring AOP板丽!

AOP(Aspect-Oriented Programming呈枉,面向切面編程)是一種編程范式,是面向?qū)ο缶幊痰难a(bǔ)充埃碱,它提供了另外一種思路來(lái)實(shí)現(xiàn)應(yīng)用系統(tǒng)的公共服務(wù)猖辫。AOP采用“橫切”技術(shù),解剖已封裝的對(duì)象砚殿,將這種公共服務(wù)封裝到一個(gè)可重用的模塊中啃憎,這模塊稱之為“Aspect”,即“切面”似炎⌒疗迹“切面”可降低系統(tǒng)代碼冗余,降低模塊間的耦合度羡藐,提升系統(tǒng)的可維護(hù)性贩毕。

AOP常見的使用場(chǎng)景:

1. 日志功能;

采用AOP之后仆嗦,不需要在每一處功能中添加日志收集代碼耳幢,而是在切面中統(tǒng)一完成這一步驟,提升了編程速度和代碼整潔度欧啤!

2. 業(yè)務(wù)方法調(diào)用的權(quán)限管理睛藻;

采用AOP在處理權(quán)限管理,我們不用在所有業(yè)務(wù)代碼處判斷用戶是否有權(quán)限調(diào)用此方法邢隧,而是在切面中統(tǒng)一完成這一步驟店印,減少了這種非核心業(yè)務(wù)的代碼!

3. 數(shù)據(jù)庫(kù)事務(wù)的管理倒慧;

采用AOP可以統(tǒng)一在執(zhí)行數(shù)據(jù)庫(kù)前先開啟事務(wù)按摘,在執(zhí)行完成后提交事務(wù),若執(zhí)行出錯(cuò)纫谅,則回滾事務(wù)等炫贤。

4. 緩存方面;

我們可采用AOP技術(shù)付秕,統(tǒng)一對(duì)數(shù)據(jù)進(jìn)行緩存兰珍,在下次調(diào)用時(shí),如果參數(shù)询吴、條件等未變掠河,則直接獲取數(shù)據(jù)亮元,而不再調(diào)取應(yīng)用方法。

5. 等唠摹。

AOP有點(diǎn)攔截的感覺爆捞!

AOP有一些術(shù)語(yǔ):

  • Aspect;
  • Joint point;
  • Pointcut;
  • Advice;
  • AOP proxy;
  • Weaving;

這些術(shù)語(yǔ)對(duì)我們理解勾拉、實(shí)踐AOP沒有太大阻礙煮甥,請(qǐng)自行腦補(bǔ)哈,我們直接上代碼開始Demo!

項(xiàng)目代碼已上傳Git Hub倉(cāng)庫(kù)藕赞,歡迎取閱:

整體步驟

  1. 創(chuàng)建AOP演示項(xiàng)目成肘;
  2. 引入AOP依賴;
  3. 創(chuàng)建演示用API找默;
  4. 編寫AOP切面類艇劫;
  5. 驗(yàn)證AOP代碼織入效果;

1. 創(chuàng)建AOP演示項(xiàng)目惩激;

Spring Boot項(xiàng)目創(chuàng)建可參考文章:5分鐘入手Spring Boot店煞,此處不再介紹。

2. 引入AOP依賴风钻;

在項(xiàng)目pom.xml中添加spring-boot-starter-aop依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
記得安裝一下依賴:
mvn install -Dmaven.test.skip=true -Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true

3. 創(chuàng)建演示用API顷蟀;

項(xiàng)目?jī)?nèi)創(chuàng)建controller包,包內(nèi)創(chuàng)建一個(gè)controller骡技,如HelloWorldController.java鸣个,HelloWorldController內(nèi)創(chuàng)建一個(gè)用于演示用的API:

package com.github.dylanz666.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author : dylanz
 * @since : 10/22/2020
 */
@RestController
public class HelloWorldController {
    @GetMapping("/api/hello")
    public String sayHello(@RequestParam String user) {
        return "Hello " + user;
    }
}

此處特地寫了一個(gè)需要參數(shù)的API,我將把API處理過(guò)程進(jìn)行橫切布朦,在API請(qǐng)求前后做一些系統(tǒng)級(jí)別的操作囤萤,但不影響業(yè)務(wù)過(guò)程。

4. 編寫AOP切面類是趴;

在項(xiàng)目?jī)?nèi)創(chuàng)建config包涛舍,在包內(nèi)創(chuàng)建一個(gè)config類,如AOPConfig.java唆途,在AOPConfig編寫如下代碼:

package com.github.dylanz666.config;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;

/**
 * @author : dylanz
 * @since : 10/22/2020
 */
@Configuration
@Aspect
public class AOPConfig {
    @Around("@within(org.springframework.web.bind.annotation.RestController)")
    public Object simpleAop(final ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        assert attributes != null;
        HttpServletRequest request = attributes.getRequest();
        System.out.println("client ip:" + request.getRemoteAddr());

        Object[] args = proceedingJoinPoint.getArgs();
        System.out.println("args:" + Arrays.asList(args));

        Object object = proceedingJoinPoint.proceed();
        System.out.println("return: " + object);

        return object;
    }
}

稍微解讀一下:

1). @Aspect富雅,聲明了這個(gè)類是個(gè)切面類;
2). @Around肛搬,聲明了一個(gè)表達(dá)式没佑,描述了要織入的目標(biāo)特性;

比如本例@within表示目標(biāo)類型帶有注解温赔,且其注解類型為 org.springframework.web.bind.annotation.RestController(如果API的注解用的是@Conrtoller蛤奢,則此處為 org.springframework.stereotype.Controller),這樣系統(tǒng)內(nèi)所有RestController方法(Rest API,也即帶有@RestController注解的controller類中的方法)被調(diào)用的時(shí)候远剩,都會(huì)執(zhí)行@Around注解的方法扣溺,也就是本例的simpleAop方法骇窍;
除了@Around(方法執(zhí)行前后織入代碼)瓜晤,還有@Before、@After腹纳、@AfterReturning痢掠、@AfterThrowing,他們均分別表示該織入代碼用于執(zhí)行方法前嘲恍、執(zhí)行方法后足画、方法返回后、方法拋出異常后佃牛,如:

@Before("@within(org.springframework.web.bind.annotation.RestController)")
public void before(JoinPoint joinPoint) throws Throwable {
    Object[] args = joinPoint.getArgs();
    System.out.println("args:" + Arrays.asList(args));

    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    assert attributes != null;
    HttpServletRequest request = attributes.getRequest();
    System.out.println("client ip:" + request.getRemoteAddr());
}

我們?cè)趫?zhí)行方法前打印請(qǐng)求參數(shù)和客戶端ip淹辞;

3). 除了Around注解的方法可以傳ProceedingJionPoint類型的參數(shù)外,其余的幾個(gè)都不能傳ProceedingJionPoint類型的參數(shù)俘侠;
4). simpleAop(名字任意)是用來(lái)織入的代碼象缀,我們可以利用參數(shù)ProceedingJoinPoint提供的方法,來(lái)對(duì)請(qǐng)求前后進(jìn)行系統(tǒng)級(jí)別操作爷速。

例如本例在接收到API請(qǐng)求還未執(zhí)行業(yè)務(wù)代碼時(shí)將客戶端ip央星、請(qǐng)求參數(shù)打印出來(lái),然后在業(yè)務(wù)代碼執(zhí)行完成后未返回給客戶端前惫东,將返回結(jié)果先打印出來(lái)莉给;

5). 通常當(dāng)切面代碼執(zhí)行完后,我們需要繼續(xù)執(zhí)行應(yīng)用代碼廉沮,并將返回對(duì)象正常返回颓遏,Object object = proceedingJoinPoint.proceed();就是為了完成這一過(guò)程;
6). 除了@within這種切面目標(biāo)匹配表達(dá)式外滞时,Spring AOP還提供了多種可選的表達(dá)式及表達(dá)式組合:
(1). within();
(2). @within;
(3). execution()叁幢,如:
  • execution(public * *(...));
  • execution(* set*(...));
  • execution(public set*(...));
  • execution(public com.xyz.service..set(...));
(4). target();
(5). @target;
(6). args();
(7). @args();
(8). @annotation();
(9). this();
(10). @Transactional;

等,讀者可自行展開學(xué)習(xí)漂洋!

5. 驗(yàn)證AOP代碼織入效果遥皂;

1). 項(xiàng)目整體結(jié)構(gòu):

項(xiàng)目整體結(jié)構(gòu)

2). 啟動(dòng)項(xiàng)目:

啟動(dòng)項(xiàng)目

3). 訪問(wèn)API:

(手機(jī)在局域網(wǎng)內(nèi)訪問(wèn)我們的應(yīng)用路徑:http://192.168.0.101:8080/api/hello?user=dylanz)

訪問(wèn)API

4). 后端執(zhí)行切面代碼:

后端執(zhí)行切面代碼

我們可以看到,在API執(zhí)行前刽漂,打印了手機(jī)的ip地址:192.168.0.100演训,同時(shí)打印了請(qǐng)求參數(shù)值:dylanz,在API對(duì)應(yīng)的方法執(zhí)行后贝咙,打印方法返回的Hello dylanz字符串給客戶端样悟,然后將該字符串傳給客戶端,之后我們便能在手機(jī)客戶端看到Hello dylanz字符串!

不難看出,我們可以將這些打印換成日志打印窟她,就能全局收集詳細(xì)的信息陈症!
或者也可在切面中做一些緩存操作、數(shù)據(jù)庫(kù)事務(wù)方面的行為等震糖。

至此录肯,我們完成了一個(gè)簡(jiǎn)單的Spring AOP案例,整個(gè)過(guò)程簡(jiǎn)單而不失靈活吊说,靈活而不失優(yōu)雅论咏,有沒有?

如果本文對(duì)您有幫助颁井,麻煩點(diǎn)贊+關(guān)注厅贪!

謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末雅宾,一起剝皮案震驚了整個(gè)濱河市养涮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌眉抬,老刑警劉巖贯吓,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異吐辙,居然都是意外死亡宣决,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門昏苏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)尊沸,“玉大人,你說(shuō)我怎么就攤上這事贤惯⊥葑ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵孵构,是天一觀的道長(zhǎng)屁商。 經(jīng)常有香客問(wèn)我,道長(zhǎng)颈墅,這世上最難降的妖魔是什么蜡镶? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮恤筛,結(jié)果婚禮上官还,老公的妹妹穿的比我還像新娘。我一直安慰自己毒坛,他們只是感情好望伦,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布林说。 她就那樣靜靜地躺著,像睡著了一般屯伞。 火紅的嫁衣襯著肌膚如雪腿箩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天劣摇,我揣著相機(jī)與錄音珠移,去河邊找鬼。 笑死饵撑,一個(gè)胖子當(dāng)著我的面吹牛剑梳,可吹牛的內(nèi)容都是我干的唆貌。 我是一名探鬼主播滑潘,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼锨咙!你這毒婦竟也來(lái)了语卤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤酪刀,失蹤者是張志新(化名)和其女友劉穎粹舵,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體骂倘,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡眼滤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了历涝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诅需。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖荧库,靈堂內(nèi)的尸體忽然破棺而出堰塌,到底是詐尸還是另有隱情,我是刑警寧澤分衫,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布场刑,位于F島的核電站,受9級(jí)特大地震影響蚪战,放射性物質(zhì)發(fā)生泄漏牵现。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一邀桑、第九天 我趴在偏房一處隱蔽的房頂上張望瞎疼。 院中可真熱鬧,春花似錦概漱、人聲如沸丑慎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)竿裂。三九已至玉吁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間腻异,已是汗流浹背进副。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留悔常,地道東北人影斑。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像机打,于是被迫代替她去往敵國(guó)和親矫户。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359