Spring Boot 中的切面AOP處理
本來已收錄到我寫的10萬字Springboot經(jīng)典學(xué)習(xí)筆記中,筆記在持續(xù)更新……文末有領(lǐng)取方式
1. 什么是AOP
AOP:Aspect Oriented Programming 的縮寫,意為:面向切面編程。面向切面編程的目標(biāo)就是分離關(guān)注點(diǎn)。什么是關(guān)注點(diǎn)呢?就是關(guān)注點(diǎn),就是你要做的事情。假如你是一位公子哥,沒啥人生目標(biāo),每天衣來伸手,飯來張口,整天只知道一件事:玩(這就是你的關(guān)注點(diǎn),你只要做這一件事)!但是有個問題,你在玩之前,你還需要起床、穿衣服、穿鞋子、疊被子、做早飯等等等等,但是這些事情你不想關(guān)注,也不用關(guān)注,你只想想玩,那么怎么辦呢?
對!這些事情通通交給下人去干。你有一個專門的仆人 A 幫你穿衣服,仆人 B 幫你穿鞋子,仆人 C 幫你疊好被子,仆人 D 幫你做飯,然后你就開始吃飯、去玩(這就是你一天的正事),你干完你的正事之后,回來,然后一系列仆人又開始幫你干這個干那個,然后一天就結(jié)束了!
這就是 AOP。AOP 的好處就是你只需要干你的正事,其它事情別人幫你干。也許有一天,你想裸奔,不想穿衣服,那么你把仆人 A 解雇就是了!也許有一天,出門之前你還想帶點(diǎn)錢,那么你再雇一個仆人 E 專門幫你干取錢的活!這就是AOP。每個人各司其職,靈活組合,達(dá)到一種可配置的、可插拔的程序結(jié)構(gòu)。
2. Spring Boot 中的 AOP 處理
2.1 AOP 依賴
使用AOP,首先需要引入AOP的依賴。
<dependency>
?<groupId>org.springframework.bootgroupId>
?<artifactId>spring-boot-starter-aopartifactId>
dependency>
2.2 實(shí)現(xiàn) AOP 切面
Spring Boot 中使用 AOP 非常簡單,假如我們要在項(xiàng)目中打印一些 log,在引入了上面的依賴之后,我們新建一個類 LogAspectHandler,用來定義切面和處理方法。只要在類上加個@Aspect注解即可。@Aspect 注解用來描述一個切面類,定義切面類的時候需要打上這個注解。@Component 注解讓該類交給 Spring 來管理。
@Aspect
@Component
public?class?LogAspectHandler?{
}
這里主要介紹幾個常用的注解及使用:
1.@Pointcut:定義一個切面,即上面所描述的關(guān)注的某件事入口。
2.@Before:在做某件事之前做的事。
3.@After:在做某件事之后做的事。
4.@AfterReturning:在做某件事之后,對其返回值做增強(qiáng)處理。
5.@AfterThrowing:在做某件事拋出異常時,處理。
2.2.1 @Pointcut 注解
@Pointcut 注解:用來定義一個切面(切入點(diǎn)),即上文中所關(guān)注的某件事情的入口。切入點(diǎn)決定了連接點(diǎn)關(guān)注的內(nèi)容,使得我們可以控制通知什么時候執(zhí)行。
@Aspect
@Component
public?class?LogAspectHandler?{
????/**
?????*?定義一個切面,攔截com.itcodai.course09.controller包和子包下的所有方法
?????*/
????@Pointcut("execution(*?com.itcodai.course09.controller..*.*(..))")
????public?void?pointCut()?{}
}
@Pointcut 注解指定一個切面,定義需要攔截的東西,這里介紹兩個常用的表達(dá)式:一個是使用 execution(),另一個是使用 annotation()。
以 execution(* com.itcodai.course09.controller..*.*(..))) 表達(dá)式為例,語法如下:
execution()為表達(dá)式主體
第一個*號的位置:表示返回值類型,*表示所有類型
包名:表示需要攔截的包名,后面的兩個句點(diǎn)表示當(dāng)前包和當(dāng)前包的所有子包,com.itcodai.course09.controller包、子包下所有類的方法
第二個*號的位置:表示類名,*表示所有類*(..):這個星號表示方法名,*表示所有的方法,后面括弧里面表示方法的參數(shù),兩個句點(diǎn)表示任何參數(shù)
annotation() 方式是針對某個注解來定義切面,比如我們對具有@GetMapping注解的方法做切面,可以如下定義切面:
@Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
public?void?annotationCut()?{}
然后使用該切面的話,就會切入注解是 @GetMapping 的方法。因?yàn)樵趯?shí)際項(xiàng)目中,可能對于不同的注解有不同的邏輯處理,比如 @GetMapping、@PostMapping、@DeleteMapping 等。所以這種按照注解的切入方式在實(shí)際項(xiàng)目中也很常用。
2.2.2 @Before 注解
@Before 注解指定的方法在切面切入目標(biāo)方法之前執(zhí)行,可以做一些 log 處理,也可以做一些信息的統(tǒng)計(jì),比如獲取用戶的請求 url 以及用戶的 ip 地址等等,這個在做個人站點(diǎn)的時候都能用得到,都是常用的方法。例如:
@Aspect
@Component
public?class?LogAspectHandler?{
????private?final?Logger?logger?=?LoggerFactory.getLogger(this.getClass());
????/**
?????*?在上面定義的切面方法之前執(zhí)行該方法
?????*?@param?joinPoint?jointPoint
?????*/
????@Before("pointCut()")
????public?void?doBefore(JoinPoint?joinPoint)?{
????????logger.info("====doBefore方法進(jìn)入了====");
????????//?獲取簽名
????????Signature?signature?=?joinPoint.getSignature();
????????//?獲取切入的包名
????????String?declaringTypeName?=?signature.getDeclaringTypeName();
????????//?獲取即將執(zhí)行的方法名
????????String?funcName?=?signature.getName();
????????logger.info("即將執(zhí)行方法為:?{},屬于{}包",?funcName,?declaringTypeName);
????????
????????//?也可以用來記錄一些信息,比如獲取請求的url和ip
????????ServletRequestAttributes?attributes?=?(ServletRequestAttributes)?RequestContextHolder.getRequestAttributes();
????????HttpServletRequest?request?=?attributes.getRequest();
????????//?獲取請求url
????????String?url?=?request.getRequestURL().toString();
????????//?獲取請求ip
????????String?ip?=?request.getRemoteAddr();
????????logger.info("用戶請求的url為:{},ip地址為:{}",?url,?ip);
????}
}
JointPoint 對象很有用,可以用它來獲取一個簽名,然后利用簽名可以獲取請求的包名、方法名,包括參數(shù)(通過 joinPoint.getArgs() 獲取)等等。
2.2.3 @After 注解
@After 注解和 @Before ?注解相對應(yīng),指定的方法在切面切入目標(biāo)方法之后執(zhí)行,也可以做一些完成某方法之后的 log 處理。
@Aspect
@Component
public?class?LogAspectHandler?{
????private?final?Logger?logger?=?LoggerFactory.getLogger(this.getClass());
????/**
?????*?定義一個切面,攔截com.itcodai.course09.controller包下的所有方法
?????*/
????@Pointcut("execution(*?com.itcodai.course09.controller..*.*(..))")
????public?void?pointCut()?{}
????/**
?????*?在上面定義的切面方法之后執(zhí)行該方法
?????*?@param?joinPoint?jointPoint
?????*/
????@After("pointCut()")
????public?void?doAfter(JoinPoint?joinPoint)?{
????????logger.info("====doAfter方法進(jìn)入了====");
????????Signature?signature?=?joinPoint.getSignature();
????????String?method?=?signature.getName();
????????logger.info("方法{}已經(jīng)執(zhí)行完",?method);
????}
}
到這里,我們來寫一個 Controller 來測試一下執(zhí)行結(jié)果,新建一個 AopController 如下:
@RestController
@RequestMapping("/aop")
public?class?AopController?{
????@GetMapping("/{name}")
????public?String?testAop(@PathVariable?String?name)?{
????????return?"Hello?"?+?name;
????}
}
啟動項(xiàng)目,在瀏覽器中輸入 localhost:8080/aop/CSDN,觀察一下控制臺的輸出信息:
====doBefore方法進(jìn)入了====??
即將執(zhí)行方法為:?testAop,屬于com.itcodai.course09.controller.AopController包??
用戶請求的url為:http://localhost:8080/aop/name,ip地址為:0:0:0:0:0:0:0:1 ?
====doAfter方法進(jìn)入了====??
方法testAop已經(jīng)執(zhí)行完
從打印出來的 log 中可以看出程序執(zhí)行的邏輯與順序,可以很直觀的掌握 @Before 和 @After 兩個注解的實(shí)際作用。
2.2.4 @AfterReturning 注解
@AfterReturning 注解和 @After 有些類似,區(qū)別在于 @AfterReturning 注解可以用來捕獲切入方法執(zhí)行完之后的返回值,對返回值進(jìn)行業(yè)務(wù)邏輯上的增強(qiáng)處理,例如:
@Aspect
@Component
public?class?LogAspectHandler?{
????private?final?Logger?logger?=?LoggerFactory.getLogger(this.getClass());
????/**
?????*?在上面定義的切面方法返回后執(zhí)行該方法,可以捕獲返回對象或者對返回對象進(jìn)行增強(qiáng)
?????*?@param?joinPoint?joinPoint
?????*?@param?result?result
?????*/
????@AfterReturning(pointcut?=?"pointCut()",?returning?=?"result")
????public?void?doAfterReturning(JoinPoint?joinPoint,?Object?result)?{
????????Signature?signature?=?joinPoint.getSignature();
????????String?classMethod?=?signature.getName();
????????logger.info("方法{}執(zhí)行完畢,返回參數(shù)為:{}",?classMethod,?result);
????????//?實(shí)際項(xiàng)目中可以根據(jù)業(yè)務(wù)做具體的返回值增強(qiáng)
????????logger.info("對返回參數(shù)進(jìn)行業(yè)務(wù)上的增強(qiáng):{}",?result?+?"增強(qiáng)版");
????}
}
需要注意的是:在 @AfterReturning注解 中,屬性 returning 的值必須要和參數(shù)保持一致,否則會檢測不到。該方法中的第二個入?yún)⒕褪潜磺蟹椒ǖ姆祷刂担?doAfterReturning 方法中可以對返回值進(jìn)行增強(qiáng),可以根據(jù)業(yè)務(wù)需要做相應(yīng)的封裝。我們重啟一下服務(wù),再測試一下(多余的 log 我不貼出來了):
方法testAop執(zhí)行完畢,返回參數(shù)為:Hello CSDN ?
對返回參數(shù)進(jìn)行業(yè)務(wù)上的增強(qiáng):Hello CSDN增強(qiáng)版
2.2.5 @AfterThrowing 注解
顧名思義,@AfterThrowing 注解是當(dāng)被切方法執(zhí)行時拋出異常時,會進(jìn)入 @AfterThrowing 注解的方法中執(zhí)行,在該方法中可以做一些異常的處理邏輯。要注意的是 throwing 屬性的值必須要和參數(shù)一致,否則會報錯。該方法中的第二個入?yún)⒓礊閽伋龅漠惓!?/p>
/**
?*?使用AOP處理log
?*?@author?shengwu?ni
?*?@date?2018/05/04?20:24
?*/
@Aspect
@Component
public?class?LogAspectHandler?{
????private?final?Logger?logger?=?LoggerFactory.getLogger(this.getClass());
????/**
?????*?在上面定義的切面方法執(zhí)行拋異常時,執(zhí)行該方法
?????*?@param?joinPoint?jointPoint
?????*?@param?ex?ex
?????*/
????@AfterThrowing(pointcut?=?"pointCut()",?throwing?=?"ex")
????public?void?afterThrowing(JoinPoint?joinPoint,?Throwable?ex)?{
????????Signature?signature?=?joinPoint.getSignature();
????????String?method?=?signature.getName();
????????//?處理異常的邏輯
????????logger.info("執(zhí)行方法{}出錯,異常為:{}",?method,?ex);
????}
}
該方法我就不測試了,大家可以自行測試一下。
3. 總結(jié)
本節(jié)課針對 Spring Boot 中的切面 AOP 做了詳細(xì)的講解,主要介紹了 Spring Boot 中 AOP 的引入,常用注解的使用,參數(shù)的使用,以及常用 api 的介紹。AOP 在實(shí)際項(xiàng)目中很有用,對切面方法執(zhí)行前后都可以根據(jù)具體的業(yè)務(wù),做相應(yīng)的預(yù)處理或者增強(qiáng)處理,同時也可以用作異常捕獲處理,可以根據(jù)具體業(yè)務(wù)場景,合理去使用 AOP。
該文已收錄到我寫的《10萬字Springboot經(jīng)典學(xué)習(xí)筆記》中,點(diǎn)擊下面小卡片,進(jìn)入【武哥聊編程】,回復(fù):筆記,即可免費(fèi)獲取。
點(diǎn)贊是最大的支持?

