目錄
- 1.概述
- 2.結構
- 3 靜態代理
- 4 JDK動態代理
- 5 CGLIB動態代理
- 6 三種代理的對比
- 7 優缺點
- 8 使用場景
1.概述
由于某些原因需要給某物件提供一個代理以控制對該物件的訪問,這時,訪問物件不適合或者不能直接參考目標物件,代理物件作為訪問物件和目標物件之間的中介,

Java中的代理按照代理類生成時機不同又分為靜態代理和動態代理,靜態代理代理類在編譯期就生成,而動態代理代理類則是在Java運行時動態生成,動態代理又有JDK代理和CGLib代理兩種,
2.結構
代理(Proxy)模式分為三種角色:
- 抽象主題(Subject)類: 通過介面或抽象類宣告真實主題和代理物件實作的業務方法,(如果我們有多個真實主題類,就需要抽象成一個抽象主題)
- 真實主題(Real Subject)類: 實作了抽象主題中的具體業務,是代理物件所代表的真實物件,是最終要參考的物件,(比如上面的聯想公司)
- 代理(Proxy)類 : 提供了與真實主題相同的介面,其內部含有對真實主題的參考,它可以訪問、控制或擴展真實主題的功能,(地方代理商)
3 靜態代理
我們通過案例來感受一下靜態代理,
【例】火車站賣票
如果要買火車票的話,需要去火車站買票,坐車到火車站,排隊等一系列的操作,顯然比較麻煩,而火車站在多個地方都有代售點,我們去代售點買票就方便很多了,這個例子其實就是典型的代理模式,火車站是目標物件,代售點是代理物件,類圖如下:

代碼如下:
//賣票介面--抽象主題類
public interface SellTickets {
void sell();
}
//火車站 火車站具有賣票功能,所以需要實作SellTickets介面
//具體主題類
public class TrainStation implements SellTickets {
public void sell() {
System.out.println("火車站賣票");
}
}
//代售點
//代理類
public class ProxyPoint implements SellTickets {
//宣告火車站類物件
private TrainStation station = new TrainStation();
public void sell() {
System.out.println("代理點收取一些服務費用");
station.sell(); //代售點賣票實際上還是呼叫了火車找賣票的作業
}
}
//測驗類
public class Client {
public static void main(String[] args) {
//創建代售點物件
ProxyPoint pp = new ProxyPoint();
pp.sell(); //呼叫方法進行賣票
}
}
從上面代碼中可以看出測驗類直接訪問的是ProxyPoint類物件,也就是說ProxyPoint作為訪問物件和目標物件的中介,同時也對sell方法進行了增強(代理點收取一些服務費用),
4 JDK動態代理
接下來我們使用動態代理實作上面案例,先說說JDK提供的動態代理,Java中提供了一個動態代理類Proxy,Proxy并不是我們上述所說的代理物件的類,而是提供了一個創建代理物件的靜態方法(newProxyInstance方法)來獲取代理物件,
代理類是程式在運行程序中動態的在記憶體中生成的類,
代碼如下:
//賣票介面
public interface SellTickets {
void sell();
}
//火車站 火車站具有賣票功能,所以需要實作SellTickets介面
public class TrainStation implements SellTickets {
public void sell() {
System.out.println("火車站賣票");
}
}
//代理工廠,用來創建代理物件,是獲取代理物件的工廠類
//代理類也實作了對應的介面
public class ProxyFactory {
//宣告目標物件
private TrainStation station = new TrainStation();
//獲取代理物件的方法
public SellTickets getProxyObject() {
//使用Proxy獲取代理物件
/*
newProxyInstance()方法引數說明:
ClassLoader loader : 類加載器,用于加載代理類,使用真實物件的類加載器即可
Class<?>[] interfaces : 真實物件所實作的介面,代理模式真實物件和代理物件實作相同的介面,
而這個是代理類實作的介面的位元組碼物件
InvocationHandler h : 代理物件的呼叫處理程式
*/
SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),
station.getClass().getInterfaces(),
new InvocationHandler() {
/*
InvocationHandler中invoke方法引數說明:
proxy : 代理物件,和sellTickets物件是同一個人物件,在invoke方法中基本不用
method : 對應于在代理物件上呼叫的介面方法的 Method 實體
args : 代理物件呼叫介面方法時傳遞的實際引數
回傳值:就是方法的回傳值
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理點收取一些服務費用(JDK動態代理方式)");
//執行真實物件
Object result = method.invoke(station, args);
return result;
}
});
return sellTickets;
}
}
//測驗類
public class Client {
public static void main(String[] args) {
//1.創建代理工廠物件
ProxyFactory factory = new ProxyFactory();
//2.使用factory物件的方法獲取代理物件
SellTickets proxyObject = factory.getProxyObject();
//3.呼叫代理物件的方法,實際上呼叫的是invoke方法
proxyObject.sell();
}
}
使用了動態代理,我們思考下面問題:
-
ProxyFactory是代理類嗎?
ProxyFactory不是代理模式中所說的代理類,而代理類是程式在運行程序中動態的在記憶體中生成的類,通過阿里巴巴開源的 Java 診斷工具(Arthas【阿爾薩斯】)查看代理類的結構:
package com.sun.proxy;
import com.itheima.proxy.dynamic.jdk.SellTickets;
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 SellTickets {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException noSuchMethodException) {
throw new NoSuchMethodError(noSuchMethodException.getMessage());
}
catch (ClassNotFoundException classNotFoundException) {
throw new NoClassDefFoundError(classNotFoundException.getMessage());
}
}
public final boolean equals(Object object) {
try {
return (Boolean)this.h.invoke(this, m1, new Object[]{object});
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString() {
try {
return (String)this.h.invoke(this, m2, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode() {
try {
return (Integer)this.h.invoke(this, m0, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void sell() {
try {
this.h.invoke(this, m3, null);
return;
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
}
從上面的類中,我們可以看到以下幾個資訊:
-
代理類($Proxy0)實作了SellTickets,這也就印證了我們之前說的真實類和代理類實作同樣的介面,
-
代理類($Proxy0)將我們提供了的匿名內部類物件傳遞給了父類,
-
動態代理的執行流程是什么樣?
下面是摘取的重點代碼:
//程式運行程序中動態生成的代理類
public final class $Proxy0 extends Proxy implements SellTickets {
private static Method m3;
//有參構造
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
//加載類com.itheima.proxy.dynamic.jdk.SellTickets,然后再去獲取方法物件
static {
m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
}
public final void sell() {
this.h.invoke(this, m3, null);
}
}
//Java提供的動態代理相關類
public class Proxy implements java.io.Serializable {
protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
this.h = h;
}
}
//代理工廠類
public class ProxyFactory {
private TrainStation station = new TrainStation();
public SellTickets getProxyObject() {
SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),
station.getClass().getInterfaces(),
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理點收取一些服務費用(JDK動態代理方式)");
Object result = method.invoke(station, args);
return result;
}
});
return sellTickets;
}
}
//測驗訪問類
public class Client {
public static void main(String[] args) {
//獲取代理物件
ProxyFactory factory = new ProxyFactory();
SellTickets proxyObject = factory.getProxyObject();
proxyObject.sell();
}
}
執行流程如下:
- 在測驗類中通過代理物件呼叫sell()方法
- 根據多型的特性,執行的是代理類($Proxy0)中的sell()方法(父類參考指向子類物件)
- 代理類($Proxy0)中的sell()方法中又呼叫了InvocationHandler介面的子實作類物件的invoke方法
- invoke方法通過反射執行了真實物件所屬類(TrainStation)中的sell()方法
5 CGLIB動態代理
同樣是上面的案例,我們再次使用CGLIB代理實作,
如果沒有定義SellTickets介面,只定義了TrainStation(火車站類),很顯然JDK代理是無法使用了,因為JDK動態代理要求必須定義介面,對介面進行代理,
CGLIB是一個功能強大,高性能的代碼生成包,它為沒有實作介面的類提供代理,為JDK的動態代理提供了很好的補充,
CGLIB是第三方提供的包,所以需要引入jar包的坐標:(pom.xnl檔案中)
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
代碼如下:
//火車站
public class TrainStation {
public void sell() {
System.out.println("火車站賣票");
}
}
//代理工廠,用來獲取代理物件
public class ProxyFactory implements MethodInterceptor {
private TrainStation target = new TrainStation();
public TrainStation getProxyObject() {
//創建Enhancer物件,類似于JDK動態代理的Proxy類,下一步就是設定幾個引數
Enhancer enhancer =new Enhancer();
//設定父類的位元組碼物件
enhancer.setSuperclass(target.getClass());
//設定回呼函式
enhancer.setCallback(this);
//創建代理物件
TrainStation obj = (TrainStation) enhancer.create();
return obj;
}
/*
intercept方法引數說明:
o : 代理物件
method : 真實物件中的方法的Method實體
args : 實際引數
methodProxy :代理物件中的方法的method實體
*/
public TrainStation intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("代理點收取一些服務費用(CGLIB動態代理方式)");
TrainStation result = (TrainStation) methodProxy.invokeSuper(o, args);
return result;
}
}
//測驗類
public class Client {
public static void main(String[] args) {
//創建代理工廠物件
ProxyFactory factory = new ProxyFactory();
//獲取代理物件
TrainStation proxyObject = factory.getProxyObject();
proxyObject.sell();
}
}
6 三種代理的對比
-
jdk代理和CGLIB代理
使用CGLib實作動態代理,CGLib底層采用ASM位元組碼生成框架,使用位元組碼技術生成代理類,在JDK1.6之前比使用Java反射效率要高,唯一需要注意的是,CGLib不能對宣告為final的類或者方法進行代理,因為CGLib原理是動態生成被代理類的子類,
在JDK1.6、JDK1.7、JDK1.8逐步對JDK動態代理優化之后,在呼叫次數較少的情況下,JDK代理效率高于CGLib代理效率,只有當進行大量呼叫的時候,JDK1.6和JDK1.7比CGLib代理效率低一點,但是到JDK1.8的時候,JDK代理效率高于CGLib代理,所以如果有介面使用JDK動態代理,如果沒有介面使用CGLIB代理,
-
動態代理和靜態代理
動態代理與靜態代理相比較,最大的好處是介面中宣告的所有方法都被轉移到呼叫處理器一個集中的方法中處理(InvocationHandler.invoke),這樣,在介面方法數量比較多的時候,我們可以進行靈活處理,而不需要像靜態代理那樣每一個方法進行中轉,
如果介面增加一個方法,靜態代理模式除了所有實作類需要實作這個方法外,所有代理類也需要實作此方法,增加了代碼維護的復雜度,而動態代理不會出現該問題
7 優缺點
優點:
- 代理模式在客戶端與目標物件之間起到一個中介作用和保護目標物件的作用;
- 代理物件可以擴展目標物件的功能;
- 代理模式能將客戶端與目標物件分離,在一定程度上降低了系統的耦合度;
缺點:
- 增加了系統的復雜度;
8 使用場景
-
遠程(Remote)代理
本地服務通過網路請求遠程服務,為了實作本地到遠程的通信,我們需要實作網路通信,處理其中可能的例外,為良好的代碼設計和可維護性,我們將網路通信部分隱藏起來,只暴露給本地服務一個介面,通過該介面即可訪問遠程服務提供的功能,而不必過多關心通信部分的細節,
-
防火墻(Firewall)代理
當你將瀏覽器配置成使用代理功能時,防火墻就將你的瀏覽器的請求轉給互聯網;當互聯網回傳回應時,代理服務器再把它轉給你的瀏覽器,
-
保護(Protect or Access)代理
控制對一個物件的訪問,如果需要,可以給不同的用戶提供不同級別的使用權限,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/258938.html
標籤:java
上一篇:Java常用類筆記
下一篇:自學Java網路爬蟲-Day1
