這篇文章將為大家詳細(xì)講解有關(guān)如何在Spring Boot中使用AOP實(shí)現(xiàn)REST接口,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。
我們提供的服務(wù)有:成都網(wǎng)站設(shè)計(jì)、成都網(wǎng)站建設(shè)、微信公眾號(hào)開(kāi)發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、英吉沙ssl等。為上千余家企事業(yè)單位解決了網(wǎng)站和推廣的問(wèn)題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的英吉沙網(wǎng)站制作公司
一、Authorized實(shí)現(xiàn)
1、定義注解
package com.power.demo.common; import java.lang.annotation.*; /* * 安全認(rèn)證 * */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Authorized { String value() default ""; }
這個(gè)注解看上去什么都沒(méi)有,僅僅是一個(gè)占位符,用于標(biāo)志是否需要安全認(rèn)證。
2、表現(xiàn)層使用注解
@Authorized @RequestMapping(value = "/getinfobyid", method = RequestMethod.POST) @ApiOperation("根據(jù)商品Id查詢商品信息") @ApiImplicitParams({ @ApiImplicitParam(paramType = "header", name = "authtoken", required = true, value = "authtoken", dataType = "String"), }) public GetGoodsByGoodsIdResponse getGoodsByGoodsId(@RequestHeader String authtoken, @RequestBody GetGoodsByGoodsIdRequest request) { return _goodsApiService.getGoodsByGoodsId(request); }
看上去就是在一個(gè)方法上加了Authorized注解,其實(shí)它也可以作用于類上,也可以類和方法混合使用。
3、請(qǐng)求認(rèn)證切面
下面的代碼是實(shí)現(xiàn)靈活的安全認(rèn)證的關(guān)鍵:
package com.power.demo.controller.tool; import com.power.demo.common.AppConst; import com.power.demo.common.Authorized; import com.power.demo.common.BizResult; import com.power.demo.service.contract.AuthTokenService; import com.power.demo.util.PowerLogger; import com.power.demo.util.SerializeUtil; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.lang.annotation.Annotation; /** * 請(qǐng)求認(rèn)證切面,驗(yàn)證自定義請(qǐng)求header的authtoken是否合法 **/ @Aspect @Component public class AuthorizedAspect { @Autowired private AuthTokenService authTokenService; @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)") public void requestMapping() { } @Pointcut("execution(* com.power.demo.controller.*Controller.*(..))") public void methodPointCut() { } /** * 某個(gè)方法執(zhí)行前進(jìn)行請(qǐng)求合法性認(rèn)證 注入Authorized注解 (先) */ @Before("requestMapping() && methodPointCut()&&@annotation(authorized)") public void doBefore(JoinPoint joinPoint, Authorized authorized) throws Exception { PowerLogger.info("方法認(rèn)證開(kāi)始..."); Class type = joinPoint.getSignature().getDeclaringType(); Annotation[] annotations = type.getAnnotationsByType(Authorized.class); if (annotations != null && annotations.length > 0) { PowerLogger.info("直接類認(rèn)證"); return; } //獲取當(dāng)前http請(qǐng)求 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); String token = request.getHeader(AppConst.AUTH_TOKEN); BizResultbizResult = authTokenService.powerCheck(token); System.out.println(SerializeUtil.Serialize(bizResult)); if (bizResult.getIsOK() == true) { PowerLogger.info("方法認(rèn)證通過(guò)"); } else { throw new Exception(bizResult.getMessage()); } } /** * 類下面的所有方法執(zhí)行前進(jìn)行請(qǐng)求合法性認(rèn)證 (后) */ @Before("requestMapping() && methodPointCut()") public void doBefore(JoinPoint joinPoint) throws Exception { PowerLogger.info("類認(rèn)證開(kāi)始..."); Annotation[] annotations = joinPoint.getSignature().getDeclaringType().getAnnotationsByType(Authorized.class); if (annotations == null || annotations.length == 0) { PowerLogger.info("類不需要認(rèn)證"); return; } //獲取當(dāng)前http請(qǐng)求 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); String token = request.getHeader(AppConst.AUTH_TOKEN); BizResult bizResult = authTokenService.powerCheck(token); System.out.println(SerializeUtil.Serialize(bizResult)); if (bizResult.getIsOK() == true) { PowerLogger.info("類認(rèn)證通過(guò)"); } else { throw new Exception(bizResult.getMessage()); } } }
需要注意的是,對(duì)類和方法上的Authorized處理,定義了重載的處理方法doBefore。AuthTokenService和上文介紹的處理邏輯一樣,如果安全認(rèn)證不通過(guò),則拋出異常。
如果我們?cè)陬惿匣蛘叻椒ㄉ隙技恿薃uthorized注解,不會(huì)進(jìn)行重復(fù)安全認(rèn)證,請(qǐng)放心使用。
4、統(tǒng)一異常處理
上文已經(jīng)提到過(guò),對(duì)所有發(fā)生異常的API,都返回統(tǒng)一格式的報(bào)文至調(diào)用方。主要代碼大致如下:
package com.power.demo.controller.exhandling; import com.power.demo.common.ErrorInfo; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; import java.util.Date; /** * 全局統(tǒng)一異常處理增強(qiáng) **/ @ControllerAdvice public class GlobalExceptionHandler { /** * API統(tǒng)一異常處理 **/ @ExceptionHandler(value = Exception.class) @ResponseBody public ErrorInfojsonApiErrorHandler(HttpServletRequest request, Exception e) { ErrorInfo errorInfo = new ErrorInfo<>(); try { System.out.println("統(tǒng)一異常處理..."); e.printStackTrace(); Throwable innerEx = e.getCause(); while (innerEx != null) { //innerEx.printStackTrace(); if (innerEx.getCause() == null) { break; } innerEx = innerEx.getCause(); } if (innerEx == null) { errorInfo.setMessage(e.getMessage()); errorInfo.setError(e.toString()); } else { errorInfo.setMessage(innerEx.getMessage()); errorInfo.setError(innerEx.toString()); } errorInfo.setData(e); errorInfo.setTimestamp(new Date()); errorInfo.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());//500錯(cuò)誤 errorInfo.setUrl(request.getRequestURL().toString()); errorInfo.setPath(request.getServletPath()); } catch (Exception ex) { ex.printStackTrace(); errorInfo.setMessage(ex.getMessage()); errorInfo.setError(ex.toString()); } return errorInfo; } }
認(rèn)證不通過(guò)的API調(diào)用結(jié)果如下:
異常的整個(gè)堆??梢苑浅7浅7奖愕貛椭覀兣挪榈絾?wèn)題。
我們?cè)俳Y(jié)合上文來(lái)看安全認(rèn)證的時(shí)間先后,根據(jù)理論分析和實(shí)踐發(fā)現(xiàn),過(guò)濾器Filter先于攔截器Interceptor先于自定義Authorized方法認(rèn)證先于Authorized類認(rèn)證。
到這里,我們發(fā)現(xiàn)通過(guò)AOP框架AspectJ,一個(gè)@Aspect注解外加幾個(gè)方法幾十行業(yè)務(wù)代碼,就可以輕松實(shí)現(xiàn)對(duì)REST API的攔截處理。
那么為什么會(huì)有@Pointcut,既然有@Before,是否有@After?
其實(shí)上述簡(jiǎn)易安全認(rèn)證功能實(shí)現(xiàn)的過(guò)程主要利用了Spring的AOP特性。
下面再簡(jiǎn)單介紹下AOP常見(jiàn)概念(主要參考Spring實(shí)戰(zhàn)),加深理解。AOP概念較多而且比較乏味,經(jīng)驗(yàn)豐富的老鳥(niǎo)到此就可以忽略這一段了。
二、AOP
1、概述
AOP(Aspect Oriented Programming),即面向切面編程,可以處理很多事情,常見(jiàn)的功能比如日志記錄,性能統(tǒng)計(jì),安全控制,事務(wù)處理,異常處理等。
AOP可以認(rèn)為是一種更高級(jí)的“復(fù)用”技術(shù),它是OOP(Object Oriented Programming,面向?qū)ο缶幊蹋┑难a(bǔ)充和完善。AOP的理念,就是將分散在各個(gè)業(yè)務(wù)邏輯代碼中相同的代碼通過(guò)橫向切割的方式抽取到一個(gè)獨(dú)立的模塊中。將相同邏輯的重復(fù)代碼橫向抽取出來(lái),使用動(dòng)態(tài)代理技術(shù)將這些重復(fù)代碼織入到目標(biāo)對(duì)象方法中,實(shí)現(xiàn)和原來(lái)一樣的功能。這樣一來(lái),我們?cè)趯?xiě)業(yè)務(wù)邏輯時(shí)就只關(guān)心業(yè)務(wù)代碼。
OOP引入封裝、繼承、多態(tài)等概念來(lái)建立一種對(duì)象層次結(jié)構(gòu),用于模擬公共行為的一個(gè)集合。不過(guò)OOP允許開(kāi)發(fā)者定義縱向的關(guān)系,但并不適合定義橫向的關(guān)系,例如日志功能。日志代碼往往橫向地散布在所有對(duì)象層次中,而與它對(duì)應(yīng)的對(duì)象的核心功能毫無(wú)關(guān)系對(duì)于其他類型的代碼,如安全性、異常處理和透明的持續(xù)性也都是如此,這種散布在各處的無(wú)關(guān)的代碼被稱為橫切(cross cutting),在OOP設(shè)計(jì)中,它導(dǎo)致了大量代碼的重復(fù),而不利于各個(gè)模塊的重用。
AOP技術(shù)恰恰相反,它利用一種稱為"橫切"的技術(shù),剖解開(kāi)封裝的對(duì)象內(nèi)部,并將那些影響了多個(gè)類的公共行為封裝到一個(gè)可重用模塊,并將其命名為"Aspect",即切面。
所謂"切面",簡(jiǎn)單說(shuō)就是那些與業(yè)務(wù)無(wú)關(guān),卻為業(yè)務(wù)模塊所共同調(diào)用的邏輯或責(zé)任封裝起來(lái),便于減少系統(tǒng)的重復(fù)代碼,降低模塊之間的耦合度,并有利于未來(lái)的可操作性和可維護(hù)性。
使用"橫切"技術(shù),AOP把軟件系統(tǒng)分為兩個(gè)部分:核心關(guān)注點(diǎn)和橫切關(guān)注點(diǎn)。
業(yè)務(wù)處理的主要流程是核心關(guān)注點(diǎn),與之關(guān)系不大的部分是橫切關(guān)注點(diǎn)。橫切關(guān)注點(diǎn)的一個(gè)特點(diǎn)是,它們經(jīng)常發(fā)生在核心關(guān)注點(diǎn)的多處,而各處基本相似,比如權(quán)限認(rèn)證、日志、事務(wù)。AOP的作用在于分離系統(tǒng)中的各種關(guān)注點(diǎn),將核心關(guān)注點(diǎn)和橫切關(guān)注點(diǎn)分離開(kāi)來(lái)。
2、AOP術(shù)語(yǔ)
深刻理解AOP,要掌握的術(shù)語(yǔ)可真不少。
Target:目標(biāo)類,需要被代理的類,如:UserService
Advice:通知,所要增強(qiáng)或增加的功能,定義了切面的“什么”和“何時(shí)”,模式有Before、After、After-returning,、After-throwing和Around
Join Point:連接點(diǎn),應(yīng)用執(zhí)行過(guò)程中,能夠插入切面的所有“點(diǎn)”(時(shí)機(jī))
Pointcut:切點(diǎn),實(shí)際運(yùn)行中,選擇插入切面的連接點(diǎn),即定義了哪些點(diǎn)得到了增強(qiáng)。切點(diǎn)定義了切面的“何處”。我們通常使用明確的類和方法名稱,或是利用正則表達(dá)式定義所匹配的類和方法名稱來(lái)指定這些切點(diǎn)。
Aspect:切面,把橫切關(guān)注點(diǎn)模塊化為特殊的類,這些類稱為切面,切面是通知和切點(diǎn)的結(jié)合。通知和切點(diǎn)共同定義了切面的全部?jī)?nèi)容:它是什么,在何時(shí)和何處完成其功能
Introduction:引入,允許我們向現(xiàn)有的類添加新方法或?qū)傩?/p>
Weaving:織入,把切面應(yīng)用到目標(biāo)對(duì)象并創(chuàng)建新的代理對(duì)象的過(guò)程,切面在指定的連接點(diǎn)被織入到目標(biāo)對(duì)象中。在目標(biāo)對(duì)象的生命周期里有多個(gè)點(diǎn)可以進(jìn)行織入:編譯期、類加載期、運(yùn)行期
下面參考自網(wǎng)上圖片,可以比較直觀地理解上述這幾個(gè)AOP術(shù)語(yǔ)和流轉(zhuǎn)過(guò)程。
3、AOP實(shí)現(xiàn)
(1)動(dòng)態(tài)代理
使用動(dòng)態(tài)代理可以為一個(gè)或多個(gè)接口在運(yùn)行期動(dòng)態(tài)生成實(shí)現(xiàn)對(duì)象,生成的對(duì)象中實(shí)現(xiàn)接口的方法時(shí)可以添加增強(qiáng)代碼,從而實(shí)現(xiàn)AOP:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 動(dòng)態(tài)代理類 */ public class DynamicProxy implements InvocationHandler { /** * 需要代理的目標(biāo)類 */ private Object target; /** * 寫(xiě)法固定,aop專用:綁定委托對(duì)象并返回一個(gè)代理類 * * @param target * @return */ public Object bind(Object target) { this.target = target; return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } /** * 調(diào)用 InvocationHandler接口定義方法 * * @param proxy 指被代理的對(duì)象。 * @param method 要調(diào)用的方法 * @param args 方法調(diào)用時(shí)所需要的參數(shù) */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; // 切面之前執(zhí)行 System.out.println("[動(dòng)態(tài)代理]切面之前執(zhí)行"); // 執(zhí)行業(yè)務(wù) result = method.invoke(target, args); // 切面之后執(zhí)行 System.out.println("[動(dòng)態(tài)代理]切面之后執(zhí)行"); return result; } }
缺點(diǎn)是只能針對(duì)接口進(jìn)行代理,同時(shí)由于動(dòng)態(tài)代理是通過(guò)反射實(shí)現(xiàn)的,有時(shí)可能要考慮反射調(diào)用的開(kāi)銷,否則很容易引發(fā)性能問(wèn)題。
(2)字節(jié)碼生成
動(dòng)態(tài)字節(jié)碼生成技術(shù)是指在運(yùn)行時(shí)動(dòng)態(tài)生成指定類的一個(gè)子類對(duì)象(注意是針對(duì)類),并覆蓋其中特定方法,覆蓋方法時(shí)可以添加增強(qiáng)代碼,從而實(shí)現(xiàn)AOP。
最常用的工具是CGLib:
import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * 使用cglib動(dòng)態(tài)代理 ** JDK中的動(dòng)態(tài)代理使用時(shí),必須有業(yè)務(wù)接口,而cglib是針對(duì)類的 */ public class CglibProxy implements MethodInterceptor { private Object target; /** * 創(chuàng)建代理對(duì)象 * * @param target * @return */ public Object getInstance(Object target) { this.target = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass()); // 回調(diào)方法 enhancer.setCallback(this); // 創(chuàng)建代理對(duì)象 return enhancer.create(); } @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { Object result = null; System.out.println("[cglib]切面之前執(zhí)行"); result = methodProxy.invokeSuper(proxy, args); System.out.println("[cglib]切面之后執(zhí)行"); return result; } }
(3)定制的類加載器
當(dāng)需要對(duì)類的所有對(duì)象都添加增強(qiáng),動(dòng)態(tài)代理和字節(jié)碼生成本質(zhì)上都需要?jiǎng)討B(tài)構(gòu)造代理對(duì)象,即最終被增強(qiáng)的對(duì)象是由AOP框架生成,不是開(kāi)發(fā)者new出來(lái)的。
解決的辦法就是實(shí)現(xiàn)自定義的類加載器,在一個(gè)類被加載時(shí)對(duì)其進(jìn)行增強(qiáng)。
JBoss就是采用這種方式實(shí)現(xiàn)AOP功能。
這種方式目前只是道聽(tīng)途說(shuō),本人沒(méi)有在實(shí)際項(xiàng)目中實(shí)踐過(guò)。
(4)代碼生成
利用工具在已有代碼基礎(chǔ)上生成新的代碼,其中可以添加任何橫切代碼來(lái)實(shí)現(xiàn)AOP。
(5)語(yǔ)言擴(kuò)展
可以對(duì)構(gòu)造方法和屬性的賦值操作進(jìn)行增強(qiáng),AspectJ是采用這種方式實(shí)現(xiàn)AOP的一個(gè)常見(jiàn)的Java語(yǔ)言擴(kuò)展。
比較:根據(jù)日志,上述流程的執(zhí)行順序依次為:過(guò)濾器、攔截器、AOP方法認(rèn)證、AOP類認(rèn)證
附:記錄API日志
最后通過(guò)記錄API日志,記錄日志時(shí)加入API耗時(shí)統(tǒng)計(jì)(其實(shí)我們?cè)陂_(kāi)發(fā).NET應(yīng)用的過(guò)程中通過(guò)AOP這種記錄日志的方式也已經(jīng)是標(biāo)配),加深上述AOP的幾個(gè)核心概念的理解:
package com.power.demo.controller.tool; import com.power.demo.apientity.BaseApiRequest; import com.power.demo.apientity.BaseApiResponse; import com.power.demo.util.DateTimeUtil; import com.power.demo.util.SerializeUtil; 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.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.util.StopWatch; /** * 服務(wù)日志切面,主要記錄接口日志及耗時(shí) **/ @Aspect @Component public class SvcLogAspect { @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)") public void requestMapping() { } @Pointcut("execution(* com.power.demo.controller.*Controller.*(..))") public void methodPointCut() { } @Around("requestMapping() && methodPointCut()") public Object around(ProceedingJoinPoint pjd) throws Throwable { System.out.println("Spring AOP方式記錄服務(wù)日志"); Object response = null;//定義返回信息 BaseApiRequest baseApiRequest = null;//請(qǐng)求基類 int index = 0; Signature curSignature = pjd.getSignature(); String className = curSignature.getClass().getName();//類名 String methodName = curSignature.getName(); //方法名 Logger logger = LoggerFactory.getLogger(className);//日志 StopWatch watch = DateTimeUtil.StartNew();//用于統(tǒng)計(jì)調(diào)用耗時(shí) // 獲取方法參數(shù) Object[] reqParamArr = pjd.getArgs(); StringBuffer sb = new StringBuffer(); //獲取請(qǐng)求參數(shù)集合并進(jìn)行遍歷拼接 for (Object reqParam : reqParamArr) { if (reqParam == null) { index++; continue; } try { sb.append(SerializeUtil.Serialize(reqParam)); //獲取繼承自BaseApiRequest的請(qǐng)求實(shí)體 if (baseApiRequest == null && reqParam instanceof BaseApiRequest) { index++; baseApiRequest = (BaseApiRequest) reqParam; } } catch (Exception e) { sb.append(reqParam.toString()); } sb.append(","); } String strParam = sb.toString(); if (strParam.length() > 0) { strParam = strParam.substring(0, strParam.length() - 1); } //記錄請(qǐng)求 logger.info(String.format("【%s】類的【%s】方法,請(qǐng)求參數(shù):%s", className, methodName, strParam)); response = pjd.proceed(); // 執(zhí)行服務(wù)方法 watch.stop(); //記錄應(yīng)答 logger.info(String.format("【%s】類的【%s】方法,應(yīng)答參數(shù):%s", className, methodName, SerializeUtil.Serialize(response))); // 獲取執(zhí)行完的時(shí)間 logger.info(String.format("接口【%s】總耗時(shí)(毫秒):%s", methodName, watch.getTotalTimeMillis())); //標(biāo)準(zhǔn)請(qǐng)求-應(yīng)答模型 if (baseApiRequest == null) { return response; } if ((response != null && response instanceof BaseApiResponse) == false) { return response; } System.out.println("Spring AOP方式記錄標(biāo)準(zhǔn)請(qǐng)求-應(yīng)答模型服務(wù)日志"); Object request = reqParamArr[index]; BaseApiResponse bizResp = (BaseApiResponse) response; //記錄日志 String msg = String.format("請(qǐng)求:%s======應(yīng)答:%s======總耗時(shí)(毫秒):%s", SerializeUtil.Serialize(request), SerializeUtil.Serialize(response), watch.getTotalTimeMillis()); if (bizResp.getIsOK() == true) { logger.info(msg); } else { logger.error(msg);//記錄錯(cuò)誤日志 } return response; } }
標(biāo)準(zhǔn)的請(qǐng)求-應(yīng)答模型,我們都會(huì)定義請(qǐng)求基類和應(yīng)答基類,本文示例給到的是BaseApiRequest和BaseApiResponse,搜集日志時(shí),可以對(duì)錯(cuò)誤日志加以區(qū)分特殊處理。
注意上述代碼中的@Around環(huán)繞通知,參數(shù)類型是ProceedingJoinPoint,而前面第一個(gè)示例的@Before前置通知,參數(shù)類型是JoinPoint。
下面是AspectJ通知和增強(qiáng)的5種模式:
@Before前置通知,在目標(biāo)方法執(zhí)行前實(shí)施增強(qiáng),請(qǐng)求參數(shù)JoinPoint,用來(lái)連接當(dāng)前連接點(diǎn)的連接細(xì)節(jié),一般包括方法名和參數(shù)值。在方法執(zhí)行前進(jìn)行執(zhí)行方法體,不能改變方法參數(shù),也不能改變方法執(zhí)行結(jié)果。
@After 后置通知,請(qǐng)求參數(shù)JoinPoint,在目標(biāo)方法執(zhí)行之后,無(wú)論是否發(fā)生異常,都進(jìn)行執(zhí)行的通知。在后置通知中,不能訪問(wèn)目標(biāo)方法的執(zhí)行結(jié)果(因?yàn)橛锌赡馨l(fā)生異常),不能改變方法執(zhí)行結(jié)果。
@AfterReturning 返回通知,在目標(biāo)方法執(zhí)行后實(shí)施增強(qiáng),請(qǐng)求參數(shù)JoinPoint,其能訪問(wèn)方法執(zhí)行結(jié)果(因?yàn)檎?zhí)行)和方法的連接細(xì)節(jié),但是不能改變方法執(zhí)行結(jié)果。(注意和后置通知的區(qū)別)
@AfterThrowing 異常通知,在方法拋出異常后實(shí)施增強(qiáng),請(qǐng)求參數(shù)JoinPoint,throwing屬性代表方法體執(zhí)行時(shí)候拋出的異常,其值一定與方法中Exception的值需要一致。
@Around 環(huán)繞通知,請(qǐng)求參數(shù)ProceedingJoinPoint,環(huán)繞通知類似于動(dòng)態(tài)代理的全過(guò)程,ProceedingJoinPoint類型的參數(shù)可以決定是否執(zhí)行目標(biāo)方法,而且環(huán)繞通知必須有返回值,返回值即為目標(biāo)方法的返回值。
關(guān)于如何在Spring Boot中使用AOP實(shí)現(xiàn)REST接口就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。