翻開to-do
,註解認證中答應你們要講解代理模式。java
正好遇到了一道這樣的題:拋開Spring
來講,如何本身實現Spring AOP
?編程
就喜歡這樣的題,能把那些每天寫增刪改查歷來不思考的人給PK
下去,今天就和你們一切學習代理模式與Spring AOP
。設計模式
代理,即替代之意,可替代全部功能,即和原類實現相同的規範。框架
代理模式和裝飾器模式很像,以前的裝飾器講的不是很好,這裏換個例子再講一遍。ide
寧靜的午後,來到咖啡館,想喝一杯咖啡。
給你一個咖啡接口:svg
public interface Coffee { /** * 打印當前咖啡的原材料,即咖啡裏有什麼 */ void printMaterial(); }
一個默認的苦咖啡的實現:函數
public class BitterCoffee implements Coffee { @Override public void printMaterial() { System.out.println("咖啡"); } }
默認的點餐邏輯:學習
public class Main { public static void main(String[] args) { Coffee coffee = new BitterCoffee(); coffee.printMaterial(); } }
點一杯咖啡。this
優雅的服務生把咖啡端了上來,抿了一口,有些苦。spa
想加點糖,對服務生說:「您好,請爲個人咖啡加些糖」。
/** * 糖裝飾器,用來給咖啡加糖 */ public class SugarDecorator implements Coffee { /** * 持有的咖啡對象 */ private final Coffee coffee; public SugarDecorator(Coffee coffee) { this.coffee = coffee; } @Override public void printMaterial() { System.out.println("糖"); this.coffee.printMaterial(); } }
而後服務生就拿走了個人咖啡,去使用SugarDecorator
爲咖啡加糖,最後把加好糖的咖啡給我。
public class Main { public static void main(String[] args) { Coffee coffee = new BitterCoffee(); coffee = new SugarDecorator(coffee); coffee.printMaterial(); } }
看一看咖啡的成分,對的,確實加上了糖!
注意看這兩行:
Coffee coffee = new BitterCoffee(); // 點了一杯苦咖啡 coffee = new SugarDecorator(coffee); // 給咖啡加了點糖
裝飾器模式適合什麼場景,我有一個對象,可是這個對象的功能不能令我滿意,我就拿裝飾器給他裝飾一下。
週末了,又抱着iPad
來到了咖啡館,準備享受一個寧靜的下午。
「先生,請問您要喝點什麼?」一旁禮貌的服務生上前問道。
上次點的咖啡太苦了,此次直接要個加糖的吧。
「我要一杯加糖咖啡。」
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(); } }
這是加糖咖啡,其實內部仍然是咖啡,只是加了些配方,就產生了一種新類,一種新的能夠在菜單上呈現的飲品。
點咖啡:
public class Main { public static void main(String[] args) { Coffee coffee = new CoffeeWithSugar(); coffee.printMaterial(); } }
正合我意,在咖啡的陪伴下度過了一個美好的下午。
故事講完了,二者實現的都是對原對象的包裝,持有原對象的實例,差異在於對外的表現。
裝飾器模式:點了咖啡,發現太苦了,不是本身想要的,而後用裝飾器加了點糖。
Coffee coffee = new BitterCoffee(); coffee = new SugarDecorator(coffee);
代理模式:直接就點的加糖咖啡。
Coffee coffee = new CoffeeWithSugar();
很細微的差異,但願你們不要弄混。
去看代理模式相關的資料,五花八門,怎麼理解的都有。
我以爲菜鳥教程的代理模式解釋的最爲正宗:在代理模式中,咱們建立具備現有對象的對象,以便向外界提供功能接口。
還有,網上許多設計模式的文章都是你抄我、我抄你,一個錯了,全都錯了。
我以爲我須要糾正一下。誰說代理模式必定要用接口的啊?代理模式時設計模式,設計模式不分語言,假如一門語言中沒有接口,那它就不能代理模式了嗎?只是Java
中的接口可讓咱們符合依賴倒置原則進行開發,下降耦合。用抽象類能夠嗎?能夠。用類繼承能夠嗎?也能夠。
思想明白了,用什麼寫還不是像玩同樣?
AOP
設計模式是思想,因此我上面說的代理模式不是僅適用於接口便與Spring AOP
息息相關。
AOP
:Aspect Oriented Programming
,面向切面編程,是面向對象編程的補充。若是你不明白這句話,好好去學學面向對象就知道爲何了。
咱們會聲明切面,即切在某方法以前、以後或先後都執行。而Spring AOP
的實現就是代理模式。
正好最近寫太短信驗證碼,就拿這個來當例子吧。
public interface SMSService { void sendMessage(); }
public class SMSServiceImpl implements SMSService { @Override public void sendMessage() { System.out.println("【夢雲智】您正在執行重置密碼操做,您的驗證碼爲:1234,5分鐘內有效,請不要將驗證碼轉發給他人。"); } }
主函數:
public class Main { public static void main(String[] args) { SMSService smsService = new SMSServiceImpl(); smsService.sendMessage(); smsService.sendMessage(); } }
老闆改需求了,發驗證碼要花錢,老闆想看看一共在短信上花了多少錢。
正常按Spring
的思路,確定是聲明一個切面,來切發短信的方法,而後在切面內統計短信費用。
只是如今沒有框架,也就是這道題:拋開Spring
來講,如何本身實現Spring AOP
?
寫框架考慮的天然多些,我上文講的代理是靜態代理,編譯期間就決定好的。而框架實現倒是動態代理,須要在運行時生成代理對象,由於須要進行類掃描,看看哪些個類有切面須要生成代理對象。
JDK
動態代理編寫一個統計短信費用的類實現InvocationHandler
接口。
寫到這,終於明白爲何每次後臺Debug
的時候都會跳轉到invoke
方法。
public class MoneyCountInvocationHandler implements InvocationHandler { /** * 被代理的目標 */ private final Object target; /** * 內部存儲錢的總數 */ 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("發送短信成功,共花了:" + moneyCount + "元"); return result; } }
將主函數裏的smsService
替換爲使用MoneyCountInvocationHandler
處理的代理對象。
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
方法動態生成一個類,該類實現SMSService
接口,代理對象,就是用這個類實例化的。
AOP
實現上面的都實現了?寫一個AOP
是否是也不是難事?
主函數的代碼,應該放在IOC
容器初始化中,掃描包,去看看哪些個類須要生成代理對象,而後構造代理對象到容器中。
而後在invoke
方法裏,把統計費用的邏輯改爲切面的邏輯不就行了嗎?
結束了嗎?固然沒有,上面的方法實現僅對接口有效。
由於JDK
的動態代理,是生成一個實現相應接口的代理類。可是Spring
又不是隻能經過接口注入。
@Autowired private Type xxxxx;
Spring
的@Autowired
是經過聲明的類型去容器裏找符合的對象而後注進來的,接口是類型,類不也是類型嗎?
@Autowired private SMSService smsService;
這樣能注進來。
@Autowired private SMSServiceImpl smsService;
這樣呢?也能注進來。
因此,JDK
動態代理針對直接注入類類型的,就代理不了。
cglib
動態代理自古以來,歷來都是時勢造英雄,而不是英雄創造了時代。
出現了問題,天然會有英雄出來解決。拯救世界的就是cglib
。
JDK
動態代理解決不了的,通通交給cglib
。
就這個來講:
@Autowired private SMSServiceImpl smsService;
不是使用接口注入的,JDK
動態代理解決不了。cglib
怎麼解決的呢?它會根據當前的類,動態生成一個子類,在子類中織入切面邏輯。
而後使用子類對象代理父類對象。這就是爲何我上面說:代理模式,不要拘泥於接口。
因此織入成功的,都是子類能把父類覆蓋的方法。
因此cglib
也不是萬能的,方法是final
的,子類重寫不了,它固然也機關用盡了。
讀書讀的是什麼?是真正理解做者的思想,明白做者想歌頌什麼、批判什麼。框架學的是什麼?不僅是爲了提升開發效率,而是在使用的時候,就像與設計者交流同樣,能真正明白框架設計者的思想,纔算用明白一款框架。
若是咱們都能作到這般,又何愁設計不出一款真正屬於本身的框架呢?