1. 程式人生 > >Java 動態代理原理及其在mybatis中的應用

Java 動態代理原理及其在mybatis中的應用

代理是一種基本的設計模式,代理模式的主要作用是為其他物件(被代理的物件,下面稱為原物件)提供一種代理以控制對這個物件的訪問。在某些情況下,一個物件不想或者不能直接引用另一個物件,而代理物件可以在客戶端和目標物件之間起到中介的作用。代理物件既可以將客戶端的請求完全轉發給原物件,也可以提供一些額外或不同的操作。這篇文章從靜態代理模式說起,然後到Java的動態代理最後分析一個動態代理在mybatis應用的例項。

1.靜態代理

靜態代理相當於為每個需要被代理的類構建一個代理類出來,然後將代理類的物件作為被代理類物件提供給外部操作。下面是一個簡單的示例:

public interface Car {
double getPrice(); String getName(); } public class Bicycle implements Car { private double price = 200; private String name = "bicycle"; @Override public double getPrice() { System.out.println("bicycle's price is " + price); return price; } public
String getName() { System.out.println("name is " + name); return name; } } public class SimpleProxy implements Car { private Car proxied; //被代理物件 public SimpleProxy(Car proxied) { this.proxied = proxied; } //對價格的訪問會被直接攔截掉 public double getPrice() { System.out.println("car price is 1000"
); return 1000; } //對名字的訪問會被直接轉發給被代理物件 public String getName() { return proxied.getName(); } }

Car是一個介面而Bicycle是Car的一個具體子類,SimpleProxy也實現了Car介面並且內部還只有一個Car型別的引用,所以可以作為Bicycle代理物件使用。下面是對上面代理的測試:

public class TestProxy {

    public static void getCarInfo(Car car) {
        car.getPrice();
        car.getName();
    }

    public static void main(String[] args) {
        //代理物件需要持有被代理物件的引用,用於做請求的轉發
        Car car = new SimpleProxy(new Bicycle());
        getCarInfo(car);
    }
}
/**
car price is 1000
name is bicycle
*/

對於getCarInfo方法來說,它接受的引數是介面型別,所以無法真正知道獲得的是Bicycle物件還是SimpleProxy物件,因為這兩種都實現了Car介面。這樣的情況下,SimpleProxy實際上已經被插入到客戶端和Bicycle物件之間作為一箇中間人的角色,它攔截了對被代理物件方法的呼叫,對於其中一些方法它直接返回了自定義的資訊,對於另外一些方法它轉發給了被代理物件。這種靜態代理的好處在於通過代理類就可以對原物件的某些操作進行修改,而不用去修改原物件的程式碼。

2.java 動態代理

對於上面展示的靜態代理來說,需要對每一個型別(實現同一個介面的子類)都手動的編寫一個代理類,在實際應用中這是一個很大的限制(比如說有時候所需要代理的類是從外部引用的,沒有原始碼)。而Java的動態代理解決了這個問題,它可以動態的建立代理並動態的處理對所代理方法的呼叫。不過與靜態代理不同,動態代理不會為被代理物件中的每一個方法都提供一個代理實現,而是將在動態代理上做的所有呼叫都重定向到一個單一處理器(方法)上,然後在這個方法上根據呼叫的型別進行轉發。先來看一個具體的例項,對上面的SimpleProxy進行改寫,將其變成動態代理模式:

public class DynamicProxyHandler implements InvocationHandler {

    //被代理物件
    private Object proxied;

    public DynamicProxyHandler(Object proxied) {
        this.proxied = proxied;
    }

    @Override
    //proxy是代理物件,Method是代理物件中被呼叫的謀改革方法,args是呼叫這個method是傳入的引數.
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         //這裡只是簡單的打印出被攔截的方法,然後呼叫proxied物件的相應方法進行處理
        System.out.println("攔截了: "+method.getName());
        return method.invoke(proxied,args);
    }
}

public class TestProxy {

    public static void getCarInfo(Car car) {
        car.getPrice();
        car.getName();
    }

    public static void main(String[] args) {
        Bicycle bicycle = new Bicycle();
        //使用Proxy建立一個動態代理
        Car car = (Car) Proxy.newProxyInstance(bicycle.getClass().getClassLoader(), new Class<?>[]{Car.class}, new DynamicProxyHandler(bicycle));
        getCarInfo(car);
    }
}

/**
攔截了: getPrice
bicycle's price is 200.0
攔截了: getName
name is bicycle
*/

上面這段程式碼首先實現了InvocationHandler介面,這個接口裡面其實就一個invoke()方法:

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

每一個動態代理物件都會關聯一個InvocationHandler物件,當代理物件的任何方法被呼叫的時候,都會被轉發到InvocationHandler物件的invoke()方法上去的。在上面DynamicProxyHandler的invoke()方法中,只是簡單的打印出被攔截方法的名稱然後直接呼叫了DynamicProxyHandler中持有的實際物件中相關的方法。
還有一點需要注意的就是invoke()方法中傳入的proxy物件是代理物件,這是為了防止需要區分請求的來源,在很多情況下我們是不需要關心這一點的,但是如果再次呼叫這個物件上的方法,會被轉發給當前的invoke()物件,這樣可能會造成死迴圈。
在測試程式碼中通過Proxy.newProxyInstance()來建立了一個代理物件,Proxy類也是java.lang.reflect包下的成員。這個方法接受三個引數:

 @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

首先是一個ClassLoader物件,一般可以通過已載入的類獲得;然後是一個Class物件陣列,這是被代理物件實現的介面的Class物件列表;最後就是前面提到的InvocationHandler物件,這裡是DynamicProxyHandler物件。
動態代理的一般模式是會在代理物件上執行被代理的操作,然後使用Method.invoke()方法將請求轉發給被代理物件處理,並傳入必須的引數。這樣看起來作用並不是很大,但實際使用的時候是可以對傳入的引數進行處理的並且還可以根據Method中獲取的資訊來決定如何對方法進行轉發,處理過程是可以非常靈活的。

3.java 動態代理的實現原理

通過上面的例子基本上能夠明白動態代理的基本用法了,但是動態代理到底是如何實現的呢,具體來說Proxy.newProxyInstance()是如何根據傳入的引數生成代理物件的,我們實現的DynamicProxyHandler中的invoke()方法時什麼時候由誰來負責進行呼叫的。為了解釋上面的問題,先從Proxy.newProxyInstance()原始碼入手:

 @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        if (h == null) {
            throw new NullPointerException();
        }

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) { //安全性檢查
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
        //根據傳入的ClassLoader和介面的Class物件,構造出代理類的Class物件
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            //拿到代理物件構造器的Constructor物件
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
                // create proxy instance with doPrivilege as the proxy class may
                // implement non-public interfaces that requires a special permission
                return AccessController.doPrivileged(new PrivilegedAction<Object>() {
                    public Object run() {
                        return newInstance(cons, ih);
                    }
                });
            } else {
                //通過反射的方式通過Constructor獲取代理物件,
                //InvocationHandler作為構造器引數傳入。這裡返回的是Object型別的物件。
                return newInstance(cons, ih);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString());
        }
    }

上面的大致流程是通過傳入的ClassLoader和interface的Class陣列物件獲得代理類的Class物件,並獲取Class物件的構造器Constructor物件,然後將InvocationHandler作為構造器器引數通過反射呼叫Constructor的newInstance()方法獲取到代理物件。
這裡的邏輯不復雜可以算是反射機制的應用,問題的關鍵在getProxyClass0(loader, intfs)方法是如何根據傳入的loader和intfs獲得代理類Class物件的。下面是這個方法的原始碼:

 private static Class<?> getProxyClass0(ClassLoader loader,
                                    Class<?>... interfaces) {

         //如果這個被代理類實現的介面數目超過65535,則直接拋異常
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }
        //proxyClassCache是用來快取代理類的一個Cache
        return proxyClassCache.get(loader, interfaces);
    }

這裡沒有具體的實現邏輯,這裡先看下上面用到的proxyClassCache物件。

 private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

其中的ProxyClassFactory就是用來建立ProxyClass的工廠。在proxyClassCache.get()方法中有很多的快取邏輯,但是如果需要的代理物件沒有快取的話,在裡面是通過ProxyClassFactory的apply()方法來獲取的,下面是apply()方法的原始碼:

          //根據傳入的loader和interfaces構造代理類的Class物件
          @Override
          public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

            //遍歷傳入介面的Class物件陣列
            for (Class<?> intf : interfaces) {
                Class<?> interfaceClass = null;
                try {
                    //使用傳入的ClassLoader將介面再重新載入一遍,主要是保證需要代理類和其實現的介面都是同一個類載入器載入的。
                    interfaceClass = Class.forName(intf.getName(), false, loader);
                } catch (ClassNotFoundException e) {
                }
                //如果兩者不是同一個類載入器載入的,則不能構造代理物件,直接丟擲異常
                if (interfaceClass != intf) {
                    throw new IllegalArgumentException(
                        intf + " is not visible from class loader");
                }
                 //確認傳入的Class物件確實是代表介面的
                if (!interfaceClass.isInterface()) {
                    throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }
                //確定沒有重複指定介面,如果傳入的介面Class物件陣列中,有兩個物件重複,則會丟擲這個異常
                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                    throw new IllegalArgumentException(
                        "repeated interface: " + interfaceClass.getName());
                }
            }

            //代理物件所屬的包路徑
            String proxyPkg = null;    

            //找到非public的介面,然後將代理類同樣放到這個包下,
            //需要保證所有非public介面都在同一個包路徑下,否則會報錯。
            for (Class<?> intf : interfaces) {
                int flags = intf.getModifiers();
                if (!Modifier.isPublic(flags)) {
                    String name = intf.getName();
                    int n = name.lastIndexOf('.');
                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                    }
                }
            }

            if (proxyPkg == null) {
                //找到不到非public型別的介面,則指定預設包為
                //com.sun.proxy
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }

            //為代理類生成全限定類名,這裡的proxyClassNamePrefix是一個固定字串"$Proxy",而num是利用AtomicLong生成的一個唯一的數字編號
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

           //下面是真正生成代理類Class物件的地方

           //首先是通過給ProxyGenerator.generateProxyClass()方法傳入剛生成的全限定和介面Class物件陣列,這個方法負責生成代理類的位元組碼。
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);
            try {
                //拿到位元組碼之後,就通過defineClass0()方法生成代理類的Class物件。而這其實是一個本地方法(native方法),也就不具體分析了。
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                /*
                 * A ClassFormatError here means that (barring bugs in the
                 * proxy class generation code) there was some other
                 * invalid aspect of the arguments supplied to the proxy
                 * class creation (such as virtual machine limitations
                 * exceeded).
                 */
                throw new IllegalArgumentException(e.toString());
            }
        }
    }

最後來看下ProxyGenerator.generateProxyClass()方法,這個方法負責根據傳入的代理類許可權定名和代理類需要實現介面的Class物件陣列來生成代理類的位元組碼陣列。

public static byte[] generateProxyClass(final String name,  
                                        Class[] interfaces)  
   {  
       ProxyGenerator gen = new ProxyGenerator(name, interfaces);  
    // 這裡動態生成代理類的位元組碼,涉及到class檔案位元組碼格式的拼接,比較繁瑣就不進去看了。 
       final byte[] classFile = gen.generateClassFile();  

    // 如果saveGeneratedFiles的值為true,則會把所生成的代理類的位元組碼以class檔案的形式儲存到磁碟。 
       if (saveGeneratedFiles) {  
           java.security.AccessController.doPrivileged(  
           new java.security.PrivilegedAction<Void>() {  
               public Void run() {  
                   try {  
                       FileOutputStream file =  
                           new FileOutputStream(dotToSlash(name) + ".class");  
                       file.write(classFile);  
                       file.close();  
                       return null;  
                   } catch (IOException e) {  
                       throw new InternalError(  
                           "I/O exception saving generated file: " + e);  
                   }  
               }  
           });  
       }  

    // 返回代理類的位元組碼  
       return classFile;  
   }  

經過上面的一番分析,對Proxy.newInstance()方法是如何生成一個動態代理物件有了直觀的概念,但是對於生成的代理類到底是怎樣的一個結構還是很模糊。通過上面的分析可以知道位元組碼的生成是通過ProxyGenerator.generateProxyClass()實現的,所以下面通過手動呼叫這個方法來為上面用到的Car類生成一個代理類的位元組碼,然後將其寫入到磁碟儲存為class檔案,再通過反編譯的方法來檢視生成的代理類的結構:

public final class $Proxy1213 extends Proxy implements Car {
    private static Method m3;
    private static Method m1;
    private static Method m4;
    private static Method m0;
    private static Method m2;

    public $Proxy1213(InvocationHandler var1) throws  {
        super(var1);
    }

   //每個方法的呼叫都會被轉發到與這個代理物件關聯的InvocationHandler物件的invoke()方法中去,傳給invoke()的Mehtod物件是通過靜態初始化塊中生成的。

    public final String getName() throws  {
        try {
            return (String)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final boolean equals(Object var1) throws  {
        try {
            return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final double getPrice() throws  {
        try {
            return ((Double)super.h.invoke(this, m4, (Object[])null)).doubleValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    //在靜態初始化塊中通過反射取得介面中方法的Method物件和其它父類中可能會被子類重寫方法的Method物件(這裡主要是Object中的幾個方法)
    static {
        try {
            m3 = Class.forName("com.sankuai.lkl.proxy.Car").getMethod("getName", new Class[0]);
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
            m4 = Class.forName("com.sankuai.lkl.proxy.Car").getMethod("getPrice", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

看了上面這個反編譯的Proxy類之後,結合前面的分析,對於整個動態代理的過程應該就很清楚了。

4.java動態代理在mybatis中的使用例項

最後一部分是關於動態代理的實際應用的,在這裡提供一個mybatis中的例子。在開始使用mybatis這個框架的時候,我很不能理解的一點就是定義了一個訪問資料庫的介面進行了一些配置之後,然後通過sqlSession.getMapper()方法就可以獲得這個介面的一個物件。大致是這樣的:

public interface BranchDao {

    Branch getBranchById(int id);

}

 public void test(){
        BranchDao branchDao = sqlSession.getMapper(BranchDao.class);
        branchDao.getBranchById(12);
    }

其實這裡的sqlSession.getMapper()方法就是為BranchDao介面生成了一個動態代理物件。下面來看下sqlSession.getMapper()方法的具體實現:

@Override
  public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }

它直接呼叫的configuration的getMapper()方法,來看下這個方法:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

再看mapperRegistry.getMapper():

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
   //快取了mapperProxyFactory物件
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      //這裡面就是具體產生代理物件的方法
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

再看mapperProxyFactory.newInstance()方法:

  //還是呼叫Proxy.newProxyInstance()來建立的反射物件
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  //mapperProxy就是提供給dao介面的代理物件
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

可以看到底層是通過Proxy.newProxyInstance()建立的代理物件,然後返回的。而且因為這裡使用了泛型,所以對Proxy.newProxyInstance()建立返回的Object物件進行了強制型別轉換,返回的物件就是BranchDao介面型別了。