靜態代理和動態代理
代理模式主要應用的場景是:當某些類由于一些原因不方便直接訪問或者修改,需要通過一個代理類作為橋梁,來實現間接訪問并擴展功能。
靜態代理
假設我們有一個游戲模塊,包含各種不同類型的游戲,我們需要在游戲開始和結束的時候加入提示,讓我們看看利用靜態代理怎么實現這個需求:

private Game game;
public GameProxy(Game game) {
this.game = game;
}
public void playGame() {
if(game!=null) {
System.out.println("Game Start!");
game.playGame();
System.out.println("Game Over!");
}
}
}
新建一個測試類:
```java
public class MyTestClass {
@Test
public void demo01() {
ShootingGame shootingGame = new ShootingGame();
Game game = new GameProxy(shootingGame);
game.playGame();
}
@Test
public void demo02() {
FightingGame fightingGame = new FightingGame();
Game game = new GameProxy(fightingGame);
game.playGame();
}
}
運行兩個測試方法,將分別打印出:
Game Start!
FightingGame
Game Over!
Game Start!
ShootingGame
Game Over!
動態代理
我們用另一個游戲場景來演示動態代理的使用(∩_∩)。
在LOL里有一個英雄是狂野女獵手(俗稱豹女),她有人形和獵豹兩種形態,每種形態下都有對應的技能,UML如圖:

上面的UML圖定義了人形態(Human)和獵豹形態(Leopard)兩種接口,女獵手類(Huntress)同時實現了這兩種接口,代碼如下:
public interface Human {
/** 變身為獵豹 */
public void transformIntoLeopard();
/** 向目標投擲標槍,返回傷害值 */
public int throwJavelin(String enemy);
/** 在指定位置放置陷阱 */
public void setTrap(int x, int y);
}
public interface Leopard {
/** 變身為人形 */
public void transformIntoHuman();
/** 爪擊,返回傷害值 */
public int clawAttack();
}
public class Huntress implements Human, Leopard {
public void transformIntoLeopard() {
System.out.println("變身為獵豹");
}
public int throwJavelin(String enemy) {
System.out.println("對" + enemy + "造成100點傷害");
return 100;
}
public void setTrap(int x, int y) {
System.out.println("在(" + x + ", " + y + ")處放置了一個陷阱");
}
public void transformIntoHuman() {
System.out.println("變身為人形");
}
public int clawAttack() {
System.out.println("對前方敵人共造成200點傷害");
return 200;
}
}
不同于靜態代理需要建立實體代理類,我們直接在測試模塊用代碼創建動態代理:
public class MyTestClass {
@Test
public void demo01() {
Huntress huntress = new Huntress();
Object proxy = Proxy.newProxyInstance(
huntress.getClass().getClassLoader(),
Huntress.class.getInterfaces(),
new HuntressInvocationHandler(huntress));
Human humanProxy = (Human)proxy;
humanProxy.transformIntoLeopard();
humanProxy.setTrap(25,50);
humanProxy.throwJavelin("武器大師");
Leopard leopardProxy = (Leopard)proxy;
leopardProxy.transformIntoHuman();
leopardProxy.clawAttack();
}
}
Proxy.newProxyInstance方法的聲明如下:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h);
loader -- 指定用哪個類加載器去加載代理類
interfaces -- 代理類需要實現的接口列表,在本例中length是2
h -- 可簡單理解為"方法調用處理器實例",InvocationHandler是JDK中專門用于實現動態代理的接口,它有一個invoke方法去處理代理實例上的方法調用并返回結果
下面的代碼定義了一個HuntressInvocationHandler,它繼承自InvocationHandler:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;
public class HuntressInvocationHandler implements InvocationHandler {
public Huntress huntress;
public HuntressInvocationHandler(Huntress huntress) {
this.huntress = huntress;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("------------------------------------------");
System.out.println("當前方法:" + method.getName());
String argsString = Arrays.toString(args);
System.out.println("當前參數:" + argsString);
System.out.println("*****開始施放技能!*****");
Object result = method.invoke(huntress, args);
System.out.println("*****結束施放技能!*****");
if(result!=null) {
System.out.println("*****造成"+result+"點傷害*****");
}
return result;
}
}
invoke方法的聲明如下:
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
proxy -- 代理類本身的一個實例。這個參數大部分情況下不需要關注。
method -- 當前調用的方法
args -- 當前調用方法的參數列表
運行測試方法,將打印下列信息:
--------------------------------------------------------
當前方法:transformIntoLeopard
當前參數:null
*****開始施放技能!*****
變身為獵豹
*****結束施放技能!*****
--------------------------------------------------------
當前方法:setTrap
當前參數:[25, 50]
*****開始施放技能!*****
在(25, 50)處放置了一個陷阱
*****結束施放技能!*****
--------------------------------------------------------
當前方法:throwJavelin
當前參數:[武器大師]
*****開始施放技能!*****
對武器大師造成100點傷害
*****結束施放技能!*****
*****造成100點傷害*****
--------------------------------------------------------
當前方法:transformIntoHuman
當前參數:null
*****開始施放技能!*****
變身為人形
*****結束施放技能!*****
--------------------------------------------------------
當前方法:clawAttack
當前參數:null
*****開始施放技能!*****
對前方敵人共造成200點傷害
*****結束施放技能!*****
*****造成200點傷害*****
總結
靜態代理要求代理類和委托類實現同一個接口,代理類在編譯期生成,效率高。相應缺點是會多出一些代理類。
動態代理不要求代理類和委托類實現同一個接口(沒有實現接口的類無法使用動態代理),它是在程序運行時根據需要動態創建目標類的代理對象,但由于它是通過反射來代理方法,在性能上會有所消耗。

浙公網安備 33010602011771號