代理類示例
-
以前很喜歡打遊戲,和隊友們一起打怪升級,那麼通過一段簡單的代碼來表示我們打遊戲的過程。
-
代碼如下:首先是一個遊戲過程的接口類
public interface IGamePlayer {
// 登錄遊戲
void login(String user, String pwd);
// 殺怪
void killBoss();
// 升級
void upgrade();
}
- 接着是具體遊戲玩家
public class GamePalyer implements IGamePlayer {
// 遊戲賬戶名
private String userName;
public GamePalyer(String userName) {
this.userName = userName;
}
@Override
public void login(String user, String pwd) {
System.out.println("登錄賬號爲:" + user + userName + "登錄成功");
}
@Override
public void killBoss() {
System.out.println(this.userName + "打boss");
}
@Override
public void upgrade() {
System.out.println("升級了。。。");
}
}
- 然後我們開始打遊戲
public static void main(String[] args) {
IGamePlayer gamePlayer = new GamePalyer("張三");
System.out.println("遊戲開始時間" + "2010年9月4日23:07:30");
gamePlayer.login("zhangsan", "password");
gamePlayer.killBoss();
gamePlayer.upgrade();
System.out.println("遊戲結束" + "2019年9月4日23:08:10");
}
- 輸出如下
遊戲開始時間2010年9月4日23:07:30
登錄賬號爲:zhangsan張三登錄成功
張三打boss
升級了。。。
遊戲結束2019年9月4日23:08:10
結果是我們想要的打遊戲的過程,不知道你們有沒有這種體驗,每天不停地打遊戲,腰痠背痛腿抽筋,學習成績也下降了,白天起不來,晚上睡不着的。如何解決呢,於是產生了一種職業:代練。我也找過代練爲我升級。接下來我們修改一下類圖。
- 類圖中增加了一個GamePlayerProxy代理類,表示代練者,代練也是需要登錄、打怪、升級。所以也是實現了IGamePlayer接口。其代碼如下:
// 代理類
public class GamePlayerProxy implements IGamePlayer {
private IGamePlayer gamePlayer;
public GamePlayerProxy(IGamePlayer gamePlayer) {
this.gamePlayer = gamePlayer;
}
// 代練登錄
@Override
public void login(String user, String pwd) {
this.gamePlayer.login(user,pwd);
}
// 代練殺怪
@Override
public void killBoss() {
this.gamePlayer.killBoss();
}
// 代練升級
@Override
public void upgrade() {
this.gamePlayer.upgrade();
}
}
- 場景類修改如下
public static void main(String[] args) {
IGamePlayer gamePlayer = new GamePalyer("張三");
IGamePlayer proxy = new GamePlayerProxy(gamePlayer);
System.out.println("遊戲開始時間" + "2010年9月4日23:07:30");
proxy.login("zhangsan", "password");
proxy.killBoss();
proxy.upgrade();
System.out.println("遊戲結束" + "2019年9月4日23:08:10");
}
- 看起來沒有任何改變。但是已經不用自己去殺怪就可以升級了。
代理模式的定義
代理模式(Proxy Pattern)是一個使用率非常高的模式,其定義如下:
Provide a surrogate or placeholder for another object to control access to it.(爲其他對象提供 一種代理以控制對這個對象的訪問。)
代理模式的通用類圖如下:
- 代理模式也叫做委託模式,它是一項基本設計技巧。許多其他的模式,如狀態模式、策 略模式、訪問者模式本質上是在更特殊的場合採用了委託模式,而且在日常的應用中,代理 模式可以提供非常好的訪問控制。在一些著名開源軟件中也經常見到它的身影,Retrofit就採用了代理模式。我們先看一下類圖中的三個角色的定義:
- Subject抽象主題角色:抽象主題類可以是抽象類也可以是接口,是一個最普通的業務類型定義,無特殊要求。
- RealSubject具體主題角色:也叫做被委託角色、被代理角色。它纔是冤大頭,是業務邏輯的具體執行者。
- Proxy代理主題角色:也叫做委託類、代理類。它負責對真實角色的應用,把所有抽象主題類定義的方法限制 委託給真實主題角色實現,並且在真實主題角色處理完畢前後做預處理和善後處理工作。
抽象主題類代碼如下:
public interface Subject {
//定義一個方法 public void request();
}
真實主題類
public class RealSubject implements Subject {
//實現方法
public void request() {
//業務邏輯處理
}
}
RealSubject是一個正常的業務實現類,代理模式的核心就在代理類上
public class Proxy implements Subject {
//要代理哪個實現類
private Subject subject = null;
// 默認被代理者
public Proxy() {
this.subject = new Proxy();
}
//通過構造函數傳遞代理者
public Proxy(Object... objects) {
}
//實現接口中定義的方法
public void request() {
this.before();
this.subject.request();
this.after();
}
//預處理
private void before() {
//do something
}
// 善後處理
private void after() {
//do something }
}
}
一個代理類可以代理多個被委託者或被代理者,因此一個代理類具體代理哪個真實主題 角色,是由場景類決定的。當然,最簡單的情況就是一個主題類和一個代理類,這是最簡潔 的代理模式。在通常情況下,一個接口只需要一個代理類就可以了,具體代理哪個實現類由 高層模塊來決定,也就是在代理類的構造函數中傳遞被代理者,例如我們可以在代理類 Proxy中增加如下所示的構造函數。
public Proxy(Subject _subject){
this.subject = _subject;
}
你要代理誰就產生該代理的實例,然後把被代理者傳遞進來,該模式在實際的項目應用 中比較廣泛。
代理模式的應用
代理模式的優點
- 職責清晰
真實的角色就是實現實際的業務邏輯,不用關心其他非本職責的事務,通過後期的代理 完成一件事務,附帶的結果就是編程簡潔清晰。
- 高擴展性
具體主題角色是隨時都會發生變化的,只要它實現了接口,甭管它如何變化,都逃不脫 如來佛的手掌(接口),那我們的代理類完全就可以在不做任何修改的情況下使用。
- 智能化
以下的動態代理章節中你就會看到代理的智能化
代理模式的使用場景
爲什麼使用代理模式,類似於打遊戲找代練,打官司找律師,蓋房子找開發商。目的就是減輕自己的負擔。
代理模式的擴展
普通代理
- 普通代理要求客戶端只能訪問代理角色,而不能訪問真實角色。
- GamePlayer 的構造函數增加了 IGamePlayer 參數,代理者只要傳入代理者的名字即可
public class GamePlayer implements IGamePlayer {
// 遊戲賬戶名
private String userName;
public GamePlayer(IGamePlayer gamePlayer, String userName) throws Exception {
if (gamePlayer == null) {
throw new Exception("不能創建真實角色");
} else {
this.userName = userName;
}
}
@Override
public void login(String user, String pwd) {
System.out.println("登錄賬號爲:" + user + userName + "登錄成功");
}
@Override
public void killBoss() {
System.out.println(this.userName + "打boss");
}
@Override
public void upgrade() {
System.out.println("升級了。。。");
}
}
// 代理類
public class GamePlayerProxy implements IGamePlayer {
private IGamePlayer gamePlayer;
public GamePlayerProxy(String userName) {
try {
gamePlayer = new GamePlayer(this, userName);
} catch (Exception e) {
e.printStackTrace();
}
}
// 代練登錄
@Override
public void login(String user, String pwd) {
this.gamePlayer.login(user, pwd);
}
// 代練殺怪
@Override
public void killBoss() {
this.gamePlayer.killBoss();
}
// 代練升級
@Override
public void upgrade() {
this.gamePlayer.upgrade();
}
}
public static void main(String[] args) {
IGamePlayer proxy = new GamePlayerProxy("代理者");
System.out.println("遊戲開始");
proxy.login("zhangsan", "password");
proxy.killBoss();
proxy.upgrade();
System.out.println("遊戲結束");
}
調用者只需要知道代理類就可以了。不用知道代理了誰。該模式下,屏蔽了真實角色變更對高層模塊的影響。真實角色想怎麼修改怎麼修改。該模式非常適合要求擴展性比較高的場合。
強制代理
- 強制代理要求必須通過真實角色找到代理角色
public interface IGamePlayer {
// 登錄遊戲
void login(String user, String pwd);
// 殺怪
void killBoss();
// 升級
void upgrade();
IGamePlayer getProxy();
}
public class GamePlayer implements IGamePlayer {
// 遊戲賬戶名
private String userName;
private IGamePlayer proxy = null;
public GamePlayer(String userName) {
this.userName = userName;
}
@Override
public void login(String user, String pwd) {
if (this.isProxy()) {
System.out.println("登錄賬號爲:" + user + userName + "登錄成功");
} else {
System.out.println("請使用代理");
}
}
@Override
public void killBoss() {
if (this.isProxy()) {
System.out.println(this.userName + "打boss");
} else {
System.out.println("請使用代理");
}
}
@Override
public void upgrade() {
if (this.isProxy()) {
System.out.println("升級了。。。");
} else {
System.out.println("請使用代理");
}
}
@Override
public IGamePlayer getProxy() {
this.proxy = new GamePlayerProxy(this);
return this.proxy;
}
private boolean isProxy() {
return this.proxy != null;
}
}
public class GamePlayerProxy implements IGamePlayer {
private IGamePlayer gamePlayer;
public GamePlayerProxy(IGamePlayer gamePlayer) {
this.gamePlayer = gamePlayer;
}
// 代練登錄
@Override
public void login(String user, String pwd) {
this.gamePlayer.login(user, pwd);
}
// 代練殺怪
@Override
public void killBoss() {
this.gamePlayer.killBoss();
}
// 代練升級
@Override
public void upgrade() {
this.gamePlayer.upgrade();
}
@Override
public IGamePlayer getProxy() {
return null;
}
}
// 直接訪問真實角色類
IGamePlayer player = new GamePlayer("張颯");
System.out.println("遊戲開始");
player.login("zhangsan", "password");
player.killBoss();
player.upgrade();
System.out.println("遊戲結束");
遊戲開始
請使用代理
請使用代理
請使用代理
遊戲結束
// 直接訪問代理角色類
IGamePlayer player = new GamePlayer("張颯");
IGamePlayer proxy = new GamePlayerProxy(player);
System.out.println("遊戲開始");
proxy.login("zhangsan", "password");
proxy.killBoss();
proxy.upgrade();
System.out.println("遊戲結束");
遊戲開始
請使用代理
請使用代理
請使用代理
遊戲結束
強制代理使用
IGamePlayer player = new GamePlayer("張颯");
IGamePlayer proxy = player.getProxy();
System.out.println("遊戲開始");
proxy.login("zhangsan", "password");
proxy.killBoss();
proxy.upgrade();
System.out.println("遊戲結束");
遊戲開始
登錄賬號爲:zhangsan張颯登錄成功
張颯打boss
升級了。。。
遊戲結束
代理是有個性的
- 一個類可以實現多個接口,完成不同任務的整合。代理類不僅可以實現主題類的接口,還可以實現其他接口完成不同的任務。代理的目的就是在目標方法基礎上做增強,增強的本質就是對目標方法的攔截和過濾。比如代練遊戲是要花錢的。
public interface IProxy {
void count();
}
// 代理類
public class GamePlayerProxy implements IGamePlayer, IProxy {
IGamePlayer gamePlayer;
public GamePlayerProxy(IGamePlayer gamePlayer) {
this.gamePlayer = gamePlayer;
}
// 代練登錄
@Override
public void login(String user, String pwd) {
this.gamePlayer.login(user, pwd);
}
// 代練殺怪
@Override
public void killBoss() {
this.gamePlayer.killBoss();
}
// 代練升級
@Override
public void upgrade() {
this.gamePlayer.upgrade();
this.count();
}
@Override
public void count() {
System.out.println("升級的費用爲100元");
}
}
動態代理
- 什麼是動態代理?動態代理是在實現階段不用關心代理誰,而在運行階段 才指定代理哪一個對象。,現在有一個非常流行的名稱叫做面向橫切面編程,也就是AOP(Aspect Oriented Programming),其核心就是採用了動態代理機制。
- InvocationHandler 是JDK中提供的動態代理接口,對被代理類的方法進行代理。其中invoke方法是必須實現的,它完成真實方法的調用。
public class GamePlayerIH implements InvocationHandler {
// 被代理者
Class clazz = null;
// 被代理實例
Object object = null;
public GamePlayerIH(Object object) {
// 我要代理誰
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(this.object, args);
}
}
- 接下來是場景類,既沒有實現IGamePlayer也沒有創建代理類。這就是動態代理。
public static void main(String[] args) {
IGamePlayer gamePlayer = new GamePlayer("張三");
InvocationHandler invocationHandler = new GamePlayerIH(gamePlayer);
System.out.println("開始時間是 早上十點");
ClassLoader cl = gamePlayer.getClass().getClassLoader();
IGamePlayer proxy = (IGamePlayer) Proxy.newProxyInstance(cl,new Class[]{IGamePlayer.class},invocationHandler);
proxy.login("張三","pwd");
proxy.upgrade();
proxy.killBoss();
System.out.println("結束時間晚上十點");
}
- 如果想讓用戶登錄後給自己一個消息提醒怎麼做
public class GamePlayerIH implements InvocationHandler {
// 被代理者
Class clazz = null;
// 被代理實例
Object object = null;
public GamePlayerIH(Object object) {
// 我要代理誰
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equalsIgnoreCase("login")){
System.out.println("有人在登錄賬號");
}
return method.invoke(this.object, args);
}
}
這就是AOP編程。
- 接下來我們看看動態代理的模型
動態代理實現代理的職責,業務邏輯Subject實現相關邏輯功能。通知Advice從另一個切面切入,最終在Client進行耦合。完成封裝任務。
/**
* 抽象主題類
*/
public interface Subject {
void doSomething(String str);
}
/**
* 真實主題類
*/
public class RealSubject implements Subject {
@Override
public void doSomething(String str) {
System.out.println("do Something - - - > " + str);
}
}
/**
* 動態代理類
*/
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 {
// 執行被代理的方法
return method.invoke(object,args);
}
}
/**
* 動態代理類
* @param <T>
*/
public class DynamicProxy<T> {
public static <T> T newProxyInstance(ClassLoader classLoader, Class<?>[] interfaces, InvocationHandler handler) {
if (true) {
// 執行通知
new BeforeAdvice().exec();
}
return (T) Proxy.newProxyInstance(classLoader, interfaces, handler);
}
}
下面是實現的簡單AOP行爲 Advice
/**
* 通知接口
*/
public interface IAdvice {
void exec();
}
/**
* 通知實現類
*/
public class BeforeAdvice implements IAdvice {
@Override
public void exec() {
System.out.println("我是前置通知。。。");
}
}
業務調用
public static void main(String[] args) {
Subject subject = new RealSubject();
InvocationHandler handler = new MyInvocationHandler(subject);
// 此方法生成了一個新的對象,getInterfaces() 查找所有的方法 並讓 handler 接管這些所有方法
Subject proxy = DynamicProxy.newProxyInstance(subject.getClass().getClassLoader(),subject.getClass().getInterfaces(),handler);
proxy.doSomething("Finish");
}
動態調用過程如下:
上面代碼還有擴展的餘地–>封裝具體業務的代理類
public class SubjectProxy extends DynamicProxy {
public static <T> T newProxyInstance(Subject subject) {
ClassLoader classLoader = subject.getClass().getClassLoader();
Class<?>[] interfaces = subject.getClass().getInterfaces();
InvocationHandler handler = new MyInvocationHandler(subject);
return newProxyInstance(classLoader, interfaces, handler);
}
}
實際調用會更簡單
public static void main(String[] args) {
Subject subject = new RealSubject();
Subject proxy = SubjectProxy.newProxyInstance(subject);
proxy.doSomething("Finish");
}