手寫(xiě)一個(gè)面向接口的動(dòng)態(tài)代理

如題威蕉,手寫(xiě)一個(gè)面向接口的動(dòng)態(tài)代理畜埋。我們需要先了解jdk中的動(dòng)態(tài)代理是怎么實(shí)現(xiàn)的望众。

理解生成的代碼和調(diào)用過(guò)程

設(shè)置vm參數(shù),-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true溃卡,可以使jdk動(dòng)態(tài)生成的class文件輸出到磁盤(pán)中。

設(shè)置vm options

使用下面代碼進(jìn)行調(diào)試

public interface IService {
    public void service(String name )throws Exception;
}

public class ServiceImplA implements IService {
    @Override
    public void service(String name) throws Exception {
        System.out.println("ServiceImplA name" + name);
    }
}

public class DynaProxyServiceA implements InvocationHandler {
    private Object object;
    /**
     *   將目標(biāo)對(duì)象關(guān)聯(lián)到InvocationHandler接口蜒简,返回代理對(duì)象obj
     *   調(diào)用代理對(duì)象的方法時(shí)瘸羡,都會(huì)自動(dòng)調(diào)用invoke方法
     */

    public Object bind(Object object){
        this.object = object;
        return Proxy.newProxyInstance(
                this.object.getClass().getClassLoader(),
                this.object.getClass().getInterfaces(),
                this);
    }

    @SuppressWarnings("unchecked")
    public <T> T bindInterface(Class<T> proxyInterface){
        object = proxyInterface;
        return (T)Proxy.newProxyInstance(
                proxyInterface.getClassLoader(),
                new Class[]{proxyInterface},
                this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
        System.out.println("log start");
        if(object instanceof Class<?>){
            Class<?> clazz = (Class<?>) object;
            for (Method clazzMethod : clazz.getDeclaredMethods()) {
                System.out.println(clazzMethod.getName());
            }
        }
        try{
            result = method.invoke(this.object,args);
            Class<?> returnType = method.getReturnType();
            System.out.println(returnType);
        }catch (Exception e){
            throw e;
        }
        System.out.println("log end");
        return result;
    }
    public static void main(String [] args) throws Exception {
        IService service = (IService)new DynaProxyServiceA()
                        .bind(new ServiceImplA());
        service.service("zhjl");
    }
}

輸出結(jié)果
log start
ServiceImplA namezhjl
void
log end

運(yùn)行完程序后,會(huì)在項(xiàng)目的根目錄生成一個(gè)文件夾com.sun.proxy搓茬,里面會(huì)生成一個(gè)$Proxy0.class的代理類(lèi)文件犹赖。

打開(kāi)文件可以看到以下生成的源代碼。

public final class $Proxy0 extends Proxy implements IService {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

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

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, 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 void service(String var1) throws Exception {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (Exception | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

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

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("org.example.aop.IService").getMethod("service", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

很容易可以發(fā)現(xiàn)垮兑,生成的源代碼是有規(guī)律可尋的冷尉。

  • 固定會(huì)重寫(xiě)java.lang.Object中的equalstoString系枪,hashCode三個(gè)方法
  • 對(duì)比靜態(tài)變量Method的聲明對(duì)應(yīng)方法的位置與static代碼塊這三塊地方雀哨,可以發(fā)現(xiàn),聲明順序與靜態(tài)代碼塊中反射獲取的順序一致私爷,并且在各方法中執(zhí)行反射出來(lái)的Method也是相等的雾棺。這里做到了提前加載被代理類(lèi)的方法,然后使用到該代理類(lèi)的方法時(shí)將被代理類(lèi)的方法當(dāng)參數(shù)傳到h.invoke中執(zhí)行衬浑。

進(jìn)入到被繼承的Proxy中捌浩,發(fā)現(xiàn)在生成的類(lèi)中使用的h就是InvocationHandler這個(gè)類(lèi)。即我們?cè)谑褂脛?dòng)態(tài)代理時(shí)所要實(shí)現(xiàn)才那個(gè)類(lèi)!

image
image.png

所以這個(gè)地方是生成一個(gè)回調(diào)代理類(lèi)的invoke方法的類(lèi)工秩,來(lái)調(diào)用invoke時(shí)決定什么時(shí)候執(zhí)行被代理類(lèi)的service方法就能達(dá)到切面增強(qiáng)這個(gè)方法的效果

執(zhí)行代理類(lèi)的流程圖

理解完了生成代碼的意義和處理流程尸饺,剩下的就是怎么構(gòu)造這些代碼并將他編譯成.class文件和被類(lèi)加載器加載并被創(chuàng)建實(shí)例被我們所使用了。

如何構(gòu)造和加載

參考Proxy.newProxyInstance的代碼助币,看到getProxyClass0,前面的代碼忽略浪听,看注釋就知道這里是生成指定代理類(lèi)的方法。直接點(diǎn)進(jìn)去就好了眉菱。

生成代碼的方法
image
image
image

記住這兩個(gè)變量分別是KeyFactoryProxyClassFactory迹栓。然后回到proxyClassCache.get(loader, interfaces)

image
image

根據(jù)實(shí)現(xiàn)的接口數(shù)量來(lái)返回Key

往下走,最后指向的類(lèi)都是Factory俭缓,并在最后執(zhí)行get方法克伊。

image

最后回到ProxyClassFactory這個(gè)類(lèi)的apply方法酥郭。

image

最后在方法的底部發(fā)現(xiàn)ProxyGenerator.generateProxyClass對(duì)應(yīng)的作用就是構(gòu)造相當(dāng)于構(gòu)造.java源文件。
defineClass0相當(dāng)于javac編譯成.class文件并loadClass返回對(duì)應(yīng)的類(lèi)

image

這個(gè)生成方法較復(fù)雜愿吹,經(jīng)過(guò)簡(jiǎn)單的查看源碼不从,已經(jīng)知道步驟如下:

  • 構(gòu)造.java源文件
  • 編譯成.class后加載類(lèi)

筆者的方法比較簡(jiǎn)單,直接使用freemaker來(lái)構(gòu)造源文件洗搂,需要傳入以下四個(gè)參數(shù)消返。

  • package->生成類(lèi)所在的包
  • className->生成的代理類(lèi)名稱
  • interface->實(shí)現(xiàn)的接口類(lèi)全類(lèi)名
  • methodList->需要重寫(xiě)的方法列表

freemaker模板如下

package ${package};
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
/**
* @Author: Jdragon
* @email: 1061917196@qq.com
* @Description: jdk動(dòng)態(tài)代理實(shí)質(zhì)
*/
public class ${className} extends Proxy implements ${interface} {
    public ${className}(InvocationHandler h) {
        super(h);
    }
<#list 0..(methodList!?size-1) as i>
    @Override
    public final ${methodList[i].retType} ${methodList[i].methodName}(
        <#list methodList[i].paramList as param>
                ${param} var${param_index}<#if param_has_next>,</#if>
        </#list>) {
        try {
            <#if (methodList[i].retType!="void")>return (${methodList[i].retType})</#if>
            <#if (methodList[i].paramList?size==0)>
            super.h.invoke(this, m${i}, (Object[])null);
            <#else>
            super.h.invoke(this, m${i}, new Object[]{
                <#list 0..(methodList[i].paramList!?size-1) as k>var${k}
                    <#if k_has_next>,</#if>
                </#list>});
            </#if>
        } catch (RuntimeException | Error e) {
            throw e;
        }catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
</#list>
<#list 0..(methodList!?size-1) as i>
    private static Method m${i};
</#list>
    static{
        try{
            <#list 0..(methodList!?size-1) as i>
                m${i} = Class.forName("${methodList[i].className}").getMethod("${methodList[i].methodName}"
                <#list methodList[i].paramList as param>
                    ,Class.forName("${param}")
                </#list>);
            </#list>
        }  catch (NoSuchMethodException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

主要代碼。以下代碼不完整耘拇,可到gitee獲取源碼

public class JdkProxyFactory {

    private final static String LN = System.lineSeparator();

    private final static AtomicInteger PROXY_INDEX = new AtomicInteger(0);

    private final static Boolean SAVE_GENERATED_FILES = Boolean.valueOf(System.getProperty("sun.misc.ProxyGenerator.saveGeneratedFiles"));

    private final static String USER_DIR = System.getProperty("user.dir") + "/com/jdragon/proxy/";

    private final static String PACKAGE_NAME = "com.jdragon.proxy";

    public static Object newProxyInstance(ClassLoader classLoader,
                                          @NotNull Class<?>[] interfaces,
                                          @NotNull InvocationHandler h) {
        try {
            if (interfaces.length == 0) {
                throw new Exception("至少要實(shí)現(xiàn)一個(gè)接口");
            }
            //使用被代理類(lèi)的類(lèi)名和自增數(shù)定義代理類(lèi)的名字
            String proxyClass = interfaces[0].getSimpleName() + "$Proxy" + PROXY_INDEX.incrementAndGet();
            //加載代理類(lèi)
            Class<?> loadClass = loadClass(interfaces[0], proxyClass);
            Constructor<?> constructor = loadClass.getDeclaredConstructor(InvocationHandler.class);
            return constructor.newInstance(h);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * @Description: 加載類(lèi)
    **/
    private static Class<?> loadClass(Class<?> interfaces, String proxyClassName) throws Exception {
        String classPath = PACKAGE_NAME + "." + proxyClassName;
        //構(gòu)建源代碼
        String sourceCode = generateSourceCode(interfaces, proxyClassName);
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        try (JavaFileManager manager = new MemoryFileManager(compiler.getStandardFileManager(null, null, null))) {
            List<JavaFileObject> files = Collections.singletonList(new MemoryJavaFileObject(proxyClassName, sourceCode));
            JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, files);
            if (!task.call()) {
                throw new Exception("任務(wù)調(diào)用異常");
            }
            ClassLoader classLoader = manager.getClassLoader(null);
            Class<?> aClass = manager.getClassLoader(null).loadClass(classPath);
            if (SAVE_GENERATED_FILES) {
                save(proxyClassName, classLoader);
            }
            return aClass;
        }
    }

    /**
     * @Description: 構(gòu)造源代碼
    **/
    private static String generateSourceCode(Class<?> interfaces, String proxyClassName) {
        String interfaceName = interfaces.getName();
        List<MethodEntity> methodEntities = new ArrayList<>();
        methodEntities.add(new MethodEntity(Object.class, "toString", null,
                String.class));
        methodEntities.add(new MethodEntity(Object.class, "hashCode", null,
                int.class));
        methodEntities.add(new MethodEntity(Object.class, "equals", Collections.singletonList(Object.class.getName()),
                boolean.class));

        for (Method declaredMethod : interfaces.getDeclaredMethods()) {
            MethodEntity methodEntity = new MethodEntity();
            methodEntity.setClassName(interfaces);
            methodEntity.setMethodName(declaredMethod.getName());
            List<String> params = new ArrayList<>();
            for (Parameter parameter : declaredMethod.getParameters()) {
                String paramTypeName = parameter.getType().getName();
                params.add(paramTypeName);
            }
            methodEntity.setParamList(params);
            methodEntity.setRetType(declaredMethod.getReturnType());
            methodEntity.setTransferType(declaredMethod.getReturnType());
            methodEntities.add(methodEntity);
        }

        //利用定義好的模板傳入?yún)?shù)到freemaker進(jìn)行遍歷填充撵颊,最后獲得源代碼
        Map<String, Object> map = new HashMap<>(8);
        map.put("package", PACKAGE_NAME);
        map.put("className", proxyClassName);
        map.put("interface", interfaceName);
        map.put("methodList", methodEntities);
        FreeMakerUtil freeMakerUtil = new FreeMakerUtil("/template/freemaker/", "ftl");
        return freeMakerUtil.printString("proxy", map);
    }
}


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市惫叛,隨后出現(xiàn)的幾起案子倡勇,更是在濱河造成了極大的恐慌,老刑警劉巖嘉涌,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妻熊,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡仑最,警方通過(guò)查閱死者的電腦和手機(jī)扔役,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)警医,“玉大人亿胸,你說(shuō)我怎么就攤上這事≡せ剩” “怎么了侈玄?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)吟温。 經(jīng)常有香客問(wèn)我序仙,道長(zhǎng),這世上最難降的妖魔是什么鲁豪? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任潘悼,我火速辦了婚禮,結(jié)果婚禮上爬橡,老公的妹妹穿的比我還像新娘挥等。我一直安慰自己,他們只是感情好堤尾,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著迁客,像睡著了一般郭宝。 火紅的嫁衣襯著肌膚如雪辞槐。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,443評(píng)論 1 302
  • 那天粘室,我揣著相機(jī)與錄音榄檬,去河邊找鬼。 笑死衔统,一個(gè)胖子當(dāng)著我的面吹牛鹿榜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播锦爵,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼舱殿,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了险掀?” 一聲冷哼從身側(cè)響起沪袭,我...
    開(kāi)封第一講書(shū)人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎樟氢,沒(méi)想到半個(gè)月后冈绊,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡埠啃,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年死宣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碴开。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡毅该,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出叹螟,到底是詐尸還是另有隱情鹃骂,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布罢绽,位于F島的核電站畏线,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏良价。R本人自食惡果不足惜寝殴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望明垢。 院中可真熱鬧蚣常,春花似錦、人聲如沸痊银。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至贞绳,卻和暖如春谷醉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背冈闭。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工俱尼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人萎攒。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓遇八,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親耍休。 傳聞我的和親對(duì)象是個(gè)殘疾皇子刃永,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容