你有被代理過嗎?講講開源框架都在用的代理模式
大家好,我是老三。
這節我們來看一個非常重要的設計模式——代理模式,儘管我們工作中可能很少用到,但它是很多框架重要功能的基石,肘,我們開始吧。
引言
節假日的地鐵站,你是否見過有人掏出電腦,原地輸出,原來是群里被瘋狂@……

用戶提問題,客服@我們解決,可能很多開發同學都經歷過這樣的場景。
用戶找到客服,提出問題,客服又找到開發同學,讓開發同學去解決問題,開發同學解決完,最後回饋給客服,客服再回饋到用戶。
站在用戶的視角,感覺就是客服解決了這個問題,這其實就是一種代理。

我們以這個例子,來看看Java怎麼實現代理模式的吧。
Java的三種代理模式實現
代理模式的定義:
Provide a surrogate or placeholder for another object to control access to it.(為其他對象提供一種代理以控制對這個對象的訪問。)
簡單說,就是設置一個中間代理來控制訪問原目標對象,達到增強原對象的功能和簡化訪問方式的目的。

Java實現代理模式分為兩類三種,兩類是靜態代理和動態代理,動態代理又可以分為JDK動態代理和CGLIB動態代理。

靜態代理
靜態代理比較簡單,代理類需要實現和目標介面類一樣的介面。

-
介面類:ISolver
public interface ISolver { void solve(); } -
目標類:Solver
public class Solver implements ISolver { @Override public void solve() { System.out.println("瘋狂掉頭髮解決問題……"); } } -
代理類:SolverProxy,代理類也要實現介面,並且還要維護一個目標對象。
public class SolverProxy implements ISolver { //目標對象 private ISolver target; public SolverProxy(ISolver target) { this.target = target; } @Override public void solve() { System.out.println("請問有什麼能幫到您?"); target.solve(); System.out.println("問題已經解決啦!"); } } -
客戶端;Client
public class Client { public static void main(String[] args) { //目標對象:程式設計師 ISolver developer = new Solver(); //代理:客服小姐姐 SolverProxy csProxy = new SolverProxy(developer); //目標方法:解決問題 csProxy.solve(); } } -
運行結果
請問有什麼能幫到您? 瘋狂掉頭髮解決問題…… 問題已經解決啦!
我們看到,通過靜態代理,可以在不修改目標對象的前提下擴展目標對象的功能。
但是,它也有一些問題:
- 冗餘:由於代理對象要實現與目標對象一致的介面,會產生過多的代理類。
- 維護性不佳:一旦介面增加方法,目標對象與代理對象都要進行修改。
JDK動態代理
JDK動態代理利用了JDK反射機制,動態地在記憶體中構建代理對象,從而實現對目標對象的代理功能。
它主要用到了兩個反射類的API:
- java.lang.reflect Proxy| static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h):返回一個指定介面的代理類實例,該介面可以將方法調用指派到指定的調用處理程式
- java.lang.reflect InvocationHandler|Object invoke(Object proxy, Method method, Object[] args) :在代理實例上調用目標方法,並返回結果。
我們來看看使用JDK動態代理之後的客服代理場景。

-
介面類:ISolver
public interface ISolver { void solve(); } -
目標類:Solver,目標類需要實現介面類。
public class Solver implements ISolver { @Override public void solve() { System.out.println("瘋狂掉頭髮解決問題……"); } } -
動態代理工廠:ProxyFactory,這裡的動態代理工廠,不需要實現介面,直接採用反射的方式生成一個目標對象的代理對象實例。
ps:這裡用了一個匿名內部類的方法,還有一種方法,動態代理類實現InvocationHandler介面,大體上類似,就不再給出例子了。
public class ProxyFactory { // 維護一個目標對象 private Object target; public ProxyFactory(Object target) { this.target = target; } // 為目標對象生成代理對象 public Object getProxyInstance() { return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("請問有什麼可以幫到您?"); // 調用目標對象方法 Object returnValue = method.invoke(target, args); System.out.println("問題已經解決啦!"); return null; } }); } } -
客戶端:Client
客戶端生成一個代理對象實例,通過代理對象調用目標對象方法的時候,就會進入invoke()方法,最後是通過反射的方式調用目標對象的方法。
public class Client { public static void main(String[] args) { //目標對象:程式設計師 ISolver developer = new Solver(); //代理:客服小姐姐 ISolver csProxy = (ISolver) new ProxyFactory(developer).getProxyInstance(); //目標方法:解決問題 csProxy.solve(); } } -
運行結果:
請問有什麼可以幫到您? 瘋狂掉頭髮解決問題…… 問題已經解決啦!
我們簡單總結一下靜態代理和動態代理的主要區別:

- 靜態代理在編譯時就已經實現,編譯完成後代理類是一個實際的class文件
- 動態代理是在運行時動態生成的,即編譯完成後沒有實際的class文件,而是在運行時動態生成類位元組碼,並載入到JVM中
我們也觀察到,JDK動態代理,目標對象必須得實現介面,也就是說它是面向介面的,假如我們不想要介面怎麼辦呢?
Cglib動態代理
CGLIB(Code Generation Library)是一個基於ASM的位元組碼生成庫,它允許我們在運行時對位元組碼進行修改和動態生成,它是通過繼承來實現的。
我們來看看使用Cglib之後,我們的客服代理是什麼樣的:

-
引入依賴:Cglib是第三方類庫,需要引入依賴
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.5</version> </dependency> -
目標類:Solver,這裡目標類不用再實現介面。
public class Solver { public void solve() { System.out.println("瘋狂掉頭髮解決問題……"); } } -
動態代理工廠:
public class ProxyFactory implements MethodInterceptor { //維護一個目標對象 private Object target; public ProxyFactory(Object target) { this.target = target; } //為目標對象生成代理對象 public Object getProxyInstance() { //工具類 Enhancer en = new Enhancer(); //設置父類 en.setSuperclass(target.getClass()); //設置回調函數 en.setCallback(this); //創建子類對象代理 return en.create(); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("請問有什麼可以幫到您?"); // 執行目標對象的方法 Object returnValue = method.invoke(target, args); System.out.println("問題已經解決啦!"); return null; } } -
客戶端:Client
public class Client { public static void main(String[] args) { //目標對象:程式設計師 Solver developer = new Solver(); //代理:客服小姐姐 Solver csProxy = (Solver) new ProxyFactory(developer).getProxyInstance(); //目標方法:解決問題 csProxy.solve(); } } -
運行結果
請問有什麼可以幫到您? 瘋狂掉頭髮解決問題…… 問題已經解決啦!
我們可以看到Cglib動態代理和JDK動態代理最大的區別就是:
- 使用JDK動態代理的對象必須實現一個或多個介面
- 使用Cglib動態代理的對象則無需實現介面,達到代理類無侵入。
我們還需要注意:
- CGLib不能對聲明為final的方法進行代理,因為是通過繼承父類的方式實現,如果父類是final的,那麼就無法繼承父類。
擴展:動態代理的應用
標題里說了,開源框架都在用的代理模式,那麼主流的開源框架哪些地方用到了代理模式呢?——確切說是動態代理呢?
比如:
- Spring AOP可以將一些非核心業務流程抽象成切面,幫助我們處理非主線的流程,它是怎麼實現的呢?
- 你平時用慣了的MyBatis,寫了那麼多Mapper介面,為什麼它不用實現類就能執行SQL呢?
後面兩期是大家期待的面渣逆襲系列,將會揭曉這兩個問題的答案。
簡單的事情重複做,重複的事情認真做,認真的事情有創造性地做!
我是三分惡,一個能文能武的普通開發。點贊、關注不迷路,咱們下期見!
參考:
[1].《設計模式之禪》
[2].Java三種代理模式:靜態代理、動態代理和cglib代理


