1. 程式人生 > >關於spring,IOC和AOP的解析原理和舉例

關於spring,IOC和AOP的解析原理和舉例

     先從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/     就瞬間好理解多了