1. 代理模式的定義和應用
1.1 代理模式的定義
Provide a surrogate or placeholder for another object to control access to it .(為其他對象提供一種代理以控制對這個對象的訪問)
代理模式的類圖如下:
代理模式也叫做委托模式,是一種基本設計技巧。許多其他的模式,如狀態模式、策略模式、訪問者模式本質上是在更特殊的場合采用了委托模式,而且在日常的應用中,代理模式可以提供非常好的訪問控制。
代理模式中的三個角色的定義:
Subject 抽象主題角色
抽象主題類可以是抽象類也可以是接口,是一個最普通的業務類型定義,無特殊要求。
RealSubject
也叫做被委托角色、被代理角色。是業務邏輯的具體執行者。
Proxy
也叫做委托類、代理類。它負責對真實角色的應用,把所有抽象主題類定義的方法限制委托給真實主題角色實現,並且在真實主題角色處理完畢前后做預處理和善后處理工作。
Subject抽象主題類的通用源碼:
- 抽象主題類
public interface Subject{
//定義一個方法
public void request();
}
- 真實主題類
public class RealSubject implements Subject{
//實現方法
public void request(){
//業務邏輯
}
}
- 代理類
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
}
}
一個代理類可以代理多個被委托者或被代理者,因此一個代理類具體代理哪個真實主題角色,是由場景類決定的。
1.2 代理模式的應用
代理模式的優點:
-
職責清晰
真實的角色就是實現實際的業務邏輯,不用關心其他非本職責的事務,通過后期的代理完成一件事務,附帶的結果就是編程簡潔清晰。
-
高擴展性
具體主題角色是隨時都會發生變化的,只要它實現了接口,代理類就能在不做任何修改的情況下使用。
-
智能化
代理模式的使用場景:
Spring 中的AOP
2.代理模式的擴展
2.1普通代理
普通代理要求客戶端只能訪問代理角色,而不能訪問真實角色。
抽象游戲者
/**
* @Title: IGamePlayer
* @description:
* @author: gaoyakang
* @date: 2018年1月22日 下午10:31:09
*
*/
public interface IGamePlayer {
public void login(String user,String password);
public void killBoss();
public void upgrade();
}
普通代理的游戲者
/**
* @Title: GamePlayer
* @description:
* @author: gaoyakang
* @date: 2018年1月22日 下午10:32:13
*
*/
public class GamePlayer implements IGamePlayer {
private String name="";
public GamePlayer(IGamePlayer _gamePlayer,String _name) throws Exception {
if(_gamePlayer==null) {
throw new Exception("不能創建真實角色!");
}else {
this.name=_name;
}
}
public void login(String user, String password) {
System.out.println("登錄名為"+user+"的用戶"+this.name+"登錄成功!");
}
public void killBoss() {
System.out.println(this.name+"在打怪!");
}
public void upgrade() {
System.out.println(this.name+"又升了一級!");
}
}
普通代理的代理者
/**
* @Title: GamePlayerProxy
* @description:
* @author: gaoyakang
* @date: 2018年1月22日 下午10:39:45
*
*/
public class GamePlayerProxy implements IGamePlayer{
private IGamePlayer gamePlayer=null;
public GamePlayerProxy(String name) {
try {
gamePlayer=new GamePlayer(this,name);
}catch (Exception e) {
// TODO: handle exception
}
}
public void login(String user, String password) {
this.gamePlayer.login(user, password);
}
public void killBoss() {
this.gamePlayer.killBoss();
}
public void upgrade() {
this.gamePlayer.upgrade();
}
}
場景類
import java.util.Date;
/**
* @Title: Client
* @description:
* @author: gaoyakang
* @date: 2018年1月23日 下午10:51:09
*
*/
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
IGamePlayer proxy=new GamePlayerProxy("張三");
System.out.println("開始時間是:"+new Date());
proxy.login("zhangsan", "admin");
proxy.killBoss();
proxy.upgrade();
System.out.println("結束時間是:"+ new Date());
}
}
與通用代理模式不同地方在於,普通代理在使用時代理角色只需要傳入代理者的名字,無需創建對象。換句話說,被代理人對象需要在代理者類中創建,也就實現了被代理人必須知道代理人的存在。實際項目中一般通過約定禁止new一個真實的角色來達到以上目的。
2.2 強制代理
強制代理類要求必須通過真實角色找到代理角色。
直觀的感受是,強制代理需要你先創建一個真實角色player並通過player的getProxy()方法來獲取代理。由於代理類和真實角色都繼承自IGamePlayer,所以代理類和真實角色都可以再獲取他們各自的代理。
接口類
public interface IGamePlayer {
public void login(String user,String password);
public void killBoss();
public void upgrade();
public IGamePlayer getProxy();
}
真實角色類
public class GamePlayer implements IGamePlayer {
private String name = "";
private IGamePlayer proxy = null;
public GamePlayer(String _name) {
this.name = _name;
}
@Override
public void login(String user, String password) {
if (isProxy()) {
System.out.println("登錄名為" + user + "的用戶" + this.name + "登錄成功!");
} else {
System.out.println("請使用指定的代理訪問");
}
}
@Override
public void killBoss() {
if (isProxy()) {
System.out.println(this.name + "在打怪!");
} else {
System.out.println("請使用指定的代理訪問");
}
}
@Override
public void upgrade() {
if (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;
public GamePlayerProxy(IGamePlayer _gamePlayer) {
this.gamePlayer=_gamePlayer;
}
@Override
public void login(String user, String password) {
this.gamePlayer.login(user, password);
}
@Override
public void killBoss() {
this.gamePlayer.killBoss();
}
@Override
public void upgrade() {
this.gamePlayer.upgrade();
}
@Override
public IGamePlayer getProxy() {
return this;
}
}
具體使用的時候,代理的獲取方式跟普通代理類有些不同。如果你直接new一個代理類,那么是無法獲取到正確的操作的,你必須用真實角色的getProxy()方法來獲取它的代理。這就是指定的代理。
IGamePlayer player=new GamePlayer("張三");
IGamePlayer proxy=player.getProxy();
完整的場景類代碼如下:
場景類
public class Client {
public static void main(String[] args) {
IGamePlayer player=new GamePlayer("張三");
IGamePlayer proxy=player.getProxy();
System.out.println("開始時間是:"+new Date());
proxy.login("zhangsan", "admin");
proxy.killBoss();
proxy.upgrade();
System.out.println("結束時間是:"+ new Date());
}
}
2.3 代理是有個性的
上面的例子中,代理類實現的接口跟真實角色是一樣的。代理這個名字,就意味着我們可以通過代理實現我們在真實角色類中無法或者不想實現的方法。因此代理類應該實現跟真實角色類不同的功能,這就是代理的個性之處。
代理的目的是在目標對象方法的基礎上做增強,這種增強的本質通常就是對目標對象的方法進行過濾和攔截。
按書中的例子我們實現一個代理收費的功能。
類圖如下:
以強制代理類為基礎,我們實現上述功能。
代理類的接口
public interface IProxy {
public void count();
}
實現了收費功能的代理類
public class GamePlayerProxy implements IGamePlayer,IProxy{
private IGamePlayer gamePlayer=null;
public GamePlayerProxy(IGamePlayer _gamePlayer) {
this.gamePlayer=_gamePlayer;
}
@Override
public void login(String user, String password) {
this.gamePlayer.login(user, password);
}
@Override
public void killBoss() {
this.gamePlayer.killBoss();
}
@Override
public void upgrade() {
this.gamePlayer.upgrade();
this.count();
}
@Override
public IGamePlayer getProxy() {
return this;
}
@Override
public void count() {
System.out.println("升級費用150元");
}
}
//運行結果
//開始時間是:Tue Jan 30 22:58:14 CST 2018
//登錄名為zhangsan的用戶張三登錄成功!
//張三在打怪!
//張三又升了一級!
//升級費用150元
//結束時間是:Tue Jan 30 22:58:14 CST 2018
真實角色類和Client端都無需任何改動,運行Client之后會發現果然實現了收費的功能。
看到這里我想到了AOP。我們知道AOP的主要作用就是不改動現有代碼而為其增加功能,如打印日志。上面代碼實現收費功能時並沒有改動真實角色,但是我們依然實現了增加功能的效果。看起來是有點AOP的影子,但是我們都知道Spring 中的AOP,使用的時候並不是在寫代碼的時候單獨寫代理類,而是在運行的過程中才指定代理去實現功能。AOP就是這樣定義的:
這種在運行時,動態地將代碼切入到類的指定方法、指定位置上的編程思想就是面向切面的編程。
那么如何實現動態的代理呢?下面我們將會了解代理模式中最重要的一個擴展——動態代理。
2.4 動態代理
動態代理是在實現階段不用關心代理誰,而在運行階段才指定代理哪一個對象
動態代理類圖中增加了一個InvocationHandler
接口和GamePlayIH
類,作用就是產生一個對象的代理對象,其中InvocationHandler
是JDK提供的動態代理接口,對被代理類的方法進行代理。
動態代理類
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class GamePlayIH implements InvocationHandler{
Class cls=null;
Object obj=null;
public GamePlayIH(Object _obj) {
this.obj=_obj;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result=method.invoke(this.obj, args);
if(method.getName().equalsIgnoreCase("login")) {
System.out.println("有人在用我的賬號登錄!");
}
return result;
}
}
其中invoke 方法時接口InvocationHandler定義必須實現的,它完成對真實方法的調用。動態代理根據被代理的接口生成所有的方法,也就是說給定一個接口,動態代理會宣稱“我已經實現該接口下的所有方法了”,那么動態代理怎么才能實現被代理接口中的方法呢?通過InvocationHandler接口,所有方法都有該Handler來進行處理,也就是所有被代理的方法都由InvocationHandler接管實際的處理任務。
我們在動態代理類中增加了一個檢驗登錄的功能,這樣更能直觀的體會到動態代理的好處。
動態代理的場景類
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Date;
public class Client {
public static void main(String[] args) {
IGamePlayer player=new GamePlayer("張三");
InvocationHandler handler=new GamePlayIH(player);
ClassLoader c1=player.getClass().getClassLoader();
IGamePlayer proxy=(IGamePlayer)Proxy.newProxyInstance(c1, new Class[] {IGamePlayer.class}, handler);
System.out.println("開始時間是:"+new Date());
proxy.login("zhangsan", "admin");
proxy.killBoss();
proxy.upgrade();
System.out.println("結束時間是:"+ new Date());
}
}
//output:
//開始時間是:Fri Feb 02 23:22:04 CST 2018
//登錄名為zhangsan的用戶張三登錄成功!
//有人在用我的賬號登錄!
//張三在打怪!
//張三又升了一級!
//結束時間是:Fri Feb 02 23:22:04 CST 2018
2.5 動態代理類的通用模型
動態代理類的通用類圖如下:
動態代理實現代理的職責,業務邏輯Subject 實現相關的邏輯功能,兩者之間沒有必然的相互耦合的關系。通知Adivice 從另一個切面切入,最終在高層模塊Client進行耦合,完成邏輯的封裝任務。
抽象主題
public interface Subject {
public void doSomething(String str);
}
真實主題
public class RealSubject implements Subject{
public void doSomething(String str) {
System.out.println("do something!---->"+str);
}
}
動態代理的Handler類
public class MyInvocationHandler implements InvocationHandler{
//被代理的對象
private Object target=null;
public MyInvocationHandler(Object _obj) {
this.target=_obj;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(this.target, args);
}
}
通知接口及實現
public interface IAdvice {
public void exec();
}
public class BeforeAdvice implements IAdvice{
public void exec() {
System.out.println("我是前置通知,我被執行了!");
}
}
動態代理類
public class DynamicProxy<T> {
public static <T> T newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) {
if(true) {
(new BeforeAdvice()).exec();
}
return (T)Proxy.newProxyInstance(loader, interfaces, h);
}
}
具體業務的動態代理
public class SubjectDynamicProxy extends DynamicProxy{
public static <T> T newProxyInstance(Subject subject) {
ClassLoader loader=subject.getClass().getClassLoader();
Class<?>[] classes=subject.getClass().getInterfaces();
InvocationHandler handler=new MyInvocationHandler(subject);
return newProxyInstance(loader, classes, handler);
}
}
動態代理的場景類
public class Client {
public static void main(String[] args) {
Subject subject=new RealSubject();
Subject proxy=SubjectDynamicProxy.newProxyInstance(subject);
proxy.doSomething("Finish");
}
}
如果你對Java中的反射有所了解的話,上面的代碼理解起來應該不難。我們知道,反射就是把java類中的各種成分映射成一個個的Java對象。Java在運行時會生成.class文件,反射的作用就是在程序運行時動態得.class文件中解析出類中的對象和方法。所以要實現在不修改現有代碼的基礎上添加功能我們肯定應該想到用反射去實現。
3.代理模式的應用
代理模式應用十分廣泛,其中最貼近我們的就是AOP了,我會在以后的文章中詳細分析AOP。