天天看點

AOP 動态代理模式(Spring反射實作 )

其實AOP的意思就是面向切面程式設計.

OO注重的是我們解決問題的方法(封裝成Method),而AOP注重的是許多解決解決問題的方法中的共同點,是對OO思想的一種補充!

還是拿人家經常舉的一個例子講解一下吧:

比如說,我們現在要開發的一個應用裡面有很多的業務方法,但是,我們現在要對這個方法的執行做全面監控,或部分監控.也許我們就會在要一些方法前去加上一條日志記錄,

我們寫個例子看看我們最簡單的解決方案

我們先寫一個接口IHello.java代碼如下:

 1package sinosoft.dj.aop.staticaop;

 2

 3public interface IHello {

 4    /** *//**

 5     * 假設這是一個業務方法

 6     * @param name

 7     */

 8    void sayHello(String name);

 9}

10

裡面有個方法,用于輸入"Hello" 加傳進來的姓名;我們去寫個類實作IHello接口

package sinosoft.dj.aop.staticaop;

public class Hello implements IHello {

    public void sayHello(String name) {

        System.out.println("Hello " + name);

    }

}

現在我們要為這個業務方法加上日志記錄的業務,我們在不改變原代碼的情況下,我們會去怎麼做呢?也許,你會去寫一個類去實作IHello接口,并依賴Hello這個類.代碼如下:

 1package sinosoft.dj.aop.staticaop;

 2

 3public class HelloProxy implements IHello {

 4    private IHello hello;

 5

 6    public HelloProxy(IHello hello) {

 7        this.hello = hello;

 8    }

 9

10    public void sayHello(String name) {

11        Logger.logging(Level.DEBUGE, "sayHello method start.");

12        hello.sayHello(name);

13        Logger.logging(Level.INFO, "sayHello method end!");

14

15    }

16

17}

18

其中.Logger類和Level枚舉代碼如下:

Logger.java

 1package sinosoft.dj.aop.staticaop;

 2

 3import java.util.Date;

 4

 5public class Logger{

 6    /** *//**

 7     * 根據等級記錄日志

 8     * @param level

 9     * @param context

10     */

11    public static void logging(Level level, String context) {

12        if (level.equals(Level.INFO)) {

13            System.out.println(new Date().toLocaleString() + " " + context);

14        }

15        if (level.equals(Level.DEBUGE)) {

16            System.err.println(new Date() + " " + context);

17        }

18    }

19

20}

21

Level.java

1package sinosoft.dj.aop.staticaop;

2

3public enum Level {

4    INFO,DEBUGE;

5}

6

那我們去寫個測試類看看,代碼如下:

Test.java

1package sinosoft.dj.aop.staticaop;

2

3public class Test {

4    public static void main(String[] args) {

5        IHello hello = new HelloProxy(new Hello());

6        hello.sayHello("Doublej");

7    }

8}

9

運作以上代碼我們可以得到下面結果:

Tue Mar 04 20:57:12 CST 2008 sayHello method start.

Hello Doublej

2008-3-4 20:57:12 sayHello method end!

從上面的代碼我們可以看出,hello對象是被HelloProxy這個所謂的代理态所建立的.這樣,如果我們以後要把日志記錄的功能去掉.那我們隻要把得到hello對象的代碼改成以下:

1package sinosoft.dj.aop.staticaop;

2

3public class Test {

4    public static void main(String[] args) {

5        IHello hello = new Hello();

6        hello.sayHello("Doublej");

7    }

8}

9

上面代碼,可以說是AOP最簡單的實作!

但是我們會發現一個問題,如果我們像Hello這樣的類很多,那麼,我們是不是要去寫很多個HelloProxy這樣的類呢.沒錯,是的.其實也是一種很麻煩的事.在jdk1.3以後.jdk跟我們提供了一個API   java.lang.reflect.InvocationHandler的類. 這個類可以讓我們在JVM調用某個類的方法時動态的為些方法做些什麼事.讓我們把以上的代碼改一下來看看效果.

同樣,我們寫一個IHello的接口和一個Hello的實作類.在接口中.我們定義兩個方法;代碼如下 :

IHello.java

 1package sinosoft.dj.aop.proxyaop;

 2

 3public interface IHello {

 4    /** *//**

 5     * 業務處理A方法

 6     * @param name

 7     */

 8    void sayHello(String name);

 9    /** *//**

10     * 業務處理B方法

11     * @param name

12     */

13    void sayGoogBye(String name);

14}

15

Hello.java

 1package sinosoft.dj.aop.proxyaop;

 2

 3public class Hello implements IHello {

 4

 5    public void sayHello(String name) {

 6        System.out.println("Hello " + name);

 7    }

 8    public void sayGoogBye(String name) {

 9        System.out.println(name+" GoodBye!");

10    }

11}

12

我們一樣的去寫一個代理類.隻不過.讓這個類去實作java.lang.reflect.InvocationHandler接口,代碼如下:

 1package sinosoft.dj.aop.proxyaop;

 2

 3import java.lang.reflect.InvocationHandler;

 4import java.lang.reflect.Method;

 5import java.lang.reflect.Proxy;

 6

 7public class DynaProxyHello implements InvocationHandler {

 8

 9    /** *//**

10     * 要處理的對象(也就是我們要在方法的前後加上業務邏輯的對象,如例子中的Hello)

11     */

12    private Object delegate;

13

14    /** *//**

15     * 動态生成方法被處理過後的對象 (寫法固定)

16     * 

17     * @param delegate

18     * @param proxy

19     * @return

20     */

21    public Object bind(Object delegate) {

22        this.delegate = delegate;

23        return Proxy.newProxyInstance(

24                this.delegate.getClass().getClassLoader(), this.delegate

25                        .getClass().getInterfaces(), this);

26    }

27    /** *//**

28     * 要處理的對象中的每個方法會被此方法送去JVM調用,也就是說,要處理的對象的方法隻能通過此方法調用

29     * 此方法是動态的,不是手動調用的

30     */

31    public Object invoke(Object proxy, Method method, Object[] args)

32            throws Throwable {

33        Object result = null;

34        try {

35            //執行原來的方法之前記錄日志

36            Logger.logging(Level.DEBUGE, method.getName() + " Method end .");

37            

38            //JVM通過這條語句執行原來的方法(反射機制)

39            result = method.invoke(this.delegate, args);

40            //執行原來的方法之後記錄日志

41            Logger.logging(Level.INFO, method.getName() + " Method Start!");

42        } catch (Exception e) {

43            e.printStackTrace();

44        }

45        //傳回方法傳回值給調用者

46        return result;

47    }

48

49}

50

上面類中出現的Logger類和Level枚舉還是和上一上例子的實作是一樣的.這裡就不貼出代碼了.

讓我們寫一個Test類去測試一下.代碼如下:

Test.java

 1package sinosoft.dj.aop.proxyaop;

 2

 3public class Test {

 4    public static void main(String[] args) {

 5        IHello hello = (IHello)new DynaProxyHello().bind(new Hello());

 6        hello.sayGoogBye("Double J");

 7        hello.sayHello("Double J");

 8        

 9    }

10}

11

運作輸出的結果如下:

Tue Mar 04 21:24:03 CST 2008 sayGoogBye Method end .

Double J GoodBye!

2008-3-4 21:24:03 sayGoogBye Method Start!

Tue Mar 04 21:24:03 CST 2008 sayHello Method end .

Hello Double J

2008-3-4 21:24:03 sayHello Method Start!

由于線程的關系,第二個方法的開始出現在第一個方法的結束之前.這不是我們所關注的!

從上面的例子我們看出.隻要你是采用面向接口程式設計,那麼,你的任何對象的方法執行之前要加上記錄日志的操作都是可以的.他(DynaPoxyHello)自動去代理執行被代理對象(Hello)中的每一個方法,一個java.lang.reflect.InvocationHandler接口就把我們的代理對象和被代理對象解藕了.但是,我們又發現還有一個問題,這個DynaPoxyHello對象隻能跟我們去在方法前後加上日志記錄的操作.我們能不能把DynaPoxyHello對象和日志操作對象(Logger)解藕呢?

結果是肯定的.讓我們來分析一下我們的需求.

我們要在被代理對象的方法前面或者後面去加上日志操作代碼(或者是其它操作的代碼),

那麼,我們可以抽象出一個接口,這個接口裡就隻有兩個方法,一個是在被代理對象要執行方法之前執行的方法,我們取名為start,第二個方法就是在被代理對象執行方法之後執行的方法,我們取名為end .接口定義如下 :

 1package sinosoft.dj.aop.proxyaop;

 2

 3import java.lang.reflect.Method;

 4

 5public interface IOperation {

 6    /** *//**

 7     * 方法執行之前的操作

 8     * @param method

 9     */

10    void start(Method method);

11    /** *//**

12     * 方法執行之後的操作

13     * @param method

14     */

15    void end(Method method);

16}

17

我們去寫一個實作上面接口的類.我們把作他真正的操作者,如下面是日志操作者的一個類:

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中的代碼.如下:

 1package sinosoft.dj.aop.proxyaop;

 2

 3import java.lang.reflect.InvocationHandler;

 4import java.lang.reflect.Method;

 5import java.lang.reflect.Proxy;

 6

 7public class DynaProxyHello implements InvocationHandler {

 8    /** *//**

 9     * 操作者

10     */

11    private Object proxy;

12    /** *//**

13     * 要處理的對象(也就是我們要在方法的前後加上業務邏輯的對象,如例子中的Hello)

14     */

15    private Object delegate;

16

17    /** *//**

18     * 動态生成方法被處理過後的對象 (寫法固定)

19     * 

20     * @param delegate

21     * @param proxy

22     * @return

23     */

24    public Object bind(Object delegate,Object proxy) {

25        

26        this.proxy = proxy;

27        this.delegate = delegate;

28        return Proxy.newProxyInstance(

29                this.delegate.getClass().getClassLoader(), this.delegate

30                        .getClass().getInterfaces(), this);

31    }

32    /** *//**

33     * 要處理的對象中的每個方法會被此方法送去JVM調用,也就是說,要處理的對象的方法隻能通過此方法調用

34     * 此方法是動态的,不是手動調用的

35     */

36    public Object invoke(Object proxy, Method method, Object[] args)

37            throws Throwable {

38        Object result = null;

39        try {

40            //反射得到操作者的執行個體

41            Class clazz = this.proxy.getClass();

42            //反射得到操作者的Start方法

43            Method start = clazz.getDeclaredMethod("start",

44                    new Class[] { Method.class });

45            //反射執行start方法

46            start.invoke(this.proxy, new Object[] { method });

47            //執行要處理對象的原本方法

48            result = method.invoke(this.delegate, args);

49//            反射得到操作者的end方法

50            Method end = clazz.getDeclaredMethod("end",

51                    new Class[] { Method.class });

52//            反射執行end方法

53            end.invoke(this.proxy, new Object[] { method });

54

55        } catch (Exception e) {

56            e.printStackTrace();

57        }

58        return result;

59    }

60

61}

62

然後我們把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類改成如下:

 1package sinosoft.dj.aop.proxyaop;

 2

 3import java.lang.reflect.Method;

 4

 5public class LoggerOperation implements IOperation {

 6

 7    public void end(Method method) {

 8        //Logger.logging(Level.DEBUGE, method.getName() + " Method end .");

 9    }

10

11    public void start(Method method) {

12        Logger.logging(Level.INFO, method.getName() + " Method Start!");

13    }

14

15}

16

運作一下.你就會發現,每個方法之後沒有記錄日志了. 這樣,我們就把代理者和操作者解藕了!

下面留一個問題給大家,如果我們不想讓所有方法都被日志記錄,我們應該怎麼去解藕呢.?

我的想法是在代理對象的public Object invoke(Object proxy, Method method, Object[] args)方法裡面加上個if(),對傳進來的method的名字進行判斷,判斷的條件存在XML裡面.這樣我們就可以配置檔案時行解藕了.如果有興趣的朋友可以把操作者,被代理者,都通過配置檔案進行配置 ,那麼就可以寫一個簡單的SpringAOP架構了.