面試官:拋開Spring來(lái)說(shuō),如何自己實(shí)現(xiàn)Spring AOP?
1、藍(lán)綠發(fā)布、滾動(dòng)發(fā)布、灰度發(fā)布,有什么區(qū)別?這下明白了
3、公司規(guī)定所有接口都用 POST請(qǐng)求,這是為什么?
| 引言
翻開to-do,注解認(rèn)證中答應(yīng)大家要講解代理模式。

正好遇到了一道這樣的題:拋開Spring來(lái)說(shuō),如何自己實(shí)現(xiàn)Spring AOP?
就喜歡這樣的題,能把那些天天寫增刪改查從來(lái)不思考的人給PK下去,今天就和大家一切學(xué)習(xí)代理模式與Spring AOP。
| 代理與裝飾器
場(chǎng)景描述
代理,即替代之意,可替代所有功能,即和原類實(shí)現(xiàn)相同的規(guī)范。
代理模式和裝飾器模式很像,之前的裝飾器講的不是很好,這里換個(gè)例子再講一遍。
寧?kù)o的午后,來(lái)到咖啡館,想喝一杯咖啡。
基礎(chǔ)實(shí)現(xiàn)
給你一個(gè)咖啡接口:
public?interface?Coffee?{
????/**
?????*?打印當(dāng)前咖啡的原材料,即咖啡里有什么
?????*/
????void?printMaterial();
}
一個(gè)默認(rèn)的苦咖啡的實(shí)現(xiàn):
public?class?BitterCoffee?implements?Coffee?{
????@Override
????public?void?printMaterial()?{
????????System.out.println("咖啡");
????}
}
默認(rèn)的點(diǎn)餐邏輯:
public?class?Main?{
????public?static?void?main(String[]?args)?{
????????Coffee?coffee?=?new?BitterCoffee();
????????coffee.printMaterial();
????}
}
點(diǎn)一杯咖啡。

裝飾器模式
優(yōu)雅的服務(wù)生把咖啡端了上來(lái),抿了一口,有些苦。
想加點(diǎn)糖,對(duì)服務(wù)生說(shuō):“您好,請(qǐng)為我的咖啡加些糖”。
/**
?*?糖裝飾器,用來(lái)給咖啡加糖
?*/
public?class?SugarDecorator?implements?Coffee?{
????/**
?????*?持有的咖啡對(duì)象
?????*/
????private?final?Coffee?coffee;
????public?SugarDecorator(Coffee?coffee)?{
????????this.coffee?=?coffee;
????}
????@Override
????public?void?printMaterial()?{
????????System.out.println("糖");
????????this.coffee.printMaterial();
????}
}
然后服務(wù)生就拿走了我的咖啡,去使用SugarDecorator為咖啡加糖,最后把加好糖的咖啡給我。
public?class?Main?{
????public?static?void?main(String[]?args)?{
????????Coffee?coffee?=?new?BitterCoffee();
????????coffee?=?new?SugarDecorator(coffee);
????????coffee.printMaterial();
????}
}
看一看咖啡的成分,對(duì)的,確實(shí)加上了糖!

注意看這兩行:
Coffee?coffee?=?new?BitterCoffee();????????//?點(diǎn)了一杯苦咖啡
coffee?=?new?SugarDecorator(coffee);???????//?給咖啡加了點(diǎn)糖
裝飾器模式適合什么場(chǎng)景,我有一個(gè)對(duì)象,但是這個(gè)對(duì)象的功能不能令我滿意,我就拿裝飾器給他裝飾一下。
代理模式
周末了,又抱著iPad來(lái)到了咖啡館,準(zhǔn)備享受一個(gè)寧?kù)o的下午。
“先生,請(qǐng)問(wèn)您要喝點(diǎn)什么?”一旁禮貌的服務(wù)生上前問(wèn)道。
上次點(diǎn)的咖啡太苦了,這次直接要個(gè)加糖的吧。
“我要一杯加糖咖啡。”
public?class?CoffeeWithSugar?implements?Coffee?{
????private?final?Coffee?coffee;
????public?CoffeeWithSugar()?{
????????this.coffee?=?new?BitterCoffee();
????}
????@Override
????public?void?printMaterial()?{
????????System.out.println("糖");
????????this.coffee.printMaterial();
????}
}
這是加糖咖啡,其實(shí)內(nèi)部仍然是咖啡,只是加了些配方,就產(chǎn)生了一種新類,一種新的可以在菜單上呈現(xiàn)的飲品。
點(diǎn)咖啡:
public?class?Main?{
????public?static?void?main(String[]?args)?{
????????Coffee?coffee?=?new?CoffeeWithSugar();
????????coffee.printMaterial();
????}
}
正合我意,在咖啡的陪伴下度過(guò)了一個(gè)美好的下午。

差別
故事講完了,兩者實(shí)現(xiàn)的都是對(duì)原對(duì)象的包裝,持有原對(duì)象的實(shí)例,差別在于對(duì)外的表現(xiàn)。
裝飾器模式:點(diǎn)了咖啡,發(fā)現(xiàn)太苦了,不是自己想要的,然后用裝飾器加了點(diǎn)糖。
Coffee?coffee?=?new?BitterCoffee();
coffee?=?new?SugarDecorator(coffee);
代理模式:直接就點(diǎn)的加糖咖啡。
Coffee?coffee?=?new?CoffeeWithSugar();
很細(xì)微的差別,希望大家不要弄混。
批評(píng)
去看代理模式相關(guān)的資料,五花八門,怎么理解的都有。
我覺(jué)得菜鳥教程的代理模式解釋的最為正宗:在代理模式中,我們創(chuàng)建具有現(xiàn)有對(duì)象的對(duì)象,以便向外界提供功能接口。

還有,網(wǎng)上許多設(shè)計(jì)模式的文章都是你抄我、我抄你,一個(gè)錯(cuò)了,全都錯(cuò)了。
我覺(jué)得我需要糾正一下。誰(shuí)說(shuō)代理模式一定要用接口的???代理模式是設(shè)計(jì)模式,設(shè)計(jì)模式不分語(yǔ)言,假如一門語(yǔ)言中沒(méi)有接口,那它就不能代理模式了嗎?只是Java中的接口可以讓我們符合依賴倒置原則進(jìn)行開發(fā),降低耦合。用抽象類可以嗎?可以。用類繼承可以嗎?也可以。
思想明白了,用什么寫還不是像玩一樣?
| AOP
設(shè)計(jì)模式是思想,所以我上面說(shuō)的代理模式不是僅適用于接口便與Spring AOP息息相關(guān)。
AOP:Aspect Oriented Programming,面向切面編程,是面向?qū)ο缶幊痰难a(bǔ)充。如果你不明白這句話,好好去學(xué)學(xué)面向?qū)ο缶椭罏槭裁戳恕?/span>
我們會(huì)聲明切面,即切在某方法之前、之后或前后都執(zhí)行。而Spring AOP的實(shí)現(xiàn)就是代理模式。
場(chǎng)景
正好最近寫過(guò)短信驗(yàn)證碼,就拿這個(gè)來(lái)當(dāng)例子吧。
public?interface?SMSService?{
????void?sendMessage();
}
public?class?SMSServiceImpl?implements?SMSService?{
????@Override
????public?void?sendMessage()?{
????????System.out.println("【夢(mèng)云智】您正在執(zhí)行重置密碼操作,您的驗(yàn)證碼為:1234,5分鐘內(nèi)有效,請(qǐng)不要將驗(yàn)證碼轉(zhuǎn)發(fā)給他人。");
????}
}
主函數(shù):
public?class?Main?{
????public?static?void?main(String[]?args)?{
????????SMSService?smsService?=?new?SMSServiceImpl();
????????smsService.sendMessage();
????????smsService.sendMessage();
????}
}
費(fèi)用統(tǒng)計(jì)
老板改需求了,發(fā)驗(yàn)證碼要花錢,老板想看看一共在短信上花了多少錢。
正常按Spring的思路,肯定是聲明一個(gè)切面,來(lái)切發(fā)短信的方法,然后在切面內(nèi)統(tǒng)計(jì)短信費(fèi)用。
只是現(xiàn)在沒(méi)有框架,也就是這道題:拋開Spring來(lái)說(shuō),如何自己實(shí)現(xiàn)Spring AOP?
寫框架考慮的自然多些,我上文講的代理是靜態(tài)代理,編譯期間就決定好的。而框架實(shí)現(xiàn)卻是動(dòng)態(tài)代理,需要在運(yùn)行時(shí)生成代理對(duì)象,因?yàn)樾枰M(jìn)行類掃描,看看哪些個(gè)類有切面需要生成代理對(duì)象。
JDK動(dòng)態(tài)代理
編寫一個(gè)統(tǒng)計(jì)短信費(fèi)用的類實(shí)現(xiàn)InvocationHandler接口。
寫到這,終于明白為什么每次后臺(tái)Debug的時(shí)候都會(huì)跳轉(zhuǎn)到invoke方法。
public?class?MoneyCountInvocationHandler?implements?InvocationHandler?{
????/**
?????*?被代理的目標(biāo)
?????*/
????private?final?Object?target;
????/**
?????*?內(nèi)部存儲(chǔ)錢的總數(shù)
?????*/
????private?Double?moneyCount;
????public?MoneyCountInvocationHandler(Object?target)?{
????????this.target?=?target;
????????this.moneyCount?=?0.0;
????}
????@Override
????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{
????????Object?result?=?method.invoke(target,?args);
????????moneyCount?+=?0.07;
????????System.out.println("發(fā)送短信成功,共花了:"?+?moneyCount?+?"元");
????????return?result;
????}
}
將主函數(shù)里的smsService替換為使用MoneyCountInvocationHandler處理的代理對(duì)象。
public?class?Main?{
????public?static?void?main(String[]?args)?{
????????SMSService?smsService?=?new?SMSServiceImpl();
????????smsService?=?(SMSService)?Proxy.newProxyInstance(Main.class.getClassLoader(),
????????????????????????????????????????????new?Class[]{SMSService.class},
????????????????????????????????????????????new?MoneyCountInvocationHandler(smsService));
????????smsService.sendMessage();
????????smsService.sendMessage();
????}
}

InvocationHandler中的invoke方法動(dòng)態(tài)生成一個(gè)類,該類實(shí)現(xiàn)SMSService接口,代理對(duì)象,就是用這個(gè)類實(shí)例化的。
AOP實(shí)現(xiàn)
上面的都實(shí)現(xiàn)了?寫一個(gè)AOP是不是也不是難事?
主函數(shù)的代碼,應(yīng)該放在IOC容器初始化中,掃描包,去看看哪些個(gè)類需要生成代理對(duì)象,然后構(gòu)造代理對(duì)象到容器中。
然后在invoke方法里,把統(tǒng)計(jì)費(fèi)用的邏輯改成切面的邏輯不就好了嗎?
不足分析
結(jié)束了嗎?當(dāng)然沒(méi)有,上面的方法實(shí)現(xiàn)僅對(duì)接口有效。
因?yàn)?/span>JDK的動(dòng)態(tài)代理,是生成一個(gè)實(shí)現(xiàn)相應(yīng)接口的代理類。但是Spring又不是只能通過(guò)接口注入。
@Autowired
private?Type?xxxxx;
Spring的@Autowired是通過(guò)聲明的類型去容器里找符合的對(duì)象然后注進(jìn)來(lái)的,接口是類型,類不也是類型嗎?
@Autowired
private?SMSService?smsService;
這樣能注進(jìn)來(lái)。
@Autowired
private?SMSServiceImpl?smsService;
這樣呢?也能注進(jìn)來(lái)。
所以,JDK動(dòng)態(tài)代理針對(duì)直接注入類類型的,就代理不了。
cglib動(dòng)態(tài)代理
自古以來(lái),從來(lái)都是時(shí)勢(shì)造英雄,而不是英雄創(chuàng)造了時(shí)代。
出現(xiàn)了問(wèn)題,自然會(huì)有英雄出來(lái)解決。拯救世界的就是cglib。
JDK動(dòng)態(tài)代理解決不了的,統(tǒng)統(tǒng)交給cglib。
就這個(gè)來(lái)說(shuō):
@Autowired
private?SMSServiceImpl?smsService;
不是使用接口注入的,JDK動(dòng)態(tài)代理解決不了。cglib怎么解決的呢?它會(huì)根據(jù)當(dāng)前的類,動(dòng)態(tài)生成一個(gè)子類,在子類中織入切面邏輯。
然后使用子類對(duì)象代理父類對(duì)象。這就是為什么我上面說(shuō):代理模式,不要拘泥于接口。
所以織入成功的,都是子類能把父類覆蓋的方法。
所以cglib也不是萬(wàn)能的,方法是final的,子類重寫不了,它當(dāng)然也無(wú)計(jì)可施了。
| 總結(jié)
讀書讀的是什么?是真正理解作者的思想,明白作者想歌頌什么、批判什么。
框架學(xué)的是什么?不只是為了提高開發(fā)效率,而是在使用的時(shí)候,就像與設(shè)計(jì)者交流一樣,能真正明白框架設(shè)計(jì)者的思想,才算用明白一款框架。
如果我們都能做到這般,又何愁設(shè)計(jì)不出一款真正屬于自己的框架呢?
轉(zhuǎn)自:張喜碩
鏈接:https://segmentfault.com/a/1190000019148468
最近熱文閱讀:
1、為什么我勸你放棄了Restful API? 2、Java8 Stream:2萬(wàn)字20個(gè)實(shí)例,玩轉(zhuǎn)集合的篩選、歸約、分組、聚合 3、公司規(guī)定所有接口都用 POST請(qǐng)求,這是為什么? 4、為什么阿里強(qiáng)制 boolean 類型變量不能使用 is 開頭? 5、面試官:InnoDB中一棵B+樹可以存放多少行數(shù)據(jù)? 6、MyBatis批量插入幾千條數(shù)據(jù),請(qǐng)慎用foreach 7、有了 for (;;) ,為什么還需要while (true) ?到底哪個(gè)更快? 8、名企公開掛“加班真好”標(biāo)語(yǔ),員工稱一年被免費(fèi)“白嫖”600多小時(shí)!網(wǎng)友看不下去了,稽查部門展開調(diào)查... 9、面試官:為什么 Java 不把基本類型放在堆中?我竟然答不上來(lái)。。 10、IDEA 注釋模板這樣搞! 關(guān)注公眾號(hào),你想要的Java都在這里
