Spring框架系列(二)-AOP

附上示例程序的github地址:https://github.com/bjtudujunlin/SpringDataExample


1、 AOP定義

AOP作爲Spring的核心功能之一,用來解決服務之間依賴的耦合問題,通過定義切點,實現服務分離,將普遍依賴的非業務服務從業務服務之中分離開來。AOP的理論知識見上一章節Spring框架系列(一)-整體架構。

Spring中AOP借鑑了AspectJ的實現,以提供註解驅動,編程模型幾乎與編寫成熟的AspectJ註解切面完全一致。Spring AOP和AspectJ的區別在於兩者作用範圍不同,Spring AOP的切點只能定義在對象方法上,AspectJ不僅能提供方法的攔截,還能提供構造器或屬性攔截。實際運用中根據自己需求進行選擇,一般基於方法的攔截已經能夠滿足日常應用。

Spring AOP支持的通知類型如下:

前置通知(Before)

在目標方法被調用之前調用通知功能

後置通知(After)

在目標方法完成之後調用通知,此時不會關心方法的輸出是什麼

返回通知(After-returning)

在目標方法成功執行之後調用通知;

異常通知(After-throwing)

在目標方法拋出異常後調用通知;

環繞通知(Around)

通知包裹了被通知的方法,在被通知的方法調用之前和調用之後執行自定義的行爲

2、 應用場景

AOP適用於哪些場景呢,下面將通過舉例進行說明。

 

A、日誌記錄,跟蹤,優化和監控

在軟件測試中,如果想在一個耗時嚴重的操作中找出其耗時的瓶頸時,一般採用的方法是在每個被調用的函數中寫進測試代碼,在運行時打出日誌。如果該操作涉及到的業務邏輯特別複雜時,插入這些測試代碼不僅工作量十分巨大,而且難以維護。如果後期剔除不乾淨,不僅增加了無關的代碼量,還會在執行時造成不必要的資源浪費。

這種情況下使用AOP攔截業務方法的調用,在通知函數裏面打印調用時間,監控各個方法耗時,實現非侵入式攔截,避免使用大量測試代碼。

 

B、 事務處理

事務是單個邏輯工作單元執行的一系列操作,要麼完整地執行,要麼完全地不執行。事務處理可以確保除非事務性單元內的所有操作都成功完成,否則不會永久更新面向數據的資源。通過將一組相關操作組合爲一個要麼全部成功要麼全部失敗的單元,可以簡化錯誤恢復並使應用程序更加可靠。一個邏輯工作單元要成爲事務,必須滿足所謂的ACID(原子性、一致性、隔離性和持久性)屬性。

利用AOP捕獲某方法,在方法執行前開始事務,在異常通知函數中統一執行事務回滾操作,方法執行結束後關閉事務。

 

C、 持久化

狹義的理解: “持久化”僅僅指把域對象永久保存到數據庫中;廣義的理解,“持久化”包括和數據庫相關的各種操作(持久化就是將有用的數據以某種技術保存起來,將來可以再次取出來應用,數據庫技術,將內存數據一文件的形式保存在永久介質中(磁盤等)都是持久化的例子.)。

持久化實際上是數據庫的事務處理,Mybatis、Hibernate等都有自己的一套事務管理機制,但需要在每個添加事務處理的方法中加入開始事務、結束事務的代碼,維護不方便,使用AOP可以解決這個問題,Spring提供了一些內置的事務管理器,如DataSourceTransactionManager、HibernateTransactionManager、JtaTransactionManager等

 

D、資源池

在開發過程中,我們常會用到一些資源池,比如線程池、數據庫連接池。在操作這些資源池之後,往往要將一開始得到的資源池對象釋放回資源池。代碼的一般實現如下:

try { 

    //從資源池獲取一個資源,進行業務處理 

} catch (Exception e) { 

    //異常處理 

}finally { 

    //釋放資源到資源池 

}

在所有調用使用資源池的地方都加入這樣的代碼會顯得特別繁瑣,管理困難。可以使用AOP進行解決。利用AOP添加業務方法的環切操作@Around,將所有獲取資源、異常處理和釋放資源的操作統一在通知方法中實現,業務方法中只需處理獲取資源後的業務。

E、  系統統一的認證、權限管理等

這個也比較好理解,攔截對方法的訪問,在通知方法裏面加入認證和權限管理代碼,實現統一的認證管理。

F、  應用系統的異常捕捉及處理

將系統異常統一進行處理,避免了大量try catch代碼。

3、 Spring AOP實現

這裏提供基於java註解的aop實現。本篇代碼在Spring框架系列(一)-整體架構例子中進行追加。

A、 添加maven依賴

           <properties>

                  <spring.version>4.3.3.RELEASE</spring.version>

                  <aspectj.version>1.6.12</aspectj.version>

           </properties>            

<dependency>

                     <groupId>org.springframework</groupId>

                     <artifactId>spring-core</artifactId>

                     <version>${spring.version}</version>

              </dependency>

 

              <dependency>

                     <groupId>org.springframework</groupId>

                     <artifactId>spring-beans</artifactId>

                     <version>${spring.version}</version>

              </dependency>

 

              <dependency>

                     <groupId>org.springframework</groupId>

                     <artifactId>spring-context</artifactId>

                     <version>${spring.version}</version>

              </dependency>

 

              <dependency>

                     <groupId>org.springframework</groupId>

                     <artifactId>spring-aop</artifactId>

                     <version>${spring.version}</version>

              </dependency>

 

              <dependency>

                     <groupId>org.aspectj</groupId>

                     <artifactId>aspectjrt</artifactId>

                     <version>${aspectj.version}</version>

              </dependency>

 

              <dependency>

                     <groupId>org.aspectj</groupId>

                     <artifactId>aspectjweaver</artifactId>

                     <version>${aspectj.version}</version>

              </dependency>

 

              <dependency>

                     <groupId>junit</groupId>

                     <artifactId>junit</artifactId>

                     <version>3.8.1</version>

                     <scope>test</scope>

              </dependency>

 

B、 添加切點

定義切點通過切點指示器來實現,指示器的表達式定義如下,在實例中,execution(public *iscas.springstudy.Person.Introduce(..))就表示在iscas.springstudy.Person.Introduce這個方法調用時創建切點。


import org.aspectj.lang.JoinPoint;

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.After;

import org.aspectj.lang.annotation.Around;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.aspectj.lang.annotation.Pointcut;

import org.springframework.context.annotation.Configuration;

 

@Configuration

@Aspect

public class VisitLog {

      

       //定義切點,

    @Pointcut("execution(public * iscas.springstudy.Person.Introduce(..))")

    public void myMethod(){};

      

    @Before("myMethod()")

       public void logPeronIntroduce(JoinPoint point){

           //通過JoinPoint參數可以獲取到攔截的方法、參數等

              System.out.println("person introduce "+point.getSignature().getDeclaringTypeName());

       }

      

       @After("myMethod()")

       public void logPeronIntroduceEnd(){

              System.out.println("end visit person introduce");

       }

      

       @Around("myMethod()")

       public void logPeronIntroduceAround(ProceedingJoinPoint point){

              try {

                     System.out.println("before around visit person introduce");

                     point.proceed();//執行被攔截的方法

                     System.out.println("after around visit person introduce");

              } catch (Throwable e) {

                     System.out.println("exception around visit person introduce");

              }

             

       }

}

C、 Spring Xml配置

添加aspect支持,添加自動掃描並把configuration類所在的路徑

       <!-- 配置aop -->

       <context:component-scan base-package="iscas.springstudy.aop" />  <!-- 自動掃描 -->

       <aop:aspectj-autoproxy />

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章