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