@
什么是AOP?
AOP 的全稱是“Aspect Oriented Programming”,即面向切面編程,和 OOP(面向對象編程)類似,也是一種編程思想。
AOP的作用
AOP 采取橫向抽取機制(動態代理),取代了傳統縱向繼承機制的重復性代碼,其應用主要體現在事務處理、日志管理、權限控制、異常處理等方面。
主要作用是分離功能性需求和非功能性需求,使開發人員可以集中處理某一個關注點或者橫切邏輯,減少對業務代碼的侵入,增強代碼的可讀性和可維護性。
簡單的說,AOP 的作用就是保證開發者在不修改源代碼的前提下,為系統中的業務組件添加某種通用功能。AOP 就是代理模式的典型應用。
AOP框架
目前最流行的 AOP 框架有兩個
- Spring AOP
- Spring AOP 是基於 AOP 編程模式的一個框架,它能夠有效的減少系統間的重復代碼,達到松耦合的目的。
- Spring AOP 使用純 Java 實現,不需要專門的編譯過程和類加載器,在運行期間通過代理方式向目標類植入增強的代碼。
- 有兩種實現方式:基於接口的 JDK 動態代理和基於繼承的 CGLIB 動態代理。
- AspectJ
- AspectJ 是一個基於 Java 語言的 AOP 框架,從 Spring 2.0 開始,Spring AOP 引入了對 AspectJ 的支持。AspectJ 擴展了 Java 語言,提供了一個專門的編譯器,在編譯時提供橫向代碼的植入。
AOP術語
為了更好地理解 AOP,我們需要了解一些它的相關術語。這些專業術語並不是 Spring 特有的,有些也同樣適用於其它 AOP 框架,如 AspectJ。
名稱 | 說明 |
---|---|
Joinpoint(連接點) | 指那些被攔截到的點,在 Spring 中,指可以被動態代理攔截目標類的方法。 |
Pointcut(切入點) | 指要對哪些 Joinpoint 進行攔截,即被攔截的連接點。 |
Advice(通知) | 指攔截到 Joinpoint 之后要做的事情,即對切入點增強的內容。 |
Target(目標) | 指代理的目標對象。 |
Weaving(植入) | 指把增強代碼應用到目標上,生成代理對象的過程。 |
Proxy(代理) | 指生成的代理對象。 |
Aspect(切面) | 切入點和通知的結合。 |
Advice 直譯為通知,也有的資料翻譯為“增強處理”,共有 5 種類型
通知 | 說明 |
---|---|
before(前置通知) | 通知方法在目標方法調用之前執行 |
after(后置通知) | 通知方法在目標方法返回或異常后調用 |
after-returning(返回后通知) | 通知方法會在目標方法返回后調用 |
after-throwing(拋出異常通知) | 通知方法會在目標方法拋出異常后調用 |
around(環繞通知) | 通知方法會將目標方法封裝起來 |
AOP的優點
AOP 是 Spring 的核心之一,在 Spring 中經常會使用 AOP 來簡化編程。在 Spring 框架中使用 AOP 主要有以下優勢。
- 提供聲明式企業服務,特別是作為 EJB 聲明式服務的替代品。最重要的是,這種服務是聲明式事務管理。
- 允許用戶實現自定義切面。在某些不適合用 OOP 編程的場景中,采用 AOP 來補充。
- 可以對業務邏輯的各個部分進行隔離,從而使業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時也提高了開發效率。
SpringAOP AspectJ開發AOP
基於XML開發
基於 XML 的聲明式是指通過 Spring 配置文件的方式來定義切面、切入點及通知,而所有的切面和通知都必須定義在 <aop:config>
元素中。
使用XML開發需要注意以下兩點:
- 需要導入一個依賴包
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>x.x.x</version>
</dependency>
</dependencies>
- 使用
<aop:config>
元素時,我們需要先導入 Spring aop 命名空間
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
</beans>
基於XML開發有兩種方式:自定義類開發和使用Spring的API接口開發
1、自定義類開發AOP
- 定義切面
<aop:aspect>
在 Spring 配置文件中,使用<aop:aspect>
元素定義切面,該元素可以將定義好的 Bean 轉換為切面 Bean,所以使用<aop:aspect>
之前需要先定義一個普通的 Spring Bean
<!-- 自定義類實現-->
<bean id="diyPoint" class="xxx.xxx"/>
<aop:config>
<!-- 自定義切面-->
<aop:aspect ref="diyPoint">
...
</aop:aspect>
</aop:config>
- 定義切入點
<aop:pointcut>
<aop:pointcut>
用來定義一個切入點,當<aop:pointcut>
元素作為<aop:config>
元素的子元素定義時,表示該切入點是全局切入點,它可被多個切面所共享;當<aop:pointcut>
元素作為<aop:aspect>
元素的子元素時,表示該切入點只對當前切面有效。
<aop:config>
<aop:pointcut id="point" expression="execution(* service.UserServiceImpl.*(..))"/>
</aop:config>
其中,id 用於指定切入點的唯一標識名稱,execution 用於指定切入點關聯的切入點表達式。
execution 格式解析:
符號 | 含義 |
---|---|
execution() | 表達式的主體; |
第一個*符號 | 表示返回值的類型任意; |
service | AOP所切的服務的包名,即我們的業務部分 |
UserServiceImpl | 表示UserServiceImpl類 |
.*(..) | 表示任何方法名,括號表示參數,兩個點表示任何參數類型 |
-
定義通知
AspectJ 支持 5 種類型的 advice
<aop:aspect ref="diyPoint">
<aop:pointcut id="point" expression="execution(* service.UserServiceImpl.*(..))"/>
<!-- 前置通知 -->
<aop:before pointcut-ref="point" method="..."/>
<!-- 后置通知 -->
<aop:after-returning pointcut-ref="point" method="..."/>
<!-- 環繞通知 -->
<aop:around pointcut-ref="point" method="..."/>
<!-- 異常通知 -->
<aop:after-throwing pointcut-ref="point" method="..."/>
<!-- 最終通知 -->
<aop:after pointcut-ref="point" method="..."/>
</aop:aspect>
下面使用IDEA演示自定義類開發AOP
如圖所示創建如下包、類、接口、配置文件
UserService接口代碼如下:
public interface UserService {
public void add();
}
UserServiceImpl類代碼如下:
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加了一個用戶");
}
}
自定義類diy
public class diyPointCut {
public void before(){
System.out.println("==========執行前===========");
}
public void after(){
System.out.println("==========執行后===========");
}
}
applicationContext.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="service.UserServiceImpl"/>
<!-- 自定義類實現-->
<bean id="diyPoint" class="diy.diyPointCut"/>
<aop:config>
<!-- 自定義切面,ref要引用的類-->
<aop:aspect ref="diyPoint">
<aop:pointcut id="point" expression="execution(* service.UserServiceImpl.*(..))"/>
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
</beans>
測試類代碼如下:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.UserService;
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userService");
userService.add();
}
}
測試結果為:
2、使用Spring的API接口開發AOP
各接口介紹如下:
- Advice:一個用於標識的接口,也是所有通知的最終父接口
- BeforeAdvice:前置通知接口,指的是某些行為觸發前的通知
- MethodBeforeAdvice:方法前置通知接口,指的是方法被執行前會觸發的通知
- AfterAdvice:后置通知接口,指的是某些行為觸發后的通知
- AfterReturningAdvice:返回后置通知接口,指的是方法執行后觸發的通知
- ThrowsAdvice:異常通知接口,指的是方法出現異常時觸發的通知。方法出現異常也代表方法已經返回了結果,需要注意,此接口也是一個標識接口。
這里只演示了MethodBeforeAdvice和AfterReturningAdvice接口,其他接口讀者可查閱資料學習。
同樣的,創建如上包、類、接口、配置文件
service包下的內容、測試類均與上面代碼一致,這里不再贅述
Log代碼如下:
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
//在調用bean的方法前執行
public class Log implements MethodBeforeAdvice {
//method 要執行目標對象的方法
//object 參數
// o 目標對象
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println(o.getClass().getName()+"的"+method.getName()+"被執行");
}
}
AfterLog代碼如下:
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
//接口AfterReturningAdvice為實現方法執行后通知
public class AfterLog implements AfterReturningAdvice {
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("執行了"+method.getName()+"方法");
}
}
applicationContext.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="service.UserServiceImpl"/>
<bean id="log" class="log.Log"/>
<bean id="afterLog" class="log.AfterLog"/>
<!-- 使用原生的API接口-->
<aop:config>
<!-- 切入點-->
<aop:pointcut id="pointcut" expression="execution(* service.UserServiceImpl.*(..))"/>
<!-- 執行環繞-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
advisor標簽
- 用於把一個Advice和一個Pointcut組合起來。一個advisor標簽對應的就是一個Advisor接口的實現類,默認是DefaultBeanFactoryPointcutAdvisor實現。
運行結果如下:
基於注解開發
在 Spring 中,盡管使用 XML 配置文件可以實現 AOP 開發,但是如果所有的相關配置都集中在配置文件中,勢必會導致 XML 配置文件過於臃腫,從而給維護和升級帶來一定的困難。
AspectJ 允許使用注解定義切面、切入點和增強處理,Spring 框架可以根據這些注解生成 AOP 代理。
名稱 | 說明 |
---|---|
@Aspect | 用於定義一個切面。 |
@Pointcut | 用於定義一個切入點。 |
@Before | 用於定義前置通知,相當於 BeforeAdvice。 |
@AfterReturning | 用於定義后置通知,相當於 AfterReturningAdvice。 |
@Around | 用於定義環繞通知,相當於MethodInterceptor。 |
@AfterThrowing | 用於定義拋出通知,相當於ThrowAdvice。 |
@After | 用於定義最終final通知,不管是否異常,該通知都會執行。 |
@DeclareParents | 用於定義引介通知,相當於IntroductionInterceptor(不要求掌握)。 |
- 定義切面@Aspect的方法
AspectJ 類和其它普通的 Bean 一樣,可以有方法和字段,不同的是 AspectJ 類需要使用 @Aspect 注解
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class AspectPointCut {
}
- 定義切入點@Pointcut
@Pointcut 注解用來定義一個切入點
// 要求:方法必須是private,返回值類型為void,名稱自定義,沒有參數,id為PointCut()
@Pointcut("execution(*net.biancheng..*.*(..))")
private void PointCut() {
}
- 定義通知advice
@AspectJ 支持 5 種類型的 advice
@Before("PointCut()")
public void beforeAdvice(){
...
}
下面使用IDEA展示基於注解開發AOP
- 創建一個干凈的Maven項目,並導入相關依賴的jar包
- 如圖創建相應的包,類
- 同樣的service包下的類、測試類MyTest與上面代碼均一致,這里不再贅述。
AnnocationPointCut類代碼如下:
import org.aspectj.lang.annotation.*;
//使用注解方式實現AOP
@Aspect //標注這個類是一個切面
public class AnnocationPointCut {
// 要求:方法必須是private,返回值類型為void,名稱自定義,沒有參數
@Pointcut("execution(* service.UserServiceImpl.*(..))")
private void selectAll(){
}
@Before("selectAll()")
public void before(){
System.out.println("==========執行前===========");
}
@After("selectAll()")
public void after(){
System.out.println("==========執行后===========");
}
}
applicationContext.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="service.UserServiceImpl"/>
<bean id="userPointCut" class="AnnocationPointCut"/>
<!-- 此時不需要在配置文件中編寫切面切入點等代碼,只需開啟注解支持-->
<aop:aspectj-autoproxy/>
</beans>
運行結果: