1. 代理模式
代理模式是一種比較好理解的設(shè)計(jì)模式。簡(jiǎn)單來(lái)說(shuō)就是 我們使用代理對(duì)象來(lái)代替對(duì)真實(shí)對(duì)象(real object)的訪(fǎng)問(wèn),這樣就可以在不修改原目標(biāo)對(duì)象的前提下,提供額外的功能操作,擴(kuò)展目標(biāo)對(duì)象的功能。
代理模式的主要作用是擴(kuò)展目標(biāo)對(duì)象的功能,比如說(shuō)在目標(biāo)對(duì)象的某個(gè)方法執(zhí)行前后你可以增加一些自定義的操作。
舉個(gè)例子:你找了小紅來(lái)幫你問(wèn)話(huà),小紅就可以看作是代理你的代理對(duì)象,代理的行為(方法)是問(wèn)話(huà)。
代理模式有靜態(tài)代理和動(dòng)態(tài)代理兩種實(shí)現(xiàn)方式,我們 先來(lái)看一下靜態(tài)代理模式的實(shí)現(xiàn)。
2. 靜態(tài)代理
靜態(tài)代理中,我們對(duì)目標(biāo)對(duì)象的每個(gè)方法的增強(qiáng)都是手動(dòng)完成的(后面會(huì)具體演示代碼),非常不靈活(比如接口一旦新增加方法,目標(biāo)對(duì)象和代理對(duì)象都要進(jìn)行修改)且麻煩(需要對(duì)每個(gè)目標(biāo)類(lèi)都單獨(dú)寫(xiě)一個(gè)代理類(lèi))。 實(shí)際應(yīng)用場(chǎng)景非常非常少,日常開(kāi)發(fā)幾乎看不到使用靜態(tài)代理的場(chǎng)景。
上面我們是從實(shí)現(xiàn)和應(yīng)用角度來(lái)說(shuō)的靜態(tài)代理,從 JVM 層面來(lái)說(shuō), 靜態(tài)代理在編譯時(shí)就將接口、實(shí)現(xiàn)類(lèi)、代理類(lèi)這些都變成了一個(gè)個(gè)實(shí)際的 class 文件。
靜態(tài)代理實(shí)現(xiàn)步驟:
- 定義一個(gè)接口及其實(shí)現(xiàn)類(lèi);
- 創(chuàng)建一個(gè)代理類(lèi)同樣實(shí)現(xiàn)這個(gè)接口
- 將目標(biāo)對(duì)象注入進(jìn)代理類(lèi),然后在代理類(lèi)的對(duì)應(yīng)方法調(diào)用目標(biāo)類(lèi)中的對(duì)應(yīng)方法。這樣的話(huà),我們就可以通過(guò)代理類(lèi)屏蔽對(duì)目標(biāo)對(duì)象的訪(fǎng)問(wèn),并且可以在目標(biāo)方法執(zhí)行前后做一些自己想做的事情。
下面通過(guò)代碼展示!
1.定義發(fā)送短信的接口
public interface SmsService {
String send(String message);
}
2.實(shí)現(xiàn)發(fā)送短信的接口
public class SmsServiceImpl implements SmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}
3.創(chuàng)建代理類(lèi)并同樣實(shí)現(xiàn)發(fā)送短信的接口
public class SmsProxy implements SmsService {
private final SmsService smsService;
public SmsProxy(SmsService smsService) {
this.smsService = smsService;
}
@Override
public String send(String message) {
//調(diào)用方法之前,我們可以添加自己的操作
System.out.println("before method send()");
smsService.send(message);
//調(diào)用方法之后,我們同樣可以添加自己的操作
System.out.println("after method send()");
return null;
}
}
4.實(shí)際使用
public class Main {
public static void main(String[] args) {
SmsService smsService = new SmsServiceImpl();
SmsProxy smsProxy = new SmsProxy(smsService);
smsProxy.send("java");
}
}
運(yùn)行上述代碼之后,控制臺(tái)打印出:
before method send()
send message:java
after method send()
可以輸出結(jié)果看出,我們已經(jīng)增加了 SmsServiceImpl
的send()
方法。
3. 動(dòng)態(tài)代理
相比于靜態(tài)代理來(lái)說(shuō),動(dòng)態(tài)代理更加靈活。我們不需要針對(duì)每個(gè)目標(biāo)類(lèi)都單獨(dú)創(chuàng)建一個(gè)代理類(lèi),并且也不需要我們必須實(shí)現(xiàn)接口,我們可以直接代理實(shí)現(xiàn)類(lèi)( CGLIB 動(dòng)態(tài)代理機(jī)制)。
從 JVM 角度來(lái)說(shuō),動(dòng)態(tài)代理是在運(yùn)行時(shí)動(dòng)態(tài)生成類(lèi)字節(jié)碼,并加載到 JVM 中的。
說(shuō)到動(dòng)態(tài)代理,Spring AOP、RPC 框架應(yīng)該是兩個(gè)不得不的提的,它們的實(shí)現(xiàn)都依賴(lài)了動(dòng)態(tài)代理。
動(dòng)態(tài)代理在我們?nèi)粘i_(kāi)發(fā)中使用的相對(duì)較小,但是在框架中的幾乎是必用的一門(mén)技術(shù)。學(xué)會(huì)了動(dòng)態(tài)代理之后,對(duì)于我們理解和學(xué)習(xí)各種框架的原理也非常有幫助。
就 Java 來(lái)說(shuō),動(dòng)態(tài)代理的實(shí)現(xiàn)方式有很多種,比如 JDK 動(dòng)態(tài)代理、CGLIB 動(dòng)態(tài)代理等等。
guide-rpc-framework 使用的是 JDK 動(dòng)態(tài)代理,我們先來(lái)看看 JDK 動(dòng)態(tài)代理的使用。
另外,雖然 guide-rpc-framework 沒(méi)有用到 CGLIB 動(dòng)態(tài)代理 ,我們這里還是簡(jiǎn)單介紹一下其使用以及和JDK 動(dòng)態(tài)代理的對(duì)比。
3.1. JDK 動(dòng)態(tài)代理機(jī)制
3.1.1. 介紹
在 Java 動(dòng)態(tài)代理機(jī)制中 InvocationHandler
接口和 Proxy
類(lèi)是核心。
Proxy
類(lèi)中使用頻率最高的方法是:newProxyInstance()
,這個(gè)方法主要用來(lái)生成一個(gè)代理對(duì)象。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
......
}
這個(gè)方法一共有 3 個(gè)參數(shù):
- loader :類(lèi)加載器,用于加載代理對(duì)象。
- interfaces : 被代理類(lèi)實(shí)現(xiàn)的一些接口;
-
h : 實(shí)現(xiàn)了
InvocationHandler
接口的對(duì)象;
要實(shí)現(xiàn)動(dòng)態(tài)代理的話(huà),還必須需要實(shí)現(xiàn)InvocationHandler
來(lái)自定義處理邏輯。 當(dāng)我們的動(dòng)態(tài)代理對(duì)象調(diào)用一個(gè)方法時(shí)候,這個(gè)方法的調(diào)用就會(huì)被轉(zhuǎn)發(fā)到實(shí)現(xiàn)InvocationHandler
接口類(lèi)的 invoke
方法來(lái)調(diào)用。
public interface InvocationHandler {
/**
* 當(dāng)你使用代理對(duì)象調(diào)用方法的時(shí)候?qū)嶋H會(huì)調(diào)用到這個(gè)方法
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
invoke()
方法有下面三個(gè)參數(shù):
- proxy :動(dòng)態(tài)生成的代理類(lèi)
- method : 與代理類(lèi)對(duì)象調(diào)用的方法相對(duì)應(yīng)
- args : 當(dāng)前 method 方法的參數(shù)
也就是說(shuō):你通過(guò)Proxy
類(lèi)的 newProxyInstance()
創(chuàng)建的代理對(duì)象在調(diào)用方法的時(shí)候,實(shí)際會(huì)調(diào)用到實(shí)現(xiàn)InvocationHandler
接口的類(lèi)的 invoke()
方法。 你可以在 invoke()
方法中自定義處理邏輯,比如在方法執(zhí)行前后做什么事情。
3.1.2. JDK 動(dòng)態(tài)代理類(lèi)使用步驟
- 定義一個(gè)接口及其實(shí)現(xiàn)類(lèi);
- 自定義
InvocationHandler
并重寫(xiě)invoke
方法,在invoke
方法中我們會(huì)調(diào)用原生方法(被代理類(lèi)的方法)并自定義一些處理邏輯; - 通過(guò)
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
方法創(chuàng)建代理對(duì)象;
3.1.3. 代碼示例
這樣說(shuō)可能會(huì)有點(diǎn)空洞和難以理解,我上個(gè)例子,大家感受一下吧!
1.定義發(fā)送短信的接口
public interface SmsService {
String send(String message);
}
2.實(shí)現(xiàn)發(fā)送短信的接口
public class SmsServiceImpl implements SmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}
3.定義一個(gè) JDK 動(dòng)態(tài)代理類(lèi)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author shuang.kou
* @createTime 2020年05月11日 11:23:00
*/
public class DebugInvocationHandler implements InvocationHandler {
/**
* 代理類(lèi)中的真實(shí)對(duì)象
*/
private final Object target;
public DebugInvocationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
//調(diào)用方法之前,我們可以添加自己的操作
System.out.println("before method " + method.getName());
Object result = method.invoke(target, args);
//調(diào)用方法之后,我們同樣可以添加自己的操作
System.out.println("after method " + method.getName());
return result;
}
}
invoke()
方法: 當(dāng)我們的動(dòng)態(tài)代理對(duì)象調(diào)用原生方法的時(shí)候,最終實(shí)際上調(diào)用到的是 invoke()
方法,然后 invoke()
方法代替我們?nèi)フ{(diào)用了被代理對(duì)象的原生方法。
4.獲取代理對(duì)象的工廠(chǎng)類(lèi)
public class JdkProxyFactory {
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 目標(biāo)類(lèi)的類(lèi)加載
target.getClass().getInterfaces(), // 代理需要實(shí)現(xiàn)的接口,可指定多個(gè)
new DebugInvocationHandler(target) // 代理對(duì)象對(duì)應(yīng)的自定義 InvocationHandler
);
}
}
getProxy()
:主要通過(guò)Proxy.newProxyInstance()
方法獲取某個(gè)類(lèi)的代理對(duì)象
5.實(shí)際使用
SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
smsService.send("java");
運(yùn)行上述代碼之后,控制臺(tái)打印出:
before method send
send message:java
after method send
3.2. CGLIB 動(dòng)態(tài)代理機(jī)制
3.2.1. 介紹
JDK 動(dòng)態(tài)代理有一個(gè)最致命的問(wèn)題是其只能代理實(shí)現(xiàn)了接口的類(lèi)。
為了解決這個(gè)問(wèn)題,我們可以用 CGLIB 動(dòng)態(tài)代理機(jī)制來(lái)避免。
CGLIB(Code Generation Library)是一個(gè)基于ASM的字節(jié)碼生成庫(kù),它允許我們?cè)谶\(yùn)行時(shí)對(duì)字節(jié)碼進(jìn)行修改和動(dòng)態(tài)生成。CGLIB 通過(guò)繼承方式實(shí)現(xiàn)代理。很多知名的開(kāi)源框架都使用到了CGLIB, 例如 Spring 中的 AOP 模塊中:如果目標(biāo)對(duì)象實(shí)現(xiàn)了接口,則默認(rèn)采用 JDK 動(dòng)態(tài)代理,否則采用 CGLIB 動(dòng)態(tài)代理。
在 CGLIB 動(dòng)態(tài)代理機(jī)制中 MethodInterceptor
接口和 Enhancer
類(lèi)是核心。
你需要自定義 MethodInterceptor
并重寫(xiě) intercept
方法,intercept
用于攔截增強(qiáng)被代理類(lèi)的方法。
public interface MethodInterceptor
extends Callback{
// 攔截被代理類(lèi)中的方法
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
MethodProxy proxy) throws Throwable;
}
- obj :被代理的對(duì)象(需要增強(qiáng)的對(duì)象)
- method :被攔截的方法(需要增強(qiáng)的方法)
- args :方法入?yún)?/li>
- methodProxy :用于調(diào)用原始方法
你可以通過(guò) Enhancer
類(lèi)來(lái)動(dòng)態(tài)獲取被代理類(lèi),當(dāng)代理類(lèi)調(diào)用方法的時(shí)候,實(shí)際調(diào)用的是 MethodInterceptor
中的 intercept
方法。
3.2.2. CGLIB 動(dòng)態(tài)代理類(lèi)使用步驟
- 定義一個(gè)類(lèi);
- 自定義
MethodInterceptor
并重寫(xiě)intercept
方法,intercept
用于攔截增強(qiáng)被代理類(lèi)的方法,和 JDK 動(dòng)態(tài)代理中的invoke
方法類(lèi)似; - 通過(guò)
Enhancer
類(lèi)的create()
創(chuàng)建代理類(lèi);
3.2.3. 代碼示例
不同于 JDK 動(dòng)態(tài)代理不需要額外的依賴(lài)。CGLIB(Code Generation Library) 實(shí)際是屬于一個(gè)開(kāi)源項(xiàng)目,如果你要使用它的話(huà),需要手動(dòng)添加相關(guān)依賴(lài)。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
1.實(shí)現(xiàn)一個(gè)使用阿里云發(fā)送短信的類(lèi)
package github.javaguide.dynamicProxy.cglibDynamicProxy;
public class AliSmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}
2.自定義 MethodInterceptor
(方法攔截器)
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 自定義MethodInterceptor
*/
public class DebugMethodInterceptor implements MethodInterceptor {
/**
* @param o 被代理的對(duì)象(需要增強(qiáng)的對(duì)象)
* @param method 被攔截的方法(需要增強(qiáng)的方法)
* @param args 方法入?yún)? * @param methodProxy 用于調(diào)用原始方法
*/
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//調(diào)用方法之前,我們可以添加自己的操作
System.out.println("before method " + method.getName());
Object object = methodProxy.invokeSuper(o, args);
//調(diào)用方法之后,我們同樣可以添加自己的操作
System.out.println("after method " + method.getName());
return object;
}
}
3.獲取代理類(lèi)
import net.sf.cglib.proxy.Enhancer;
public class CglibProxyFactory {
public static Object getProxy(Class<?> clazz) {
// 創(chuàng)建動(dòng)態(tài)代理增強(qiáng)類(lèi)
Enhancer enhancer = new Enhancer();
// 設(shè)置類(lèi)加載器
enhancer.setClassLoader(clazz.getClassLoader());
// 設(shè)置被代理類(lèi)
enhancer.setSuperclass(clazz);
// 設(shè)置方法攔截器
enhancer.setCallback(new DebugMethodInterceptor());
// 創(chuàng)建代理類(lèi)
return enhancer.create();
}
}
4.實(shí)際使用
AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
aliSmsService.send("java");
運(yùn)行上述代碼之后,控制臺(tái)打印出:
before method send
send message:java
after method send
3.3. JDK 動(dòng)態(tài)代理和 CGLIB 動(dòng)態(tài)代理對(duì)比
- JDK 動(dòng)態(tài)代理只能只能代理實(shí)現(xiàn)了接口的類(lèi)或者直接代理接口,而 CGLIB 可以代理未實(shí)現(xiàn)任何接口的類(lèi)。 另外, CGLIB 動(dòng)態(tài)代理是通過(guò)生成一個(gè)被代理類(lèi)的子類(lèi)來(lái)攔截被代理類(lèi)的方法調(diào)用,因此不能代理聲明為 final 類(lèi)型的類(lèi)和方法。
- 就二者的效率來(lái)說(shuō),大部分情況都是 JDK 動(dòng)態(tài)代理更優(yōu)秀,隨著 JDK 版本的升級(jí),這個(gè)優(yōu)勢(shì)更加明顯。
4. 靜態(tài)代理和動(dòng)態(tài)代理的對(duì)比
- 靈活性 :動(dòng)態(tài)代理更加靈活,不需要必須實(shí)現(xiàn)接口,可以直接代理實(shí)現(xiàn)類(lèi),并且可以不需要針對(duì)每個(gè)目標(biāo)類(lèi)都創(chuàng)建一個(gè)代理類(lèi)。另外,靜態(tài)代理中,接口一旦新增加方法,目標(biāo)對(duì)象和代理對(duì)象都要進(jìn)行修改,這是非常麻煩的!
- JVM 層面 :靜態(tài)代理在編譯時(shí)就將接口、實(shí)現(xiàn)類(lèi)、代理類(lèi)這些都變成了一個(gè)個(gè)實(shí)際的 class 文件。而動(dòng)態(tài)代理是在運(yùn)行時(shí)動(dòng)態(tài)生成類(lèi)字節(jié)碼,并加載到 JVM 中的。
5. 總結(jié)
這篇文章中主要介紹了代理模式的兩種實(shí)現(xiàn):靜態(tài)代理以及動(dòng)態(tài)代理。涵蓋了靜態(tài)代理和動(dòng)態(tài)代理實(shí)戰(zhàn)、靜態(tài)代理和動(dòng)態(tài)代理的區(qū)別、JDK 動(dòng)態(tài)代理和 Cglib 動(dòng)態(tài)代理區(qū)別等內(nèi)容。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-638080.html
文中涉及到的所有源碼,你可以在這里找到:https://github.com/Snailclimb/guide-rpc-framework-learning/tree/master/src/main/java/github/javaguide/proxy 。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-638080.html
到了這里,關(guān)于代理模式:靜態(tài)代理+JDK/CGLIB 動(dòng)態(tài)代理的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!