spring-aop 3.創(chuàng)建代理對象

1.前言

在上一篇中,我分析了 Spring 是如何為目標 bean 篩選合適的通知器的。現在通知器選好了斑唬,接下來就要通過代理的方式將通知器(Advisor)所持有的通知(Advice)織入到 bean 的某些方法前后急灭。與篩選合適的通知器相比眶蕉,創(chuàng)建代理對象的過程則要簡單不少嗅战,本文所分析的源碼不過100行贯溅,相對比較簡單糕篇。在接下里的章節(jié)中,我將會首先向大家介紹一些背景知識该窗,然后再去分析源碼弟蚀。那下面,我們先來了解一下背景知識酗失。

2.代理

2.1proxy-target-class

在 Spring AOP 配置中粗梭,proxy-target-class 屬性可影響 Spring 生成的代理對象的類型。以 XML 配置為例级零,可進行如下配置:

<aop:aspectj-autoproxy proxy-target-class="true"/>
<aop:config proxy-target-class="true">
    <aop:aspect id="xxx" ref="xxxx">
        <!-- 省略 -->
    </aop:aspect>
</aop:config>

如上,默認情況下 proxy-target-class 屬性為 false滞乙。當目標 bean 實現了接口時奏纪,Spring 會基于 JDK 動態(tài)代理為目標 bean 創(chuàng)建代理對象。若未實現任何接口斩启,Spring 則會通過 CGLIB 創(chuàng)建代理序调。而當 proxy-target-class 屬性設為 true 時,則會強制 Spring 通過 CGLIB 的方式創(chuàng)建代理對象兔簇,即使目標 bean 實現了接口发绢。

關于 proxy-target-class 屬性的用途這里就說完了硬耍,下面我們來看看兩種不同創(chuàng)建動態(tài)代理的方式。

2.2動態(tài)代理

2.2.1基于 JDK 的動態(tài)代理

基于 JDK 的動態(tài)代理主要是通過 JDK 提供的代理創(chuàng)建類 Proxy 為目標對象創(chuàng)建代理边酒,下面我們來看一下 Proxy 中創(chuàng)建代理的方法聲明经柴。如下:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

簡單說一下上面的參數列表:

loader - 類加載器
interfaces - 目標類所實現的接口列表
h - 用于封裝代理邏輯
JDK 動態(tài)代理對目標類是有一定要求的,即要求目標類必須實現了接口墩朦,JDK 動態(tài)代理只能為實現了接口的目標類生成代理對象坯认。至于 InvocationHandler,是一個接口類型氓涣,定義了一個 invoke 方法牛哺。使用者需要實現該方法,并在其中封裝代理邏輯劳吠。

關于 JDK 動態(tài)代理的介紹引润,就先說到這。下面我來演示一下 JDK 動態(tài)代理的使用方式痒玩,如下:

目標類定義:

public interface UserService {

    void save(User user);

    void update(User user);
}

public class UserServiceImpl implements UserService {

    @Override
    public void save(User user) {
        System.out.println("save user info");
    }

    @Override
    public void update(User user) {
        System.out.println("update user info");
    }
}

代理創(chuàng)建者定義:

public interface ProxyCreator {

    Object getProxy();
}

public class JdkProxyCreator implements ProxyCreator, InvocationHandler {

    private Object target;

    public JdkProxyCreator(Object target) {
        assert target != null;
        Class<?>[] interfaces = target.getClass().getInterfaces();
        if (interfaces.length == 0) {
            throw new IllegalArgumentException("target class don`t implement any interface");
        }
        this.target = target;
    }

    @Override
    public Object getProxy() {
        Class<?> clazz = target.getClass();
        // 生成代理對象
        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println(System.currentTimeMillis() + " - " + method.getName() + " method start");
        // 調用目標方法
        Object retVal = method.invoke(target, args);
        System.out.println(System.currentTimeMillis() + " - " + method.getName() + " method over");

        return retVal;
    }
}

如上淳附,invoke 方法中的代理邏輯主要用于記錄目標方法的調用時間,和結束時間凰荚。下面寫點測試代碼簡單驗證一下燃观,如下:

public class JdkProxyCreatorTest {

    @Test
    public void getProxy() throws Exception {
        ProxyCreator proxyCreator = new JdkProxyCreator(new UserServiceImpl());
        UserService userService = (UserService) proxyCreator.getProxy();
        
        System.out.println("proxy type = " + userService.getClass());
        System.out.println();
        userService.save(null);
        System.out.println();
        userService.update(null);
    }
}

測試結果:



如上,從測試結果中便瑟。我們可以看出缆毁,我們的代理邏輯正常執(zhí)行了。另外到涂,注意一下 userService 指向對象的類型脊框,并非是 xyz.coolblog.proxy.UserServiceImpl,而是 com.sun.proxy.$Proxy4践啄。

關于 JDK 動態(tài)代理浇雹,這里先說這么多。下一節(jié)屿讽,我來演示一下 CGLIB 動態(tài)代理昭灵,繼續(xù)往下看吧。

2.2.2基于 CGLIB 的動態(tài)代理

當我們要為未實現接口的類生成代理時伐谈,就無法使用 JDK 動態(tài)代理了烂完。那么此類的目標對象生成代理時應該怎么辦呢?當然是使用 CGLIB 了诵棵。在 CGLIB 中抠蚣,代理邏輯是封裝在 MethodInterceptor 實現類中的,代理對象則是通過 Enhancer 類的 create 方法進行創(chuàng)建履澳。下面我來演示一下 CGLIB 創(chuàng)建代理對象的過程嘶窄,如下:

public class Tank59 {

    void run() {
        System.out.println("極速前行中....");
    }

    void shoot() {
        System.out.println("轟...轟...轟...轟...");
    }
}

CGLIB 代理創(chuàng)建者

public class CglibProxyCreator implements ProxyCreator {

    private Object target;

    private MethodInterceptor methodInterceptor;

    public CglibProxyCreator(Object target, MethodInterceptor methodInterceptor) {
        assert (target != null && methodInterceptor != null);
        this.target = target;
        this.methodInterceptor = methodInterceptor;
    }

    @Override
    public Object getProxy() {
        Enhancer enhancer = new Enhancer();
        // 設置代理類的父類
        enhancer.setSuperclass(target.getClass());
        // 設置代理邏輯
        enhancer.setCallback(methodInterceptor);
        // 創(chuàng)建代理對象
        return enhancer.create();
    }
}

方法攔截器 - 坦克再制造:

public class TankRemanufacture implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        if (method.getName().equals("run")) {
            System.out.println("正在重造59坦克...");
            System.out.println("重造成功怀跛,已獲取 ?59改 之 超音速飛行版?");
            System.out.print("已起飛,正在突破音障柄冲。");

            methodProxy.invokeSuper(o, objects);

            System.out.println("已擊落黑鳥 SR-71吻谋,正在返航...");
            return null;
        }

        return methodProxy.invokeSuper(o, objects);
    }
}

好了,下面開始演示羊初,測試代碼如下:

public class CglibProxyCreatorTest {

    @Test
    public void getProxy() throws Exception {
        ProxyCreator proxyCreator = new CglibProxyCreator(new Tank59(), new TankRemanufacture());
        Tank59 tank59 = (Tank59) proxyCreator.getProxy();
        
        System.out.println("proxy class = " + tank59.getClass() + "\n");
        tank59.run();
        System.out.println();
        System.out.print("射擊測試:");
        tank59.shoot();
    }
}

測試結果如下:


image.png

3. 源碼

為目標 bean 創(chuàng)建代理對象前滨溉,需要先創(chuàng)建 AopProxy 對象,然后再調用該對象的 getProxy 方法創(chuàng)建實際的代理類长赞。我們先來看看 AopProxy 這個接口的定義晦攒,如下

public interface AopProxy {

    /** 創(chuàng)建代理對象 */
    Object getProxy();
    
    Object getProxy(ClassLoader classLoader);
}

在 Spring 中,有兩個類實現了 AopProxy得哆,如下:


image.png

Spring 在為目標 bean 創(chuàng)建代理的過程中脯颜,要根據 bean 是否實現接口,以及一些其他配置來決定使用 AopProxy 何種實現類為目標 bean 創(chuàng)建代理對象贩据。下面我們就來看一下代理創(chuàng)建的過程栋操,如下

protected Object createProxy(
        Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {

    if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
        AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
    }

    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.copyFrom(this);

    /*
     * 默認配置下,或用戶顯式配置 proxy-target-class = "false" 時饱亮,
     * 這里的 proxyFactory.isProxyTargetClass() 也為 false
     */
    if (!proxyFactory.isProxyTargetClass()) {
        if (shouldProxyTargetClass(beanClass, beanName)) {
            proxyFactory.setProxyTargetClass(true);
        }
        else {
            /*
             * 檢測 beanClass 是否實現了接口矾芙,若未實現,則將 
             * proxyFactory 的成員變量 proxyTargetClass 設為 true
             */ 
            evaluateProxyInterfaces(beanClass, proxyFactory);
        }
    }

    // specificInterceptors 中若包含有 Advice近上,此處將 Advice 轉為 Advisor
    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
    proxyFactory.addAdvisors(advisors);
    proxyFactory.setTargetSource(targetSource);
    customizeProxyFactory(proxyFactory);

    proxyFactory.setFrozen(this.freezeProxy);
    if (advisorsPreFiltered()) {
        proxyFactory.setPreFiltered(true);
    }

    // 創(chuàng)建代理
    return proxyFactory.getProxy(getProxyClassLoader());
}

public Object getProxy(ClassLoader classLoader) {
    // 先創(chuàng)建 AopProxy 實現類對象剔宪,然后再調用 getProxy 為目標 bean 創(chuàng)建代理對象
    return createAopProxy().getProxy(classLoader);
}
protected final synchronized AopProxy createAopProxy() {
    if (!this.active) {
        activate();
    }
    return getAopProxyFactory().createAopProxy(this);
}

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

    @Override
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        /*
         * 下面的三個條件簡單分析一下:
         *
         *   條件1:config.isOptimize() - 是否需要優(yōu)化,這個屬性沒怎么用過壹无,
         *         細節(jié)我不是很清楚
         *   條件2:config.isProxyTargetClass() - 檢測 proxyTargetClass 的值葱绒,
         *         前面的代碼會設置這個值
         *   條件3:hasNoUserSuppliedProxyInterfaces(config) 
         *         - 目標 bean 是否實現了接口
         */
        if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
            Class<?> targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class: " +
                        "Either an interface or a target is required for proxy creation.");
            }
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                return new JdkDynamicAopProxy(config);
            }
            // 創(chuàng)建 CGLIB 代理,ObjenesisCglibAopProxy 繼承自 CglibAopProxy
            return new ObjenesisCglibAopProxy(config);
        }
        else {
            // 創(chuàng)建 JDK 動態(tài)代理
            return new JdkDynamicAopProxy(config);
        }
    }
}

如上斗锭,DefaultAopProxyFactory 根據一些條件決定生成什么類型的 AopProxy 實現類對象地淀。生成好 AopProxy 實現類對象后,下面就要為目標 bean 創(chuàng)建代理對象了岖是。這里以 JdkDynamicAopProxy 為例帮毁,我們來看一下,該類的 getProxy 方法的邏輯是怎樣的豺撑。如下:

public Object getProxy() {
    return getProxy(ClassUtils.getDefaultClassLoader());
}

public Object getProxy(ClassLoader classLoader) {
    if (logger.isDebugEnabled()) {
        logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
    }
    Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
    findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
    
    // 調用 newProxyInstance 創(chuàng)建代理對象
    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

如上作箍,請把目光移至最后一行有效代碼上,會發(fā)現 JdkDynamicAopProxy 最終調用 Proxy.newProxyInstance 方法創(chuàng)建代理對象前硫。到此,創(chuàng)建代理對象的整個過程也就分析完了荧止,不知大家看懂了沒屹电。好了阶剑,關于創(chuàng)建代理的源碼分析,就先說到這里吧危号。

4.總結

本篇文章對 Spring AOP 創(chuàng)建代理對象的過程進行了較為詳細的分析牧愁,并在分析源碼前介紹了相關的背景知識⊥饬總的來說猪半,本篇文章涉及的技術點不是很復雜,相信大家都能看懂偷线。限于個人能力磨确,若文中有錯誤的地方,歡迎大家指出來声邦。好了乏奥,本篇文章到此結束,謝謝閱讀亥曹。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末邓了,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子媳瞪,更是在濱河造成了極大的恐慌骗炉,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛇受,死亡現場離奇詭異句葵,居然都是意外死亡,警方通過查閱死者的電腦和手機龙巨,發(fā)現死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門笼呆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人旨别,你說我怎么就攤上這事诗赌。” “怎么了秸弛?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵铭若,是天一觀的道長。 經常有香客問我递览,道長叼屠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任绞铃,我火速辦了婚禮镜雨,結果婚禮上,老公的妹妹穿的比我還像新娘儿捧。我一直安慰自己荚坞,他們只是感情好挑宠,可當我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著颓影,像睡著了一般各淀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上诡挂,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天碎浇,我揣著相機與錄音,去河邊找鬼璃俗。 笑死奴璃,一個胖子當著我的面吹牛,可吹牛的內容都是我干的旧找。 我是一名探鬼主播溺健,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼钮蛛!你這毒婦竟也來了鞭缭?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤魏颓,失蹤者是張志新(化名)和其女友劉穎岭辣,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體甸饱,經...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡沦童,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了叹话。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片偷遗。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖驼壶,靈堂內的尸體忽然破棺而出氏豌,到底是詐尸還是另有隱情,我是刑警寧澤热凹,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布泵喘,位于F島的核電站,受9級特大地震影響般妙,放射性物質發(fā)生泄漏纪铺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一碟渺、第九天 我趴在偏房一處隱蔽的房頂上張望鲜锚。 院中可真熱鬧,春花似錦、人聲如沸芜繁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽浆洗。三九已至,卻和暖如春集峦,著一層夾襖步出監(jiān)牢的瞬間伏社,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工塔淤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留摘昌,地道東北人。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓高蜂,卻偏偏與公主長得像聪黎,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子备恤,可洞房花燭夜當晚...
    茶點故事閱讀 43,697評論 2 351