AOP基礎

背景問題

  1. 代碼混亂:越來越多的非業務需求(日誌和驗證等)加入後,原有的業務方法急劇膨脹.每個方法在處理核心邏輯的同時還必須兼顧其他多個關注點
  2. 代碼分散:以日誌需求爲例,只是爲了滿足這個單一需求,就不得不在多個模塊(方法)裏多次重複相同的日誌打印代碼.如果日誌需求發生變化,必須修改所有模塊

AOP簡介

  1. AOP(Aspect-Oriented Programming,面向切面編程):是一種新的方法論,是對傳統OOP(Object-Oriented Programming,面向對象編程)的補充
  2. AOP的主要編程對象是切面(aspect),而切面模塊化橫切關注點
  3. 在應用AOP編程時,仍然需要定義公共功能,但可以明確的定義這個功能在哪裏,以什麼方式應用,並且不必修改受影響的類.這樣一來橫切關注點就被模塊化到特殊的對象(切面)裏.
  4. AOP的好處:
    1. 每個事物邏輯位於一個位置,代碼不分散,便於維護和升級
    2. 業務模塊更簡潔,只包含核心業務代碼

AOP術語

  1. 切面(Aspect):橫切關注點(跨越應用程序多個模塊的功能)被模塊化的特殊對象
  2. 通知(Advice):切面必須要完成的工作
  3. 目標(Target):被通知的對象
  4. 代理(Proxy):向目標對象應用通知之後創建的對象
  5. 連接點(Joinpoint):程序執行的某個特定位置:如類某個方法調用前、調用後、方法拋出異常後等。連接點由兩個信息確定:方法表示的程序執行點;相對點表示的方位。例如ArithmethicCalculator#add()方法執行前的連接點,執行點爲ArithmethicCalculator#add();方位爲該方法執行前的位置
  6. 切點(pointcut):每個類都擁有多個連接點:例如ArithmethicCalculator的所有方法實際上都是連接點,即連接點是程序類中客觀存在的事務AOP通過切點定位到特定的連接點。類比:連接點相當於數據庫中的記錄,切點相當於查詢條件。切點和連接點不是一對一的關係,一個切點匹配多個連接點,切點通過org.springframework.aop.Pointcut接口進行描述,它使用類和方法作爲連接點的查詢條件

用AspectJ註解聲明切面

  1. 要在Spring中聲明AspectJ切面,只需要在IOC容器中將切面聲明爲Bean實例.當在Spring IOC容器中初始化AspectJ切面之後,Spring IOC容器就會爲那麼與AspectJ切面相匹配的Bean創建代理
  2. 在AspectJ註解中,切面只是一個帶有@Aspect註解的Java類
  3. 通知是標註有某種註解的簡單的Java方法
  4. AspectJ支持5中類型的通知註解:
    1. @Before:前置通知,在方法執行之前執行
    2. @After:後置通知,在方法執行之後執行
    3. @AfterRunning:返回通知,在方法返回結果之後執行
    4. @AfterThrowing:異常通知,在方法拋出異常之後執行
    5. @Around:環繞通知,圍繞着方法執行

前置通知

  1. @Before(“execution(方法路徑)”)
  2. AspectJ:Java社區裏最完整最流行的AOP框架,在Spring2.0以上版本中,可以使用基於AspectJ註解或基於XML配置的AOP
  3. 在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

  1. 先在配置文件中配置bean
  2. 配置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>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章