更新時(shí)間:2022-05-13 09:31:35 來(lái)源:動(dòng)力節(jié)點(diǎn) 瀏覽941次
這篇文章是關(guān)于Java 的動(dòng)態(tài)代理——它是我們?cè)谡Z(yǔ)言中可用的主要代理機(jī)制之一。
簡(jiǎn)單地說(shuō),代理是通過(guò)自己的設(shè)施(通常是真實(shí)方法)傳遞函數(shù)調(diào)用的前端或包裝器——可能會(huì)添加一些功能。
動(dòng)態(tài)代理允許使用一種方法的單個(gè)類為具有任意數(shù)量方法的任意類的多個(gè)方法調(diào)用提供服務(wù)。動(dòng)態(tài)代理可以被認(rèn)為是一種外觀,但它可以偽裝成任何接口的實(shí)現(xiàn)。在幕后,它將所有方法調(diào)用路由到單個(gè)處理程序——invoke ()方法。
雖然它不是用于日常編程任務(wù)的工具,但動(dòng)態(tài)代理對(duì)于框架編寫(xiě)者來(lái)說(shuō)非常有用。它也可以用于那些直到運(yùn)行時(shí)才知道具體類實(shí)現(xiàn)的情況。
此功能內(nèi)置在標(biāo)準(zhǔn) JDK 中,因此不需要額外的依賴項(xiàng)。
讓我們構(gòu)建一個(gè)簡(jiǎn)單的代理,它實(shí)際上不做任何事情,除了打印請(qǐng)求調(diào)用的方法并返回一個(gè)硬編碼的數(shù)字。
首先,我們需要?jiǎng)?chuàng)建java.lang.reflect.InvocationHandler的子類型:
public class DynamicInvocationHandler implements InvocationHandler {
private static Logger LOGGER = LoggerFactory.getLogger(
DynamicInvocationHandler.class);
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
LOGGER.info("Invoked method: {}", method.getName());
return 42;
}
}
在這里,我們定義了一個(gè)簡(jiǎn)單的代理,它記錄調(diào)用了哪個(gè)方法并返回 42。
我們剛剛定義的調(diào)用處理程序所服務(wù)的代理實(shí)例是通過(guò)對(duì)java.lang.reflect.Proxy類的工廠方法模式調(diào)用創(chuàng)建的:
Map proxyInstance = (Map) Proxy.newProxyInstance(
DynamicProxyTest.class.getClassLoader(),
new Class[] { Map.class },
new DynamicInvocationHandler());
一旦我們有了代理實(shí)例,我們就可以正常調(diào)用它的接口方法:
proxyInstance.put("hello", "world");
正如預(yù)期的那樣,在日志文件中會(huì)打印出有關(guān)調(diào)用put()方法的消息。
由于InvocationHandler是一個(gè)函數(shù)式接口,因此可以使用 lambda 表達(dá)式內(nèi)聯(lián)定義處理程序:
Map proxyInstance = (Map) Proxy.newProxyInstance(
DynamicProxyTest.class.getClassLoader(),
new Class[] { Map.class },
(proxy, method, methodArgs) -> {
if (method.getName().equals("get")) {
return 42;
} else {
throw new UnsupportedOperationException(
"Unsupported method: " + method.getName());
}
});
在這里,我們定義了一個(gè)處理程序,它為所有 get 操作返回 42,并為其他所有操作拋出UnsupportedOperationException。
它以完全相同的方式調(diào)用:
(int) proxyInstance.get("hello"); // 42
proxyInstance.put("hello", "world"); // exception
讓我們來(lái)看看動(dòng)態(tài)代理的一種潛在的真實(shí)世界場(chǎng)景。
假設(shè)我們想記錄我們的函數(shù)執(zhí)行需要多長(zhǎng)時(shí)間。為此,我們首先定義一個(gè)能夠包裝“真實(shí)”對(duì)象、跟蹤時(shí)間信息和反射調(diào)用的處理程序:
public class TimingDynamicInvocationHandler implements InvocationHandler {
private static Logger LOGGER = LoggerFactory.getLogger(
TimingDynamicInvocationHandler.class);
private final Map<String, Method> methods = new HashMap<>();
private Object target;
public TimingDynamicInvocationHandler(Object target) {
this.target = target;
for(Method method: target.getClass().getDeclaredMethods()) {
this.methods.put(method.getName(), method);
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
long start = System.nanoTime();
Object result = methods.get(method.getName()).invoke(target, args);
long elapsed = System.nanoTime() - start;
LOGGER.info("Executing {} finished in {} ns", method.getName(),
elapsed);
return result;
}
}
隨后,此代理可用于各種對(duì)象類型:
Map mapProxyInstance = (Map) Proxy.newProxyInstance(
DynamicProxyTest.class.getClassLoader(), new Class[] { Map.class },
new TimingDynamicInvocationHandler(new HashMap<>()));
mapProxyInstance.put("hello", "world");
CharSequence csProxyInstance = (CharSequence) Proxy.newProxyInstance(
DynamicProxyTest.class.getClassLoader(),
new Class[] { CharSequence.class },
new TimingDynamicInvocationHandler("Hello World"));
csProxyInstance.length()
在這里,我們代理了一個(gè)地圖和一個(gè)字符序列(字符串)。
代理方法的調(diào)用將委托給被包裝的對(duì)象并產(chǎn)生日志語(yǔ)句:
Executing put finished in 19153 ns
Executing get finished in 8891 ns
Executing charAt finished in 11152 ns
Executing length finished in 10087 ns
以上就是關(guān)于“什么是動(dòng)態(tài)代理”的介紹,大家如果對(duì)此比較感興趣,想了解更多相關(guān)知識(shí),可以關(guān)注一下動(dòng)力節(jié)點(diǎn)的Java在線學(xué)習(xí),里面的課程內(nèi)容細(xì)致全面,很適合沒(méi)有基礎(chǔ)的小伙伴學(xué)習(xí),希望對(duì)大家能夠有所幫助哦。
Java實(shí)驗(yàn)班
0基礎(chǔ) 0學(xué)費(fèi) 15天面授
Java就業(yè)班
有基礎(chǔ) 直達(dá)就業(yè)
Java夜校直播班
業(yè)余時(shí)間 高薪轉(zhuǎn)行
Java在職加薪班
工作1~3年,加薪神器
Java架構(gòu)師班
工作3~5年,晉升架構(gòu)
提交申請(qǐng)后,顧問(wèn)老師會(huì)電話與您溝通安排學(xué)習(xí)