Java?靜態(tài)代理與動(dòng)態(tài)代理解析
靜態(tài)代理: 由我們開發(fā)者自己手動(dòng)創(chuàng)建或者在程序運(yùn)行前就已經(jīng)存在的代理類,靜態(tài)代理通常只代理一個(gè)類,動(dòng)態(tài)代理是代理一個(gè)接口下的多個(gè)實(shí)現(xiàn)類。
動(dòng)態(tài)代理: 在程序運(yùn)行時(shí),運(yùn)用java反射機(jī)制動(dòng)態(tài)創(chuàng)建而成,靜態(tài)代理事先知道要代理的是什么,而動(dòng)態(tài)代理不知道要代理什么東西,只有在運(yùn)行時(shí)才知道,通常動(dòng)態(tài)代理實(shí)現(xiàn)方式是通過實(shí)現(xiàn) jdk 的 InvocationHandler 接口的 ??invoke?? 方法
一、代碼實(shí)踐
需要代理的接口:
interface IBook { ? ? fun toBookName(name: String) }
靜態(tài)代理
class BookImpl : IBook { ? ? override fun toBookName(name: String) { ? ? ? ? println("書名|$name") ? ? } }
// TODO: 2020/11/25 靜態(tài)代理最大的特點(diǎn)就是,對(duì)于具體的代理提前聲明 class BookProxy( ? ? private val iBook: IBook, ? ? private var objStart: () -> Unit = { println("開始前的操作") }, ? ? private var objStop: () -> Unit = { println("結(jié)束時(shí)的操作") } ) : IBook { ? ? override fun toBookName(name: String) { ? ? ? ? //我們可以在具體的實(shí)現(xiàn)前做一些處理操作 ? ? ? ? objStart.invoke() ? ? ? ? iBook.toBookName(name) ? ? ? ? //具體的處理后做一些操作 ? ? ? ? objStop.invoke() ? ? } } fun main() { ? ? val bookProxy = BookProxy(BookImpl()) ? ? bookProxy.toBookName("Android&Petterp") }
靜態(tài)代理相對(duì)簡(jiǎn)單,也有局限性,對(duì)于代理類我們需要提前聲明,而且每一個(gè)代理都需要提前創(chuàng)建好。
動(dòng)態(tài)代理
class BookImpl : IBook { ? ? override fun toBookName(name: String) { ? ? ? ? println("測(cè)試輸出文本$name") ? ? } } /** 用于幫助代理的類 ?* BookImplHandler看起來很像一個(gè)比較奇怪的代理,實(shí)際上其只是用于幫助代理的類,我們最終生產(chǎn)的代理類會(huì)把調(diào)用發(fā)給它讓其處理。 ?* 代理類本身是通過 Proxy.newProxyInstance() 方法在運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建 ?* */ class BookImplHandler( ? ? private val book: IBook, ? ? private var objStart: () -> Unit = { println("開始前的操作") }, ? ? private var objStop: () -> Unit = { println("結(jié)束時(shí)的操作") } ) : ? ? InvocationHandler { ? ? // TODO: 2020/11/25 ? ? // ?(1)在invoke方法中接收可變長參數(shù),在Kotlin語法中,數(shù)組是array,可變長參數(shù)類型是vararg,類型不匹配。 ? ? // ?(2)Kotlin中數(shù)組轉(zhuǎn)為可變長參數(shù),通過前面加*符號(hào)。 ? ? // ?(3)如果方法沒有任何參數(shù),args將為null,并將其傳播到Kotlin將導(dǎo)致NullPointerException. ? ? // ?作為一種解決方法,使用*(args?:arrayOfNulls< Any>(0)),并在所描述的極端情況下選擇正確的部分并將其擴(kuò)展為零參數(shù). ? ? override fun invoke(proxy: Any, method: Method?, args: Array<out Any>?): Any? { ? ? ? ? objStart.invoke() ? ? ? ? val invoke = method?.invoke(book, *(args ?: emptyArray())) ? ? ? ? objStop.invoke() ? ? ? ? return invoke ? ? } } fun main() { ? ? val bookImplHandler = BookImplHandler(BookImplDynamic()) ? ? val iBook = Proxy.newProxyInstance( ? ? ? ? BookImpl::class.java.classLoader, ? ? ? ? BookImpl::class.java.interfaces, bookImplHandler ? ? ) as IBook ? ? iBook.toBookName("測(cè)試的文本") }
二、常見的動(dòng)態(tài)代理場(chǎng)景
Retrofit中的動(dòng)態(tài)代理
使用Retrofit時(shí),當(dāng)我們使用Retrofit.Builder().create()
時(shí),傳入了我們的接口類,其 ??create?? 方法內(nèi)部就使用了動(dòng)態(tài)代理,從而生成了相應(yīng)的代理類。
public <T> T create(final Class<T> service) { ? //判斷是否為接口 ? validateServiceInterface(service); ? return (T) ? ? ? //代理實(shí)現(xiàn) ? ? ? Proxy.newProxyInstance( ? ? ? ? ? service.getClassLoader(), ? ? ? ? ? new Class<?>[] {service}, ? ? ? ? ? new InvocationHandler() { ? ? ? ? ? ? ... ? ? ? ? ? ? @Override ? ? ? ? ? ? public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) ? ? ? ? ? ? ? ? throws Throwable { ? ? ? ? ? ? ? //如果是object方法,則直接觸發(fā) ? ? ? ? ? ? ? if (method.getDeclaringClass() == Object.class) { ? ? ? ? ? ? ? ? return method.invoke(this, args); ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ... ? ? ? ? ? ? } ? ? ? ? ? }); }
使用動(dòng)態(tài)代理實(shí)現(xiàn) onClick注入
如下所示,我們聲明一個(gè)注解,其應(yīng)用于方法,并加入了一個(gè)Activity的擴(kuò)展函數(shù),用于對(duì)Activity重所有使用了該注解的方法進(jìn)行onclick注入, 其方法內(nèi)部使用反射+動(dòng)態(tài)代理,從而實(shí)現(xiàn)。
@Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.FUNCTION) annotation class InjectClick(@IdRes val ids: IntArray) fun Activity.injectClicks() { ? ? javaClass.methods.asSequence().filter { ? ? ? ? it.isAnnotationPresent(InjectClick::class.java) ? ? }.forEach { ? ? ? ? it.isAccessible = true ? ? ? ? it.getAnnotation(InjectClick::class.java).ids.forEach { id -> ? ? ? ? ? ? findViewById<View>(id).apply { ? ? ? ? ? ? ? ? val clickProxy = Proxy.newProxyInstance( ? ? ? ? ? ? ? ? ? ? javaClass.classLoader, arrayOf(View.OnClickListener::class.java) ? ? ? ? ? ? ? ? ) { _, _, _ -> ? ? ? ? ? ? ? ? ? ? it.invoke(this@injectClicks) ? ? ? ? ? ? ? ? } as View.OnClickListener ? ? ? ? ? ? ? ? setOnClickListener(clickProxy) ? ? ? ? ? ? } ? ? ? ? } ? ? } }
三、源碼探索 Jdk 中的動(dòng)態(tài)代理
動(dòng)態(tài)代理的源碼實(shí)現(xiàn)相對(duì)簡(jiǎn)單,我們先進(jìn)入Proxy.newProxyInstance
方法,一探究竟。
Proxy.newProxyInstance public static Object newProxyInstance(ClassLoader loader, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Class<?>[] interfaces, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? InvocationHandler h) ? ? ? ? ? ? throws IllegalArgumentException { ? ? ? ? Objects.requireNonNull(h); ? ? ? ? //先克隆一個(gè)傳遞過來的代理對(duì)象 ? ? ? ? final Class<?>[] intfs = interfaces.clone(); ? ? ? ? //拿到生成的代理類,內(nèi)部維護(hù)了一個(gè)map ? ? ? ? Class<?> cl = getProxyClass0(loader, intfs); ? ? ? ? //獲取代理類構(gòu)造函數(shù) ? ? ? ? final Constructor<?> cons = cl.getConstructor(constructorParams); ? ? ? ? //授予權(quán)限 ? ? ? ? if (!Modifier.isPublic(cl.getModifiers())) { ? ? ? ? ? ? cons.setAccessible(true); ? ? ? ? } ? ? ? ? //反射創(chuàng)建代理類,并傳入用戶自己實(shí)現(xiàn)的中間層接口 ? ? ? ? return cons.newInstance(new Object[]{h}); }
具體如上述描述,然后我們自己生成一個(gè)代理類,來看看內(nèi)部的調(diào)用:
生成代理類
fun writeFileProxy() { ? ? //IApple是我要代理的接口 ? ? val name = IApple::class.java.name + "Proxy()" ? ? val bytes = ProxyGenerator.generateProxyClass(name, arrayOf(IApple::class.java)) ? ? val fos = FileOutputStream("$name.class") ? ? fos.write(bytes) ? ? fos.close() }
示例代碼:
public final class IAppleProxy() extends Proxy implements IApple { ? ? private static Method m3; ? ? public IAppleProxy__/* $FF was: IAppleProxy()*/(InvocationHandler var1) throws ?{ ? ? ? ? super(var1); ? ? } ? ? public final void count() throws ?{ ? ? ? ? try { ? ? ? ? ? ? super.h.invoke(this, m3, (Object[])null); ? ? ? ? } catch (RuntimeException | Error var2) { ? ? ? ? ? ? throw var2; ? ? ? ? } catch (Throwable var3) { ? ? ? ? ? ? throw new UndeclaredThrowableException(var3); ? ? ? ? } ? ? } ? ? static { ? ? ? ? ? ? m3 = Class.forName("com.android.readbook.proxy.IApple").getMethod("count") ? ? } }
生成的代理類如上所示,其實(shí)現(xiàn)了我們的接口,并且代理了相應(yīng)的所有方法,這里做了一部分刪減。
觀察代理類具體方法的話,其具體實(shí)現(xiàn)里,通過InvocationHandler
對(duì)象,也就是我們自己實(shí)現(xiàn)的輔助了,并調(diào)用其invoke方法, 以接口回調(diào)的方式回調(diào)到我們具體的實(shí)現(xiàn)處。整個(gè)過程比較容易理解,也并沒有什么太高深的難度。
四、總結(jié)
關(guān)于jdk中的動(dòng)態(tài)代理,當(dāng)我們調(diào)用 Proxy.newProxyInstance
時(shí),傳入了一個(gè)當(dāng)前類的classLoader以及要代理的接口數(shù)組及實(shí)現(xiàn)了InvocationHandler 接口 的輔助類對(duì)象,其會(huì)在運(yùn)行時(shí)在內(nèi)存中生成一個(gè)代理類,這個(gè)代理類實(shí)現(xiàn)了我們的接口并接收 一個(gè)我們外部傳入的 InvocationHandler 輔助類對(duì)象,并在具體的方法實(shí)現(xiàn)位置通過調(diào)用輔助類的 ??invoke?? 方法,從而實(shí)現(xiàn)我們的接口方法代理。
到此這篇關(guān)于Java 靜態(tài)代理與動(dòng)態(tài)代理解析的文章就介紹到這了,更多相關(guān)Java 靜態(tài)代理與動(dòng)態(tài)代理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring中propagation的7種事務(wù)配置及說明
這篇文章主要介紹了Spring中propagation的7種事務(wù)配置及說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06jvm crash的崩潰日志詳細(xì)分析及注意點(diǎn)
本篇文章主要介紹了jvm crash的崩潰日志詳細(xì)分析及注意點(diǎn)。具有很好的參考價(jià)值,下面跟著小編一起來看下吧2017-04-04Java如何使用Optional與Stream取代if判空邏輯(JDK8以上)
這篇文章主要給大家介紹了關(guān)于Java如何使用Optional與Stream取代if判空邏輯(JDK8以上)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09@JsonFormat?和?@DateTimeFormat?時(shí)間格式化注解(場(chǎng)景示例代碼)
這篇文章主要介紹了@JsonFormat和@DateTimeFormat時(shí)間格式化注解,本文通過場(chǎng)景示例代碼詳解給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-05-05SpringBoot使用Redis實(shí)現(xiàn)分布式緩存
這篇文章主要介紹了SpringBoot redis分布式緩存實(shí)現(xiàn)過程解析,文中通過示例代碼解析的非常詳細(xì),感興趣的同學(xué)可以參考閱讀2023-04-04java通過URLClassLoader類加載器加載外部jar代碼示例
ClassLoader翻譯過來就是類加載器,普通的java開發(fā)者其實(shí)用到的不多,但對(duì)于某些框架開發(fā)者來說卻非常常見,下面這篇文章主要給大家介紹了關(guān)于java通過URLClassLoader類加載器加載外部jar的相關(guān)資料,需要的朋友可以參考下2024-01-01