模擬AOP動態代理

 

動態代理(dynamic proxy) 

動態代理(如下稱代理),利用Java的反射技術(Java Reflection),在運行時建立一個實現某些給定接口的新類(也稱「動態代理類」)及其實例(對象)html

(Using Java Reflection to create dynamic implementations of interfaces at runtime)。java

代理的是對象是接口(Interfaces),不是類(Class),更不是抽象類。git

 

動態代理做用

解決特定問題:一個接口的實如今編譯時沒法知道,須要在運行時才能實現github

實現某些設計模式:適配器(Adapter)或修飾器(Decorator)編程

面向切面編程:如AOP in Spring.設計模式

 

建立動態代理

利用Java的Proxy類,調用Proxy.newProxyInstance(),api

而Proxy.newProxyInstance()方法有三個參數:數組

1. 類加載器(Class Loader);緩存

2. 須要實現的接口數組;oracle

3. InvocationHandler接口,全部動態代理類的方法調用,都會交由InvocationHandler接口實現類裏的invoke()方法去處理。這是動態代理的要義所在。

下面是官方文檔解釋:

ublic static Object newProxyInstance(ClassLoader loader,
                      Class<?>[] interfaces,
                      InvocationHandler h)
                               throws IllegalArgumentException

Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler. This method is equivalent to:

Proxy.getProxyClass(loader, interfaces).
         getConstructor(new Class[] { InvocationHandler.class }).
         newInstance(new Object[] { handler });

Proxy.newProxyInstance throws IllegalArgumentException for the same reasons that Proxy.getProxyClass does.

Parameters:

loader - the class loader to define the proxy class

interfaces - the list of interfaces for the proxy class to implement

h - the invocation handler to dispatch method invocations to

Returns:

a proxy instance with the specified invocation handler of a proxy class that is defined by the specified class loader and that implements the specified interfaces

Throws:

IllegalArgumentException - if any of the restrictions on the parameters that may be passed to getProxyClass are violated

NullPointerException - if the interfaces array argument or any of its elements are null, or if the invocation handler, h, is null

如何使用InvocationHandler接口

接口裏有一個invoke()方法。基本的作法是,建立一個類,實現這個方法,利用反射在invoke()方法裏實現需求:

public class MyInvocationHandler implements InvocationHandler{

  public Object invoke(Object proxy, Method method, Object[] args)
  throws Throwable {
    //do something "dynamic"
  }
}

 

invoke()方法也一樣有三個參數:

1. 動態代理類的引用,一般狀況下不須要它。但可使用getClass()方法,獲得proxy的Class類從而取得實例的類信息,如方法列表,annotation等。

2. 方法對象的引用,表明被動態代理類調用的方法。從中可獲得方法名,參數類型,返回類型等等。

3. args對象數組,表明被調用方法的參數。注意基本類型(int,long)會被裝箱成對象類型(Interger, Long)。

動態代理模擬

//interface
public interface DynamicQuiz {
    void run();
}

//do something
public class DynamicProxyTest implements DynamicQuiz {
    @Override
    public void run() {
        System.out.println("this is a dynmic quiz");
    }
}

//proxy process
class DynamicProxy implements InvocationHandler {
    DynamicQuiz dynamicQuiz;

    DynamicProxy() {
    }
    DynamicQuiz bind(DynamicQuiz dynamicQuiz) {
        this.dynamicQuiz = dynamicQuiz;
        return (DynamicQuiz) Proxy.newProxyInstance(dynamicQuiz.getClass().getClassLoader(), dynamicQuiz.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("welcome");
        Object result = method.invoke(dynamicQuiz, args);
        System.out.println("see you agin");
        return result;
    }
}

//main 
public class Main {
    public static void main(String[] args) {
        DynamicQuiz dynamicQuiz = new DynamicProxyTest();
        DynamicQuiz quizeResult = new DynamicProxy().bind(dynamicQuiz);
        quizeResult.run();

    }
}

從上面的代碼能夠看出,對DynamicQuiz接口的調用,會交由Proxy的invoke方法處理,並在不改變run()的源代碼下,新增了動態的邏輯(before running/after running),這正式AOP所作的。

深刻講,Proxy.newInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)作了如下幾件事.
(1)根據參數loader和interfaces調用方法 getProxyClass(loader, interfaces)建立代理類$Proxy.
$Proxy0類實現了interfaces的接口,並繼承了Proxy類.
(2)實例化$Proxy0並在構造方法中把BusinessHandler傳過去,接着$Proxy0調用父類Proxy的構造器,爲h賦值.

注意兩個地方:

1.接口的run()會觸發invoke(), 因此在proxy裏面切不可將invoke()替換成(DynamicQuiz)poxy.run(),不然就形成死循環,形成棧溢出。

2.跟蹤源碼能夠看到,程序進行了驗證、優化、緩存、同步、生成字節碼、顯示類加載等操做,前面的步驟並非咱們關注的重點,而最後它調用了sun.misc.ProxyGenerator.generateProxyClass()方法完成生成字節碼的動做,這個方法能夠在運行時產生一個描述代理類的字節碼byte[]數組。若是想看這個運行時產生的代理類中的內容,能夠在main方法加以下這句代碼:

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

運行會生成一個名爲"$Proxy).class"的代理class文件,反編譯以後就能看見內容。

 

代碼地址:https://github.com/wangtao1/dynamic

 

參考:

深刻理解java虛擬機第9章節

http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html

http://www.cnblogs.com/techyc/p/3455950.html

相關文章
相關標籤/搜索