Spring AOP源碼解析——專治你不會(huì)看源碼的壞毛参呤恪!

第一托启,要形成的習(xí)慣:

1.有空時(shí)隔一段時(shí)間要做幾道算法題宅倒,C語言和JAVA都可以,主要是訓(xùn)練思維屯耸。

2.定期閱讀spring的源碼拐迁。因?yàn)閟pring是框架蹭劈,重設(shè)計(jì),能夠培養(yǎng)大局觀

3.閱讀底層的書籍唠亚,如linux方面链方,虛擬機(jī)方面,這是內(nèi)功灶搜。越高級(jí)的語言只是招式。

4.不要忘記做了一半的東西工窍,如搜索引擎方面割卖,redis方面,可以過一段時(shí)間再做患雏,因?yàn)榈綍r(shí)候自己的境界有提升鹏溯,深入程度也會(huì)有所增加。


第二淹仑,進(jìn)入正題丙挽,了解原理,再看源碼匀借。

1.看源碼看熟了颜阐,以后再遇到問題,就可以通過源碼去了解原理了吓肋。

2.spring的AOP凳怨,原理懂了,代碼相當(dāng)簡(jiǎn)單是鬼。這也是為什么我記得我還是個(gè)菜鳥的時(shí)候肤舞,面試人家經(jīng)常問我這個(gè)。

3.先有個(gè)大局觀均蜜,畫張整體的spring結(jié)構(gòu)圖李剖。

? ? 以下是備受吐槽的手繪時(shí)間:

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

AOP面向切面編程是面向?qū)ο蟮难a(bǔ)充篙顺。它利用一種橫切技術(shù),將一些公共行為封裝成叫做“方面”的可重用模塊紫皇,解耦慰安,增加可維護(hù)性。

AOP將系統(tǒng)分為核心關(guān)注點(diǎn)和橫切關(guān)注點(diǎn)兩部分聪铺。核心關(guān)注點(diǎn)就是主業(yè)務(wù)流程化焕,橫切關(guān)注點(diǎn)就是上面提到的“方面”。那么看AOP的源碼就是要看橫切關(guān)注點(diǎn)是怎樣和核心關(guān)注點(diǎn)整合來發(fā)揮作用的铃剔。

主業(yè)務(wù)流程歸根到底是一個(gè)java方法撒桨,而且是對(duì)象的方法查刻。

在AOP中被稱為被通知或被代理對(duì)象POJO。AOP的作用就是將核心關(guān)注點(diǎn)和橫切關(guān)注點(diǎn)組合起來凤类,術(shù)語叫做“增強(qiáng)”穗泵。最后實(shí)際用的是增強(qiáng)后的代理對(duì)象。

對(duì)核心關(guān)注點(diǎn)進(jìn)行增強(qiáng)就涉及到在哪些地方增強(qiáng)的問題谜疤。如方法調(diào)用或者異常拋出時(shí)做增強(qiáng)這些時(shí)機(jī)叫做連接點(diǎn)Joinpoint佃延。一個(gè)通知將被引發(fā)的連接點(diǎn)集合叫做切入點(diǎn),理解時(shí)就可以想正則表達(dá)式夷磕,通配符來指定多個(gè)履肃,而不是單單一個(gè)連接點(diǎn)。

在連接點(diǎn)都做了哪些增強(qiáng)呢坐桩?增強(qiáng)的內(nèi)容AOP術(shù)語叫“通知”Advice尺棋。

Spring里定義了四種Advice:BeforeAdvice,AfterAdvice绵跷,ThrowAdvice膘螟,DynamicIntroducationAdvice。

許多AOP框架包括spring都是以攔截器作為通知模型碾局。維護(hù)一個(gè)圍繞連接點(diǎn)的攔截器鏈荆残。其中DynamicIntroducationAdvice是可以引入方法或者字段到核心關(guān)注點(diǎn)。

這里有個(gè)Introduction擦俐,AOP術(shù)語叫引入脊阴。將增強(qiáng)后的AOP代理組裝到系統(tǒng)叫做織入。

上面就是AOP的核心概念了蚯瞧『倨冢總結(jié)一下:

AOP要做的事情就是:生成代理對(duì)象,然后織入埋合。

生成代理對(duì)象是經(jīng)常會(huì)被問到的一個(gè)問題:Spring提供了兩種方式來生成代理對(duì)象备徐,JDKProxy和Cglib。

具體使用哪種方式由AopProxyFactory根據(jù)AdvisedSupport對(duì)象的配置來決定甚颂。

默認(rèn)的策略是如果目標(biāo)類是接口蜜猾,則使用JDK動(dòng)態(tài)代理技術(shù),否則使用Cglib來生成代理振诬。

Cglib是基于字節(jié)碼技術(shù)的蹭睡,使用的是ASM。asm是一個(gè)java字節(jié)碼操縱框架赶么,它能被用來動(dòng)態(tài)生成類或者增強(qiáng)既有類的功能肩豁。

ASM可以直接產(chǎn)生二進(jìn)制class文件,也可以在類被加載入JVM之前動(dòng)態(tài)改變類行為。

下面重點(diǎn)來看看JDK動(dòng)態(tài)代理技術(shù)清钥。這是我還是個(gè)很菜很菜的菜鳥時(shí)為數(shù)不多能看懂的源碼琼锋。因?yàn)橹翱催^Java設(shè)計(jì)模式,寫過類似的例子祟昭,所以會(huì)比較順暢缕坎。今天先講這一部分。

下面是調(diào)用測(cè)試類:


package?dynamic.proxy;?

import?java.lang.reflect.InvocationHandler;

import?java.lang.reflect.Method;

import?java.lang.reflect.Proxy;

/**

?*?實(shí)現(xiàn)自己的InvocationHandler

?*?@author?zyb

?*?@since?2012-8-9

?*

?*/

public?class?MyInvocationHandler?implements?InvocationHandler?{

?//?目標(biāo)對(duì)象?

?private?Object?target;

?/**

?*?構(gòu)造方法

?*?@param?target?目標(biāo)對(duì)象?

?*/

?public?MyInvocationHandler(Object?target)?{

?super();

?this.target?=?target;

?}

?/**

?*?執(zhí)行目標(biāo)對(duì)象的方法

?*/

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

?//?在目標(biāo)對(duì)象的方法執(zhí)行之前簡(jiǎn)單的打印一下

?System.out.println("------------------before------------------");

?//?執(zhí)行目標(biāo)對(duì)象的方法

?Object?result?=?method.invoke(target,?args);

?//?在目標(biāo)對(duì)象的方法執(zhí)行之后簡(jiǎn)單的打印一下

?System.out.println("-------------------after------------------");

?return?result;

?}

?/**

?*?獲取目標(biāo)對(duì)象的代理對(duì)象

?*?@return?代理對(duì)象

?*/

?public?Object?getProxy()?{

?return?Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),?

?target.getClass().getInterfaces(),?this);

?}

}

package?dynamic.proxy;

/**

?*?目標(biāo)對(duì)象實(shí)現(xiàn)的接口篡悟,用JDK來生成代理對(duì)象一定要實(shí)現(xiàn)一個(gè)接口

?*?@author?zyb

?*?@since?2012-8-9

?*

?*/

public?interface?UserService?{

?/**

?*?目標(biāo)方法?

?*/

?public?abstract?void?add();

}

package?dynamic.proxy;?

/**

?*?目標(biāo)對(duì)象

?*?@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;

/**

?*?動(dòng)態(tài)代理測(cè)試類

?*?@author?zyb

?*?@since?2012-8-9

?*

?*/

public?class?ProxyTest?{

?@Test

?public?void?testProxy()?throws?Throwable?{

?//?實(shí)例化目標(biāo)對(duì)象

?UserService?userService?=?new?UserServiceImpl();

?//?實(shí)例化InvocationHandler

?MyInvocationHandler?invocationHandler?=?new?MyInvocationHandler(userService);

?//?根據(jù)目標(biāo)對(duì)象生成代理對(duì)象

?UserService?proxy?=?(UserService)?invocationHandler.getProxy();

?//?調(diào)用代理對(duì)象的方法

?proxy.add();

?}

}

執(zhí)行結(jié)果如下:

------------------before---------------

--------------------add---------------

-------------------after-----------------

很簡(jiǎn)單谜叹,核心就是 invocationHandler.getProxy();這個(gè)方法調(diào)用的Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),target.getClass().getInterfaces(), this); 怎么生成對(duì)象的。


/**

?*?Returns?an?instance?of?a?proxy?class?for?the?specified?interfaces

?*?that?dispatches?method?invocations?to?the?specified?invocation

?*?handler.

?*

?*?

{@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,?s,?is?present

?*?and?any?of?the?following?conditions?is?met:

?*?

?*?

?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;

?*?

?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};

?*?

?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.

?*?

?*?@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()?{

?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);

?}

?}


這個(gè)代碼是JDK1.8中的恰力,里面用到了1.8的一些語法叉谜,如果不太了解,建議先看看這本書踩萎。

代碼看著不少,實(shí)際上都在進(jìn)行一些安全校驗(yàn)很钓,包裝之類的香府,真正有用的就兩句:

Class cl = getProxyClass0(loader, intfs);這句話查找或者生成代理類。跟進(jìn)去:


/**

?*?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);

?}


對(duì)码倦,就是從緩存里把接口拿將出來企孩。然后用return cons.newInstance(new Object[]{h}) 這一句將接口用invocationHandler進(jìn)行包裝。

具體源碼可以跟進(jìn)去看袁稽,不詳述勿璃。想必看到這里,JDK動(dòng)態(tài)代理的原理都已經(jīng)很明白了推汽。

這里要說一點(diǎn)理論性的東西:

AOP解決的問題往往可以用代理模式來解決补疑。Java開發(fā)中常說動(dòng)態(tài)代理和靜態(tài)代理,而AOP就是動(dòng)態(tài)代理歹撒,因?yàn)榇淼念愂窃谶\(yùn)行時(shí)才生成的莲组。

而一般說的代理模式寫成的代碼是編譯期就已經(jīng)生成的,叫靜態(tài)代理暖夭。

想繼續(xù)了解JAVA知識(shí)的锹杈,記得關(guān)注+關(guān)注+關(guān)注!


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末迈着,一起剝皮案震驚了整個(gè)濱河市竭望,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌裕菠,老刑警劉巖咬清,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡枫振,警方通過查閱死者的電腦和手機(jī)喻圃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來粪滤,“玉大人斧拍,你說我怎么就攤上這事≌刃。” “怎么了肆汹?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)予权。 經(jīng)常有香客問我昂勉,道長(zhǎng),這世上最難降的妖魔是什么扫腺? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任岗照,我火速辦了婚禮,結(jié)果婚禮上笆环,老公的妹妹穿的比我還像新娘攒至。我一直安慰自己,他們只是感情好躁劣,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布迫吐。 她就那樣靜靜地躺著,像睡著了一般账忘。 火紅的嫁衣襯著肌膚如雪志膀。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天鳖擒,我揣著相機(jī)與錄音溉浙,去河邊找鬼。 笑死败去,一個(gè)胖子當(dāng)著我的面吹牛放航,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播圆裕,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼广鳍,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了吓妆?” 一聲冷哼從身側(cè)響起赊时,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎行拢,沒想到半個(gè)月后祖秒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年竭缝,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了房维。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡抬纸,死狀恐怖咙俩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情湿故,我是刑警寧澤阿趁,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站坛猪,受9級(jí)特大地震影響脖阵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜墅茉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一命黔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧就斤,春花似錦纷铣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽以躯。三九已至槐秧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間忧设,已是汗流浹背刁标。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留址晕,地道東北人膀懈。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像谨垃,于是被迫代替她去往敵國和親启搂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容