目錄
1. 以增加方法執行時間為例使用AOP
1.1 引入AOP依賴
1.2?編寫AOP程序
2. AOP的重要概念
3. AOP通知類型與通知方法標注
3.1 在通知方法前使用對應注解
3.2 使用@Pointcut注解提取公共切點表達式
3.3 跨類使用切點
3.4 切面類排序
1. 以增加方法執行時間為例使用AOP
1.1 引入AOP依賴
在pom.xml中增加關于AOP的配置:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>
1.2?編寫AOP程序
以圖書管理系統為例,為每個方法增加耗時計算與日志打印。
創建aspect包,在其下創建TimeAspect類:
package com.example.bookmanagementsystem.aspect;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;@Aspect
@Slf4j
@Component
public class TimeAspect {@Around("execution(* com.example.bookmanagementsystem.controller.*.*(..))")public Object timeCost(ProceedingJoinPoint joinPoint) throws Throwable {long start=System.currentTimeMillis();// 執行目標方法Object result=joinPoint.proceed();long end=System.currentTimeMillis();log.info(joinPoint+"消耗時間:"+(end-start)+"ms");return result;}
}
關于注解:
1、@Aspect:表示該類是一個切面類;
2、@Around:用于指明切面類的作用域與作用方式(在哪個環節,對哪些方法);
關于切面類成員方法:
1、timeCost方法的參數是一個ProceedingJoinPoint類型的對象,表示目標方法;
2、代碼被分為三大部分:
2. AOP的重要概念
1、切點:一組通過表達式描述的規則;
2、連接點:切面作用/描述的方法,即被AOP控制的目標方法;
3、通知:具體要做處理的邏輯;
4、切面:切點+通知即切面,一個類可以有多個切面;
以上述切面類為例:
3. AOP通知類型與通知方法標注
1、@Around:環繞通知,此注解標注的通知方法在目標方法前、后都被執行;(最常用)
2、@Before:前置通知,此注解標注的通知方法在目標方法前被執行;
3、@After:
后置通知,此注解標注的通知方法在目標方法后被執行,無論是否發生異常都會執行;
4、@AfterReturning:
返回后通知,此注解標注的通知方法在目標方法后被執行,有異常不會執行;
5、@AfterThrowing:異常后通知,此注解標注的通知方法在異常后被執行;
3.1 在通知方法前使用對應注解
現在controller包下創建一個HelloController類,編寫方法作為目標方法:
由于@AfterReturning與@AfterThrowing標注的方法在異常發生與否時返回不同,故編寫兩個方法使其滿足一個正常運行,一個運行時報異常:
package com.zhouyou.demos.controller;import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class HelloController {@RequestMapping("/test1")public String test1(){return "hello";}@RequestMapping("/test2")public int test2(){return 10/0;}
}
在aspect包下創建TestAspect類,用于編寫通知方法:
package com.zhouyou.demos.aspect;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Aspect
@Slf4j
@Component
public class AspectDemo {@Around("execution(* com.zhouyou.demos.controller.*.*(..))")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {log.info("AspectDemo around 前");Object result=joinPoint.proceed();log.info("AspectDemo around 后");return result;}@Before("execution(* com.zhouyou.demos.controller.*.*(..))")public void doBefore(){log.info("AspectDemo before");}@After("execution(* com.zhouyou.demos.controller.*.*(..))")public void doAfter(){log.info("AspectDemo after");}@AfterReturning("execution(* com.zhouyou.demos.controller.*.*(..))")public void doAfterReturning(){log.info("AspectDemo afterReturning");}@AfterThrowing("execution(* com.zhouyou.demos.controller.*.*(..))")public void doAfterThrowing(){log.info("AspectDemo afterThrowing");}
}
啟動項目,依次根據路由映射訪問test1方法和test2方法,可查看日志觀察通知方法執行順序:
3.2 使用@Pointcut注解提取公共切點表達式
上述使用方法中,在使用對應注解標注通知方法時,需要重復編寫公共切點表達式,這很不方便;
可使用@Pointcut提取公共切點表達式,在后續使用公共切點表達式時則直接使用@Pointcut標注的方法名即可;
修改3.1中的代碼
package com.zhouyou.demos.aspect;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Aspect
@Slf4j
@Component
public class AspectDemo {@Pointcut("execution(* com.zhouyou.demos.controller.*.*(..))")private void pc(){}@Around("pc()")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {log.info("AspectDemo around 前");Object result=joinPoint.proceed();log.info("AspectDemo around 后");return result;}@Before("pc()")public void doBefore(){log.info("AspectDemo before");}@After("pc()")public void doAfter(){log.info("AspectDemo after");}@AfterReturning("pc()")public void doAfterReturning(){log.info("AspectDemo afterReturning");}@AfterThrowing("pc()")public void doAfterThrowing(){log.info("AspectDemo afterThrowing");}
}
3.3 跨類使用切點
現在aspect包中再創建一個切面類aspectDemo2,以@Before為例使用aspectDemo的切點。
package com.zhouyou.demos.aspect;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;@Aspect
@Slf4j
@Component
public class AspectDemo2 {@Before("com.zhouyou.demos.aspect.AspectDemo.pc()")public void doBefore(){log.info("AspectDemo2 doBefore");}
}
啟動項目,根據方法路由映射即可在日志處觀察到通知方法的具體執行順序情況;
使用方法注意事項:
(1)在跨類使用公共切點時,需要使用全限定類名;
(2)在當前類中,若在其類中實現的切點需要在其他類中使用,則切點必須以public修飾。
使用private修飾的切點只能在當前類中使用;
3.4 切面類排序
現aspect包下由AspectDemo、AspectDemo1、AspectDemo2三個切面類,并在每個切面類中實現@Before通知和@After通知。
創建目標方法并指定路由映射,并進行訪問,查看日志輸出:
在采取默認排序的情況下,默認采取按照切面類的類名字母排序:
對于@Before通知,排名越靠前的先執行;
對于@After通知,排名越靠后的先執行;
但這并不便于管理,Spring提供了@Order注解,用于給切面類設置優先級:
對于@Before通知,@Order中的數字越小越先執行;
對于@After通知,@Order中的數字越大越先執行;
現對AspectDemo切面類使用Order(3)、AspectDemo1切面類使用Order(2)、AspectDemo2切面類使用Order(1),啟動程序查看日志輸出: