AOP和代理模式


什么是AOP

顧IoC

  我們都知道Spring框架的核心思想就是兩個:IoC和AOP。Ioc簡單來講就是為了實現項目各層級設計的充分解耦,使軟件更加地滿足高內聚、低耦合的要求。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,這里暫且不說。

總結

  AOP通過代理模式,將自己要處理的事務橫切入系統中,實現了面向切面的編程。同時,實現AOP的核心技術是反射。而關於反射,只有將JVM摸了一遍后才能總結的較為完善。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM