您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關SpringBoot中如何使用Aop,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
AOP(Aspect OrientedProgramming):面向切面編程,面向切面編程(也叫面向方面編程),是目前軟件開發中的一個熱點,也是Spring框架中的一個重要內容。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
利用AOP可以對我們邊緣業務進行隔離,降低無關業務邏輯耦合性。提高程序的可重用性,同時提高了開發的效率。一般用于日志記錄,性能統計,安全控制,權限管理,事務處理,異常處理,資源池管理
。使用場景
面向對象編程(OOP)的好處是顯而易見的,缺點也同樣明顯。當需要為多個不具有繼承關系的對象添加一個公共的方法的時候,例如日志記錄、性能監控等,如果采用面向對象編程的方法,需要在每個對象里面都添加相同的方法,這樣就產生了較大的重復工作量和大量的重復代碼,不利于維護。面向切面編程(AOP)是面向對象編程的補充,簡單來說就是統一處理某一“切面”的問題的編程思想。如果使用AOP的方式進行日志的記錄和處理,所有的日志代碼都集中于一處,不需要再每個方法里面都去添加,極大減少了重復代碼。
通知(Advice)包含了需要用于多個應用對象的橫切行為,完全聽不懂,沒關系,通俗一點說就是定義了“什么時候”和“做什么”。
連接點(Join Point)是程序執行過程中能夠應用通知的所有點。
切點(Poincut)是定義了在“什么地方”進行切入,哪些連接點會得到通知。顯然,切點一定是連接點。
切面(Aspect)是通知和切點的結合。通知和切點共同定義了切面的全部內容——是什么,何時,何地完成功能。
引入(Introduction)允許我們向現有的類中添加新方法或者屬性。
織入(Weaving)是把切面應用到目標對象并創建新的代理對象的過程,分為編譯期織入、類加載期織入和運行期織入。
在springboot中使用aop要導aop依賴
<!--aop 切面--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
注意這里版本依賴于spring-boot-start-parent
父pom中的spring-boot-dependencies
這里我們定義一個controller
用于攔截所有請求的記錄
@RestController public class AopController { @RequestMapping("/hello") public String sayHello(){ System.out.println("hello"); return "hello"; } }
SpringBoot在使用切面的時候采用@Aspect
注解對POJO進行標注,該注解表明該類不僅僅是一個POJO,還是一個切面容器
切點是通過@Pointcut
注解和切點表達式
定義的。
@Pointcut注解可以在一個切面內定義可重用
的切點。
由于Spring切面粒度最小是達到方法級別
,而execution表達式
可以用于明確指定方法返回類型,類名,方法名和參數名等與方法相關的部件,并且實際中,大部分需要使用AOP的業務場景也只需要達到方法級別即可,因而execution表達式的使用是最為廣泛的。如圖是execution表達式的語法:
execution表示在方法執行的時候觸發。以“”開頭,表明方法返回值類型為任意類型。然后是全限定的類名和方法名,“”可以表示任意類和任意方法。對于方法參數列表,可以使用“..”表示參數為任意類型。如果需要多個表達式,可以使用“&&”、“||”和“!”完成與、或、非的操作。
通知有五種類型,分別是:
前置通知(@Before):在目標方法調用之前調用通知
后置通知(@After):在目標方法完成之后調用通知
環繞通知(@Around):在被通知的方法調用之前和調用之后執行自定義的方法
返回通知(@AfterReturning):在目標方法成功執行之后調用通知
異常通知(@AfterThrowing):在目標方法拋出異常之后調用通知
代碼中定義了三種類型的通知,使用@Before注解標識前置通知,打印“beforeAdvice...”,使用@After注解標識后置通知,打印“AfterAdvice...”,使用@Around注解標識環繞通知,在方法執行前和執行之后分別打印“before”和“after”。這樣一個切面就定義好了,代碼如下:
@Aspect @Component public class AopAdvice { @Pointcut("execution (* com.shangguan.aop.controller.*.*(..))") public void test() { } @Before("test()") public void beforeAdvice() { System.out.println("beforeAdvice..."); } @After("test()") public void afterAdvice() { System.out.println("afterAdvice..."); } @Around("test()") public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) { System.out.println("before"); try { proceedingJoinPoint.proceed(); } catch (Throwable t) { t.printStackTrace(); } System.out.println("after"); } }
運行結果
這里我們通過一個日志記錄場景來完整的使用Aop切面業務層只需關心代碼邏輯實現而不用關心請求參數和響應參數的日志記錄
那么首先我們需要自定義一個全局日志記錄的切面類GlobalLogAspect
然后在該類添加@Aspect注解,然后在定義一個公共的切入點(Pointcut),指向需要處理的包,然后在定義一個前置通知(添加@Before注解),后置通知(添加@AfterReturning)和環繞通知(添加@Around)方法實現即可
package cn.soboys.core; import lombok.Data; /** * @author kenx * @version 1.0 * @date 2021/6/18 18:48 * 日志信息 */ @Data public class LogSubject { /** * 操作描述 */ private String description; /** * 操作用戶 */ private String username; /** * 操作時間 */ private String startTime; /** * 消耗時間 */ private String spendTime; /** * URL */ private String url; /** * 請求類型 */ private String method; /** * IP地址 */ private String ip; /** * 請求參數 */ private Object parameter; /** * 請求返回的結果 */ private Object result; /** * 城市 */ private String city; /** * 請求設備信息 */ private String device; }
package cn.soboys.core; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.reflect.MethodSignature; import java.lang.reflect.Method; /** * @author kenx * @version 1.0 * @date 2021/6/18 14:52 * 切面 */ public class BaseAspectSupport { public Method resolveMethod(ProceedingJoinPoint point) { MethodSignature signature = (MethodSignature)point.getSignature(); Class<?> targetClass = point.getTarget().getClass(); Method method = getDeclaredMethod(targetClass, signature.getName(), signature.getMethod().getParameterTypes()); if (method == null) { throw new IllegalStateException("無法解析目標方法: " + signature.getMethod().getName()); } return method; } private Method getDeclaredMethod(Class<?> clazz, String name, Class<?>... parameterTypes) { try { return clazz.getDeclaredMethod(name, parameterTypes); } catch (NoSuchMethodException e) { Class<?> superClass = clazz.getSuperclass(); if (superClass != null) { return getDeclaredMethod(superClass, name, parameterTypes); } } return null; } }
GlobalLogAspect
類
package cn.soboys.core; import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.TimeInterval; import cn.hutool.json.JSONUtil; import cn.soboys.core.utils.HttpContextUtil; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @author kenx * @version 1.0 * @date 2021/6/18 15:22 * 全局日志記錄器 */ @Slf4j @Aspect @Component public class GlobalLogAspect extends BaseAspectSupport { /** * 定義切面Pointcut */ @Pointcut("execution(public * cn.soboys.mallapi.controller.*.*(..))") public void log() { } /** * 環繞通知 * * @param joinPoint * @return */ @Around("log()") public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { LogSubject logSubject = new LogSubject(); //記錄時間定時器 TimeInterval timer = DateUtil.timer(true); //執行結果 Object result = joinPoint.proceed(); logSubject.setResult(result); //執行消耗時間 String endTime = timer.intervalPretty(); logSubject.setSpendTime(endTime); //執行參數 Method method = resolveMethod(joinPoint); logSubject.setParameter(getParameter(method, joinPoint.getArgs())); HttpServletRequest request = HttpContextUtil.getRequest(); // 接口請求時間 logSubject.setStartTime(DateUtil.now()); //請求鏈接 logSubject.setUrl(request.getRequestURL().toString()); //請求方法GET,POST等 logSubject.setMethod(request.getMethod()); //請求設備信息 logSubject.setDevice(HttpContextUtil.getDevice()); //請求地址 logSubject.setIp(HttpContextUtil.getIpAddr()); //接口描述 if (method.isAnnotationPresent(ApiOperation.class)) { ApiOperation apiOperation = method.getAnnotation(ApiOperation.class); logSubject.setDescription(apiOperation.value()); } String a = JSONUtil.toJsonPrettyStr(logSubject); log.info(a); return result; } /** * 根據方法和傳入的參數獲取請求參數 */ private Object getParameter(Method method, Object[] args) { List<Object> argList = new ArrayList<>(); Parameter[] parameters = method.getParameters(); Map<String, Object> map = new HashMap<>(); for (int i = 0; i < parameters.length; i++) { //將RequestBody注解修飾的參數作為請求參數 RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class); //將RequestParam注解修飾的參數作為請求參數 RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class); String key = parameters[i].getName(); if (requestBody != null) { argList.add(args[i]); } else if (requestParam != null) { map.put(key, args[i]); } else { map.put(key, args[i]); } } if (map.size() > 0) { argList.add(map); } if (argList.size() == 0) { return null; } else if (argList.size() == 1) { return argList.get(0); } else { return argList; } } }
關于SpringBoot中如何使用Aop就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。