1. 程式人生 > >理解動態代理及AOP原理

理解動態代理及AOP原理

1.理解的前提知識

在理解動態代理之前,我們得先知道幾個概念,否則會像我剛開始學Java一樣,不能理解,明明比較簡單得事情,為什麼要用動態代理來做。

何為耦合

我們要知道,動態代理得主要目的是來降低程式碼耦合度。那程式碼耦合是什麼意思?

“低耦合”這個詞相信大家已經耳熟能詳,我們在看spring的書籍、MVC的資料、設計模 式的書籍,無處不提到“低耦合、高內聚”,它已經成為軟體設計質量的標準之一。那麼什麼是低耦合?耦合就是對某元素與其它元素之間的連線、感知和依賴的量 度。這裡所說的元素,即可以是功能、物件(類),也可以指系統、子系統、模組。假如一個元素A去連線元素B,或者通過自己的方法可以感知B,或者當B不存 在的時候就不能正常工作,那麼就說元素A與元素B耦合。耦合帶來的問題是,當元素B發生變更或不存在時,都將影響元素A的正常工作,影響系統的可維護性和 易變更性。同時元素A只能工作於元素B存在的環境中,這也降低了元素A的可複用性。正因為耦合的種種弊端,我們在軟體設計的時候努力追求“低耦合”。低耦 合就是要求在我們的軟體系統中,某元素不要過度依賴於其它元素。請注意這裡的“過度”二字。系統中低耦合不能過度,比如說我們設計一個類可以不與JDK耦 合,這可能嗎?除非你不是設計的Java程式。再比如我設計了一個類,它不與我的系統中的任何類發生耦合。如果有這樣一個類,那麼它必然是低內聚(關於內 聚的問題我隨後討論)。耦合與內聚常常是一個矛盾的兩個方面。最佳的方案就是尋找一個合適的中間點。

哪些是耦合呢?

1.元素B是元素A的屬性,或者元素A引用了元素B的例項(這包括元素A呼叫的某個方法,其引數中包含元素B)。

2.元素A呼叫了元素B的方法。

3.元素A直接或間接成為元素B的子類。

4.元素A是介面B的實現。

在Java裡面,因為多型的特性,父類或者介面的引用是可以接受子類對的。 比如:

interface Test{} ,class Test1 implements Test{}, class Test2 implements Test{} ,上面宣告一個Test介面,兩個類Test1和Test2,分別實現Test介面。 表現耦合低的意思是說,刪除呼叫層與接受層無關,比如:有這樣一個方法可以這樣設定引數型別 void say(Test test){System.out.println("");} 然後呼叫時可以這樣傳引數 Test1 t1 = new Test1(); Test2 t2 = new Test2(): say(t1); 或 say(t2); 這就是降低耦合,say方法只需要介面型別,具體傳入哪個物件是無需關注的,因為介面是一種規則,say方法進來的引數一定是實現Test介面類的物件。即使有一天將Test1與Test2兩個類刪除,Test介面還在,那麼say方法就有用,並且隨意擴充套件一個新的類來實現Test介面,這叫做

向後相容

何為硬編碼

電腦科學中,只有硬編碼(hardcode),以及非硬編碼,有人也成為“軟編碼”。
    硬編碼和軟編碼的區別是:軟編碼可以在執行時確定,修改;而硬編碼是不能夠改變的。所有的硬編碼和軟編碼的區別都可以有這個意思擴充套件開。
    在計算機程式或文字編輯中,硬編碼是指將可變變數用一個固定值來代替的方法。用這種方法編譯後,如果以後需要更改此變數就非常困難了。大部分程式語言裡,可以將一個固定數值定義為一個標記,然後用這個特殊標記來取代變數名稱。當標記名稱改變時,變數名不變,這樣,當重新編譯整個程式時,所有變數都不再是固定值,這樣就更容易的實現了改變變數的目的。
    儘管通過編輯器的查詢替換功能也能實現整個變數名稱的替換,但也很有可能出現多換或者少換的情況,而在計算機 程式中,任何小錯誤的出現都是不可饒恕的。最好的方法是單獨為變數名劃分空間,來實現這種變化,就如同前面說的那樣,將需要改變的變數名暫時用一個定義好 的標記名稱來代替就是一種很好的方法。通常情況下,都應該避免使用硬編碼方法。    
    java小例子: int a=2,b=2;   
    硬編碼:if(a==2) return false;   
    非硬編碼 if(a==b) return true;   (就是把數值寫成常數而不是變數 )
    一個簡單的版本:如求圓的面積 的問題 PI(3.14)   
    那麼3.14*r*r 就是硬編碼,而PI*r*r 就不是硬編碼。

動態代理及AOP

首先,我們從最基本得問題開始講,開發實際應用軟體系統時,肯定會出現相同程式碼段得情況,那最簡單的方法就是複製,貼上,就實現了功能。如果只看功能的話,確實實現了軟體開發。

那問題來了,如果有一天,需要修改這些重複程式碼段怎麼辦?要是使用了100處就要改100次,要是1000次,10000次呢?那時候維護程式碼的人肯定想罵娘。

所以,正常的開發人員,都會將這一段程式碼定義為一個方法,然後呼叫。修改的話就只需要修改一次就可以了。

但這時候問題又出現了,就是每一處使用這個方法的程式碼段和這個特定的方法耦合了,耦合的概念和壞處上面已經說了。所以最理想的是,程式碼段可以使用這個方法,但是又沒有用硬編碼的方式呼叫程式碼段,使程式碼耦合。這就是動態代理出現的意義。

下面就是建立代理物件的示例。

public interface Runner {
    //介面
    void info();
    void run();
}
//實現類
public class Wujie extends WechatServer implements Runner {
    @Override
    public void info(){
        System.out.println("我要出去跑步啦!");
    }
    @Override
    public void run(){
        System.out.println("我跑完啦!");
    }
}
//handler,執行代理物件的方法
//實現InvocationHandler介面
public class MyInvokationHandler implements InvocationHandler {
    //需要被代理的物件
    private Object target;
    public void setTarget(Object target){
        this.target = target;
    }
    //執行動態代理物件的所有方法,都會被替換成執行如下的Invoke方法
    //proxy:動態代理物件
    //method:代表正在執行的方法
    //args:代表呼叫目標方法傳入的實參
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //以target作為主調來執行method方法
        Object result = method.invoke(target,args);
        return result;
    }
}
//生成動態代理示例
public class MyProxyFactory {
    public static Object getProxy(Object target)throws Exception{
        //建立MyInvokationHandler物件
        MyInvokationHandler handler = new MyInvokationHandler();
        //設定target物件
        handler.setTarget(target);
        //返回動態代理物件
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),handler);
    }
}
//測試類
public class Test {
    public static void main(String[] args) throws Exception{
        Runner runner = new Wujie();
        Runner proxy = (Runner) MyProxyFactory.getProxy(runner);
        proxy.info();
        proxy.run();
    }
}

測試結果,在執行info方法之前和之和,會分別呼叫Server類裡面的BeforeRun,AfterRun方法。不難發現使用動態代理已經實現了我們上面所說的解耦,因為他呼叫了Server裡面的方法,但是沒有直接硬編碼在測試方法裡面,也就是符合解耦的原則。

1.元素B是元素A的屬性,或者元素A引用了元素B的例項(這包括元素A呼叫的某個方法,其引數中包含元素B)。

2.元素A呼叫了元素B的方法。

3.元素A直接或間接成為元素B的子類。

4.元素A是介面B的實現。

上面最關鍵的程式碼就是handler類,實現了Invoke方法,用target主調來執行Method方法,就是回調了target的原有方法。在呼叫之前先執行了server的beforeRun,之後執行了afterRun方法。

當然,我們不會沒事就來個動態代理來寫程式碼,這樣沒有什麼實際意義,通常都是為指定的目標物件來生成動態代理,它的主要用處在框架和底層設計上面。

比如Spring中的AOP代理,它的原理就是基於動態代理,可以使得我們面向切面程式設計,它的代理包含了目標物件的所有方法。但是它的實現肯定不會那麼簡單的,具體的實現等以後理解了再寫吧

 

https://blog.csdn.net/hhhuuu2020/article/details/52440279