回 顧IoC
拿掉IoC容器后的系統如下圖所示
簡單來講,我沒可以把IoC容器理解為一個和黑箱,它起到了類似於一種“粘合劑”的作用,得到了對象的控制權,使得我們系統各個對象的依賴關系降到了最低限度。
關於OOP
在說AOP之前,我們必須得先聊一下OOP。
OOP,即面向對象編程。OOP是對於真實世界的映射,即將萬事萬物進行歸類並建立模型,抽象出屬性和方法,萬物皆是對象。運用OOP的思想,我們在編程時就像搭積木一樣,一點一點建立。簡單來講,OOP更接近於人類對於現實世界構建的真實想法。
引入AOP
我們要思考這樣一個問題,既然OOP就能解決問題,為什么還要創造出AOP呢?
對於面向對象編程,我們的系統功能設計總體上來看是縱向的。以web開發為例。控制器調用服務,服務調用Dao。我們假設這樣一種需求場景:
你有一段業務代碼,有一天,老板讓你統計一下每天使用功能A的人數。於是,你在A的代碼后面加上了一段代碼,實現了統計每天的使用人數;
又過了一周,老板說為了提高系統運行效率,我們需要看一下功能A每次的執行時間。於是,你在A的代碼前后加上了新的代碼,實現記錄功能A執行時間的功能;
又過了一周,老板說咱們這個系統萬一出錯了,得記錄下日志。於是,你又在功能A的代碼后面加上了一段實現日志功能的代碼;
又過了一周.......
時間久了,你會發現功能A的內部代碼不僅變得臃腫無比,而且大量充斥着和功能A的業務無關的代碼。更為恐怖的是,新加的功能也許不僅僅是功能A需要,或許功能B、功能C、功能D....都需要,如果不想出一個優雅的解決方案,我么就需要對功能B、功能C、功能D....一個個進行方法注入,工作量巨大無比且不說,代碼也極為冗余。就算你把這些代碼寫的再規范,再怎么抽時間去優化,這些代碼也已經破壞了OOP特性之一的封裝性,造成了代碼污染。
所以,AOP就誕生了。AOP就是為了解決OOP無法滿足的需求,即提供一個橫切的方法,不破原始代碼的封裝性,對已有的代碼進行擴展,從而實現更為豐富的功能。而對於上面的例子而言,我們可以運用AOP的思想,把和主干業務無關的功能代碼給抽取出來,然后在這些功能運行時將其動態的橫向插入進去,減少耦合度和代碼冗余。
那么,AOP是如何實現這種“動態的橫向插入”的呢?這里就要說到另一種設計模式:代理模式。
代理模式
舉一個現實中的例子,假設我們此刻要去租一間房子住,有兩種方法
這個實例其實可以大概說明代理模式的含義。在這里,中介其實充當的就是租客的代理,租客之和中介接觸,不直接接觸房東。也就是說,中介其實幫租客把找房過程中的找地段、談租金等,和房東簽房屋租賃合同的事情給干了,租客只需要完成租房這個抽象的主干業務即可。代理模式又分為靜態代理和動態代理,我們先來看一下靜態代理的代碼實現。
靜態代理
1 /* 靜態代理 2 第一步:定義抽象的主干業務,即租房 3 */ 4 public interface RentHouse { 5 void rentHouse(); 6 } 7 /* 8 第二步:定義租客,實現租房接口,表明租客有租房的需求 9 */ 10 public class Tenant implements RentHouse{ 11 public void rentHouse() { 12 System.out.println("租客:我想租房!"); 13 } 14 } 15 /* 16 第三步:定義中介,中介是要幫租戶實現租房子的 17 需求,所以也要實現租房接口 18 */ 19 public class RentHouseProxy implements RentHouse{ 20 private RentHouse rentHouse; 21 public RentHouseProxy(RentHouse rentHouse){ 22 this.rentHouse=rentHouse; 23 } 24 public void rentHouse() { 25 System.out.println("中介:搜集房源......"); 26 System.out.println("中介:對比市場價格......"); 27 System.out.println("中介:確定租金......"); 28 System.out.println("中介:前房屋租賃合同......"); 29 System.out.println("中介:裝修裝修,擺點物件......"); 30 rentHouse.rentHouse(); 31 System.out.println("中介:租房完畢......"); 32 } 33 } 34 /* 35 測試 36 結果:中介:搜集房源...... 37 中介:對比市場價格...... 38 中介:確定租金...... 39 中介:前房屋租賃合同...... 40 中介:裝修裝修,擺點物件...... 41 租客:我想租房! 42 中介:租房完畢...... 43 */ 44 public class ProxyDemo { 45 public static void main(String[] args) { 46 RentHouse rentHouse=new Tenant(); 47 RentHouseProxy proxy=new RentHouseProxy(rentHouse); 48 proxy.rentHouse(); 49 } 50 }
通過以上案例代碼我們可以看到,代理模式在不修改被代理對象的基礎下,進行了一些功能的附加與增強。至於為什么被稱為靜態代理,那是因為我們的代理類是事先預定好的,即已經寫死了。同時,我們也應該注意到它的缺陷之處:每一個抽象的事務都是一個接口,我們的代理類需要實現這個接口,即我們得為每一個服務都創建一個代理類,工作量太大,不易管理。而且,接口一旦發生改變,代理類也得同步進行相應的修改
動態代理
動態代理我們不需要再手動創建代理類,只需要編寫一個動態代理處理器即可,真正的代理對象由JVM在運行時為我們動態的創建,這就是它之所以被稱為動態代理的由來。我們看一下案例代碼
1 /* 2 動態代理 3 前兩步與靜態代理相同 4 */ 5 public class DynamicRentHouseProxy implements InvocationHandler { 6 private Object rentHouse; 7 8 public DynamicRentHouseProxy(Object rentHouse) { 9 this.rentHouse = rentHouse; 10 } 11 12 @Override 13 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 14 System.out.println("中介:搜集房源......"); 15 System.out.println("中介:對比市場價格......"); 16 System.out.println("中介:確定租金......"); 17 System.out.println("中介:前房屋租賃合同......"); 18 System.out.println("中介:裝修裝修,擺點物件......"); 19 Object result=method.invoke(rentHouse,args); 20 System.out.println("中介:租房完畢......"); 21 return result; 22 } 23 } 24 public class ProxyDemo { 25 public static void main(String[] args) { 26 /*RentHouse rentHouse=new Tenant(); 27 RentHouseProxy proxy=new RentHouseProxy(rentHouse); 28 proxy.rentHouse();*/ 29 RentHouse tenant=new Tenant(); 30 InvocationHandler handler=new DynamicRentHouseProxy(tenant); 31 //newProxyInstance運用反射為我們生成一個代理類對象 32 RentHouse rentHouse=(RentHouse) Proxy.newProxyInstance(Tenant.class.getClassLoader(), 33 Tenant.class.getInterfaces(),handler); 34 rentHouse.rentHouse(); 35 } 36 }
看下Proxy類newProxyInstance()的源碼
1 //ClassLoader:根據類的字節碼直接生成這個類的Class對象 2 //interfaces:由委托實現的接口的Class對象數組,主要是包含了最重要的代理類需要 3 // 實現的接口方法的信息 4 //h:一個實現了InvocationHandler接口的對象 5 //簡單來講第一個參數ClassLoader和第二參數接口的Class對象是用來動態生成委托類的 6 //包括類名,方法名,繼承關系在內的一個空殼。只有接口定義的方法名,沒有實際操作。實 7 //際的操作是由第三個參數InvocationHandler的invoke()方法來執行 8 public static Object newProxyInstance(ClassLoader loader, 9 Class<?>[] interfaces, 10 InvocationHandler h) { 11 Objects.requireNonNull(h); 12 13 final Class<?> caller = System.getSecurityManager() == null 14 ? null 15 : Reflection.getCallerClass(); 16 17 /* 18 * Look up or generate the designated proxy class and its constructor. 19 */ 20 Constructor<?> cons = getProxyConstructor(caller, loader, interfaces); 21 22 return newProxyInstance(caller, cons, h); 23 }
以上就是動態代理的案例代碼。顯而易見,我們的代理類並不知道事務的具體名稱,也就是說,就算接口中的方法變了,我們的代理類也絲毫不受影響,代理邏輯與業務邏輯是相互獨立的,沒有耦合。
代理模式還有一種方式,叫做CGLib,這里暫且不說。
總結