本文原文地址:什么是AOP面向切面編程?怎么簡單理解?
什么是AOP面向切面編程
面向切面編程(AOP)通過將橫切關注點(cross-cutting concerns)分離出來,提供了一種增強代碼模塊化和可維護性的方法。
簡單來說,AOP就是將公共的模塊封裝成公共的方法,然后在需要的時候(這個就是切入點),直接就可以調用,而不用在各個對象里面具體的實現(xiàn)。
AOP是一種新的編程方式,它和OOP不同,OOP把系統(tǒng)看作多個對象的交互,AOP把系統(tǒng)分解為不同的關注點,或者稱之為切面(Aspect)。這個可以理解為把系統(tǒng)理解為一個流程,一個對象負責流程上的一個節(jié)點。
當然,AOP和公共模塊抽取調用的方式的差別在于切入點的調用方式的不同。AOP是通過某種方式(下面AOP原理會解釋)自動的調用,而不管是抽取公共方法,還是通過Proxy模式實現(xiàn)調用,都需要在每個業(yè)務方法上重復編寫調用。
以AOP的視角來編寫上述業(yè)務,可以依次實現(xiàn):
- 核心邏輯,即BookService;
- 切面邏輯,即:
- 權限檢查的Aspect;
- 日志的Aspect;
- 事務的Aspect。
然后,以某種方式,讓框架來把上述3個Aspect以Proxy的方式“織入”到BookService中,這樣一來,就不必編寫復雜而冗長的Proxy模式或者公共方法調用。
AOP原理
如何把切面織入到核心邏輯中?這正是AOP需要解決的問題。換句話說,如果客戶端獲得了BookService的引用,當調用bookService.createBook()時,如何對調用方法進行攔截,并在攔截前后進行安全檢查、日志、事務等處理,就相當于完成了所有業(yè)務功能。
在Java平臺上,對于AOP的織入,有3種方式:
- 編譯期:在編譯時,由編譯器把切面調用編譯進字節(jié)碼,這種方式需要定義新的關鍵字并擴展編譯器,AspectJ就擴展了Java編譯器,使用關鍵字aspect來實現(xiàn)織入;
- 類加載器:在目標類被裝載到JVM時,通過一個特殊的類加載器,對目標類的字節(jié)碼重新“增強”;
- 運行期:目標對象和切面都是普通Java類,通過JVM的動態(tài)代理功能或者第三方庫實現(xiàn)運行期動態(tài)織入。
最簡單的方式是第三種,Spring的AOP實現(xiàn)就是基于JVM的動態(tài)代理。由于JVM的動態(tài)代理要求必須實現(xiàn)接口,如果一個普通類沒有業(yè)務接口,就需要通過CGLIB或者Javassist這些第三方庫實現(xiàn)。
AOP技術看上去比較神秘,但實際上,它本質就是一個動態(tài)代理,讓我們把一些常用功能如權限檢查、日志、事務等,從每個業(yè)務方法中剝離出來。
需要特別指出的是,AOP對于解決特定問題,例如事務管理非常有用,這是因為分散在各處的事務代碼幾乎是完全相同的,并且它們需要的參數(shù)(JDBC的Connection)也是固定的。另一些特定問題,如日志,就不那么容易實現(xiàn),因為日志雖然簡單,但打印日志的時候,經常需要捕獲局部變量,如果使用AOP實現(xiàn)日志,我們只能輸出固定格式的日志,因此,使用AOP時,必須適合特定的場景。
核心概念
- 切面(Aspect):切面是封裝橫切關注點的模塊。它定義了在何處以及如何應用這些關注點。
- 連接點(Join Point):連接點是程序執(zhí)行過程中可以插入切面的點。例如,方法調用、方法執(zhí)行、構造函數(shù)調用、字段訪問等。
- 切入點(Pointcut):切入點定義了在哪些連接點上應用切面。它通常使用表達式來匹配特定的連接點。
- 通知(Advice):通知是在特定的切入點上執(zhí)行的代碼。通知可以在方法執(zhí)行之前、之后或異常拋出時執(zhí)行。常見的通知類型包括:
- 前置通知(Before):在方法執(zhí)行之前執(zhí)行。
- 后置通知(After):在方法執(zhí)行之后執(zhí)行。
- 返回通知(After Returning):在方法成功返回之后執(zhí)行。
- 異常通知(After Throwing):在方法拋出異常之后執(zhí)行。
- 環(huán)繞通知(Around):包圍方法的執(zhí)行,可以在方法執(zhí)行之前和之后自定義行為。
- 織入(Weaving):織入是將切面應用到目標對象的過程??椚肟梢栽诰幾g時、類加載時或運行時進行。
示例
以下是一個使用 Spring AOP 的簡單示例,展示了如何定義和應用切面。
- 定義切面
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBeforeMethod() {
System.out.println("Method is about to be executed");
}
}
在這個示例中,LoggingAspect 是一個切面,它包含一個前置通知 logBeforeMethod。這個通知將在 com.example.service 包中的所有方法執(zhí)行之前運行。
- 配置 Spring AOP
在 Spring 配置文件中啟用 AOP 支持,并注冊切面:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy/>
<bean id="loggingAspect" class="com.example.aspect.LoggingAspect"/>
</beans>
- 使用目標對象
package com.example.service;
public class UserService {
public void createUser() {
System.out.println("Creating user");
}
}
- 測試 AOP
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.example.service.UserService;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userService");
userService.createUser();
}
}
運行這個示例時,輸出將會是:
Method is about to be executed
Creating user
這表明前置通知在 createUser 方法執(zhí)行之前被調用了。
AOP 通過將橫切關注點分離出來,提供了一種增強代碼模塊化和可維護性的方法。通過定義切面、連接點、切入點和通知,可以在不修改現(xiàn)有代碼的情況下,動態(tài)地將橫切關注點織入到程序中。