Java動態代理機制研讀

java動態加載類(反射機制)

/*MyClass.java*/
public class MyClass {
    public int id;
    public String name;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public MyClass(int id, String name) {
        this.id = id;
        this.name = name;
    }
    public MyClass(int id) {
        this.id = id;
    }
    public void sayHallo(){
        System.out.println("Hallo " + this.getName() + ", your id is: " + this.getId());
    }
}

/*Main.java*/
public class Main {
    public static void main(String [] args){
        try{
            //根據參數表(int.class)來指定用哪一個構造方法
            /*
            * 這裏還想起一道面試題,"返回值不一樣,參數相同的兩個java 方法,能夠存在麼?"從這裏能夠看出答案了,若是一個方法只有返回值不一樣,那麼咱們經過一樣的調用參數是沒法定義其中一個方法的.
            */
            Constructor constructor = Class.forName("com.yy.MyClass").getConstructor(int.class);
            Object object = constructor.newInstance(10);

            Method method = Class.forName("MyClass").getMethod("sayHallo");
            method.invoke(object);

            Field field = Class.forName("MyClass").getField("id");
            Integer id = field.getInt(object);
        }
        catch (Exception e){
            e.printStackTrace();
        }
    }
}

 

那麼這種動態加載類機制(也叫反射機制)有什麼牛的呢?從代碼能夠看出來,咱們在不瞭解目標MyClass 的狀況下(僅知道它的類名和方法),構造了它的一個實例.而且像經過普通的構造方法構造的對象同樣調用它內部的方法和屬性. 而這一切的一切,都只是創建在咱們知道它的」名字」這麼簡單的條件之上. 
so?這又能作什麼呢?先不看代碼,先從邏輯的層面來推理下這樣的一個特性能夠實現怎樣神奇的功能.java

  • 由於咱們只須要知道類名就能夠經過Class.forName("MyClass").getConstructor(int.class); 來獲取類的構造方法,並構造它的實例.那麼,咱們只知道類名,在代碼真正運行以前,甚至不知道這個類是否真的存在(換句話說,咱們不知道有沒有編譯好的.class文件).也可讓本身這部分代碼經過編譯. 很恐怖是否是?確實,這讓咱們的代碼在完整性上存在風險,若是找不到咱們經過名字指定的那個類.熟悉的ClassNotFoundException就會出現. 可是,這樣換來的好處是讓項目各部分代碼能夠相互獨立的靈活性.能夠動態地調用ClassLoader.class文件裝載到虛擬機當中來,而後使用其中的方法.這才使得動態連接程序組件成爲可能.
  • 另外,由於咱們能夠跳過目標代碼自己,直接調用其中的方法.根據各類設計模式的思想,咱們是否是能夠在調用方法的時候作一些文章呢? 例如打個日誌,格式化參數,改變業務邏輯.並且更重要的是,咱們還不會觸動到目標代碼.這就爲AOP打下了基礎

動態代理

一句話歸納動態代理,就是經過動態加載類的方式實現的代理模式
因此,本質上,動態代理也是代理的一個子集.先盜張圖來看看純粹的代理模式. 
純粹代理模式 
用戶直接操做的是Proxy,由於它和RealSubject同樣都實現了Subject藉口,因此在接口中定義的方法,能夠在Proxy或者RealSubject中隨意使用.而實際上Proxy內部經過保留一個RealSubject對象來實現這種一致性,看起來就是借了Proxy之手的代理來訪問RealSubject.這就是簡單的代理模式. 
動態代理簡單地說就是在這個基礎上把RealSubject的實例化從簡單的new建立變成了動態建立.(雖說的過於簡單,但核心就是這個思想)面試

在看java官方的實現以前,先來看一個本身寫的簡單範例.它解釋了什麼叫動態代理,以及感性地認識到動態代理所帶來的好處.算法

*須要代理的目標接口*/
public interface MyProxy {
    int add(int arg1, int arg2);
}

/*代理類*/
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyProxyImpl implements MyProxy {
    protected InvocationHandler handler;
    public MyProxyImpl(InvocationHandler handler){
        this.handler = handler;
    }
    @Override
    public int add(int arg1, int arg2) {
        Method method = null;
        try{
            method = MyProxy.class.getMethod("add",new Class[]{int.class, int.class});
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        Object result = null;
        try {
            result = handler.invoke(this,method,new Object[]{new Integer(arg1), new Integer(arg2)});
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return (Integer)result;
    }
}

/*真正執行方法的handler,這裏採用a+b+...的方法來計算add()方法*/
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyHandler implements InvocationHandler {
    public MyHandler(){
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("add")){
            Integer sum = 0;
            for (Object object : args){
                sum = (Integer) object + sum;
            }
            return sum;
        }
        else {
            return null;
        }
    }
}
/*真正執行方法的handler2,與handler不一樣,本類把add()方法按照a*b...的方法來處理**/
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyHandler2 implements InvocationHandler {
    public MyHandler2(){
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("add")){
            Integer sum = 1;
            for (Object object : args){
                sum = (Integer) object * sum;
            }
            return sum;
        }
        else {
            return null;
        }
    }
}

/*業務類*/
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
public class MyMain {
    public static void main(String [] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //只須要簡單的修改實際執行add()方法的類名,即可徹底一致地調用兩種算法.業務代碼不用作任何修改.
        //反過來,若是業務代碼中類名不變,無論MyHandler怎樣去實現add()方法都是能夠的.
        Class cl = MyMain.class.forName("com.yy.MyHandler");
        //Class cl = MyMain.class.forName("com.yy.MyHandler2");
        Constructor constructor = cl.getConstructor();
        InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance();
        MyProxy myProxy = new MyProxyImpl(invocationHandler);
        Integer i = myProxy.add(32,18);
        System.out.println(i);
    }
}

 

經過上面這個例子,咱們能夠看到利用動態代理.咱們一方面能夠保證業務代碼和底層代碼實現的解耦,另外一方面又能夠對任何可能添加進來的底層組件進行修飾.舉個例子,咱們能夠對MyProxyImpl.add()作如下修改設計模式

 @Override
    public int add(int arg1, int arg2) {
        System.out.println("計算的參數是:" + arg1 + "," + arg2);//加了這一行
        Method method = null;
        try{
            method = MyProxy.class.getMethod("add",new Class[]{int.class, int.class});
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        Object result = null;
        try {
            result = handler.invoke(this,method,new Object[]{new Integer(arg1), new Integer(arg2)});
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return (Integer)result;
    }

注意到咱們添加了一行代碼,讓任何經過代理調用add()方法的操做都會打印出參數.不論底層你調用的是hander1仍是handler2.是否是很方便?!app

固然,咱們這樣所謂的動態代理還有不少問題,下面仍是來看看Java官方利用Proxy實現的動態代理機制.ide

java.lang.reflect.Proxy

經過官方的Proxy,要實現上面的代碼邏輯,能夠這樣寫:this

public class MyMain {

    public static void main(String [] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class cl = MyMain.class.forName("MyHandler");
        Constructor constructor = cl.getConstructor();
        InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance();
        MyProxy myProxy = (MyProxy) Proxy.newProxyInstance(MyMain.class.getClassLoader(),new Class[]{MyProxy.class},invocationHandler);
        Integer result = myProxy.add(1,2);
        System.out.println(result);
    }
}

核心在Proxy.newProxyInstance(MyMain.class.getClassLoader(),new Class[]{MyProxy.class},invocationHandler); 用心的讀者會發現,其實這裏跟咱們本身的實現差了一個MyProxyImpl實現類. 咱們最終調用的是類MyProxyImpl的實例對象,而官方方法調用的倒是MyProxy接口的實例對象. 
並且,若是你試圖得到一個實現類的實例對象,會報錯說MyProxyImpl不是一個接口!spa

MyProxyImpl myProxy = (MyProxyImpl) Proxy.newProxyInstance(MyMain.class.getClassLoader(),new Class[]{MyProxyImpl.class},invocationHandler);

>>>Exception in thread "main" java.lang.IllegalArgumentException: MyProxyImpl is not an interface

看到Proxy類有一個私有內部靜態類ProxyClassFactory,這個類就是用來獲取Class實例的.裏面有一個apply()方法,這個方法會遍歷Proxy.newProxyInstance()第二個參數傳進來的全部類是否是接口.設計

/*
  * Verify that the Class object actually represents an
  * interface.
  */
 if (!interfaceClass.isInterface()) {
     throw new IllegalArgumentException(
         interfaceClass.getName() + " is not an interface");
 }

因此纔有了上面咱們遇到的報錯.那麼,如今問題成了爲何Proxy要作這個限制呢?簡單的理解就是,java的單繼承機制決定了.若是第二個參數傳進來的是多個實體類,那麼他們是沒法實例化一個對象的.代理

相關文章
相關標籤/搜索