【Spring 學習筆記 (十一)】基於註解的 Spring AOP

語言: CN / TW / HK

寫在前面:kissing_heart:

大一電子資訊工程新生,請多多關照,希望能在 InfoQ 社群 記錄 自己的學習歷程!

【Spring 學習筆記】系列教程基於 Spring 5.2.10.RELEASE 講解。

一、AOP 簡介

前面說過, Spring 的核心思想就是 IoC 和 AOP ,有關 IoC 的內容已經介紹過一部分了,接下來就來講下 Spring 另一大重點: AOP

1、什麼是 AOP

AOP ,“ Aspect Oriented Programming ”,譯為“ 面向切面程式設計 ”,和 OOP(面向物件程式設計)類似, 它也是一種程式設計思想

2、AOP 的作用(特點)

代理模式

Spring AOP 的實現原理是 代理模式 ,AOP 的作用是 通過代理類為原始類增加一些額外功能 :如日誌管理、許可權管理、事務管理、異常管理等一些 非業務性功能

無入侵式

與傳統的公共方法不同,Spring AOP 並不是直接呼叫的,AOP 是通過 橫向的抽取機制 實現的。它將一些 非業務的通用功能 抽取出來單獨維護,並 通過配置檔案或註解的形式定義這些功能 要以哪種方式作用在哪個模組中,可以在無須修改任何業務程式碼的基礎上完成對這些通用功能的呼叫和修改,即 無入侵式 的。

解耦合

事務與非事務功能分離,Spring AOP 還減少程式碼的重複,讓我們更專注於專注業務邏輯程式碼。

3、AOP 相關的專業術語

4、AOP 框架

目前最流行的 AOP 實現(框架)主要有兩個,分別為 Spring AOPAspectJ

  • 注:一般這兩個框架一起整合使用。

二、AOP 入門案例

SpringAOP 的開發有兩種方式,XML 和 註解, 本篇文章及之後的教程都 使用註解開發 演示.

1、新增 AOP 依賴

pom.xml 檔案裡新增 Spring AOPAspectJ 的 jar 包依賴

  • Spring AOP使用純 Java 實現,不需要專門的編譯過程和類載入器,在執行期通過代理方式向目標類織入增強程式碼。

  • AspectJ是一個基於 Java 語言的 AOP 框架。從 Spring2.0 開始,Spring AOP 引入對 Aspect 的支援,AspectJ 擴充套件了 Java 語言,提供了一個專門的編譯器,在編譯時提供橫向程式碼的織入。

<dependencies>    <!--包含Spring AOP:有基本的AOP功能-->    <dependency>        <groupId>org.springframework</groupId>        <artifactId>spring-context</artifactId>        <version>5.2.10.RELEASE</version>    </dependency>    <!--AspectJ框架有更強大的AOP功能-->    <dependency>        <groupId>org.aspectj</groupId>        <artifactId>aspectjweaver</artifactId>        <version>1.9.5</version>    </dependency></dependencies>

複製程式碼

可以看到 spring-context 中已經匯入了 spring-aop ,所以不需要再單獨匯入 spring-aop

2、建立目標介面和實現類

目標介面和實現類就是所謂的 Target(目標),即要被代理的物件。

/*UserDao介面*/public interface UserDao {    public void add();    public void delete();    public void update();    public void select();}/*UserDaoImpl實現類*/@Repositorypublic class UserDaoImpl implements UserDao {    @Override    public void add() {        System.out.println("正在執行 UserDao 的 add 方法");    }    @Override    public void delete() {        System.out.println("正在執行 UserDao 的 delete 方法");    }    @Override    public void update() {        System.out.println("正在執行 UserDao 的 update 方法");    }    @Override    public void select() {        System.out.println("正在執行 UserDao 的 select 方法");    }}

複製程式碼

3、建立切面類(通知類)

3.1 定義切面 @Aspect

通過 @Aspect 註解將一個 Bean 定義為切面。

@Component//將這個類定義成 Bean@Aspect//將這個Bean定義為切面public class MyAdvice {}

複製程式碼

3.2 定義切點 @Pointcut

在 AspectJ 中,我們可以使用 @Pointcut 註解用來定義一個切點。

//切點方法必須是private,無返回值,無引數@Pointcut(value ="execution(* com.bighorn.*.*Dao.*(..))")private void pointCut() {}

複製程式碼

注意:

  • 定義為切點的方法是一個無意義的 private 方法 ,即無引數、無返回值(void)、無方法體

  • @Pointcut 註解中有一個 value 屬性,這個屬性的值就是 切入點表示式 (插個眼在這,下篇文章詳細嗦嗦)

3.3 定義通知

**通知(Advice)**就是將共性功能抽取出來後形成的方法,即對切入點增強的內容

通知有很多型別:前置通知、後置通知、環繞通知、異常通知、返回通知。(插個眼在這,下篇文章詳細嗦嗦)

通知註解中有一個 value 屬性,value 屬性的取值就是這些 通知(Advice) 所要 織入(Weaving) 切點(PointCut) ,它既可以是切入點表示式,也可以是切入點引用(切入點對應的方法名稱)

//使用切入點引用@Before("MyAdvice.pointCut()")public void beforeAdvice() {    System.out.println("這是前置通知……");}//使用切入點表示式@After(value ="execution(* com.bighorn.*.*Dao.*(..))")public void afterAdvice(){    System.out.println("這是後置通知……");}

複製程式碼

3.4 切面類完整程式碼

/*切面類(通知類)*/@Component//將這個類定義成 Bean@Aspect//將這個Bean定義為切面public class MyAdvice {    //切點方法必須是private,無返回值,無引數    @Pointcut(value = "execution(* com.bighorn.*.*Dao.*(..))")    private void pointCut() {    }
//使用切入點引用 @Before("MyAdvice.pointCut()") public void beforeAdvice() { System.out.println("這是前置通知……"); } //使用切入點表示式 @After(value ="execution(* com.bighorn.*.*Dao.*(..))") public void afterAdvice(){ System.out.println("這是後置通知……"); }}

複製程式碼

4、啟用 @AspectJ 註解支援

在 Spring 的配置類中 使用@AspectJ 註解開啟 AspectJ 的自動代理 ,使用 @ComponentScan 註解開啟註解掃描。

/*Spring核心配置類*/@Configuration@ComponentScan("com.bighorn") //開啟註解掃描@EnableAspectJAutoProxy //開啟 AspectJ 的自動代理public class SpringConfig {}

複製程式碼

5、編寫執行程式

public static void main(String[] args) throws SQLException {    //獲取配置類初始化容器    ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);    //從容器中獲取UserDao物件    UserDao userDao = context.getBean(UserDao.class);    //呼叫userDao的方法    userDao.add();    userDao.delete();    userDao.update();    userDao.select();}

複製程式碼

執行結果如下,可以發現對每個方法都進行的加強:前置通知和後置通知

寫在後面:beers:

感謝觀看啦:sparkles:

有什麼不足,歡迎指出哦:sparkling_heart: