背景問題
- 代碼混亂:越來越多的非業務需求(日誌和驗證等)加入後,原有的業務方法急劇膨脹.每個方法在處理核心邏輯的同時還必須兼顧其他多個關注點
- 代碼分散:以日誌需求爲例,只是爲了滿足這個單一需求,就不得不在多個模塊(方法)裏多次重複相同的日誌打印代碼.如果日誌需求發生變化,必須修改所有模塊
AOP簡介
- AOP(Aspect-Oriented Programming,面向切面編程):是一種新的方法論,是對傳統OOP(Object-Oriented Programming,面向對象編程)的補充
- AOP的主要編程對象是切面(aspect),而切面模塊化橫切關注點
- 在應用AOP編程時,仍然需要定義公共功能,但可以明確的定義這個功能在哪裏,以什麼方式應用,並且不必修改受影響的類.這樣一來橫切關注點就被模塊化到特殊的對象(切面)裏.
- AOP的好處:
- 每個事物邏輯位於一個位置,代碼不分散,便於維護和升級
- 業務模塊更簡潔,只包含核心業務代碼
AOP術語
- 切面(Aspect):橫切關注點(跨越應用程序多個模塊的功能)被模塊化的特殊對象
- 通知(Advice):切面必須要完成的工作
- 目標(Target):被通知的對象
- 代理(Proxy):向目標對象應用通知之後創建的對象
- 連接點(Joinpoint):程序執行的某個特定位置:如類某個方法調用前、調用後、方法拋出異常後等。連接點由兩個信息確定:方法表示的程序執行點;相對點表示的方位。例如ArithmethicCalculator#add()方法執行前的連接點,執行點爲ArithmethicCalculator#add();方位爲該方法執行前的位置
- 切點(pointcut):每個類都擁有多個連接點:例如ArithmethicCalculator的所有方法實際上都是連接點,即連接點是程序類中客觀存在的事務。AOP通過切點定位到特定的連接點。類比:連接點相當於數據庫中的記錄,切點相當於查詢條件。切點和連接點不是一對一的關係,一個切點匹配多個連接點,切點通過org.springframework.aop.Pointcut接口進行描述,它使用類和方法作爲連接點的查詢條件
用AspectJ註解聲明切面
- 要在Spring中聲明AspectJ切面,只需要在IOC容器中將切面聲明爲Bean實例.當在Spring IOC容器中初始化AspectJ切面之後,Spring IOC容器就會爲那麼與AspectJ切面相匹配的Bean創建代理
- 在AspectJ註解中,切面只是一個帶有@Aspect註解的Java類
- 通知是標註有某種註解的簡單的Java方法
- AspectJ支持5中類型的通知註解:
- @Before:前置通知,在方法執行之前執行
- @After:後置通知,在方法執行之後執行
- @AfterRunning:返回通知,在方法返回結果之後執行
- @AfterThrowing:異常通知,在方法拋出異常之後執行
- @Around:環繞通知,圍繞着方法執行
前置通知
- @Before(“execution(方法路徑)”)
- AspectJ:Java社區裏最完整最流行的AOP框架,在Spring2.0以上版本中,可以使用基於AspectJ註解或基於XML配置的AOP
- 在Spring中啓用AspectJ註解支持:
1. 要在Spring應用中使用AspectJ註解,必須在classpath下包含AspectJ類庫:aoplliance.jar、aspectj.weaver.jar和spring-aspects.jar
2. 將aopSchema添加到<beans>
根元素中.
3. 要在SpringIOC容器中啓用AspectJ註解支持,只要在Bean配置文件中定義一個空的XML元素<aop:aspectj-autoproxy>
4. 當Spring IOC容器偵測到Bean配置文件中的<aop:aspectj-autoproxy>
元素時,會自動爲與AspectJ切面匹配的Bean創建代理
/聲明該方法是一個前置通知:在目標方法開始前執行/
@Before("execution(public int spring_aop.ArithmeticCalculatorImpl.*(String))")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("The method is begining");
System.out.println("The method is " + methodName + " and args are " + args);
}
後置通知
// 聲明該方法是一個後置通知:在目標方法結束後執行(無論是否發生異常)
// 在後置通知中不能訪問目標方法執行的結果
@After("execution(public int spring_aop.ArithmeticCalculatorImpl.*(String))")
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("The method is end");
System.out.println("The method is " + methodName + " and args are " + args);
}
返回通知
// 聲明該方法是一個返回通知:在目標方法正常結束後執行
// 在返回通知中可以訪問目標方法執行的結果
@AfterReturning(value = "execution(public int spring_aop.ArithmeticCalculatorImpl.*(String))", returning = "result")
public void afterReturnMethod(Object result){
System.out.println("The method is end");
System.out.println("The result is " + result);
}
異常通知
// 聲明該方法是一個異常通知:在目標方法拋出異常後執行
@AfterThrowing(value = "execution(public int spring_aop.ArithmeticCalculatorImpl.*(String))", throwing = "exception")
public void afterThrowMethod(NullPointerException exception){
System.out.println("The method throwing exception ");
System.out.println("The exception is " + exception);
}*/
環繞通知
// 聲明該方法是一個環繞通知,需要攜帶ProceedingJoinPoint類型的參數
// 環繞通知類似於動態代理的全過程;ProceedingJoinPoint類型的參數可以決定是否執行目標方法
// 且環繞通知必須有返回值,返回值即爲目標方法的返回值
@Around("execution(public int spring_aop.ArithmeticCalculatorImpl.*(String))")
public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint){
Object result = null;
String methodName = proceedingJoinPoint.getSignature().getName();
// 執行目標方法
try{
// 前置通知
System.out.println("The method:" + methodName + " begings with:" + Arrays.asList(proceedingJoinPoint.getArgs()));
result = proceedingJoinPoint.proceed();
System.out.println("The result is:" + result);
} catch (Throwable throwable) {
System.out.println("異常通知");
throwable.printStackTrace();
}
// 後置通知
System.out.println("The method is ended");
return result;
}
引入通知(較少使用)
引入通知是一種特殊的通知類型。它通過爲接口提供實現類,允許對象動態的實現接口,就像對象已經在運行時擴展了實現類一樣
切面的優先級
使用@Order類註解指定優先級,值越小優先級越高如@Order(2)
重用切點表達式
可以定義一個方法,用於聲明切入點表達式.一般地,該方法中不再需要填入其他的代碼,例如:
在切面類中定義方法:
//使用@Pointcut來聲明切入點表達式,其他通知直接使用方法名來引用當前的切入點表達式
@Pointcut("execution(public int spring_aop.ArithmeticCalculatorImpl.*(String))")
public void declareJointPointExpression(){}
然後,上面前置通知、後置通知…使用引用“execution(public int spring_aop.ArithmeticCalculatorImpl.*(String))”的地方可以替換爲declareJointPointExpression(),如果是在其他類(包)中引用,需要帶上類名(及包名)
基於配置文件配置AOP
- 先在配置文件中配置bean
- 配置AOP:
//<!-- 配置AOP -->
<aop:config>
<!-- 配置切點表達式 -->
<aop:pointcut expression="execution(public int spring_aop.ArithmeticCalculatorImpl.*(String))" id="pointcut">
<!-- 配置切面及通知-->
<aop:aspect ref="切面bean的id" order="2">
<aop:before method="beforeMethod" pointcut-ref="pointcut">
</aop:aspect>
</aop:config>