專治不會看源碼的毛病--spring源碼解析AOP篇

来源:http://www.cnblogs.com/xiexj/archive/2017/08/17/7366890.html
-Advertisement-
Play Games

昨天有個大牛說我啰嗦,眼光比較細碎,看不到重點。太他爺爺的有道理了!要說看人品,還是女孩子強一些。原來記得看到一個男孩子的抱怨,說怎麼兩人剛剛開始在一起,女孩子在心裡就已經和他過完了一輩子。哥哥們,不想這麼遠行嗎?看看何潔,看看帶著倆娃跳樓的媽媽。所以現在的女孩子是很明白的,有些男孩子個子不高,其貌 ...


  昨天有個大牛說我啰嗦,眼光比較細碎,看不到重點。太他爺爺的有道理了!要說看人品,還是女孩子強一些。原來記得看到一個男孩子的抱怨,說怎麼兩人剛剛開始在一起,女孩子在心裡就已經和他過完了一輩子。哥哥們,不想這麼遠行嗎?看看何潔,看看帶著倆娃跳樓的媽媽。所以現在的女孩子是很明白的,有些男孩子個子不高,其貌不揚,但是一看那人品氣質就知道能找個不錯的女盆友。不過要說看人的技術能力,男孩子確實更勝一籌,咱得努力了。

  總結一下要形成的習慣:

  1>有空時隔一段時間要做幾道演算法題,C語言和JAVA都可以,主要是訓練思維。

       2>定期閱讀spring的源碼。因為spring是框架,重設計,能夠培養大局觀。

  3>閱讀底層的書籍,如linux方面,虛擬機方面,這是內功。越高級的語言只是招式。

  4>不要忘記做了一半的東西,如搜索引擎方面,redis方面,可以過一段時間再做,因為到時候自己的境界有提升,深入程度也會有所增加。

  

  下麵是今天的正題。我也很菜,看源碼也很費力,所以都會從最容易的入手。先瞭解其原理,再去看源碼。看源碼看熟了,以後再遇到問題,就可以通過源碼去瞭解原理了。spring的AOP,原理懂了,代碼相當簡單。這也是為什麼我記得我還是個菜鳥的時候,面試人家經常問我這個。

  先有個大局觀,畫張整體的spring結構圖。以下是備受吐槽的手繪時間:

  如果你覺得我左手字寫的實在是不能再難看了的話,我有空可以展示一下右手字

  天生做不好的兩件事:寫不好字,梳不整齊頭髮。自我感覺最近梳頭技術有所改觀。

 

  AOP面向方面編程是面向對象的補充。它利用一種橫切技術,將一些公共行為封裝成叫做“方面”的可重用模塊,解耦,增加可維護性。AOP將系統分為核心關註點和橫切關註點兩部分。核心關註點就是主業務流程,橫切關註點就是上面提到的“方面”。那麼看AOP的源碼就是要看橫切關註點是怎樣和核心關註點整合來發揮作用的。

  主業務流程歸根到底是一個java方法,而且是對象的方法。在AOP中被稱為被通知或被代理對象POJO。AOP的作用就是將核心關註點和橫切關註點組合起來,術語叫做“增強”。最後實際用的是增強後的代理對象。

  對核心關註點進行增強就涉及到在哪些地方增強的問題。如方法調用或者異常拋出時做增強這些時機叫做連接點Joinpoint。一個通知將被引發的連接點集合叫做切入點,理解時就可以想正則表達式,通配符來指定多個,而不是單單一個連接點。在連接點都做了哪些增強呢?增強的內容AOP術語叫“通知”Advice。Spring里定義了四種Advice:BeforeAdvice,AfterAdvice,ThrowAdvice,DynamicIntroducationAdvice。許多AOP框架包括spring都是以攔截器作為通知模型。維護一個圍繞連接點的攔截器鏈。其中DynamicIntroducationAdvice是可以引入方法或者欄位到核心關註點。這裡有個Introduction,AOP術語叫引入。將增強後的AOP代理組裝到系統叫做織入。

  上面就是AOP的核心概念了。總結一下:

方面(Aspect)
連接點(Joinpoint)
通知(Advice)
切入點(Pointcut)
引入(Introduction)
目標對象(Target Object)
AOP代理(AOP Proxy)
織入(Weaving)

  AOP要做的事情就是:生成代理對象,然後織入。

  生成代理對象是經常會被問到的一個問題:Spring提供了兩種方式來生成代理對象,JDKProxy和Cglib。具體使用哪種方式由AopProxyFactory根據AdvisedSupport對象的配置來決定。預設的策略是如果目標類是介面,則使用JDK動態代理技術,否則使用Cglib來生成代理。Cglib是基於位元組碼技術的,使用的是ASM。asm是一個java位元組碼操縱框架,它能被用來動態生成類或者增強既有類的功能。ASM可以直接產生二進位class文件,也可以在類被載入入JVM之前動態改變類行為。下麵重點來看看JDK動態代理技術。這是我還是個很菜很菜的菜鳥時為數不多能看懂的源碼。因為之前看過Java設計模式,寫過類似的例子,所以會比較順暢。今天先講這一部分。

下麵是調用測試類: 

package dynamic.proxy; 

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 實現自己的InvocationHandler
 * @author zyb
 * @since 2012-8-9
 *
 */
public class MyInvocationHandler implements InvocationHandler {
    
    // 目標對象 
    private Object target;
    
    /**
     * 構造方法
     * @param target 目標對象 
     */
    public MyInvocationHandler(Object target) {
        super();
        this.target = target;
    }


    /**
     * 執行目標對象的方法
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        
        // 在目標對象的方法執行之前簡單的列印一下
        System.out.println("------------------before------------------");
        
        // 執行目標對象的方法
        Object result = method.invoke(target, args);
        
        // 在目標對象的方法執行之後簡單的列印一下
        System.out.println("-------------------after------------------");
        
        return result;
    }

    /**
     * 獲取目標對象的代理對象
     * @return 代理對象
     */
    public Object getProxy() {
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), 
                target.getClass().getInterfaces(), this);
    }
}

package dynamic.proxy;

/**
 * 目標對象實現的介面,用JDK來生成代理對象一定要實現一個介面
 * @author zyb
 * @since 2012-8-9
 *
 */
public interface UserService {

    /**
     * 目標方法 
     */
    public abstract void add();

}

package dynamic.proxy; 

/**
 * 目標對象
 * @author zyb
 * @since 2012-8-9
 *
 */
public class UserServiceImpl implements UserService {

    /* (non-Javadoc)
     * @see dynamic.proxy.UserService#add()
     */
    public void add() {
        System.out.println("--------------------add---------------");
    }
}

package dynamic.proxy; 

import org.junit.Test;

/**
 * 動態代理測試類
 * @author zyb
 * @since 2012-8-9
 *
 */
public class ProxyTest {

    @Test
    public void testProxy() throws Throwable {
        // 實例化目標對象
        UserService userService = new UserServiceImpl();
        
        // 實例化InvocationHandler
        MyInvocationHandler invocationHandler = new MyInvocationHandler(userService);
        
        // 根據目標對象生成代理對象
        UserService proxy = (UserService) invocationHandler.getProxy();
        
        // 調用代理對象的方法
        proxy.add();
        
    }
}
View Code

執行結果如下: 
------------------before------------------ 
--------------------add--------------- 
-------------------after------------------
 

很簡單,核心就是 invocationHandler.getProxy(); 這個方法調用的Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),target.getClass().getInterfaces(), this);  怎麼生成對象的。

    /**
     * Returns an instance of a proxy class for the specified interfaces
     * that dispatches method invocations to the specified invocation
     * handler.
     *
     * <p>{@code Proxy.newProxyInstance} throws
     * {@code IllegalArgumentException} for the same reasons that
     * {@code Proxy.getProxyClass} does.
     *
     * @param   loader the class loader to define the proxy class
     * @param   interfaces the list of interfaces for the proxy class
     *          to implement
     * @param   h the invocation handler to dispatch method invocations to
     * @return  a proxy instance with the specified invocation handler of a
     *          proxy class that is defined by the specified class loader
     *          and that implements the specified interfaces
     * @throws  IllegalArgumentException if any of the restrictions on the
     *          parameters that may be passed to {@code getProxyClass}
     *          are violated
     * @throws  SecurityException if a security manager, <em>s</em>, is present
     *          and any of the following conditions is met:
     *          <ul>
     *          <li> the given {@code loader} is {@code null} and
     *               the caller's class loader is not {@code null} and the
     *               invocation of {@link SecurityManager#checkPermission
     *               s.checkPermission} with
     *               {@code RuntimePermission("getClassLoader")} permission
     *               denies access;</li>
     *          <li> for each proxy interface, {@code intf},
     *               the caller's class loader is not the same as or an
     *               ancestor of the class loader for {@code intf} and
     *               invocation of {@link SecurityManager#checkPackageAccess
     *               s.checkPackageAccess()} denies access to {@code intf};</li>
     *          <li> any of the given proxy interfaces is non-public and the
     *               caller class is not in the same {@linkplain Package runtime package}
     *               as the non-public interface and the invocation of
     *               {@link SecurityManager#checkPermission s.checkPermission} with
     *               {@code ReflectPermission("newProxyInPackage.{package name}")}
     *               permission denies access.</li>
     *          </ul>
     * @throws  NullPointerException if the {@code interfaces} array
     *          argument or any of its elements are {@code null}, or
     *          if the invocation handler, {@code h}, is
     *          {@code null}
     */
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }
View Code

 這個代碼是JDK1.8中的,裡面用到了1.8的一些語法,如果不太瞭解,建議先看看<java8 in action>這本書。代碼看著不少,實際上都在進行一些安全校驗,包裝之類的,真正有用的就兩句: Class<?> cl = getProxyClass0(loader, intfs);這句話查找或者生成代理類。跟進去:

    /**
     * Generate a proxy class.  Must call the checkProxyAccess method
     * to perform permission checks before calling this.
     */
    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }
View Code

對,就是從緩存里把介面拿將出來。然後用return cons.newInstance(new Object[]{h});這一句將介面用invocationHandler進行包裝。具體源碼可以跟進去看,不詳述。想必看到這裡,JDK動態代理的原理都已經很明白了。這裡要說一點理論性的東西:

  AOP解決的問題往往可以用代理模式來解決。Java開發中常說動態代理和靜態代理,而AOP就是動態代理,因為代理的類是在運行時才生成的。而一般說的代理模式寫成的代碼是編譯期就已經生成的,叫靜態代理。

 

介紹我的家鄉

   下麵是一如既往的跑題時間:俺是山東人。生在濰坊,長在棗莊。濰坊有一些名勝古跡,文化名人。棗莊也有一些名人,鐵道游擊隊的故鄉。額,但是也有些名聲不太好。特別是我們那個縣,叫“薛城”,是孟嘗君被封的薛地,所以……盛產雞鳴狗盜之徒。傳乾隆下江南路過此地,進一家人家討口水喝,被此人家婆娘打將出來,乾隆怒嗔曰:此地窮山惡水,潑婦刁民。

 

  


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

-Advertisement-
Play Games
更多相關文章
  • 使用過HttpServlet的都應該用過其doGet和doPost方法,接下來看看DispatcherServlet對這兩個方法的實現(源碼在DispatcherServlet的父類FrameworkServlet中): 方法里又將邏輯交由processRequest(request, respon ...
  • 題目描述 Copy從盧牛那裡聽說在一片叫yz的神的領域埋藏著不少寶藏,於是Copy來到了這個被劃分為個區域的神地。盧牛告訴了Copy這裡共有個寶藏,分別放在第Pi個(1<=Pi<=N)區域。Copy還得知了每個區域之間的距離。現在Copy從1號區域出發,要獲得所有的寶藏併到n號區域離開。Copy很懶 ...
  • 想挖個坑督促自己練技術,有時候想到一個項目,大概想了一些要實現的功能,怎麼實現。現在覺得自己差不多能完成QQ空間的主要功能了。準備立個牌坊,寫一個類似功能的網站。並且把進度放到這裡來。 初步計劃實現以下功能 1、用戶註冊、登錄、信息修改; 2、用戶進行好友關註、推送好用動態; 3、發表日誌、評論和評 ...
  • 目錄 題目描述 思路 程式(C++版&java版) 詳解 題目描述: 思路: 這道題實在是太經典,一道題裡面考察了幾個知識點: 1.鏈表是否有環的判斷 2.鏈表若有環,要找到環的入口節點 3.兩個鏈表的多種情況分析 另外,左老師講得實在是太贊了. 程式(詳解在後面): 詳解 先把幾種情況羅列一下: ...
  • 附圖一張: 今天閑來無事做,就想起同程SRC有一個公開漏洞模塊。然而閑的蛋疼的我就有了對其寫一個爬蟲將漏洞列表爬下來的衝動。有兩個版本,一個是單線程的。另一個是多線程的版本。 單線程版本: 多線程版本: 多線程使用的是Threading+Queue #coding=utf-8 import requ ...
  • 二、初識C語言 編程語言有很多種,主要分為三大類,分別是機器語言、彙編語言、高級語言。電腦只能識別和理解二進位代碼,也就是0和1。想一下,用機器語言來開發,我們得記住多少用0和1兩個數字所組成的指令代碼以及其代碼涵義?顯然,這是不合理的,這樣的編程枯燥且繁瑣費力,也容易出錯。彙編語言應運而生,彙編 ...
  • os模塊調用操作系統介面的模塊 相關方法或屬性: getcwd() 獲取當前的操作目錄,等同於linux中的pwd命令。 調用:os.getcwd() chdir() 改變python腳本的工作目錄。 調用:os.chdir(path) (path以字元串形式傳入) 例如: curdir 當前目錄 ...
  • 1.Rock Paper Scissors(剪刀 石頭 布) 說明:在電腦科學中有一種特別重要的游戲,因為它本身很簡單,可以用來創建非常狡猾的人工智慧演算法來對抗人類(或彼此),預測對手的行為。 這個古老的游戲是在兩個參與者之間進行的,他們同時用他們的手勢——石頭,紙或者剪刀。如果兩人都投了相同的手 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...