理解動態代理及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