您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關如何進行AOP的應用分析,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
AOP(Aspect-Oriented Programming,面向切面的編程),它是可以通過預編譯方式和運行期動態代理實現在不修改源代碼的情況下給程序動態統一添加功能的一種技術。它是一種新的方法論,它是對傳統OOP編程的一種補充。
OOP是關注將需求功能劃分為不同的并且相對獨立,封裝良好的類,并讓它們有著屬于自己的行為,依靠繼承和多態等來定義彼此的關系;AOP是希望能夠將通用需求功能從不相關的類當中分離出來,能夠使得很多類共享一個行為,一旦發生變化,不必修改很多類,而只需要修改這個行為即可。
AOP是使用切面(aspect)將橫切關注點模塊化,OOP是使用類將狀態和行為模塊化。在OOP的世界中,程序都是通過類和接口組織的,使用它們實現程序的核心業務邏輯是十分合適。但是對于實現橫切關注點(跨越應用程序多個模塊的功能需求)則十分吃力,比如日志記錄,驗證。
/*計算器接口*/ public interface Calculator { public double add(double num1, double num2) throws Exception; public double sub(double num1, double num2) throws Exception; public double div(double num1, double num2) throws Exception; public double mul(double num1, double num2) throws Exception; }
/*計算器接口的實現類*/ public class ArithmeticCalculator implements Calculator { @Override public double add(double num1, double num2) { double result = num1 + num2; return result; } @Override public double sub(double num1, double num2) { double result = num1 - num2; return result; } /*示意代碼 暫時不考慮除數0的情況*/ @Override public double div(double num1, double num2) { double result = num1 / num2; return result; } @Override public double mul(double num1, double num2) { double result = num1 * num2; return result; } }
大多數應用程序都有一個通用的需求,即在程序運行期間追蹤正在發生的活動。為了給計算機添加日志功能,ArithmeticCalculator類改變如下:
/*計算器接口的實現類,添加記錄日志功能*/ public class ArithmeticCalculator implements Calculator { @Override public double add(double num1, double num2) { System.out.println("the method [add()]"+"begin with args ("+num1+","+num2+")"); double result = num1 + num2; System.out.println("the method [add()]"+"end with result ("+result+")"); return result; } @Override public double sub(double num1, double num2) { System.out.println("the method [sub()]"+"begin with args ("+num1+","+num2+")"); double result = num1 - num2; System.out.println("the method [sub()]"+"end with result ("+result+")"); return result; } /*示意代碼 暫時不考慮除數0的情況*/ @Override public double div(double num1, double num2) { System.out.println("the method [div()]"+"begin with args ("+num1+","+num2+")"); double result = num1 / num2; System.out.println("the method [div()]"+"end with result ("+result+")"); return result; } @Override public double mul(double num1, double num2) { System.out.println("the method [mul()]"+"begin with args ("+num1+","+num2+")"); double result = num1 * num2; System.out.println("the method [mul()]"+"end with result ("+result+")"); return result; } }
若ArithmeticCalculator規定只能計算正數時,又需要添加參數驗證方法:
/*計算器接口的實現類,添加記錄日志功能*/ public class ArithmeticCalculator implements Calculator { @Override public double add(double num1, double num2) throws Exception { this.argsValidatior(num1); this.argsValidatior(num2); /*同上*/ } @Override public double sub(double num1, double num2) throws Exception { this.argsValidatior(num1); this.argsValidatior(num2); /*同上*/ } /*示意代碼 暫時不考慮除數0的情況*/ @Override public double div(double num1, double num2) throws Exception { this.argsValidatior(num1); this.argsValidatior(num2); /*同上*/ } @Override public double mul(double num1, double num2) throws Exception { this.argsValidatior(num1); this.argsValidatior(num2); /*同上*/ } private void argsValidatior(double arg)throws Exception { if(arg < 0) throw new Exception("參數不能為負數"); } }
上面的程序一個很直觀的特點就是,好多重復的代碼,并且當加入越來越多的非業務需求(例如日志記錄和參數驗證),原有的計算器方法變得膨脹冗長。這里有一件非常痛苦的事情,無法使用原有的編程方式將他們模塊化,從核心業務中提取出來。例如日志記錄和參數驗證,AOP里將他們稱為橫切關注點(crosscutting concern),它們屬于系統范圍的需求通常需要跨越多個模塊。
在使用傳統的面向對象的編程方式無法理想化的模塊化橫切關注點,程序員不能不做的就是將這些橫切關注點放置在每一個模塊里與核心邏輯交織在一起,這將會導致橫切關注點在每一個模塊里到處存在。使用非模塊化的手段實現橫切關注將會導致,代碼混亂,代碼分散,代碼重復。你想想看如果日志記錄需要換一種顯示方式,那你要改多少代碼,一旦漏掉一處(概率很高),將會導致日志記錄不一致。這樣的代碼很維護。種種原因表明,模塊只需要關注自己原本的功能需求,需要一種方式來將橫切關注點沖模塊中提取出來。
忍無可忍的大牛們提出了AOP,它是一個概念,一個規范,本身并沒有設定具體語言的實現,也正是這個特性讓它變的非常流行,現在已經有許多開源的AOP實現框架了。本次不是介紹這些框架的,我們將不使用這些框架,而是使用底層編碼的方式實現最基本的AOP解決上面例子出現的問題。AOP實際是GoF設計模式的延續,設計模式孜孜不倦追求的是調用者和被調用者之間的解耦,AOP可以說也是這種目標的一種實現。AOP可以使用"代理模式"來實現。
代理模式的原理是使用一個代理將對象包裝起來,然后用該代理對象取代原始的對象,任何對原始對象的調用首先要經過代理。代理對象負責決定是否以及何時將方法調用信息轉發到原始對象上。與此同時,圍繞著每個方法的調用,代理對象也可以執行一些額外的工作。可以看出代理模式非常適合實現橫切關注點。
由于本人只了解Java,所以姑且認為代理模式有兩種實現方式,一種是靜態代理、另一種是動態代理。他們的區別在于編譯時知不知道代理的對象是誰。在模塊比較多的系統中,靜態代理是不合適也非常低效的,因為靜態代理需要專門為每一個接口設計一個代理類,系統比較大成百上千的接口是很正常的,靜態代理模式太消耗人力了。動態代理是JDK所支持的代理模式,它可以非常好的實現橫切關注點。
/*使用動態代理需要實現InvocationHandler接口*/ public class ArithmeticCalculatorInvocationHandler implements InvocationHandler { /*要代理的對象,動態代理只有在運行時才知道代理誰,所以定義為Object類型,可以代理任意對象*/ private Object target = null; /*通過構造函數傳入原對象*/ public ArithmeticCalculatorInvocationHandler(Object target) { this.target = target; } /*InvocationHandler接口的方法,proxy表示代理,method表示原對象被調用的方法,args表示方法的參數*/ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { /*原對象方法調用前處理日志信息*/ System.out.println("the method ["+method.getName()+"]"+"begin with args ("+Arrays.toString(args)+")"); Object result = method.invoke(this.target, args); /*原對象方法調用后處理日志信息*/ System.out.println("the method ["+method.getName()+"]"+"end with result ("+result+")"); return result; } /*獲取代理類*/ public Object getProxy() { return Proxy.newProxyInstance(this.target.getClass().getClassLoader(), this.getClass().getInterfaces(), this); } }
場景類調用:
public class Client { public static void main(String[] args) throws Exception { /*獲得代理*/ Calculator arithmeticCalculatorProxy = (Calculator)new ArithmeticCalculatorInvocationHandler( new ArithmeticCalculator()).getProxy(); /*調用add方法*/ arithmeticCalculatorProxy.add(10, 10); } }
控制臺的輸出:
the method [add]begin with args ([10.0, 10.0]) the method [add]end with result (20.0)
可以看到使用動態代理實現了橫切關注點。
若需要添加參數驗證功能,只需要再創建一個參數驗證代理即可:
public class ArithmeticCalculatorArgsInvocationHandler implements InvocationHandler { /*要代理的對象,動態代理只有在運行時才知道代理誰,所以定義為Object類型,可以代理任意對象*/ private Object target = null; /*通過構造函數傳入原對象*/ public ArithmeticCalculatorArgsInvocationHandler(Object target) { this.target = target; } /*InvocationHandler接口的方法,proxy表示代理,method表示原對象被調用的方法,args表示方法的參數*/ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("begin valid method ["+method.getName()+"] with args "+Arrays.toString(args)); for(Object arg : args) { this.argValidtor((Double)arg); } Object result = method.invoke(this.target, args); return result; } /*獲取代理類*/ public Object getProxy() { return Proxy.newProxyInstance(this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), this); } private void argValidtor(double arg) throws Exception { if(arg < 0) throw new Exception("參數不能為負數!"); } }
場景類調用:
public class Client { public static void main(String[] args) throws Exception { /*獲得代理*/ Calculator arithmeticCalculatorProxy = (Calculator)new ArithmeticCalculatorInvocationHandler( new ArithmeticCalculator()).getProxy(); Calculator argValidatorProxy = (Calculator)new ArithmeticCalculatorArgsInvocationHandler(arithmeticCalculatorProxy).getProxy(); /*調用add方法*/ argValidatorProxy.add(10, 10); } }
控制臺輸出:
begin valid method [add] with args [10.0, 10.0] the method [add]begin with args ([10.0, 10.0]) the method [add]end with result (20.0)
輸入一個負數數據:
public class Client { public static void main(String[] args) throws Exception { /*獲得代理*/ Calculator arithmeticCalculatorProxy = (Calculator)new ArithmeticCalculatorInvocationHandler( new ArithmeticCalculator()).getProxy(); Calculator argValidatorProxy = (Calculator)new ArithmeticCalculatorArgsInvocationHandler(arithmeticCalculatorProxy).getProxy(); /*調用add方法*/ argValidatorProxy.add(-10, 10); } }
控制臺輸出:
begin valid method [add] with args [-10.0, 10.0] Exception in thread "main" java.lang.Exception: 參數不能為負數! at com.beliefbetrayal.aop.ArithmeticCalculatorArgsInvocationHandler.argValidtor(ArithmeticCalculatorArgsInvocationHandler.java:46) at com.beliefbetrayal.aop.ArithmeticCalculatorArgsInvocationHandler.invoke(ArithmeticCalculatorArgsInvocationHandler.java:29) at $Proxy0.add(Unknown Source) at com.beliefbetrayal.aop.Client.main(Client.java:14)
不知道你有沒有使用過Struts2,這個結構和Struts2的攔截器非常相似,一個個Action對象好比我們的原對象業務核心,一個個攔截器好比是這里的代理,通用的功能實現成攔截器,讓Action可以共用,Struts2的攔截器也是AOP的優秀實現。
關于如何進行AOP的應用分析就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。