咱們都知道,Spring 框架做爲後端主流框架之一,最有特色的三部分就是IOC控制反轉、依賴注入、以及AOP切面。固然AOP做爲一個Springhtml
的重要組成模塊,固然IOC是不依賴於Spring框架的,這就說明你有權選擇是否要用AOP來完成一些業務。前端
AOP面向切面編程,經過另外一種思考的方式,來彌補面向對象編程OOP當中的不足,OOP當中最重要的單元是類,因此萬物皆對象,萬物皆是git
對象類。而在AOP的模塊單元中,最基礎的單元是切面,切面對切點進行模塊化的管理。web
最後再提一句:Spring當中的AOP是利用Java的代理模式實現的spring
讓咱們從一些基礎的術語開始瞭解面向切面編程AOP,術語不是特別的直觀,最好的方式就是經過文本理解+圖像理解+代碼實例理解數據庫
這樣對於咱們來講纔是真正意義上的理解。編程
說了這麼多,都感受迷迷糊糊的,咱們首先來看一個例子吧,經過這個例子來理解AOP切面,經過例子在具體說明後端
版本信息:Springboot 2.1.6api
添加依賴 Web AOPapp
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
寫一個最簡單的控制器
@RestController @RequestMapping("/") public class AuthController { private Logger logger = LoggerFactory.getLogger(AuthController.class); @GetMapping("login") public String login(String user,String pass) { logger.info("login--->user",user); logger.info("login--->pass",pass); return "success"; } }
這樣的控制器在SpringBoot的Web開發當中是很常見的,收到前端的請求,將參數進行校驗,咱們這裏爲了簡單,不做校驗,只是打印出來,爲了配合咱們後面的AOP的理解而
作的一個最簡單的控制器,測試請求如下路徑,控制檯打印如下內容
http://localhost:8080/login?user=root&pass=root
2019-11-09 09:59:00.585 INFO 11100 --- [nio-8080-exec-1] c.e.demo.controller.AuthController : login--->user
2019-11-09 09:59:00.588 INFO 11100 --- [nio-8080-exec-1] c.e.demo.controller.AuthController : login--->pass
一、定義一個切面類,加入@Aspect註解和@Component註解
@Aspect @Component public class WebLogAsp {}
@Aspect 註解將找個類定義爲一個切面對象,經過@Component註解將這個類對象注入到IOC容器,交給Spring來進行管理。
二、定義一個切入點 經過@Pointcut
@Pointcut("execution(public * com.example.demo.controller.*.*(..))") public void controllerLog(){}
@Pointcut這個註解主要用來定義切入點,經過表達式的方式,來告訴Spring,我這個切點要切到什麼位置,經常使用的就是execution去匹配鏈接點。
主要來講一下execution 匹配表達是的表達方法,咱們按照如下的例子來講明:
@Pointcut("execution(public * com.example.demo.controller.*.*(..))")
語法:execution( [方法修飾符(可選)]__返回類型__類路徑__方法名__(參數)__[異常模式(可選)] )
這裏我用下劃線來代替空格,比較直觀的能夠看出,咱們這個例子裏面,我將這個切點切入到com.example.demo.controller包下全部類的全部方法上面。
*就是通配符,(..)表明任意多個參數,也就說明我切入到的方法它的參數我是不限定的,能夠有任意個參數。
三、定義通知項,@Before定義一個前置通知
@Before("controllerLog()") public void beforeAdvice(JoinPoint joinPoint){}
@Before註解傳入字符串方法名,也就是咱們上面定義的切入點的方法名,告訴它這個通知是在切入點 controllerLog()上面執行的通知,它會在切入點方法執行以前率先執行
這個方法傳入了一個JoinPoint 對象,也就是咱們所說的鏈接點對象,鏈接點能夠理解爲切入點方法執行時候所產生的一個對象。
這裏有幾個具體的方法很重要,須要說明一下:
Object[] getArgs();獲取切入點方法執行時候傳入的參數。
Object getTarget();獲取的是切入方法所在的類對象,簡單理解例子裏面的切入點是login()方法,因此返回的對象就是AuthController對象
Object getThis();返回AOP框架爲目標對象生成的代理對象。
這裏將詳細代碼舉例,經過打印的方式進行輸出,經過SpringMvc的RequestContextHolder獲取線程的請求對象,這裏咱們能夠拿到當前請求
的一些具體參數,好比訪問人的IP信息,它所請求的URL以及請求所帶的參數等待。
具體請參考:https://www.runoob.com/servlet/servlet-client-request.html
@Before("controllerLog()") public void beforeAdvice(JoinPoint joinPoint){ logger.info("前置通知開始--->"); /*獲取當前請求的HttpServletRequest*/ RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest(); logger.info("URL-->"+request.getRequestURL().toString()); logger.info("IP-->"+request.getRemoteAddr()); logger.info("HTTP_Method-->"+request.getMethod()); logger.info("Request_args-->"+ Arrays.toString(joinPoint.getArgs())); logger.info("前置通知結束--->"); }
四、環繞通知方法 @Around
首先來看一部分代碼,這裏傳遞了一個 ProceedingJoinPoint 對象,它是JoinPoint 切點對象的一個子類,也能夠爲它是切點所在的方法執行時候所產生的一個對象
這裏最重要的一個方法當屬於 proceed() ;執行proceed()就表示執行切點所在的方法執行,它會返回一個Object對象,也就是目標方法所返回的對象。
@Around("controllerLog()") public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { logger.info("環繞通知開始-->"); Object result = null; /*proceed()方法表示鏈接點方法執行 result 爲鏈接點方法的返回值*/ result = proceedingJoinPoint.proceed(); logger.info("環繞通知返回值-->" + result); logger.info("環繞通知結束-->"); return result; }
踩坑:環繞方法必定要將result對象返回出去,若定義爲void 無返回值的話,將在後面的@AfterReturning 後置通知中沒法取到值!!!!
五、後置通知 @AfterReturning 鏈接點正常執行完畢後的通知
@AfterReturning(value = "controllerLog()",returning = "obj") public void afterReturning(JoinPoint joinPoint,Object obj){ logger.info("後置通知正常返回-->"+obj); }
這裏傳遞了兩個參數,第一個參數仍是指定切點,第二個參數指定的是返回值,這個返回值就是鏈接點所返回的參數值,須要在環繞通知裏面將其返回出來才能夠
取到值,否則返回的就是Null
六、異常通知 @AfterThrowing
@AfterThrowing(value = "controllerLog()", throwing = "ex") public void afterThrowing(JoinPoint joinPoint,Exception ex){ logger.info("異常通知-->"+ex.getMessage()); }
這裏和上面的後置通知差很少,不過第二個參數是一個異常類型對象,能夠取出發生異常時候的異常信息。
七、最終通知 @After
這個和@Before同樣,再也不細說!
這裏能夠很明確的看到,一個正常的返回路徑,相信我不說你們均可以看的清楚,畫圖最清楚的,接收到請求後,首先工做的是環繞通知,
環繞通知裏面執行proceed()方法後,纔會進入鏈接點方法執行,Before是在鏈接點方法執行以前所執行的,然後執行鏈接點方法內容,
正常執行完後,不發生異常狀況,環繞通知結束,就會執行最終通知After After執行完畢後,纔會執行後置通知AfterReturning
咱們在控制器裏面模擬進行一個異常的拋出,看一下執行的順序
logger.info("將要拋出異常"); int a = 1/0;
能夠很明明顯的看到,咱們在控制器裏面經過一個除0的操做,進行拋出一個異常,這裏環繞通知是沒有執行完畢的,由於拋出異常,中止了運行
從接收到請求開始,進入環繞通知,而後環繞通知裏面調用了proceed()方法,其實就是讓鏈接點的方法開始運行,這時候前置通知首先跑起來,能夠看到
前置通知是完徹底全走完的。前置通知完畢後,下來是鏈接點的方法運行起來了,這裏由於拋出了異常,沒有進行捕獲,最終通知仍是同樣正常執行,不過最後執行的是異常的通知,
而不是像上面同樣。這裏不一樣的地方仍是要多進行理解。
經過對SpringAOP框架的研究,以及畫圖的理解,可以更深入的理解裏面運行時候的內層含義,以及AOP的一些應用,好比系統裏面的日誌系統,經過學習AOP
完徹底全能夠很輕鬆的經過切面,將須要記錄的日誌信息存到數據庫, 節約大量時間。
https://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/aop-api.html
http://shouce.jb51.net/spring/aop.html
http://www.javashuo.com/article/p-riqaztfy-gq.html