1. 程式人生 > >Spring中的各種Utils(六):AopProxyUtils詳解

Spring中的各種Utils(六):AopProxyUtils詳解

原創文章,轉載請註明出處

本節介紹AopProxyUtils,這個工具類方法本身很少,但是涉及到很多AOP相關重要介面,所以本節主要會對涉及到的介面進行初步的介紹;

Class<?> ultimateTargetClass(Object candidate)
獲取一個代理物件的最終物件型別,先做一個測試(測試的準備工作參考https://www.jianshu.com/p/a4a815f42cd2):

@Test
public void testUltimateTargetClass() {
    // class cn.wolfcode.springboot.utilstest.MyComponent
    System.out.println(AopProxyUtils.ultimateTargetClass(component));
    // class cn.wolfcode.springboot.utilstest.EmployeeServiceImpl
    System.out.println(AopProxyUtils.ultimateTargetClass(service));
}

分別獲取了代理物件的最終物件型別;我們來看看該方法的實現:

public static Class<?> ultimateTargetClass(Object candidate) {
    Assert.notNull(candidate, "Candidate object must not be null");
    //current用於判斷;
    Object current = candidate;
    Class<?> result = null;
    //直到當前獲得的物件不是TargetClassAware型別,TargetClassAware後面介紹;
    while (current instanceof TargetClassAware) {
        //獲得當前物件(一個代理物件)代理的目標物件(這個物件可能還是一個代理物件)型別;
        result = ((TargetClassAware) current).getTargetClass();
        Object nested = null;
        //如果當前獲取的目標物件是一個Advised型別物件;
        if (current instanceof Advised) {
            //通過getTargetSource方法獲取代理目標;
            TargetSource targetSource = ((Advised) current).getTargetSource();
            //如果代理目標物件是一個SingletonTargetSource;
            if (targetSource instanceof SingletonTargetSource) {
                //獲取當前代理物件的真正目標物件(可能還是一個代理物件)
                nested = ((SingletonTargetSource) targetSource).getTarget();
            }
        }
        current = nested;
    }
    //如果獲取到的目標物件是一個cglib代理物件,獲取父類型別(才是目標型別)
    if (result == null) {
        result = (AopUtils.isCglibProxy(candidate) ? candidate.getClass().getSuperclass() : candidate.getClass());
    }
    return result;
}

其實這段程式碼本身不難,但是這裡面涉及到了很多SpringAOP中的介面,比如TargetClassAware,Advised,TargetSource,SingletonTargetSource等;這些介面都是SpringAOP中的一些重要介面,在這裡我們做一個簡單介紹,更多的內容後面單開文章介紹;

我們先看下TargetClassAware,Advised和TargetSource三個介面:
TargetClassAware:所有的Aop代理物件或者代理工廠(proxy factory)都要實現的介面,該介面用於暴露出被代理目標物件型別;

TargetSource:該介面代表一個目標物件,在aop呼叫目標物件的時候,使用該介面返回真實的物件。比如該介面的一個實現:PrototypeTargetSource,那就是每次呼叫都返回一個全新的物件例項;

Advised:該介面用於儲存一個代理的相關配置(簡單理解為這個物件儲存了怎麼建立一個代理物件的資訊),比如這個代理配置相關的攔截器,建議(advisor)或者增強器(advice);所有的代理物件都實現了該介面(我們就能夠通過一個代理物件獲取這個代理物件怎麼被代理出來的相關資訊);

我們可以來做個測試:

@Test
public void testUltimateTargetClass2() {
    /**
     * interface cn.wolfcode.springboot.utilstest.IEmployeeService interface
     * cn.wolfcode.springboot.utilstest.IAddition interface
     * org.springframework.aop.SpringProxy interface
     * org.springframework.aop.framework.Advised interface
     * java.io.Serializable
     */
    System.out.println(StringUtils.collectionToDelimitedString(
            ClassUtils.getAllInterfacesAsSet(service), "\r\n"));
    /**
     * interface org.springframework.aop.SpringProxy interface
     * org.springframework.aop.framework.Advised interface
     * org.springframework.cglib.proxy.Factory
     */
    System.out.println(StringUtils.collectionToDelimitedString(
            ClassUtils.getAllInterfacesAsSet(component), "\r\n"));
}

可以看到,不管是JDKproxy,還是cglib proxy,代理出來的物件都實現了org.springframework.aop.framework.Advised介面;

三個介面的關係為:

image.png

介面中的方法我們先不研究,我們明確一個點來輔助我們理解這個關係,那就是Advised介面中的getTargetSource返回的就是TargetSource。意思就是Advised和TargetSource介面雖然在繼承關係上,都是繼承了TargetClassAware介面,看似平級關係,實際上確實組合關係:

@Test
public void testUltimateTargetClass3() {
    if (service instanceof Advised) {
        TargetSource ts = ((Advised) service).getTargetSource();
        //class org.springframework.aop.target.SingletonTargetSource
        System.out.println(ts.getClass());
    }
}

可以看到,得到的最終是一個SingletonTargetSource,那麼就是說這種TargetSource就是每次呼叫,都返回相同物件;

而Advised裡面包含的Advisor和Advice,我們後面再詳細介紹;
希望通過這個介紹,對這三個介面有更深入的理解;

Class<?>[] completeProxiedInterfaces(AdvisedSupport advised)
很牛逼的方法來了,判斷一個advised真正需要代理的目標介面列表。簡單理解,比如在spring使用JDK proxy做代理的時候,這個方法返回的型別列表就是真正需要交給Proxy.newProxyInstance方法的介面列表,我們先來簡單看看這個流程在Spring中的實現:

public Object getProxy(ClassLoader classLoader) {
    Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
    findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

這段程式碼摘取自JdkDynamicAopProxy,這個就是我們常常說的Spring中針對實現了介面的物件,使用JDK的動態代理完成的真正程式碼,可以看到,傳給newProxyInstance方法第二個引數的介面列表,就是通過completeProxiedInterfaces方法獲取的。足見該方法的重要性;

該方法具體的實現我們這裡先不介紹,因為涉及到另一個AOP中重要的類:AdvisedSupport,該類是Spring中用於蒐集代理配置的物件(是不是感覺這句話很熟,是的,這個類就是實現了上面介紹的Advised介面,但是注意,代理出來的類可以轉成Advised,但是不是AdvisedSupport);

Class<?>[] proxiedUserInterfaces(Object proxy) 
該方法用於獲取一個代理物件中的使用者定義的介面,即非(Advised介面體系)之外的其他介面;

@Test
public void testProxiedUserInterfaces() {
    /**
     * interface cn.wolfcode.springboot.utilstest.IEmployeeService interface
     * cn.wolfcode.springboot.utilstest.IAddition interface
     * org.springframework.aop.SpringProxy interface
     * org.springframework.aop.framework.Advised interface
     * java.io.Serializable
     */
    System.out.println(StringUtils.collectionToDelimitedString(
            ClassUtils.getAllInterfacesAsSet(service), "\r\n"));

    /**
     * interface cn.wolfcode.springboot.utilstest.IEmployeeService interface
     * cn.wolfcode.springboot.utilstest.IAddition
     */
    System.out.println(StringUtils.arrayToDelimitedString(
            AopProxyUtils.proxiedUserInterfaces(service), "\r\n"));

}

可以看到,只得到了使用者定義的介面,Spring自動增加的系統介面沒有包含;
這個方法的實現也很有意思:

public static Class<?>[] proxiedUserInterfaces(Object proxy) {
    //得到所有介面
    Class<?>[] proxyInterfaces = proxy.getClass().getInterfaces();
    int nonUserIfcCount = 0;
    //如果是代理,一定實現了SpringProxy;
    if (proxy instanceof SpringProxy) {
        nonUserIfcCount++;
    }
    //如果是代理,可能實現了Advised;
    if (proxy instanceof Advised) {
        nonUserIfcCount++;
    }
    Class<?>[] userInterfaces = new Class<?>[proxyInterfaces.length - nonUserIfcCount];
    //拷貝proxyInterfaces中從第0位~第proxyInterfaces.length - nonUserIfcCount個
    //去掉尾巴上的nonUserIfcCount個;
    System.arraycopy(proxyInterfaces, 0, userInterfaces, 0, userInterfaces.length);
    Assert.notEmpty(userInterfaces, "JDK proxy must implement one or more interfaces");
    return userInterfaces;
}

boolean equalsProxiedInterfaces(AdvisedSupport a, AdvisedSupport b)
判斷兩個(即將)代理出來的物件是否擁有相同介面;

boolean equalsAdvisors(AdvisedSupport a, AdvisedSupport b)
判斷兩個(即將)代理出來的物件是否擁有相同的建議者(Advisor);

boolean equalsInProxy(AdvisedSupport a, AdvisedSupport b)
判斷兩個(即將)代理出來的物件是否相同;

小結

AopProxyUtils中的方法不多,但是其中的ultimateTargetClass和completeProxiedInterfaces方法確是Spring AOP中比較重要的方法,也給了我們一個入手觀察Spring AOP真正實現過程的一個突破口;我認為這個,才是AopProxyUtils給我們的價值;


相關推薦

Spring各種UtilsAopProxyUtils

原創文章,轉載請註明出處本節介紹AopProxyUtils,這個工具類方法本身很少,但是涉及到很多AOP相關重要介面,所以本節主要會對涉及到的介面進行初步的介紹;Class<?> ultimateTargetClass(Object candidate)獲取一個代

Spring Boot快速入門thymeleaf

return 之前 err static 默認 示例 圖片資源 css 官網 原文地址:https://lierabbit.cn/articles/8 靜態資源 在我們開發Web應用的時候,需要引用大量的js、css、圖片等靜態資源。 Spring Boot的默認位置是re

袋鼠雲數據臺專欄企業數據指標的那些事兒

span 帶來 什麽 如何解決 煙囪 color system data 混亂 本文作者:子璽袋鼠雲數據中臺解決方案專家。擁有近10年大數據從業經驗,擁有PMP項目管理資格認證,精通數據類項目的開發實施和管理。曾服務過國家工商總局、北京市工商局、北京市財政局、廣州開發區大數

Spring Boot 2.x優雅的統一返回值

為什麼要統一返回值 在我們做後端應用的時候,前後端分離的情況下,我們經常會定義一個數據格式,通常會包含code,message,data這三個必不可少的資訊來方便我們的交流,下面我們直接來看程式碼 ReturnVO package indi.viyoung.viboot.util; import ja

Spring Cloud基礎教程Feign熔斷器使用Hystrix

上一篇部落格講解了Ribbon使用Hystrix,本篇部落格講解下Feign使用Hystrix。一、準備將服務消費者(Ribbon)使用部落格中的Consumer-Ribbon工程,複製一份,命名為Co

Spring的AOP——定義切入點和切入點指示符

定義切入點     在前文(點選檢視)中使用到的AdviceTest類中同一個切點(即* com.abc.service.*.advice*(..)匹配的連線點)卻重複定義了多次,這顯然不符合軟體設計的原則,為了解決這個問題,AspectJ和Spring都提供了切入點的定義。所謂定義切入點,其實質就是為一個

C語言面向物件程式設計繼承

    C++ 中的繼承,從派生類與基類的關係來看(出於對比 C 與 C++,只說公有繼承): 派生類內部可以直接使用基類的 public 、protected 成員(包括變數和函式) 使用派生類的物件,可以像訪問派生類自己的成員一樣訪問基類的成員  對於被派生

zookeeper入門學習原理

一 .Zookeeper功能簡介 ZooKeeper 是一個開源的分散式協調服務,由雅虎建立,是 Google Chubby 的開源實現。 分散式應用程式可以基於 ZooKeeper 實現諸如資料釋出/訂閱、負載均衡、命名服務、分散式協 調/通知

解讀ASP.NET 5 & MVC6系列6Middleware

在第1章專案結構分析中,我們提到Startup.cs作為整個程式的入口點,等同於傳統的Global.asax檔案,即:用於初始化系統級的資訊(例如,MVC中的路由配置)。本章我們就來一一分析,在這裡如何初始化這些系統級的資訊。 新舊版本之間的Pipeline區別 ASP.NET 5和之前版本的最大區別是對HT

目標檢測與分割SSD

SSD github : https://github.com/weiliu89/caffe/tree/ssd SSD paper : https://arxiv.org/abs/1512.02325 SSD eccv2016 slide pdf : http://d

MVC之前的那點事兒系列3HttpRuntime分析

文章內容 話說,經過各種各樣複雜的我們不知道的內部處理,非託管程式碼正式開始呼叫ISPAIRuntime的ProcessRequest方法了(ISPAIRuntime繼承了IISPAIRuntime介面,該介面可以和COM進行互動,並且暴露了ProcessRequest介面方法)。至於為什麼要呼叫這個方法,

MVC之前的那點事兒系列2HttpRuntime分析

文章內容 從上章文章都知道,asp.net是執行在HttpRuntime裡的,但是從CLR如何進入HttpRuntime的,可能大家都不太清晰。本章節就是通過深入分析.Net4的原始碼來展示其中的重要步驟。請先看下圖:   首先,CLR在初始化載入的時候,會載入一個非常重要的類AppManagerApp

Spring Cloud Eureka 入門 服務消費者

摘要: 原創出處:www.bysocket.com 泥瓦匠BYSocket 希望轉載,保留摘要,謝謝! “真正的進步,不在於學習,而在於反思”  「Spring Cloud Eureka 入門系列」本文提綱 1.  springcloud-eureka-sample 工程介紹 2. 執行 spring

SpringBoot 2.0 系列流程

寫在前面 本節將詳細介紹如何使用Spring Boot。它涵蓋了諸如專案管理及自動構建工具、自動配置以及如何執行應用程式等主題。我們還介紹了一些Spring Boot最佳實踐。Spring Boot沒有什麼特別之處(它只是另一個我們可以使用的庫),但是有一些約

深入理解AndroidGradle

作者 鄧凡平 編者按:隨著移動裝置硬體能力的提升,Android系統開放的特質開始顯現,各種開發的奇技淫巧、黑科技不斷湧現,InfoQ特聯合《深入理解Android》系列圖書作者鄧凡平,開設深入理解Android專欄,探索Android從框架到應用開

Servlet學習筆記Session

Session是伺服器端技術,伺服器在執行時可以為每個使用者的瀏覽器建立一個其獨享的Session物件,由於Session為使用者瀏覽器獨享,所以使用者在訪問伺服器的web資源時,可以把各自的資源放在各自的Session中,當用戶再去訪問伺服器中的其他web資源時,其他we

Spring Cloud Eureka 入門 服務提供者

2017-07-10 16:03:15.075 INFO 11020 --- [ main] o.s.c.n.e.s.EurekaServiceRegistry : Registering application provider-service with eureka

前端基礎進階圖例那道setTimeout與迴圈閉包的經典面試題

配圖與本文無關 我在詳細圖解作用域鏈與閉包一文中的結尾留下了一個關於setTimeout與迴圈閉包的思考題。 利用閉包,修改下面的程式碼,讓迴圈輸出的結果依次為1, 2, 3, 4, 5 for (var i=1; i<=5; i++) { setTimeout( function ti

Pandas系列-時間序列

htm 轉換 req 標準 2.0 使用 日期時間 別名 目錄 內容目錄 1. 基礎概述 2. 轉換時間戳 3. 生成時間戳範圍 4. DatetimeIndex 5. DateOffset對象 6. 與時間序列相關的方法 6.1 移動 6.2 頻率轉換

SpringBoot系列整合thymeleaf

SpringBoot系列(六)整合thymeleaf詳解版 1. thymeleaf簡介  1. Thymeleaf是適用於Web和獨立環境的現代伺服器端Java模板引擎。  2. Thymeleaf的主要目標是為您的開發工作流程帶來優雅的自然模板 -HTML可以在瀏覽器中正確顯示,也可以作為靜態原型工作,從