spring之旅第五篇-AOP詳解

来源:https://www.cnblogs.com/yuanqinnan/archive/2019/03/10/10507411.html
-Advertisement-
Play Games

一、什麼是AOP? Aspect oritention programming(面向切麵編程),AOP是一種思想,高度概括的話是“橫向重覆,縱向抽取”,如何理解呢?舉個例子:訪問頁面時需要許可權認證,如果每個頁面都去實現方法顯然是不合適的,這個時候我們就可以利用切麵編程。 每個頁面都去實現這個方法就是 ...


一、什麼是AOP?

Aspect oritention programming(面向切麵編程),AOP是一種思想,高度概括的話是“橫向重覆,縱向抽取”,如何理解呢?舉個例子:訪問頁面時需要許可權認證,如果每個頁面都去實現方法顯然是不合適的,這個時候我們就可以利用切麵編程。

每個頁面都去實現這個方法就是橫向的重覆,我們直接從中切入,封裝一個與主業務無關的許可權驗證的公共方法,這樣可以減少系統的重覆代碼,降低模塊之間的耦合度,簡單的示意圖如下:

二、應用場景

AOP用來封裝橫切關註點,具體可以在下麵的場景中使用:

Authentication 許可權

Caching 緩存

Context passing 內容傳遞

Error handling 錯誤處理

Lazy loading 懶載入

Debugging  調試 l

ogging, tracing, profiling and monitoring 記錄跟蹤 優化 校準

Performance optimization 性能優化

Persistence  持久化

Resource pooling 資源池

Synchronization 同步

Transactions 事務

三、相關概念

1.連接點(Joinpoint) 所謂連接點是指那些可能被攔截到的方法。例如:所有可以增加的方法

2.切點(Pointcut) 已經被增強的連接點

3.增強(Advice) 增強的代碼

4.目標對象(Target) 目標類,需要被代理的類

5.織入(Weaving) 是指把增強advice應用到目標對象target來創建新的代理對象proxy的過程

6.代理(Proxy) 一個類被AOP織入增強後,就產生出了一個結果類,它是融合了原類和增強邏輯的代理類。

7.切麵(Aspect)切入點+通知

通知類型:Spring按照通知Advice在目標類方法的連接點位置,可以分為5類

  • 前置通知 (在目標方法執行前實施增強)

  • 後置通知(在目標方法執行後實施增強)

  • 環繞通知(在目標方法執行前後實施增加)

  • 異常拋出通知(在方法跑出異常時通知)

  • 引介通知(在目標類中添加一些新的方法和屬性)

四、實現原理

AOP的實現關鍵在於AOP框架自動創建的AOP代理。AOP代理主要分為兩大類:

靜態代理:使用AOP框架提供的命令進行編譯,從而在編譯階段就可以生成AOP代理類,因此也稱為編譯時增強;靜態代理一Aspectj為代表。

動態代理:在運行時藉助於JDK動態代理,CGLIB等在記憶體中臨時生成AOP動態代理類,因此也被稱為運行時增強,Spring AOP用的就是動態代理。

4.1 靜態代理

例子:在增加員工和刪除員工時增加事務處理

//員工類
public class Employee {
    private Integer uid;

    public void setUid(Integer uid) {
        this.uid = uid;
    }

    public Integer getUid() {

        return uid;
    }

    private Integer age;

    private String name;

    public Integer getAge() {
        return age;
    }

    public String getName() {
        return name;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }
}

員工介面:

//員工介面
public interface EmployeeService {
    //新增方法
    void addEmployee(Employee employee);
    //刪除方法
    void deleteEmployee(Integer uid);
}

員工實現:

//員工方法實現
public class EmployeeServiceImpl implements EmployeeService {
    @Override
    public void addEmployee(Employee employee) {
        System.out.println("新增員工");
    }

    @Override
    public void deleteEmployee(Integer uid) {
        System.out.println("刪除員工");

    }
}

事務類:

//事務類
public class MyTransaction {
    //開啟事務
    public void before(){
        System.out.println("開啟事務");
    }
    //提交事務
    public void after(){
        System.out.println("提交事務");
    }
}

代理類:

//代理類
public class ProxyEmployee implements EmployeeService {
    //
    private EmployeeService employeeService;

    private MyTransaction myTransaction;

    public ProxyEmployee(EmployeeService employeeService,MyTransaction myTransaction)
    {
       this.employeeService=employeeService;
       this.myTransaction=myTransaction;
    }
    @Override
    public void addEmployee(Employee employee) {
         myTransaction.before();
         employeeService.addEmployee(employee);
         myTransaction.after();
    }

    @Override
    public void deleteEmployee(Integer uid) {
         myTransaction.before();
         employeeService.deleteEmployee(uid);
         myTransaction.after();
    }
}

測試:

@Test
    public void fun1(){
        MyTransaction transaction = new MyTransaction();
        EmployeeService EmployeeService = new EmployeeServiceImpl();
        //產生靜態代理對象
        ProxyEmployee proxy = new ProxyEmployee(EmployeeService, transaction);
        proxy.addEmployee(null);
        proxy.deleteEmployee(0);
    }

結果:

這是靜態代理的實現方式,靜態代理有明顯的缺點:

1、代理對象的一個介面只服務於一種類型的對象,如果要代理的方法很多,勢必要為每一種方法都進行代理,靜態代理在程式規模稍大時就無法勝任了。

2、如果介面增加一個方法,比如 EmployeeService增加修改 updateEmployee()方法,則除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了代碼維護的複雜度。

4.2 動態代理

動態代理就不要自己手動生成代理類了,我們去掉 ProxyEmployee.java 類,增加一個 ObjectInterceptor.java 類

public class ObjectInterceptor implements InvocationHandler {

    //目標類
    private Object target;
    //切麵類(這裡指事務類)
    private MyTransaction transaction;

    //通過構造器賦值
    public ObjectInterceptor(Object target,MyTransaction transaction){
        this.target = target;
        this.transaction = transaction;
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        this.transaction.before();
        method.invoke(target,args);
        this.transaction.after();
        return  null;
    }
}

測試:

@Test
public void fun2(){
    //目標類
    Object target = new EmployeeServiceImpl ();
    //事務類
    MyTransaction transaction = new MyTransaction();
    ObjectInterceptor proxyObject = new ObjectInterceptor(target, transaction);
    /**
     * 三個參數的含義:
     * 1、目標類的類載入器
     * 2、目標類所有實現的介面
     * 3、攔截器
     */
    EmployeeService employeeService = (EmployeeService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
            target.getClass().getInterfaces(), proxyObject);
    employeeService.addEmployee(null);
    employeeService.deleteEmployee(0);
}

結果:

 五、spring的處理AOP的方式

spring 有兩種方式實現AOP的:一種是採用聲明的方式來實現(基於XML),一種是採用註解的方式來實現(基於AspectJ)

5.1 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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

     <!--目標類-->
     <bean name="employeeService" class="com.yuanqinnan.aop.EmployeeServiceImpl"></bean>
     <bean name="transaction" class="com.yuanqinnan.aop.MyTransaction"></bean>
     <aop:config>
          <aop:aspect ref="transaction">
               <aop:pointcut id="pointcut" expression="execution(* com.yuanqinnan.aop.EmployeeServiceImpl..*(..))"/>
               <!-- 配置前置通知,註意 method 的值要和 對應切麵的類方法名稱相同 -->
               <aop:before method="before" pointcut-ref="pointcut"></aop:before>
               <!--配置後置通知,註意 method 的值要和 對應切麵的類方法名稱相同-->
               <aop:after-returning method="after" pointcut-ref="pointcut"/>
          </aop:aspect>
     </aop:config>

</beans>

測試:

@Test
public void fun3(){
    ApplicationContext context = new ClassPathXmlApplicationContext("META-INF/applicationContext.xml");
    EmployeeService employeeService = (EmployeeService) context.getBean("employeeService");
    employeeService.addEmployee(null);
}

結果:

補充:

1.aop:pointcut如果位於aop:aspect元素中,則命名切點只能被當前aop:aspect內定義的元素訪問到,為了能被整個aop:config元素中定義的所有增強訪問,則必須在aop:config下定義切點。

2.如果在aop:config元素下直接定義aop:pointcut,必須保證aop:pointcutaop:aspect之前定義。aop:config下還可以定義aop:advisor,三者在aop:config中的配置有先後順序的要求:首先必須是aop:pointcut,然後是aop:advisor,最後是aop:aspect。而在aop:aspect中定義的aop:pointcut則沒有先後順序的要求,可以在任何位置定義。

aop:pointcut:用來定義切入點,該切入點可以重用;

aop:advisor:用來定義只有一個通知和一個切入點的切麵;

aop:aspect:用來定義切麵,該切麵可以包含多個切入點和通知,而且標簽內部的通知和切入點定義是無序的;和advisor的區別就在此,advisor只包含一個通知和一個切入點。

3.在使用spring框架配置AOP的時候,不管是通過XML配置文件還是註解的方式都需要定義pointcut"切入點" 例如定義切入點表達式 execution(* com.sample.service.impl...(..)) execution()是最常用的切點函數,其語法如下所示:

整個表達式可以分為五個部分: (1)、execution(): 表達式主體。

(2)、第一個號:表示返回類型,號表示所有的類型。

(3)、包名:表示需要攔截的包名,後面的兩個句點表示當前包和當前包的所有子包,com.sample.service.impl包、子孫包下所有類的方法。

(4)、第二個號:表示類名,號表示所有的類。

(5)、(..):最後這個星號表示方法名,號表示所有的方法,後面括弧裡面表示方法的參數,兩個句點表示任何參數。

5.2 註解配置

新建註解類:

@Component
@Aspect
public class AopAspectJ {
    /**
     * 必須為final String類型的,註解里要使用的變數只能是靜態常量類型的
     */
    public static final String EDP="execution(* com.yuanqinnan.aop.EmployeeServiceImpl..*(..))";

    /**
     * 切麵的前置方法 即方法執行前攔截到的方法
     * 在目標方法執行之前的通知
     * @param jp
     */
    @Before(EDP)
    public void doBefore(JoinPoint jp){

        System.out.println("=========執行前置通知==========");
    }


    /**
     * 在方法正常執行通過之後執行的通知叫做返回通知
     * 可以返回到方法的返回值 在註解後加入returning
     * @param jp
     * @param result
     */
    @AfterReturning(value=EDP,returning="result")
    public void doAfterReturning(JoinPoint jp,String result){
        System.out.println("===========執行後置通知============");
    }

    /**
     * 最終通知:目標方法調用之後執行的通知(無論目標方法是否出現異常均執行)
     * @param jp
     */
    @After(value=EDP)
    public void doAfter(JoinPoint jp){
        System.out.println("===========執行最終通知============");
    }

    /**
     * 環繞通知:目標方法調用前後執行的通知,可以在方法調用前後完成自定義的行為。
     * @param pjp
     * @return
     * @throws Throwable
     */
    @Around(EDP)
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable{

        System.out.println("======執行環繞通知開始=========");
        // 調用方法的參數
        Object[] args = pjp.getArgs();
        // 調用的方法名
        String method = pjp.getSignature().getName();
        // 獲取目標對象
        Object target = pjp.getTarget();
        // 執行完方法的返回值
        // 調用proceed()方法,就會觸發切入點方法執行
        Object result=pjp.proceed();
        System.out.println("輸出,方法名:" + method + ";目標對象:" + target + ";返回值:" + result);
        System.out.println("======執行環繞通知結束=========");
        return result;
    }

    /**
     * 在目標方法非正常執行完成, 拋出異常的時候會走此方法
     * @param jp
     * @param ex
     */
    @AfterThrowing(value=EDP,throwing="ex")
    public void doAfterThrowing(JoinPoint jp,Exception ex) {
        System.out.println("===========執行異常通知============");
    }
}

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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <context:component-scan base-package="com.yuanqinnan.aop" ></context:component-scan>
    <!-- 聲明spring對@AspectJ的支持 -->
    <aop:aspectj-autoproxy/>
    <!--目標類-->
    <bean name="employeeService" class="com.yuanqinnan.aop.EmployeeServiceImpl"></bean>
</beans>

測試:

@Test
public void fun4(){
    ApplicationContext act =  new ClassPathXmlApplicationContext("META-INF/applicationContext.xml");
    EmployeeService employeeService = (EmployeeService) act.getBean("employeeService");
    employeeService.addEmployee(null);
}

結果:

pringAOP的知識就總結到這裡

 


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

-Advertisement-
Play Games
更多相關文章
  • 註: 本文僅對個人嘗試作一小結, 專欄中其他作者的嘗試還望各自總結 對自研中文編程語言的嘗試也許始於 "2015年" 對CoffeeScript的部分關鍵詞漢化和 "Java實現的彙編語言編譯器" 的開發. "2017年10月那場請求C 提供漢化版的討論" 也給了更多的動力. 之後對 "開源非英文關 ...
  • 報這個錯誤是因為我的 application_context.service.xml 文件里的的dubbo聲明暴露口時的ref屬性寫錯了。 ...
  • 這段時間在學習Spring,依賴註入DI和麵向切麵編程AOP是Spring框架最核心的部分。這次主要是總結依賴註入的bean的裝配方式。 什麼是依賴註入呢?也可以稱為控制反轉,簡單的來說,一般完成稍微複雜的業務邏輯,可能需要多個類,會出現有些類要引用其他類的實例,也可以稱為依賴其他類。傳統的方法就是 ...
  • 晚上在閱讀go lang的資料時突然想到一個問題,go是如何分配變數的記憶體結構的呢?好在網上的一篇文章做了透徹的分析見【go語言局部變數分配在棧還是堆】。 其結論是go語言局部變數的分配是由編譯器決定的。go語言編譯器會自動決定把一個變數放在棧還是放在堆,編譯器會做逃逸分析(escape analy ...
  • 忙瘋警告,這兩天可能進度很慢,下午打了一下午訓練賽,訓練賽的題我就不拿過來的,pta就做了一點點,明天又是滿課的一天,所以進度很慢啦~ L1-021 重要的話說三遍 這道超級簡單的題目沒有任何輸入。 你只需要把這句很重要的話 —— “I'm gonna WIN!”——連續輸出三遍就可以了。 註意每遍 ...
  • 因為極驗官網給的是用session作為驗證的,而我們做前後端分離的用的是token,而不是session,所以對於目前來說就不適用了,所以需要根據具體業務邏輯來改動。當然,大佬可以直接忽略 好的,直接上例子: 還是用的 Python高級應用(3)—— 為你的項目添加驗證碼 這文章最後的Lo... ...
  • 新聞 ".NET Core 3預覽版3之宣告" .NET Core 3.0將在2019年下半年發佈 .NET Standard 2.1的首個預覽版 Docker與cgroup的記憶體限制 "LambdAle 2019徵文" "使用TypeShape生成透鏡" "為什麼使用Ply(F的高性能TPL類庫) ...
  • Problem Description In this problem, your task is to calculate SUM(n) = 1 + 2 + 3 + ... + n. Input The input will consist of a series of integers n, o ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...