Spring AOP 基於AspectJ

来源:https://www.cnblogs.com/yangyuanhu/archive/2020/01/11/12180732.html
-Advertisement-
Play Games

簡介 AspectJ是一個基於Java語言的AOP框架,Spring2.0以後新增了對AspectJ切點表達式支持。因為Spring1.0的時候Aspectj還未出現; AspectJ1.5中新增了對註解的支持,允許直接在Bean類中定義切麵。新版本的Spring框架建 議我們都使用AspectJ方 ...


簡介

AspectJ是一個基於Java語言的AOP框架,Spring2.0以後新增了對AspectJ切點表達式支持。因為Spring1.0的時候Aspectj還未出現;

AspectJ1.5中新增了對註解的支持,允許直接在Bean類中定義切麵。新版本的Spring框架建
議我們都使用AspectJ方式來開發AOP,並提供了非常靈活且強大的切點表達式 ;

當然無論使用Spring自己的AOP還是AspectJ相關的概念都是相同的;

註解配置

依賴導入:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.2.RELEASE</version>
</dependency>

通知類型

@AspectJ提供的通知類型:

  1. @Before 前置通知 在原始方法執行前執行

  2. @AfterReturning 後置通知 在原始方法執行前執行

  3. @Around 環繞通知 徹底攔截原始方法的執行,執行前後都可以增加邏輯,也可以不執行原始方法

  4. @AfterThrowing拋出通知,執行原始方法出現異常時執行

  5. @After 最終final通知,不管是否異常,原始方法調用後都會執行

  6. @DeclareParents 引介通知,相當於IntroductionInterceptor (瞭解即可)

定義切點

通過execution函數來定義切點

語法:execution(訪問修飾符 返回類型 方法名 參數 異常)

表達式示例:

匹配所有類public方法:execution(public * *(..))第一個*表示返回值 ..表示任意個任意類型參數

匹配指定包下所有方法: execution(* cn.xxx.dao.*(..)) 第一個想*表示忽略許可權和返回值類型

匹配指定包下所有方法:execution(* cn.xxx.dao..*(..))包含子包

匹配指定類所有方法: execution(* cn.xxx.service.UserService.*(..))

匹配實現特定介面所有類方法 : execution(* cn.xxx.dao.GenericDAO+.*(..))

匹配所有save開頭的方法: execution(* save*(..))

前置通知

pom依賴:

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.2.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.2.RELEASE</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.2.RELEASE</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

xml需要添加aop名稱空間及xsd:

<?xml version="1.0" encoding="UTF-8"?>
<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">
<!--    啟用aspectj    -->
    <aop:aspectj-autoproxy/>
<!--    目標-->
    <bean id="personDao" class="com.yh.demo1.PersonDao"/>
<!--    切麵-->
    <bean class="com.yh.demo1.MyAspect"/>
</beans>

test:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Test1 {
    @Autowired
    PersonDao personDao;

    @Test
    public void test(){
        personDao.delete();
        personDao.update();
    }
}

切麵類:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class MyAspect {
      //表示PersonDao下所有方法都作為切點
    @Before(value = "execution(* com.yh.demo1.PersonDao.*(..))")
    public void beforeAdvice(){
        System.out.println("before code run.....");
    }
}

當我們需要獲取切點信息(被增強的代碼)時,可以在通知添加參數,想下麵這樣

@Aspect
public class MyAspect {
    @Before(value = "execution(* com.yh.demo1.PersonDao.*(..))")
    public void beforeAdvice2(JoinPoint point){
        System.out.println("before code run2....." + point);
    }
}

後置通知:

//當需要獲取原始方法的返回值時可以在註解中添加returning參數來指定參數名  Aspectj會自動將返回值放到參數中
@AfterReturning(value = "execution(* com.yh.demo1.PersonDao.delete(..))",returning = "result")
public void afterAdvice(Object result){
    System.out.println("刪除方法執行後 .....   返回值為:"+ result);
}

後置通知可以獲取目標方法的返回值

環繞通知:

@Around(value = "execution(* com.yh.demo1.PersonDao.insert(..))")
public void aroundAdvice(ProceedingJoinPoint point) throws Throwable {
    //code............
    System.out.println("環繞前置..");

    //執行原始方法 __當需要獲取返回值時可以聲明變數接收
    Object result = point.proceed();
    System.out.println("原始方法返回值: "+result);
    //code............
    System.out.println("環繞後置..");
}

環繞通知與其他通知最大的區別在於環繞通知可以控制是否調用原始方法

註意:參數類型必須為ProceedingJoinPoint,否則 無法執行原始方法,

異常通知

@AfterThrowing(value = "execution(* com.yh.demo1.PersonDao.save(..))",throwing = "e")
public void exceptionHandler(JoinPoint point,Exception e){
    System.out.println(point + " 方法出現"+e.getMessage()+"異常");
}

當方法中出現時才會執行該通知,若需要獲取異常信息,可在註解中添加throwing指定參數名稱

我們可以使用環繞+異常通知來處理資料庫事務,在環繞中開啟事務以及提交事務,異常通知中回滾事務,當然Spring已經對事務進行了封裝不需要自己寫

最終通知

@After(value = "execution(* *delete(..))")
public void afterRun(){
    System.out.println("最終");
}

最終通知叫做after 即調用原始方法之後執行無論原始方法中是否出現異常

而後置叫做afterReturning表示在成功返回後才會執行執行

帶有邏輯符的表達式:

在表達式中可以使用戶邏輯操運算符,與&&||!

示例:

/*
execution(* cn.xxx.service.UserDao.insert(..))||execution(* cn.xxx.service.UserDao.delete(..))

execution(* cn.xxx.service.UserDao.*nsert(..))&&execution(* cn.xxx.service.UserDao.inser*(..))

!execution(* cn.xxx.service.UserDao.insert(..))
*/

切點命名

假設有多個通知應用在同一個切點上時,我們需要重覆編寫execution表達式,且後續要修改切點時則多個通知都需要修改,維護起來非常麻煩,我們可以通過給切點指定名稱從而完成對切點的重覆使用和統一操作,以提高開發維護效率;

//定義命名切點  方法名稱即切點名稱
@Pointcut(value = "execution(* com.yh.demo1.PersonDao.save(..))")
private void savePointcut(){}

@Pointcut(value = "execution(* com.yh.demo1.PersonDao.delete(..))")
private void deletePointcut(){}

多個通知應用到同一個切點:

//使用命名切點
@Before(value = "savePointcut()")
public void beforeAdvice(){
    System.out.println("before code run.....");
}
//使用命名切點
@Around(value = "savePointcut()")
public void beforeAdvice2(ProceedingJoinPoint point) throws Throwable {
    System.out.println("環繞前");
    point.proceed();
    System.out.println("環繞後");

一個通知應用到多個切點

//同一個通知對應多個切點
@After(value = "savePointcut()||deletePointcut()")
public void afterAdvice(){
    System.out.println("after code run.....");
}

XML配置

XML配置所需的jar 以及各個對象之間的依賴關以及表達式的寫法都是一樣的,僅僅是換種方式來寫而已;

xml:

        <!--目標-->
    <bean id="studentDao" class="com.yh.demo2.StudentDao"/>
        <!--通知-->
    <bean id="advices" class="com.yh.demo2.XMLAdvice"/>

        <!--織入信息-->
    <aop:config>
                <!--切點定義-->
        <aop:pointcut id="select" expression="execution(* com.yh.demo2.StudentDao.select(..))"/>
                <!--切麵定義-->
        <aop:aspect ref="advices">
            <aop:before method="before" pointcut-ref="select"/>
            <aop:after-returning method="afterReturning" pointcut-ref="select" returning="result"/>
            <aop:after method="after" pointcut-ref="select" />
            <aop:after-throwing method="exception" pointcut-ref="select" throwing="e"/>
            <aop:around method="around" pointcut-ref="select"/>
        </aop:aspect>
      
        <!--入侵式通知 即通知需要實現指定介面 兩種不能同時使用 -->
        <aop:advisor advice-ref="advice2" pointcut-ref="select"/>
    </aop:config>
  <!--入侵式通知Bean-->
  <bean id="advice2" class="com.yh.demo2.XMLAdvice2"/>

通知類:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.JoinPoint;

public class XMLAdvice {
    public void before(JoinPoint pointcut){ System.out.println("前置通知 切點:"+pointcut); }

    public void afterReturning(JoinPoint point,Object result){
        System.out.println("後置通知 切點:"+point);
    }

    public void after(JoinPoint point){ System.out.println("最終通知 切點:"+point); }

    public void exception(JoinPoint point,Throwable e){
        System.out.println("異常通知: " + e+"切點:"+point);
    }

    public void around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("環繞前");
        point.proceed();
        System.out.println("環繞後");
    }
}

你會發現 ,無論是XML還是註解都不需要手動指定代理,以及目標對象,Aspectj會從切點中獲取目標對象信息並自動創建代理;

AspectJ是目前更流行的方式,具體採用XML還是註解需要根據項目具體情況,小組協作開發推薦xml;


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 【導讀】2020年,你又立了什麼新的 Flag?新一年,我們先為大家準備 30 個非常優秀的 Python 實踐技巧。希望這些訣竅能在實際工作中幫助大家,並且學到一些有用的知識。 1、使用 python 3由於官方從2020年1月1日起就停止了對python2.7的更新支持,因此本教程的大部分例子都 ...
  • 用法說明 python中有一些非常有趣的函數,面試的時候可能會遇到。今天也來總結一下,不過該類的網上資料也相當多,也沒多少乾貨,只是習慣性將一些容易遺忘的功能進行整理。 lambda 為關鍵字。filter,map,reduce為內置函數。 lambda:實現python中單行最小函數。 filte ...
  • python裝飾器@wraps作用 修複被裝飾後的函數名等屬性的改變 Python裝飾器(decorator)在實現的時候,被裝飾後的函數其實已經是另外一個函數了(函數名等函數屬性會發生改變), 為了不影響,Python的functools包中提供了一個叫wraps的decorator來消除這樣的副 ...
  • 怎麼快速的對列表進行去重呢,去重之後原來的順序會不會改變呢? 去重之後順序會改變 set去重 列表去重改變原列表的順序了 但是,可以通過列表中索引(index)的方法保證去重後的順序不變。 itertools.groupby fromkeys 通過刪除索引 去重不改變順序 建立新列表[] reduc ...
  • 功能介紹 整理生信小知識庫,一些技巧一些知識。 昨天 以下配置環境基於window操作系統,安裝python3版本為例,推薦基礎版配置。 ! METHOD 1 (基礎版) 官網下載對應電腦版本的python3:https://www.python.org/downloads/windows/​​ 下 ...
  • 小伙伴們新年好啊,又有半個月沒有更新博客了。更新也比較隨性,想起什麼就寫點什麼,方便和大家工作同學習總結。 最近和同事說起了PHP安全相關的問題,記錄下一些心得體會。 由於腳本語言和早期版本設計的諸多原因,php項目存在不少安全隱患。從配置選項來看,可以做如下的優化。 1.屏蔽PHP錯誤輸出。在/e ...
  • 在指定範圍內生成一個隨機數作為目標值,用戶對目標值進行猜測。 import java.util.Random; // 隨機數 import java.util.Scanner; // 獲取用戶輸入 public class Example { public static void main(Stri ...
  • 生成指定範圍內的隨機數 Math.random() 生成隨機數,隨機數在0到1之間,類型是 double。 public class randCase { public static void main(String[] args) { double rand = 0; for (int i = 0 ...
一周排行
    -Advertisement-
    Play Games
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...
  • 目錄前言PostgreSql安裝測試額外Nuget安裝Person.cs模擬運行Navicate連postgresql解決方案Garnet為什麼要選擇Garnet而不是RedisRedis不再開源Windows版的Redis是由微軟維護的Windows Redis版本老舊,後續可能不再更新Garne ...
  • C#TMS系統代碼-聯表報表學習 領導被裁了之後很快就有人上任了,幾乎是無縫銜接,很難讓我不想到這早就決定好了。我的職責沒有任何變化。感受下來這個系統封裝程度很高,我只要會調用方法就行。這個系統交付之後不會有太多問題,更多應該是做小需求,有大的開發任務應該也是第二期的事,嗯?怎麼感覺我變成運維了?而 ...
  • 我在隨筆《EAV模型(實體-屬性-值)的設計和低代碼的處理方案(1)》中介紹了一些基本的EAV模型設計知識和基於Winform場景下低代碼(或者說無代碼)的一些實現思路,在本篇隨筆中,我們來分析一下這種針對通用業務,且只需定義就能構建業務模塊存儲和界面的解決方案,其中的數據查詢處理的操作。 ...
  • 對某個遠程伺服器啟用和設置NTP服務(Windows系統) 打開註冊表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer 將 Enabled 的值設置為 1,這將啟用NTP伺服器功 ...
  • title: Django信號與擴展:深入理解與實踐 date: 2024/5/15 22:40:52 updated: 2024/5/15 22:40:52 categories: 後端開發 tags: Django 信號 松耦合 觀察者 擴展 安全 性能 第一部分:Django信號基礎 Djan ...
  • 使用xadmin2遇到的問題&解決 環境配置: 使用的模塊版本: 關聯的包 Django 3.2.15 mysqlclient 2.2.4 xadmin 2.0.1 django-crispy-forms >= 1.6.0 django-import-export >= 0.5.1 django-r ...
  • 今天我打算整點兒不一樣的內容,通過之前學習的TransformerMap和LazyMap鏈,想搞點不一樣的,所以我關註了另外一條鏈DefaultedMap鏈,主要調用鏈為: 調用鏈詳細描述: ObjectInputStream.readObject() DefaultedMap.readObject ...
  • 後端應用級開發者該如何擁抱 AI GC?就是在這樣的一個大的浪潮下,我們的傳統的應用級開發者。我們該如何選擇職業或者是如何去快速轉型,跟上這樣的一個行業的一個浪潮? 0 AI金字塔模型 越往上它的整個難度就是職業機會也好,或者說是整個的這個運作也好,它的難度會越大,然後越往下機會就會越多,所以這是一 ...
  • @Autowired是Spring框架提供的註解,@Resource是Java EE 5規範提供的註解。 @Autowired預設按照類型自動裝配,而@Resource預設按照名稱自動裝配。 @Autowired支持@Qualifier註解來指定裝配哪一個具有相同類型的bean,而@Resourc... ...