其實這篇文章已經寫完很久了,但是最近沉迷於JUC源碼,所以一直放在草稿箱沒有發。這幾天把AQS以及相關的子類擼了一遍,寫了幾篇JUC源碼解讀,準備發到公號上面來,於是乎決定把草稿箱的這篇文章先解決了
我上一篇文章已經通過代購的例子討論了靜態代理模式,也知道了它不滿足開閉原則,擴展性差的缺點,因此,衍生出了動態代理。動態代理分兩種,一種是基於接口的,也是jdk本身支持的,還有一種是基於類的,需引入第三方類cglib來實現,今天我們主要討論基於接口的動態代理(看這篇之前建議看看我前一篇講靜態代理的文章,代購小哥發家史還是值得學習的)
一. 概述
其實動態代理的類之間的關係和靜態代理是一樣
JDK的動態代理類需要依靠兩個類(接口)來實現,Proxy靜態類,InvocationHandler接口。Proxy類的職責是產生代理Class對象與實例;InvocationHandle接口的職責是負責調用服務和增強服務的(這兩個類與接口都嚴格的遵守了設計模式中的單一職責原則)
是不是有點懵逼,我們還是繼續通過代購小哥來理解吧(不瞭解代購小哥發家史的,可以看看我公號文章:“通過代購理解:靜態代理”)
二. 如何使用動態代理
代購小哥生意越做越大,一個人已經忙不過來了,所以他決定召集周圍的代購同行,成立一個代購公司。以後來一個顧客,代購公司就分配一個代購小哥哥給他,這樣,就可以滿足更多妹子的買買買需求啦
這個代購公司的員工就是由Proxy靜態類來生成,員工所需要執行的服務則是由實現InvocationHandler接口的類來確定的,我們看下代碼吧。
首先我們建立包包商店和鞋鞋商店的接口與實現類
public interface IBagShop {
void buyBag(String num);
}
public class BagShop implements IBagShop {
@Override
public void buyBag(String money) {
System.out.println("買了一個價值"+money+"限量款包包!");
}
}
public interface IShoesShop {
void buyShoes(String num);
}
public class ShoesShop implements IShoesShop{
@Override
public void buyShoes(String money) {
System.out.println("買了一雙價值" + money + "的喬丹!!");
}
}
然後我們再看看代理公司類咋寫
public class PurchasingAgentCompany implements InvocationHandler {
private Object shop;
public void setShop(Object shop) {
this.shop = shop;
}
//通過Proxy獲取動態代理的對象(代購小哥)
public Object getProxyInstance() {
return Proxy.newProxyInstance(shop.getClass().getClassLoader(), shop.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代購售前服務:砍價");
Object ret = method.invoke(shop, args);
System.out.println("代購售後服務:送小禮品");
return ret;
}
}
我們可以看到代購公司PurchasingAgentCompany類繼承了InvocationHandler接口,並實現了其invoke方法來調用服務和增強服務
而getProxyInstance方法中使用了Prxoy靜態類的newProxyInstance方法來獲取代理對象
最後我們創建一個妹子類來找代購公司買東西
public class Girl {
public static void main(String[] args) {
// 包包商店 與 鞋鞋商店
IBagShop bagShop = new BagShop();
IShoesShop shoesShop = new ShoesShop();
// 成立一家代購公司
PurchasingAgentCompany company = new PurchasingAgentCompany();
// 一個顧客買包(所以代購公司需要引入實際的服務提供者:包包商店)
company.setShop(bagShop);
// 代購公司分配了一個包包商店的代購bagShopProxy
IBagShop bagShopProxy = (IBagShop) company.getProxyInstance();
// 代購買包包
bagShopProxy.buyBag("10萬");
System.out.println("------------------------------------");
// 一個顧客買鞋子(所以代購公司需要引入實際的服務提供者:謝謝商店)
company.setShop(shoesShop);
// 代購公司分配了一個鞋鞋商店的代購shoesShopProxy
IShoesShop shoesShopProxy = (IShoesShop) company.getProxyInstance();
// 代購買鞋鞋
shoesShopProxy.buyShoes("1萬");
}
}
我們可以看到,如果我們顧客還需要代購公司代購傢俱,我們只需要擴展傢俱接口與其實現類,妹子就可以直接通過代購公司類來購買了,而代購公司的代碼不需要動一下。所以動態代理類很好的保證了開閉原則。我們看下輸出結果
通過上面的例子與代碼,相信大家已經知道如何通過Proxy靜態類和InvocationHandler接口來實現動態代理了。
但大家現在一定還是會一臉懵逼,Proxy是如何創造出代理對象的呢?代理對象又是怎麼通過實現InvocationHandler的invoke方法來調用服務呢?動態代理用起來是很簡單的,但如何實現動態代理纔是我們需要去關注的。下面我們通過源碼來看看是如何實現動態代理的。
三. 源碼解讀
前面一節我們知道,方法newProxyInstance是生成動態代理對象的,而生成動態代理對象可以分爲兩步,首先我們需要生成Class對象,然後再利用反射技術通過Class對象和InvocationHandler對象來生成Class對象的實例對象,即動態代理對象。那我們按照步驟來分析源碼吧
我們看下Proxy靜態類的newProxyInstance方法
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
// 省略 ...
final Class<?>[] intfs = interfaces.clone();
// 省略 ...
Class<?> cl = getProxyClass0(loader, intfs);
// 省略 ...
final Constructor<?> cons = cl.getConstructor(constructorParams);
// 省略 ...
return cons.newInstance(new Object[]{h});
}
可以看到,上面就4行源碼,爲什麼只有4行呢?因爲與生成代理對象直接相關的源碼就這4行啊,其他都是安全檢查校驗之類的,所以我們抓主幹,其他的大家有興趣可以自己看看(我反正是沒太多興趣,哈哈,至少我現階段是沒必須要去追求這種細枝末節與主幹無關的邏輯)我們先解釋下這4行代碼是幹啥的:
-
我們首先將傳入的接口通過clone方法複製一份(即代購實例中的IShoesShop與IBagShop接口),
-
通過傳入類加載器與接口(被代理對象的類加載器與其實現的接口)給getProxyClass0方法生成代理Class對象(即代購例子中的ShoesShop和BagShop類的代理Class對象),
-
通過Class對象獲得構造器
-
通過構造器生成Class對象的實例對象(構造器的參數是實現了InvocationHandler接口的對象,即代購例子中的代購公司PurchasingAgentCompany)
所以第1、2行代碼是生成我們代理Class對象的,我們先把重點放在弄清楚如何生成代理Class對象,並且知道這個代理Class對象到底是長啥樣的,只有清楚這兩點,我們才能理解3、4行代碼。
既然要知道如何生成代理Class對象,我們就要進入getProxyClass0方法
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
return proxyClassCache.get(loader, interfaces);
}
getProxyClass0方法首先會檢查下接口長度,如果沒超過65535字節,則會通過調用WeakCache類中的get方法。proxyClassCache即爲WeakCache的實例。
我們繼續進入proxyClassCache.get方法
public V get(K key, P parameter) {
// 省略 ...
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
// 省略 ...
}
subKeyFactory是Proxy靜態類中的內部靜態類ProxyClassFactory的實例,我們看這個名字就知道(工廠類啊),最終生成的代理Class對象應該就在這個內部靜態類ProxyClassFactory中的某個方法中,沒錯,就是apply方法中。好,我們進入subKeyFactory.apply方法中看看
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
// 省略 ...
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
try {
return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
// 省略 ...
}
可以看裏面ProxyGenerator.generateProxyClass這個方法嗎,只要有代理類名字(代理類的名字是apply方法中生成的,代碼被我省略掉了)和被代理類所繼承的接口(爲啥需要接口,因爲接口裏面有我們需要的方法呀),我們就可以通過ProxyGenerator.generateProxyClass方法產生二進制數據類型的Class文件(ProxyGenerator這個類是sun公司提供的sun.misc包中的一個類,這裏我們就不展開了)
再通過defineClass0方法將此class文件通過類加載器生成Class對象放在元空間(1.8之前叫方法區),看到沒,Class對象就是這樣產生的
我們一般都是先寫java文件,然後編譯成class文件,最後再生成class對象及其實例。但是我們生成的代理Class文件是直接通過generateProxyClass方法在內存中生成的,它沒有java文件,所以我們不知道這個代理對象到底長啥樣,這裏我們可以用反編譯將生成的class文件反編譯成java文件
我們寫了一個小工具類來反編譯生成BagShop的代理Class文件的java文件(工具代碼和反編譯網址貼在最後了)
import com.yy.demo16_proxyPattern.IBagShop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class IbagShop extends Proxy implements IBagShop {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public IbagShop(InvocationHandler var1) throws {
super(var1);
}
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 void buyBag(String var1) throws {
try {
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
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);
}
}
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);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m3 = Class.forName("com.yy.demo16_proxyPattern.IBagShop").getMethod("buyBag", new Class[]{Class.forName("java.lang.String")});
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
可以看到,這個類繼承了Proxy類,實現了IBagShop接口。我們還可以看到一個有參構造方法,參數是InvocationHandler類型,然後我們再看buyBag這個方法,裏面方法的調用語句爲:
super.h.invoke(this, m3, new Object[]{var1});
這不就是h通過invoke來調用方法嗎(對invoke不瞭解的可以補一下反射相關的知識)。那h是啥,不就是構造函數傳進來的InvocationHander嗎?我們再回到最開始的newProxyInstance方法,看下它的第3、4行代碼:
final Constructor<?> cons = cl.getConstructor(constructorParams);
return cons.newInstance(new Object[]{h});
上面第一行代碼是獲取代理Class對象的構造器,第二行代碼就是通過構造器生成代理Class對象的實例對象,即我們的動態代理對象。
最後總結一下生成動態代理對象的步驟
-
sun.misc.generateProxyClass類的generateProxyClass方法根據傳入的被代理對象繼承的接口信息直接生成代理對象的Class文件
-
defineClass0方法通過被代理對象類加載器將代理對象的Class文件加載成Class對象
-
我們通過反射技術獲得Class對象的有參構造器,參數是實現了InvocationHandler接口的代理類
-
最後通過構造器生成動態代理對象
最後的最後,我把前文提到的工具代碼和反編譯網站貼出來
package com.yy.demo16_proxyPattern;
import com.yy.demo1_mapStruct.User;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* @Author: 24只羊
* @Description:
* @Date: 2020-01-17
*/
public class ProxyGeneratorUtil {
private static String DEFAULT_CLASS_NAME = "$Proxy";
public static byte [] saveGenerateProxyClass(String proxyName, Class clazz) {
byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, new Class[]{clazz});
FileOutputStream out = null;
try {
String filePath = "這裏填寫自己" + proxyName + ".class";
out = new FileOutputStream(filePath);
out.write(classFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return classFile;
}
public static void main(String[] args) {
Class<?> interfaces [] = {User.class};
saveGenerateProxyClass("程序員進階之路")
}
}
在線反編譯鏈接地址:http://javare.cn/
(完)
歡迎大家關注我的公衆號 “程序員進階之路”,裏面記錄了一個非科班程序員的成長之路