關於spring,IOC和AOP的解析原理和舉例
阿新 • • 發佈:2019-01-30
先從IOC說起,這個概念其實是從我們平常new一個物件的對立面來說的,我們平常使用物件的時候,一般都是直接使用關鍵字類new一個物件,那這樣有什麼壞處呢?其實很顯然的,使用new那麼就表示當前模組已經不知不覺的和new的物件耦合了,而我們通常都是更高層次的抽象模組呼叫底層的實現模組,這樣也就產生了模組依賴於具體的實現,這樣與我們JAVA中提倡的面向介面面向抽象程式設計是相沖突的,而且這樣做也帶來系統的模組架構問題。很簡單的例子,我們在進行資料庫操作的時候,總是業務層呼叫DAO層,當然我們的DAO一般都是會採用介面開發,這在一定程度上滿足了鬆耦合,使業務邏輯層不依賴於具體的資料庫DAO層。但是我們在使用的時候還是會new一個特定資料庫的DAO層,這無形中也與特定的資料庫綁定了,雖然我們可以使用抽象工廠模式來獲取DAO實現類,但除非我們一次性把所有資料庫的DAO寫出來,否則在進行資料庫遷移的時候我們還是得修改DAO工廠類。 那我們使用IOC能達到什麼呢?IOC,就是DAO介面的實現不再是業務邏輯層呼叫工廠類去獲取,而是通過容器(比如spring)來自動的為我們的業務層設定DAO的實現類。這樣整個過程就反過來,以前是我們業務層主動去獲取DAO,而現在是DAO主動被設定到業務邏輯層中來了,這也就是反轉控制的由來。通過IOC,我們就可以在不修改任何程式碼的情況下,無縫的實現資料庫的換庫遷移,當然前提還是必須得寫一個實現特定資料庫的DAO。我們把DAO普遍到更多的情況下,那麼IOC就為我們帶來更大的方便性,比如一個介面的多個實現,我們只需要配置一下就ok了,而不需要再一個個的寫工廠來來獲取了。這就是IOC為我們帶來的模組的鬆耦合和應用的便利性。 那為什麼說IOC很簡單呢?說白了其實就是由我們平常的new轉成了使用反射來獲取類的例項,相信任何人只要會用java的反射機制,那麼自己寫一個IOC框架也不是不可能的。比如: …… public ObjectgetInstance(String className) throws Exception { Object obj = Class.forName(className).newInstance(); Method[] methods = obj.getClass().getMethods(); for (Method method : methods) { if (method.getName().intern() == "setString") { method.invoke(obj, "hello world!"); } } } …… 上面的一個方法我們就很簡單的使用了反射為指定的類的setString方法來設定一個hello world!字串。其實可以看到IOC真的很簡單,當然了IOC簡單並不表示spring的IOC就簡單,spring的IOC的功能強大就在於有一系列非常強大的配置檔案維護類,它們可以維護spring配置檔案中的各個類的關係,這才是spring的IOC真正強大的地方。在spring的Bean定義檔案中,不僅可以為定義Bean設定屬性,還支援Bean之間的繼承、Bean的抽象和不同的獲取方式等等功能。 下次俺再把spring的Bean配置的相關心得和大家一起分享下,如果說的不好,大家可以提意見哦,可千萬不要仍臭雞蛋,嘿嘿~~~~ .關於spring aop 反射實現 AOP 動態代理模式(Spring AOP 的實現 原理) 好長時間沒有用過Spring了. 突然拿起書.我都發現自己對AOP都不熟悉了. 其實AOP的意思就是面向切面程式設計. OO注重的是我們解決問題的方法(封裝成Method),而AOP注重的是許多解決解決問題的方法中的共同點,是對OO思想的一種補充! 還是拿人家經常舉的一個例子講解一下吧: 比如說,我們現在要開發的一個應用裡面有很多的業務方法,但是,我們現在要對這個方法的執行做全面監控,或部分監控.也許我們就會在要一些方法前去加上一條日誌記錄, 我們寫個例子看看我們最簡單的解決方案 我們先寫一個介面IHello.java程式碼如下: package sinosoft.dj.aop.staticaop; public interface IHello { void sayHello(String name); } 裡面有個方法,用於輸入"Hello" 加傳進來的姓名;我們去寫個類實現IHello介面 package sinosoft.dj.aop.staticaop; public class Hello implements IHello { public void sayHello(String name) { System.out.println("Hello " + name); } } 現在我們要為這個業務方法加上日誌記錄的業務,我們在不改變原始碼的情況下,我們會去怎麼做呢?也許,你會去寫一個類去實現IHello介面,並依賴Hello這個類.程式碼如下: package sinosoft.dj.aop.staticaop; public class HelloProxy implements IHello { private IHello hello; public HelloProxy(IHello hello) { this.hello = hello; } public void sayHello(String name) { Logger.logging(Level.DEBUGE, "sayHello method start."); hello.sayHello(name); Logger.logging(Level.INFO, "sayHello method end!"); } } 其中.Logger類和Level列舉程式碼如下: Logger.java package sinosoft.dj.aop.staticaop; import java.util.Date; public class Logger{ public static void logging(Level level, String context) { if (level.equals(Level.INFO)) { System.out.println(new Date().toLocaleString() + " " + context); } if (level.equals(Level.DEBUGE)) { System.err.println(new Date() + " " + context); } } } Level.java package sinosoft.dj.aop.staticaop; public enum Level { INFO,DEBUGE; } 那我們去寫個測試類看看,程式碼如下: Test.java package sinosoft.dj.aop.staticaop; public class Test { public static void main(String[] args) { IHello hello = new HelloProxy(new Hello()); hello.sayHello("Doublej"); } } 執行以上程式碼我們可以得到下面結果: Tue Mar :: CST sayHello method start. Hello Doublej -- :: sayHello method end! 從上面的程式碼我們可以看出,hello物件是被HelloProxy這個所謂的代理態所建立的.這樣,如果我們以後要把日誌記錄的功能去掉.那我們只要把得到hello物件的程式碼改成以下: package sinosoft.dj.aop.staticaop; public class Test { public static void main(String[] args) { IHello hello = new Hello(); hello.sayHello("Doublej"); } } 上面程式碼,可以說是AOP最簡單的實現! 但是我們會發現一個問題,如果我們像Hello這樣的類很多,那麼,我們是不是要去寫很多個HelloProxy這樣的類呢.沒錯,是的.其實也是一種很麻煩的事.在jdk.以後.jdk跟我們提供了一個API java.lang.reflect.InvocationHandler的類. 這個類可以讓我們在JVM呼叫某個類的方法時動態的為些方法做些什麼事.讓我們把以上的程式碼改一下來看看效果. 同樣,我們寫一個IHello的介面和一個Hello的實現類.在介面中.我們定義兩個方法;程式碼如下 : IHello.java package sinosoft.dj.aop.proxyaop; public interface IHello { void sayHello(String name); void sayGoogBye(String name); } Hello.java package sinosoft.dj.aop.proxyaop; public class Hello implements IHello { public void sayHello(String name) { System.out.println("Hello " + name); } public void sayGoogBye(String name) { System.out.println(name+" GoodBye!"); } } 我們一樣的去寫一個代理類.只不過.讓這個類去實現java.lang.reflect.InvocationHandler介面,程式碼如下: package sinosoft.dj.aop.proxyaop; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class DynaProxyHello implements InvocationHandler { private Object delegate; public Object bind(Object delegate) { this.delegate = delegate; return Proxy.newProxyInstance( this.delegate.getClass().getClassLoader(), this.delegate .getClass().getInterfaces(), this); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; try { //執行原來的方法之前記錄日誌 Logger.logging(Level.DEBUGE, method.getName() + " Method end ."); //JVM通過這條語句執行原來的方法(反射機制) result = method.invoke(this.delegate, args); //執行原來的方法之後記錄日誌 Logger.logging(Level.INFO, method.getName() + " Method Start!"); } catch (Exception e) { e.printStackTrace(); } //返回方法返回值給呼叫者 return result; } } 上面類中出現的Logger類和Level列舉還是和上一上例子的實現是一樣的.這裡就不貼出程式碼了. 讓我們寫一個Test類去測試一下.程式碼如下: Test.java package sinosoft.dj.aop.proxyaop; public class Test { public static void main(String[] args) { IHello hello = (IHello)new DynaProxyHello().bind(new Hello()); hello.sayGoogBye("Double J"); hello.sayHello("Double J"); } } 執行輸出的結果如下: Tue Mar :: CST sayGoogBye Method end . Double J GoodBye! -- :: sayGoogBye Method Start! Tue Mar :: CST sayHello Method end . Hello Double J -- :: sayHello Method Start! 由於執行緒的關係,第二個方法的開始出現在第一個方法的結束之前.這不是我們所關注的! 從上面的例子我們看出.只要你是採用面向介面程式設計,那麼,你的任何物件的方法執行之前要加上記錄日誌的操作都是可以的.他(DynaPoxyHello)自動去代理執行被代理物件(Hello)中的每一個方法,一個java.lang.reflect.InvocationHandler介面就把我們的代理物件和被代理物件解藕了.但是,我們又發現還有一個問題,這個DynaPoxyHello物件只能跟我們去在方法前後加上日誌記錄的操作.我們能不能把DynaPoxyHello物件和日誌操作物件(Logger)解藕呢? 結果是肯定的.讓我們來分析一下我們的需求. 我們要在被代理物件的方法前面或者後面去加上日誌操作程式碼(或者是其它操作的程式碼), 那麼,我們可以抽象出一個介面,這個接口裡就只有兩個方法,一個是在被代理物件要執行方法之前執行的方法,我們取名為start,第二個方法就是在被代理物件執行方法之後執行的方法,我們取名為end .介面定義如下 : package sinosoft.dj.aop.proxyaop; import java.lang.reflect.Method; public interface IOperation { void start(Method method); void end(Method method); } 我們去寫一個實現上面介面的類.我們把作他真正的操作者,如下面是日誌操作者的一個類: LoggerOperation.java package sinosoft.dj.aop.proxyaop; import java.lang.reflect.Method; public class LoggerOperation implements IOperation { public void end(Method method) { Logger.logging(Level.DEBUGE, method.getName() + " Method end ."); } public void start(Method method) { Logger.logging(Level.INFO, method.getName() + " Method Start!"); } } 然後我們要改一下代理物件DynaProxyHello中的程式碼.如下: package sinosoft.dj.aop.proxyaop; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class DynaProxyHello implements InvocationHandler { private Object proxy; private Object delegate; public Object bind(Object delegate,Object proxy) { this.proxy = proxy; this.delegate = delegate; return Proxy.newProxyInstance( this.delegate.getClass().getClassLoader(), this.delegate .getClass().getInterfaces(), this); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; try { //反射得到操作者的例項 Class clazz = this.proxy.getClass(); //反射得到操作者的Start方法 Method start = clazz.getDeclaredMethod("start", new Class[] { Method.class }); //反射執行start方法 start.invoke(this.proxy, new Object[] { method }); //執行要處理物件的原本方法 result = method.invoke(this.delegate, args); // 反射得到操作者的end方法 Method end = clazz.getDeclaredMethod("end", new Class[] { Method.class }); // 反射執行end方法 end.invoke(this.proxy, new Object[] { method }); } catch (Exception e) { e.printStackTrace(); } return result; } } 然後我們把Test.java中的程式碼改一下.測試一下: package sinosoft.dj.aop.proxyaop; public class Test { public static void main(String[] args) { IHello hello = (IHello)new DynaProxyHello().bind(new Hello(),new LoggerOperation()); hello.sayGoogBye("Double J"); hello.sayHello("Double J"); } } 結果還是一樣的吧. 如果你想在每個方法之前加上日誌記錄,而不在方法後加上日誌記錄.你就把LoggerOperation類改成如下: package sinosoft.dj.aop.proxyaop; import java.lang.reflect.Method; public class LoggerOperation implements IOperation { public void end(Method method) { //Logger.logging(Level.DEBUGE, method.getName() + " Method end ."); } public void start(Method method) { Logger.logging(Level.INFO, method.getName() + " Method Start!"); } } 執行一下.你就會發現,每個方法之後沒有記錄日誌了. 這樣,我們就把代理者和操作者解藕了! 下面留一個問題給大家,如果我們不想讓所有方法都被日誌記錄,我們應該怎麼去解藕呢.? 我的想法是在代理物件的public Object invoke(Object proxy, Method method, Object[] args)方法裡面加上個if(),對傳進來的method的名字進行判斷,判斷的條件存在XML裡面.這樣我們就可以配置檔案時行解藕了.如果有興趣的朋友可以把操作者,被代理者,都通過配置檔案進行配置 ,那麼就可以寫一個簡單的SpringAOP框架了.
然後在看看這個 http://blog.csdn.net/moreevan/article/details/11977115/ 就瞬間好理解多了