SpringAOP詳解


@

什么是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

  1. 創建一個干凈的Maven項目,並導入相關依賴的jar包
  2. 如圖創建相應的包,類
    在這里插入圖片描述
  3. 同樣的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>

運行結果:
在這里插入圖片描述


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM