代理模式也叫委托模式
定義:為其他對象提供一種代理以控制對這個對象的訪問杭攻。
代理模式通用的類圖如下:
我們來分析類圖中的幾個角色定義:
①Subject 抽象主題角色:可以是抽象類狮腿,也可以是接口聘芜,是一個最普通的業(yè)務類型定義拓提。
②RealSubject 真實主題角色:也叫做被委托角色棍厂,被代理角色豆拨,是Subject 抽象主題角色的具體實現(xiàn)者源葫,業(yè)務邏輯具體執(zhí)行者
③Proxy 代理主題角色:也叫做委托類雄坪,代理類产还。負責對真實角色的應用匹厘,把所有抽象主題委托的方法限制給真實主題角色實現(xiàn),并且在真實主題角色處理完畢前后做一些預處理和善后工作脐区。
通用代碼實現(xiàn)
Subject 抽象主題角色
public interface Subject {
public void request();
}
RealSubject 真實主題角色
public class RealSubject implements Subject {
@Override
public void request() {
//業(yè)務處理
}
}
Proxy 代理主題角色:
public class Proxy implements Subject {
private Subject subject = null;
public Proxy(Subject subject) { //通過構造函數(shù)指定傳遞被代理者
this.subject = subject;
}
@Override
public void request() {
this.Before();
this.subject.request();
this.After();
}
//預處理
private void Before(){
//代理前業(yè)務處理
}
//善后處理
private void After(){
//代理后業(yè)務處理
}
}
一個代理類可以代理多個被委托者愈诚,因此我們需要在高層模塊中自己指定代理類具體代理哪個真實主題角色,也就是代碼中所寫的牛隅,通過構造函數(shù)傳遞被代理者炕柔。
代理模式的優(yōu)點
①職責清晰:真實的角色就是實現(xiàn)實際的業(yè)務邏輯,不用關系其他非本職責的事物媒佣。
②高擴展性:真實主題角色是隨時變化的匕累,不管它怎么變,只要是實現(xiàn)了接口默伍,都能被在不修改代理類下使用
③智能化:動態(tài)代理
場景模擬
使用代理模式實現(xiàn)代練公司給玩家代練游戲賬號
玩家接口(抽象主題角色):定義登錄欢嘿,殺怪衰琐,升級 三種行為
public interface IGamePlayer {
//登錄賬戶名與密碼
public void login(String username,String password);
//殺怪
public void killBoss();
//升級
public void upgrade();
}
玩家實現(xiàn)類(真實主題角色)
public class GamePlayer implements IGamePlayer{
private String name = "";
public GamePlayer(String name) {
this.name = name;
}
@Override
public void login(String username, String password) {
System.out.println("登錄名為:"+username+" 密碼為:"+password+" 登錄成功");
}
@Override
public void killBoss() {
System.out.println(this.name+" 在打boss");
}
@Override
public void upgrade() {
System.out.println(this.name+ " 升級了!");
}
}
代理類(代理主題角色類)
public class GamePlayerProxy implements IGamePlayer {
private IGamePlayer gamePlayer = null;
//通過構造函數(shù)傳遞對誰進行代練
public GamePlayerProxy(IGamePlayer gamePlayer) {
this.gamePlayer = gamePlayer;
}
@Override
public void login(String username, String password) {
this.gamePlayer.login(username,password);
}
@Override
public void killBoss() {
this.gamePlayer.killBoss();
}
@Override
public void upgrade() {
this.gamePlayer.upgrade();
}
}
客戶端
public class Client {
public static void main(String[] args) {
System.out.println("---------未使用代理模式----------");
IGamePlayer player = new GamePlayer("張三");
player.login("zhangsan","123456");
player.killBoss();
player.upgrade();
System.out.println("---------代理模式----------");
GamePlayerProxy gamePlayerProxy = new GamePlayerProxy(player);
gamePlayerProxy.login("張三","123456");
gamePlayerProxy.killBoss();
gamePlayerProxy.upgrade();
}
}
代理模式的擴展:
類似于網(wǎng)絡代理服務器設置炼蹦,代理模式也分為:普通代理與強制代理
(1)普通代理:用戶必須知道代理的存在羡宙,通過代理尋找真實角色,也就是Proxy 代理主題角色這個類的存在是對用戶透明的掐隐。普通代理的要求是客戶端只能訪問代理角色狗热,而不能訪問RealSubject 真實主題角色。那么使用普通代理改造上述場景模擬虑省,代理類與玩家實現(xiàn)類修改如下
玩家實現(xiàn)類
public class GamePlayer implements IGamePlayer{
private String name = "";
//通過構造函數(shù)確認誰能創(chuàng)建真實對象
public GamePlayer(IGamePlayer gamePlayer,String name) {
if (gamePlayer == null){
throw new RuntimeException("不能創(chuàng)建真實角色");
}else {
this.name = name;
}
}
/.....以下省略...../
代理類
public class GamePlayerProxy implements IGamePlayer {
private IGamePlayer gamePlayer = null;
//通過構造函數(shù)傳遞對誰進行代練
public GamePlayerProxy(String username) {
try {
//將代理類傳遞過去是為了檢查誰能創(chuàng)建真實角色
this.gamePlayer = new GamePlayer(this,username);
} catch (Exception e) {
e.printStackTrace();
}
}
/.....以下省略...../
客戶端
public class Client {
public static void main(String[] args) {
System.out.println("---------使用普通代理模式----------");
IGamePlayer player = new GamePlayerProxy("張三");
player.login("zhangsan","123456");
player.killBoss();
player.upgrade();
}
}
經(jīng)過如上代碼修改斗搞,我們不需要知道真實角色是誰,屏蔽真實角色對高層模塊的影響慷妙,擴展性強。
(2)強制代理:與普通代理相反允悦,強制代理是通過真實角色尋找代理膝擂,否則無法訪問(無論是通過代理還是直接new一個主題類對象)。那么使用強制代理改造上述場景模擬隙弛,代碼修改如下
既然是通過真實角色獲取代理對象架馋,那么玩家類就需要修改
玩家接口修改
public interface IGamePlayer {
//登錄賬戶名與密碼
public void login(String username,String password);
//殺怪
public void killBoss();
//升級
public void upgrade();
//獲取代理類
public IGamePlayer getProxy();
}
玩家實現(xiàn)類修改
public class GamePlayer implements IGamePlayer{
private String name = "";
private IGamePlayer proxy = null;
//通過構造函數(shù)確認誰能創(chuàng)建真實對象
public GamePlayer(String name) {
this.name = name;
}
@Override
public void login(String username, String password) {
if (this.isProxy()){
System.out.println("登錄名為:"+username+" 密碼為:"+password+" 登錄成功");
}else {
System.out.println("請使用指定的代理訪問");
}
}
@Override
public void killBoss() {
if(this.isProxy()){
System.out.println(this.name+" 在打boss");
}else {
System.out.println("請使用指定的代理訪問");
}
}
@Override
public void upgrade() {
if (this.isProxy()){
System.out.println(this.name+ " 升級了!");
}else {
System.out.println("請使用指定的代理訪問");
}
}
@Override
public IGamePlayer getProxy() {
//指定代理對像
this.proxy = new GamePlayerProxy(this);
return this.proxy;
}
//檢測是否是代理訪問
private boolean isProxy(){
if (this.proxy == null){
return false;
}else {
return true;
}
}
}
代理類修改
public class GamePlayerProxy implements IGamePlayer {
private IGamePlayer gamePlayer = null;
//通過構造函數(shù)傳遞對誰進行代練
public GamePlayerProxy(IGamePlayer gamePlayer) {
this.gamePlayer = gamePlayer;
}
@Override
public void login(String username, String password) {
this.gamePlayer.login(username,password);
}
@Override
public void killBoss() {
this.gamePlayer.killBoss();
}
@Override
public void upgrade() {
this.gamePlayer.upgrade();
}
@Override
public IGamePlayer getProxy() {
//代理的代理沒有全闷,默認返回自己
return this;
}
}
客戶端:
public class Client {
public static void main(String[] args) {
System.out.println("---------直接訪問真實角色----------");
IGamePlayer player1 = new GamePlayer("張三");
player1.login("zhangsan","123456");
player1.killBoss();
player1.upgrade();
System.out.println("---------直接訪問真實角色----------");
IGamePlayer player2 = new GamePlayer("張三");
IGamePlayer proxy = new GamePlayerProxy(player2);
proxy.login("zhangsan","123456");
proxy.killBoss();
proxy.upgrade();
System.out.println("---------使用強制代理模式----------");
IGamePlayer player3 = new GamePlayer("張三");
IGamePlayer proxy3 = player3.getProxy();
proxy3.login("zhangsan","123456");
proxy3.killBoss();
proxy3.upgrade();
}
}
-------------output--------------
---------直接訪問真實角色----------
請使用指定的代理訪問
請使用指定的代理訪問
請使用指定的代理訪問
---------直接訪問真實角色----------
請使用指定的代理訪問
請使用指定的代理訪問
請使用指定的代理訪問
---------使用強制代理模式----------
登錄名為:zhangsan 密碼為:123456 登錄成功
張三 在打boss
張三 升級了叉寂!
經(jīng)過上面代碼的演示,可以看出強制代理的概念就是要從真實角色查找到代理角色总珠,不允許直接訪問真實角色屏鳍。 在客戶端中使用getProxy方法就可以取得真實角色的代理,然后訪問真是角色的所有方法局服。
動態(tài)代理
定義:在實現(xiàn)階段不需要知道代理誰钓瞭,在運行階段才指定代理對象。
使用JDK提供的動態(tài)代理接口:java.lang.reflect.InvocationHandler淫奔,對被代理類的方法進行處理山涡。
具體代碼如下:
public class GamePlayIH implements InvocationHandler {
//被代理者類
Class cls = null;
//被代理的實例對象
Object obj = null;
//通過構造函數(shù)傳遞被代理的實例對象
public GamePlayIH(Object obj) {
this.obj = obj;
}
//調(diào)用被代理的方法,invoke方法必須重寫唆迁,它完成對真實方法的調(diào)用
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(this.obj,args);
return result;
}
}
動態(tài)代理是根據(jù)被代理的接口獲取所有的方法鸭丛,也就是給定一個接口,動態(tài)代理就會自動實現(xiàn)該接口下的所有方法唐责,默認情況下所有方法返回值都為空值鳞溉,也就是雖然動態(tài)代理雖然實現(xiàn)接口下的所有方法,但是并沒有包含任何邏輯鼠哥,因此還需要通過InvocationHandler接口來處理所有方法的實際任務穿挨。
客戶端
public class Client {
public static void main(String[] args) {
//定義一個玩家
IGamePlayer player = new GamePlayer("張三");
//定義一個handler
InvocationHandler handler = new GamePlayIH(player);
//獲取類加載器
ClassLoader classLoader = player.getClass().getClassLoader();
//在這里給定IGamePlayer接口與InvocationHandler接口月弛,通過Proxy動態(tài)產(chǎn)生一個代理者
IGamePlayer gamePlayerProxy = (IGamePlayer) Proxy.newProxyInstance(classLoader,new Class[]{IGamePlayer.class},handler);
gamePlayerProxy.login("zhangsan","123456");
gamePlayerProxy.killBoss();
gamePlayerProxy.upgrade();
}
}
讓我們來看看動態(tài)代理的模型:動態(tài)代理通用類圖
根據(jù)通用類圖,我們來寫出通用的模式代碼
public interface Subject {
public void doSomething(String str);
}
public class RealSubject implements Subject {
@Override
public void doSomething(String str) {
System.out.println("doSomething ---> "+str);
}
}
public class MyInvocationHandler implements InvocationHandler {
//被代理的實例對象
Object obj = null;
//通過構造函數(shù)傳遞被代理的實例對象
public MyInvocationHandler(Object obj) {
this.obj = obj;
}
//調(diào)用被代理的方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(this.obj,args);
return result;
}
}
public interface IAdvice {
//通知只有一個執(zhí)行方法
public void exec();
}
public class BeforeAdvice implements IAdvice {
@Override
public void exec() {
System.out.println("前置通知執(zhí)行科盛!");
}
}
public class DynamicProxy<T> {
public static <T> T newProxyInstance(ClassLoader loader,Class<?>[]interfaces,InvocationHandler h){
//尋找JoinPoint連接點帽衙,AOP框架使用元數(shù)據(jù)定義
if (true){
//執(zhí)行一個前置通知
new BeforeAdvice().exec();
}
//執(zhí)行目標,并返回結果
return (T)Proxy.newProxyInstance(loader,interfaces,h);
}
}
客戶端
public class Client {
public static void main(String[] args) {
//定義一個真實主題
Subject subject = new RealSubject();
//定義一個代理handler
InvocationHandler handler = new MyInvocationHandler(subject);
//定義主題的代理
Subject proxy = DynamicProxy.newProxyInstance(subject.getClass().getClassLoader(),subject.getClass().getInterfaces(),handler);
//執(zhí)行代理的行為
proxy.doSomething("zhangsan");
}
}
在這里注意看:
DynamicProxy.newProxyInstance(subject.getClass().getClassLoader(),subject.getClass().getInterfaces(),handler);
這段代碼贞绵。subject.getClass().getClassLoader():獲取subject對象的類加載器厉萝,subject.getClass().getInterfaces()是獲取Subject類的所有接口,最后指定代理handler后榨崩,會實現(xiàn)該類的所有方法谴垫,由MyInvocationHandler類中的invoke方法接管所有的方法實現(xiàn)。
因此proxy.doSomething("zhangsan")這段代碼的調(diào)用過程為:Client類 --> DynamicProxy代理類 --> InvocationHandler接口 --> MyInvocationHandler 實現(xiàn)類 --> 接管Subject接口的所有方法 --> RealSubject實現(xiàn)類
動態(tài)代理與靜態(tài)代理的區(qū)別:動態(tài)代理是面向切面編程母蛛,在不改變我們已有的代碼結構下增強或控制對象的行為翩剪。
注意:實現(xiàn)動態(tài)代理的首要條件是:被代理類必須實現(xiàn)一個接口,當然在CGLIB等技術上不需要接口也可以實現(xiàn)彩郊。
參考書籍:設計模式之禪 --- 秦小波 著