一起來談談 Spring AOP!

擁抱心中的夢想發表於2018-03-13

一、什麼是AOP?

要談AOP,那麼AOP到底是什麼呢?AOP即面向切面程式設計,相比OOP--物件導向程式設計,由於物件導向中最基本的單位是類,例項,很自然我們會想到AOP中最基本的單位可能就是所謂的切面了,你可能會問,那切面又是個什麼東西,我想說,現在不懂沒關係,下面我會講到。我們先來看一段Spring中關於AOP的定義:

面向切面——Spring提供了面向切面程式設計的豐富支援,允許通過分離應用的業務邏輯與系統級服務(例如審計(auditing)和事務(transaction)管理)進行內聚性的開發。應用物件只實現它們應該做的——完成業務邏輯——僅此而已。它們並不負責(甚至是意識)其它的系統級關注點,例如日誌或事務支援。

上面談到,AOP可以分離系統的業務邏輯和系統服務(日誌,安全等),這個功能我想是不難明白(原理是使用了代理模式),但關鍵是為什麼要將這兩種進行分離呢?或者說這樣做有什麼好處?

在日常的軟體開發中,拿日誌來說,一個系統軟體的開發都是必須進行日誌記錄的,不然萬一系統出現什麼bug,你都不知道是哪裡出了問題。舉個小栗子,當你開發一個登陸功能,你可能需要在使用者登陸前後進行許可權校驗並將校驗資訊(使用者名稱,密碼,請求登陸時間,ip地址等)記錄在日誌檔案中,當使用者登入進來之後,當他訪問某個其他功能時,也需要進行合法性校驗。想想看,當系統非常地龐大,系統中專門進行許可權驗證的程式碼是非常多的,而且非常地散亂,我們就想能不能將這些許可權校驗、日誌記錄等非業務邏輯功能的部分獨立拆分開,並且在系統執行時需要的地方(連線點)進行動態插入執行,不需要的時候就不理,因此AOP是能夠解決這種狀況的思想吧!

下圖就很直觀地展示這個過程:

一起來談談 Spring AOP!

二、AOP中的基本概念

不得不說,AOP的概念是真的多且難以理解,不過不用擔心,聰明的你已經準備好戰勝它們了。

  • 通知(Adivce)

通知有5種型別:

  • Before 在方法被呼叫之前呼叫

  • After 在方法完成後呼叫通知,無論方法是否執行成功

  • After-returning 在方法成功執行之後呼叫通知

  • After-throwing 在方法丟擲異常後呼叫通知

  • Around 通知了好、包含了被通知的方法,在被通知的方法呼叫之前後呼叫之後執行自定義的行為

    我們可能會問,那通知對應系統中的程式碼是一個方法、物件、類、還是介面什麼的呢?我想說一點,其實都不是,你可以理解通知就是對應我們日常生活中所說的通知,比如‘某某人,你2019年9月1號來學校報個到’,通知更多地體現一種告訴我們(告訴系統何)何時執行,規定一個時間,在系統執行中的某個時間點(比如拋異常啦!方法執行前啦!),並非對應程式碼中的方法!並非對應程式碼中的方法!並非對應程式碼中的方法!

  • 切點(Pointcut)

    哈哈,這個你可能就比較容易理解了,切點在Spring AOP中確實是對應系統中的方法。但是這個方法是定義在切面中的方法,一般和通知一起使用,一起組成了切面。

  • 連線點(Join point)

    比如:方法呼叫、方法執行、欄位設定/獲取、異常處理執行、類初始化、甚至是 for 迴圈中的某個點

    理論上, 程式執行過程中的任何時點都可以作為作為織入點, 而所有這些執行時點都是 Joint point

    但 Spring AOP 目前僅支援方法執行 (method execution) 也可以這樣理解,連線點就是你準備在系統中執行切點和切入通知的地方(一般是一個方法,一個欄位)

  • 切面(Aspect)

    切面是切點和通知的集合,一般單獨作為一個類。通知和切點共同定義了關於切面的全部內容,它是什麼時候,在何時和何處完成功能。

  • 引入(Introduction)

    引用允許我們向現有的類新增新的方法或者屬性

  • 織入(Weaving)

    組裝方面來建立一個被通知物件。這可以在編譯時完成(例如使用AspectJ編譯器),也可以在執行時完成。Spring和其他純Java AOP框架一樣,在執行時完成織入。

三、Spring中對AOP的支援

首先AOP思想的實現一般都是基於代理模式,在JAVA中一般採用JDK動態代理模式,但是我們都知道,JDK動態代理模式只能代理介面,如果要代理類那麼就不行了。因此,Spring AOP 會這樣子來進行切換,因為Spring AOP 同時支援 CGLIB、ASPECTJ、JDK動態代理,當你的真實物件有實現介面時,Spring AOP會預設採用JDK動態代理,否則採用cglib代理。

  • 如果目標物件的實現類實現了介面,Spring AOP 將會採用 JDK 動態代理來生成 AOP 代理類;
  • 如果目標物件的實現類沒有實現介面,Spring AOP 將會採用 CGLIB 來生成 AOP 代理類——不過這個選擇過程對開發者完全透明、開發者也無需關心。

四、說了這麼多,來點餅乾解解渴!直接上程式碼!

話說餅乾能解渴嗎?

  • 定義主題介面,這些介面的方法可以成為我們的連線點
package wokao666.club.aop.spring01;

public interface Subject {
	
	//登陸
	public void login();
	
	//下載
	public void download();
	
}
複製程式碼
  • 定義實現類,這是代理模式中真正的被代理人(如果你有參與代購,這個就像你在代購中的角色)
package wokao666.club.aop.spring02;

import wokao666.club.aop.spring01.Subject;

public class SubjectImpl implements Subject {

	public void login() {
		
		System.err.println("借書中...");
		
	}

	public void download() {
		
		System.err.println("下載中...");
		
	}
}
複製程式碼
  • 定義切面(切面中有切點和通知)
package wokao666.club.aop.spring01;

import org.aspectj.lang.JoinPoint;

public class PermissionVerification {
 /**
  * 許可權校驗
  * @param args 登陸引數
  */
	public void canLogin() {
		
		//做一些登陸校驗
		System.err.println("我正在校驗啦!!!!");
		
	}
	
	/**
	 * 校驗之後做一些處理(無論是否成功都做處理)
	 * @param args 許可權校驗引數
	 */
	public void saveMessage() {
		
		//做一些後置處理
			System.err.println("我正在處理啦!!!!");
	}
	
}
複製程式碼
  • 再來看看我們的SpringAOP.xml檔案
    <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
        
        <bean id="SubjectImpl1" class="wokao666.club.aop.spring02.SubjectImpl" />
        <bean id="SubjectImpl2" class="wokao666.club.aop.spring02.SubjectImpl" />
        <bean id="PermissionVerification" class="wokao666.club.aop.spring01.PermissionVerification" />
        
        <aop:config>
        <!-- 這是定義一個切面,切面是切點和通知的集合-->
            <aop:aspect id="do" ref="PermissionVerification">
            	<!-- 定義切點 ,後面是expression語言,表示包括該介面中定義的所有方法都會被執行-->
                <aop:pointcut id="point" expression="execution(* wokao666.club.aop.spring01.Subject.*(..))" />
                <!-- 定義通知 -->
                <aop:before method="canLogin" pointcut-ref="point" />
                <aop:after method="saveMessage" pointcut-ref="point" />
            </aop:aspect>
        </aop:config>
</beans>
複製程式碼
  • 由於我基於MAVEN構建,來看看我們的pom.xml檔案
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>wokao666.club</groupId>
  <artifactId>aop</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>aop</name>
  <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring.version>4.3.0.RELEASE</spring.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
            <version>4.10</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.9</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.4</version>
        </dependency>
  </dependencies>
</project>
複製程式碼
  • 最後看看我們的測試類和結果
package wokao666.club.aop.spring01;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
	
	public static void main(String[] args) {
		ApplicationContext ctx = 
	      new ClassPathXmlApplicationContext("SpringAOP.xml");
	  
				Subject subject1 = (Subject)ctx.getBean("SubjectImpl1");
				Subject subject2 = (Subject)ctx.getBean("SubjectImpl2");
				
				subject1.login();
				subject1.download();
				
				
				System.err.println("==================");
				
				
				
				
				subject1.login();
				subject1.download();
				
				
				
				
	}
}
複製程式碼
三月 13, 2018 4:59:44 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
資訊: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@31cefde0: startup date [Tue Mar 13 16:59:44 CST 2018]; root of context hierarchy
三月 13, 2018 4:59:45 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
資訊: Loading XML bean definitions from class path resource [SpringAOP.xml]
我正在校驗啦!!!!
借書中...
我正在處理啦!!!!
我正在校驗啦!!!!
下載中...
我正在處理啦!!!!
==================
我正在校驗啦!!!!
借書中...
我正在處理啦!!!!
我正在校驗啦!!!!
下載中...
我正在處理啦!!!!
複製程式碼

我想,上面的實現方式只是皮毛而已啦!如果想要深入學習,那麼可以看下JDK動態代理的原始碼分析(裡邊非常地巧妙,還運用了二級快取,本來我想寫出來的,就留給下一篇文吧!)、Spring AOP原始碼分析等等,當然上面的實現方式是基於老式的xml檔案,不推薦我們這樣做,為什麼?我覺得當你係統大了之後,那系統中將會有很多的beanxml檔案,這不容易維護和管理,我建議採用基於註解的方式進行AOP程式設計會更好!

好了,本來想結束的,但是在關掉eclipse前發現了一個小問題,既然切點是需要執行的方法,那麼我們如何獲取連線點的資料(或者說是校驗引數呢?),這裡推薦一篇博文,我就不寫啦!小夥伴們,一起加油吧!

spring AOP 通知引數的傳遞

相關文章