終于有人把 java代理 講清楚了,萬字詳解!
掃描二維碼
隨時隨地手機看文章
什么是代理
代理模式是常用的java設(shè)計模式,他的特征是代理類與委托類有同樣的接口,代理類主要負責為委托類預(yù)處理消息、過濾消息、把消息轉(zhuǎn)發(fā)給委托類,以及事后處理消息等。代理類與委托類之間通常會存在關(guān)聯(lián)關(guān)系,一個代理類的對象與一個委托類的對象關(guān)聯(lián),代理類的對象本身并不真正實現(xiàn)服務(wù),而是通過調(diào)用委托類的對象的相關(guān)方法,來提供特定的服務(wù)。
代理其實不僅僅是在軟件開發(fā)領(lǐng)域,在我們的日常生活中也是時??梢?。比如某p2p老板突然攜款帶著小姨子跑路了,可憐了下面一堆的程序員背負一身房貸,上有老下有小,程序員只能被迫去申請勞動仲裁,勞動局就會為其指派一位代理律師全權(quán)負責程序員的仲裁事宜(PS:p2p跑路仲裁拿回工資的可能性非常低,沒讓你把工資退回就算好的了)。那這里面就是使用了代理模式,因為在勞動仲裁這個活動中,代理律師會全權(quán)代理程序員。比如:房東要將房子出售,于是到房地產(chǎn)中介公司找一個中介(代理),由他來幫房東完成銷售房屋,簽訂合同、網(wǎng)簽、貸款過戶等等事宜。
代理模式
這是常見代理模式常見的 UML 示意圖。 需要注意的有下面幾點:
-
用戶只關(guān)心接口功能,而不在乎誰提供了功能。上圖中接口是 Subject
。 -
接口真正實現(xiàn)者是上圖的 RealSubject
,但是它不與用戶直接接觸,而是通過代理。 -
代理就是上圖中的 Proxy
,由于它實現(xiàn)了Subject
接口,所以它能夠直接與用戶接觸。 -
用戶調(diào)用 Proxy
的時候,Proxy
內(nèi)部調(diào)用了RealSubject
。所以,Proxy
是中介者,它可以增強RealSubject
操作。
-
代理又可以分為靜態(tài)代理和動態(tài)代理兩種。我們先來看下靜態(tài)代理。
靜態(tài)代理
電影是電影公司委托給影院進行播放的,但是影院可以在播放電影的時候,產(chǎn)生一些自己的經(jīng)濟收益,比如提供按摩椅,娃娃機(這個每次去電影院都會嘗試下,基本上是夾不起來,有木有大神可以傳授下訣竅),賣爆米花、飲料(貴的要死,反正吃不起)等。我們平常去電影院看電影的時候,在電影開始的階段是不是經(jīng)常會放廣告呢?然后在影片開始結(jié)束時播放一些廣告。 下面我們通過代碼來模擬下電影院這一系列的賺錢操作。 首先得有一個接口,通用的接口是代理模式實現(xiàn)的基礎(chǔ)。這個接口我們命名為 Movie
,代表電影播放的能力。
package com.workit.demo.proxy;
public interface Movie {
void play();
}
-
接下來我們要創(chuàng)建一個真正的實現(xiàn)這個 Movie
接口的類,和一個實現(xiàn)該接口的代理類。 真正的類《美國隊長》
電影:
package com.workit.demo.proxy;
public class CaptainAmericaMovie implements Movie {
@Override
public void play() {
System.out.println("普通影廳正在播放的電影是《美國隊長》");
}
}
代理類:
package com.workit.demo.proxy;
public class MovieStaticProxy implements Movie {
Movie movie;
public MovieStaticProxy(Movie movie) {
this.movie = movie;
}
@Override
public void play() {
playStart();
movie.play();
playEnd();
}
public void playStart() {
System.out.println("電影開始前正在播放廣告");
}
public void playEnd() {
System.out.println("電影結(jié)束了,接續(xù)播放廣告");
}
}
測試類:
package com.workit.demo.proxy;
package com.workit.demo.proxy;
public class StaticProxyTest {
public static void main(String[] args) {
Movie captainAmericaMovie = new CaptainAmericaMovie();
Movie movieStaticProxy = new MovieStaticProxy(captainAmericaMovie);
movieStaticProxy.play();
}
}
運行結(jié)果:
電影開始前正在播放廣告
正在播放的電影是《美國隊長》
電影結(jié)束了,接續(xù)播放廣告
現(xiàn)在可以看到,代理模式可以在不修改被代理對象的基礎(chǔ)上,通過擴展代理類,進行一些功能的附加與增強。值得注意的是,代理類和被代理類應(yīng)該共同實現(xiàn)一個接口,或者是共同繼承某個類。這個就是是靜態(tài)代理的內(nèi)容,為什么叫做靜態(tài)呢?因為它的類型是事先預(yù)定好的,比如上面代碼中的 MovieStaticProxy
這個類。
優(yōu)點
-
代理模式在客戶端與目標對象之間起到一個中介作用和保護目標對象的作用 -
代理對象可以擴展目標對象的功能 -
代理模式能將客戶端與目標對象分離,在一定程度上降低了系統(tǒng)的耦合度。
缺點
-
代理對象需要與目標對象實現(xiàn)一樣的接口,所以會有很多代理類,類太多.同時,一旦接口增加方法,目標對象與代理對象都要維護。
jdk動態(tài)代理
與靜態(tài)代理類對照的是動態(tài)代理類,動態(tài)代理類的字節(jié)碼在程序運行時由Java反射機制動態(tài)生成,無需程序員手工編寫它的源代碼。動態(tài)代理類不僅簡化了編程工作,而且提高了軟件系統(tǒng)的可擴展性,因為Java 反射機制可以生成任意類型的動態(tài)代理類。java.lang.reflect
包中的Proxy類和InvocationHandler
接口提供了生成動態(tài)代理類的能力。
-
接著上面的例子,剛看完《美國隊長》不過癮,還想繼續(xù)去看一場《鋼鐵俠》。一直在普通影廳看電影覺得沒啥意思,那就趕緊去VIP影廳( 至今不知道長啥樣子
)體驗一把。既然 實體店沒體驗過那就用代碼來體驗一次吧。創(chuàng)建一個VIPMovie電影接口
package com.workit.demo.proxy;
public interface VIPMovie {
void vipPlay();
}
緊接著創(chuàng)建一個VIP影廳的播放實現(xiàn)類
package com.workit.demo.proxy;
public class IronManVIPMovie implements VIPMovie {
@Override
public void vipPlay() {
System.out.println("VI影廳正在播放的電影是《鋼鐵俠》");
}
}
如果按照靜態(tài)代理我們是不是又要創(chuàng)建一個VIP影廳播放的代理實現(xiàn)類,這種方式我們就不演示了。下面我們來看看通過動態(tài)代理怎么來實現(xiàn)吧。
package com.workit.demo.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler {
private Object object;
public MyInvocationHandler(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
playStart();
Object invoke = method.invoke(object, args);
playEnd();
return invoke;
}
public void playStart() {
System.out.println("電影開始前正在播放廣告");
}
public void playEnd() {
System.out.println("電影結(jié)束了,接續(xù)播放廣告");
}
}
MyInvocationHandler
實現(xiàn)了 InvocationHandler
這個類,這個類是什么意思呢?大家不要慌張,下面我會解釋。然后,我們就可以在VIP影廳看電影了。
package com.workit.demo.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class DynamicProxyTest {
public static void main(String[] args) {
IronManVIPMovie ironManVIPMovie = new IronManVIPMovie();
InvocationHandler invocationHandler = new MyInvocationHandler(ironManVIPMovie);
VIPMovie dynamicProxy = (VIPMovie) Proxy.newProxyInstance(IronManVIPMovie.class.getClassLoader(),
IronManVIPMovie.class.getInterfaces(), invocationHandler);
dynamicProxy.vipPlay();
}
}
輸出結(jié)果:
電影開始前正在播放廣告
VI影廳正在播放的電影是《鋼鐵俠》
電影結(jié)束了,接續(xù)播放廣告
看到?jīng)]有,我們并沒有像靜態(tài)代理那樣為 VIPMovie
接口實現(xiàn)一個代理類,但最終它仍然實現(xiàn)了相同的功能,這其中的差別,就是之前討論的動態(tài)代理所謂“動態(tài)”的原因。 我們順帶把《美國隊長》也用動態(tài)代理實現(xiàn)下吧。
package com.workit.demo.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class DynamicProxyTest {
public static void main(String[] args) {
// VIP 影廳《鋼鐵俠》
IronManVIPMovie ironManVIPMovie = new IronManVIPMovie();
InvocationHandler invocationHandler = new MyInvocationHandler(ironManVIPMovie);
VIPMovie dynamicProxy = (VIPMovie) Proxy.newProxyInstance(IronManVIPMovie.class.getClassLoader(),
IronManVIPMovie.class.getInterfaces(), invocationHandler);
dynamicProxy.vipPlay();
// 普通影廳《美國隊長》
CaptainAmericaMovie captainAmericaMovie = new CaptainAmericaMovie();
InvocationHandler invocationHandler1 = new MyInvocationHandler(captainAmericaMovie);
Movie dynamicProxy1 = (Movie) Proxy.newProxyInstance(CaptainAmericaMovie.class.getClassLoader(),
CaptainAmericaMovie.class.getInterfaces(), invocationHandler1);
dynamicProxy1.play();
}
}
輸出結(jié)果:
電影開始前正在播放廣告
VI影廳正在播放的電影是《鋼鐵俠》
電影結(jié)束了,接續(xù)播放廣告
電影開始前正在播放廣告
正在播放的電影是《美國隊長》
電影結(jié)束了,接續(xù)播放廣告
我們通過 Proxy.newProxyInstance()
方法,卻產(chǎn)生了 Movie
和 VIPMovie
兩種接口的實現(xiàn)類代理,這就是動態(tài)代理的魔力。
JDK動態(tài)代理到底是怎么實現(xiàn)的呢
動態(tài)代碼涉及了一個非常重要的類 Proxy
。正是通過 Proxy
的靜態(tài)方法 newProxyInstance
才會動態(tài)創(chuàng)建代理。具體怎么去創(chuàng)建代理類就不分析了,感興趣的可以去看下源碼。我們直接看下生成的代理類。 如何查看生成的代理類? 在生成代理類之前加上以下代碼(我用的jdk1.8):
//新版本 jdk產(chǎn)生代理類
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
如果上述代碼加上不生效可以考慮加下下面的代碼:
// 老版本jdk
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 該設(shè)置用于輸出cglib動態(tài)代理產(chǎn)生的類
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\\class");
代碼如下:
public static void main(String[] args) {
//新版本 jdk產(chǎn)生代理類
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
// VIP 影廳《鋼鐵俠》
IronManVIPMovie ironManVIPMovie = new IronManVIPMovie();
InvocationHandler invocationHandler = new MyInvocationHandler(ironManVIPMovie);
VIPMovie dynamicProxy = (VIPMovie) Proxy.newProxyInstance(IronManVIPMovie.class.getClassLoader(),
IronManVIPMovie.class.getInterfaces(), invocationHandler);
dynamicProxy.vipPlay();
// 普通影廳《美國隊長》
CaptainAmericaMovie captainAmericaMovie = new CaptainAmericaMovie();
InvocationHandler invocationHandler1 = new MyInvocationHandler(captainAmericaMovie);
Movie dynamicProxy1 = (Movie) Proxy.newProxyInstance(CaptainAmericaMovie.class.getClassLoader(),
CaptainAmericaMovie.class.getInterfaces(), invocationHandler1);
dynamicProxy1.play();
System.out.println("VIP 影廳《鋼鐵俠》代理類:"+dynamicProxy.getClass());
System.out.println("普通影廳《美國隊長》:"+dynamicProxy1.getClass());
}
我們可以看到結(jié)果
電影開始前正在播放廣告
VI影廳正在播放的電影是《鋼鐵俠》
電影結(jié)束了,接續(xù)播放廣告
電影開始前正在播放廣告
正在播放的電影是《美國隊長》
電影結(jié)束了,接續(xù)播放廣告
VIP 影廳《鋼鐵俠》代理類:class com.sun.proxy.$Proxy0
普通影廳《美國隊長》:class com.sun.proxy.$Proxy1
產(chǎn)生了兩個代理類分別是$Proxy0
和$Proxy1
。 下面?zhèn)儊砜聪?鋼鐵俠"的代理類$Proxy0
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.sun.proxy;
import com.workit.demo.proxy.VIPMovie;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements VIPMovie {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void vipPlay() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.workit.demo.proxy.VIPMovie").getMethod("vipPlay");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
},
通過上述代碼我們可以看到 $Proxy0 extends Proxy implements VIPMovie
繼承了Proxy
且實現(xiàn)了VIPMovie
接口,這也就是為什么jdk動態(tài)代理必須基于接口,java 是單繼承的。 然后再看下代理類實現(xiàn)的方法:
public final void vipPlay() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
這個supper.h.invoke Proxy
中的h的invoke方法,即InvocationHandler.invoke
也就是上面 MyInvocationHandler.invok
e方法,至此整個流程就清晰了。這就是jdk的動態(tài)代理。
cglib動態(tài)代理
上面說jdk動態(tài)代理只能基于接口,那么如果是類要動態(tài)代理怎么辦呢?cglib動態(tài)代理就可解決關(guān)于類的動態(tài)代理。 下面我們來創(chuàng)建一個“《美國隊長2》”
package com.workit.demo.proxy;
public class CaptainAmerica2MovieImpl {
public void play(){
System.out.println("正在播放的電影是《美國隊長2》");
}
}
引入cglib pom依賴
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
創(chuàng)建一個自定義MethodInterceptor。
package com.workit.demo.proxy;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxyInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
playStart();
Object object = methodProxy.invokeSuper(o, objects);
playEnd();
return object;
}
public void playStart() {
System.out.println("電影開始前正在播放廣告");
}
public void playEnd() {
System.out.println("電影結(jié)束了,接續(xù)播放廣告");
}
}
測試類
package com.workit.demo.proxy;
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
public class CglibProxyTest {
public static void main(String[] args) {
// //在指定目錄下生成動態(tài)代理類
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\\class");
//創(chuàng)建Enhancer對象,類似于JDK動態(tài)代理的Proxy類,下一步就是設(shè)置幾個參數(shù)
Enhancer enhancer = new Enhancer();
//設(shè)置目標類的字節(jié)碼文件
enhancer.setSuperclass(CaptainAmerica2MovieImpl.class);
//設(shè)置回調(diào)函數(shù)
enhancer.setCallback(new CglibProxyInterceptor());
//這里的creat方法就是正式創(chuàng)建代理類
CaptainAmerica2MovieImpl captainAmerica2Movie = (CaptainAmerica2MovieImpl)enhancer.create();
//調(diào)用代理類的play方法
captainAmerica2Movie.play();
System.out.println("cglib動態(tài)代理《美國隊長2》:"+captainAmerica2Movie.getClass());
}
}
輸出結(jié)果:
電影開始前正在播放廣告
正在播放的電影是《美國隊長2》
電影結(jié)束了,接續(xù)播放廣告
cglib動態(tài)代理《美國隊長2》:class com.workit.demo.proxy.CaptainAmerica2MovieImpl$$EnhancerByCGLIB$$5c3ddcfe
我們看下最終創(chuàng)建的代理類生成的play
方法
public class CaptainAmerica2MovieImpl$$EnhancerByCGLIB$$5c3ddcfe extends CaptainAmerica2MovieImpl implements Factory {
public final void play() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$play$0$Method, CGLIB$emptyArgs, CGLIB$play$0$Proxy);
} else {
super.play();
}
}
從代理對象反編譯源碼可以知道,代理對象繼承于CaptainAmerica2MovieImpl
,攔截器調(diào)用intercept
()方法, intercept
()方法由自定義CglibProxyInterceptor
實現(xiàn),所以,最后調(diào)用CglibProxyInterceptor
中的intercept
()方法,從而完成了由代理對象訪問到目標對象的動態(tài)代理實現(xiàn)。
-
CGlib是一個強大的,高性能,高質(zhì)量的Code生成類庫。它可以在運行期擴展Java類與實現(xiàn)Java接口。 -
用CGlib生成代理類是目標類的子類。 -
用CGlib生成 代理類不需要接口。 -
用CGLib生成的代理類重寫了父類的各個方法。 -
攔截器中的intercept方法內(nèi)容正好就是代理類中的方法體。
總結(jié)
-
代理分為靜態(tài)代理和動態(tài)代理兩種。 -
靜態(tài)代理,代理類需要自己編寫代碼寫成。 -
動態(tài)代理有jdk和cglib,代理類通過 Proxy.newInstance()
或者ASM
生成。 -
靜態(tài)代理和動態(tài)代理的區(qū)別是在于要不要開發(fā)者自己定義 Proxy 類。 動態(tài)代理通過 Proxy
動態(tài)生成proxy class
,但是它也指定了一個InvocationHandler
或者MethodInterceptor
的實現(xiàn)類。 -
代理模式本質(zhì)上的目的是為了增強現(xiàn)有代碼的功能。
結(jié)束
-
由于自己才疏學(xué)淺,難免會有紕漏,假如你發(fā)現(xiàn)了錯誤的地方,還望留言給我指出來,我會對其加以修正。 -
如果你覺得文章還不錯,你的轉(zhuǎn)發(fā)、分享、贊賞、點贊、留言就是對我最大的鼓勵。 -
感謝您的閱讀,十分歡迎并感謝您的關(guān)注。
https://blog.csdn.net/m0_37314675/article/details/77850967 https://www.cnblogs.com/cC-Zhou/p/9525638.html https://www.jianshu.com/p/4539e6d9f337
特別推薦一個分享架構(gòu)+算法的優(yōu)質(zhì)內(nèi)容,還沒關(guān)注的小伙伴,可以長按關(guān)注一下:
長按訂閱更多精彩▼
如有收獲,點個在看,誠摯感謝
免責聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!