解釋
為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問
示例引入
以打游戲?yàn)槔悠寐樱蟾耪麄€(gè)打游戲的過程可以概括為登錄泰佳、打怪、升級(jí)、砍人虱疏、被人砍等等一系列動(dòng)作。由于游戲打的時(shí)間長了阔馋,腰酸背痛扔罪、眼睛干澀、手臂麻木等等梭冠,其結(jié)果就類似于吃了那個(gè)“一日喪命散”辕狰,“筋脈逆流,胡思亂想妈嘹,而致走火入魔”柳琢,那怎么辦?我們想玩游戲润脸,但又不想碰觸到游戲中的煩惱柬脸,如何解決呢?有辦法毙驯,現(xiàn)在游戲代練的公司非常多倒堕,可以把自己的賬號(hào)交給代練人員,由他們?nèi)臀覀兩?jí)打怪爆价,非常好的想法垦巴,整個(gè)過程抽象成程序如下:
游戲類圖
/**
* Created by zs on 2017/3/23.
*
* 游戲者接口
*/
public interface IGamePlayer{
//登錄
public void login(String user,String password);
//殺怪
public void killBoss();
//升級(jí)
public void upgrade();
}
/**
* Created by zs on 2017/3/23.
*
* 游戲者
*/
public class GamePlayer implements IGamePlayer {
private String mName = "";
public GamePlayer(String name){
this.mName = name;
}
@Override
public void login(String user, String password) {
System.out.println("登錄名為" +user + "的用戶" + this.mName + "登錄成功媳搪!" );
}
@Override
public void killBoss() {
System.out.println(this.mName + "在打怪");
}
@Override
public void upgrade() {
System.out.println(this.mName + "又升了一級(jí)");
}
}
/**
* Created by zs on 2017/3/23.
*
* 代練者
*/
public class GamePlayerProxy implements IGamePlayer {
private IGamePlayer mGamePlayer = null;
public GamePlayerProxy(IGamePlayer gamePlayer){
this.mGamePlayer = gamePlayer;
}
@Override
public void login(String user, String password) {
this.mGamePlayer.login(user,password);
}
@Override
public void killBoss() {
this.mGamePlayer.killBoss();
}
@Override
public void upgrade() {
this.mGamePlayer.upgrade();
}
}
/**
* Created by zs on 2017/3/23.
*
* 場景類
*/
public class Client {
public static void main(String[] args) {
IGamePlayer player = new GamePlayer("張三");
IGamePlayer proxy = new GamePlayerProxy(player);
proxy.login("San","123456");
proxy.killBoss();
proxy.upgrade();
}
}
通用類圖
類圖解析
Subject:抽象主題角色,可以是抽象類也可以是接口骤宣,是一個(gè)最普通的業(yè)務(wù)類型定義秦爆,無特殊要求。(目標(biāo)接口)
RealSubject:具體主題角色憔披,也叫做被委托角色等限、被代理角色、是業(yè)務(wù)邏輯的具體執(zhí)行者芬膝。(目標(biāo)類)
Proxy:代理主題角色望门,也叫委托類、代理類锰霜。它負(fù)責(zé)最真實(shí)角色的應(yīng)用筹误,把所有抽象主題類定義的方法限制委托給真實(shí)主題角色實(shí)現(xiàn),并且在真實(shí)主題角色處理完畢前后做預(yù)處理和善后處理工作(代理類)
寫法
靜態(tài)代理:目標(biāo)類和代理類實(shí)現(xiàn)或繼承目標(biāo)接口或抽象類癣缅,代理類中有目標(biāo)類的引用
動(dòng)態(tài)代理:見下文描述
通用代碼
/**
* Created by zs on 2017/3/24.
*
* 抽象主題類
*/
public interface Subject {
public void request();
}
/**
* Created by zs on 2017/3/24.
*
* 真實(shí)主題類
*/
public class RealSubject implements Subject {
@Override
public void request() {
// to do you work
}
}
/**
* Created by zs on 2017/3/24.
*
* 代理類
*/
public class Proxy implements Subject {
private Subject mSubject = null;
//默認(rèn)代理者
public Proxy(){
this.mSubject = new Proxy();
}
//通過構(gòu)造函數(shù)傳遞代理者
public Proxy(Subject subject){
this.mSubject = subject;
}
@Override
public void request() {
this.before();
this.mSubject.request();
this.after();
}
//預(yù)處理
private void before(){
//to do you work...
}
//善后
private void after(){
// to do you work...
}
}
代理模式分類
總的分為:靜態(tài)代理和動(dòng)態(tài)代理
遠(yuǎn)程代理:隱藏了一個(gè)對(duì)象存在于不同的地址空間的事實(shí)厨剪,也即是客戶通過遠(yuǎn)程代理去訪問一個(gè)對(duì)象,根本就不關(guān)心這個(gè)對(duì)象在哪里所灸,也不關(guān)心如何通過網(wǎng)絡(luò)去訪問到這個(gè)對(duì)象丽惶,從客戶的角度來講,它只是在使用代理對(duì)象而已爬立。
虛擬代理:可以根據(jù)需要來創(chuàng)建“大”對(duì)象,只有到必須創(chuàng)建對(duì)象的時(shí)候侠驯,虛代理才會(huì)創(chuàng)建對(duì)象抡秆,從而大大加快程序運(yùn)行速度,并節(jié)省資源吟策。通過虛代理可以對(duì)系統(tǒng)進(jìn)行優(yōu)化儒士。
保護(hù)代理:可以在訪問一個(gè)對(duì)象的前后,執(zhí)行很多附加的操作檩坚,除了進(jìn)行權(quán)限控制之外着撩,還可以進(jìn)行很多跟業(yè)務(wù)相關(guān)的處理,而不需要修改被代理的對(duì)象匾委。也就是說拖叙,可以通過代理來給目標(biāo)對(duì)象增加功能。
智能引用代理:允許在訪問一個(gè)對(duì)象的前后赂乐,執(zhí)行很多附加的操作薯鳍,這樣一來就可以做很多額外的事情。(本文中重點(diǎn)解釋描述)
應(yīng)用場景
需要為一個(gè)對(duì)象在不同的地址空間提供局部代表的時(shí)候挨措,可以使用遠(yuǎn)程代理挖滤;
需要按照需要?jiǎng)?chuàng)建開銷很大的對(duì)象的時(shí)候崩溪,可以使用虛擬代理;
需要控制對(duì)原始對(duì)象的訪問的時(shí)候斩松,可以使用保護(hù)代理伶唯;
需要在訪問對(duì)象的時(shí)候執(zhí)行一些附加操作的時(shí)候,可以使用智能引用代理惧盹;(本文中重點(diǎn)解釋描述 )--->(重要的事說三遍)
壓軸大戲之動(dòng)態(tài)代理
問題:
- 對(duì)于靜態(tài)代理而言抵怎,我們每一個(gè)代理類都只能為一個(gè)接口服務(wù),這樣一來程序開發(fā)必然會(huì)產(chǎn)生過多的代理
- 如果Subject接口發(fā)生變化岭参,那么代理類和具體的目標(biāo)實(shí)現(xiàn)都要變化,不是很靈活
解決這些問題的最好的做法是可以通過一個(gè)代理類完成全部的代理功能尝艘,那么此時(shí)就必須使用動(dòng)態(tài)代理完成⊙莺睿現(xiàn)在有一個(gè)非常流行的名稱叫做面向切面編程,也就是AOP背亥,其核心就是采用了動(dòng)態(tài)代理機(jī)制秒际。
對(duì)于動(dòng)態(tài)代理jdk和cglib都已經(jīng)做了實(shí)現(xiàn),我們?cè)陂_發(fā)中使用它們封裝好的動(dòng)態(tài)代理很方便狡汉。(本文重點(diǎn)描述jdk是如何實(shí)現(xiàn)動(dòng)態(tài)代理)Java對(duì)代理模式提供了內(nèi)建的支持娄徊,在java.lang.reflect包下面,提供了一個(gè)Proxy的類和一個(gè)InvocationHandler的接口盾戴。
示例代碼
/**
* Created by zs on 2017/3/23.
*
* 游戲者接口
*/
public interface IGamePlayer {
//登錄
public void login(String user, String password);
//殺怪
public void killBoss();
//升級(jí)
public void upgrade();
}
/**
* Created by zs on 2017/3/23.
*
* 游戲者
*/
public class GamePlayer implements IGamePlayer {
private String mName = "";
public GamePlayer(String name){
this.mName = name;
}
@Override
public void login(String user, String password) {
System.out.println("登錄名為" +user + "的用戶" + this.mName + "登錄成功寄锐!" );
}
@Override
public void killBoss() {
System.out.println(this.mName + "在打怪");
}
@Override
public void upgrade() {
System.out.println(this.mName + "又升了一級(jí)");
}
}
/**
* Created by zs on 2017/3/24.
*
* 動(dòng)態(tài)代理類
* 動(dòng)態(tài)代理是根據(jù)被代理的接口生成所有的方法,
* 也就是說給定一個(gè)接口尖啡,動(dòng)態(tài)代理會(huì)宣稱"我已經(jīng)實(shí)現(xiàn)了該接口的所有方法了"
*/
public class GamePlayerIH implements InvocationHandler{
//被代理者
Class cls = null;
//被代理實(shí)例
Object obj = null;
//我要代理誰
public GamePlayerIH(Object obj){
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(this.obj,args);
return result;
}
}
/**
* Created by zs on 2017/3/23.
*
* 場景類
*/
public class Client {
public static void main(String[] args) {
//定義一個(gè)玩家
IGamePlayer player = new GamePlayer("張三");
//定義一個(gè)handler
InvocationHandler handler = new GamePlayerIH2(player);
//獲得類的class loader
ClassLoader cl = player.getClass().getClassLoader();
//動(dòng)態(tài)產(chǎn)生一個(gè)代理者
IGamePlayer proxy = (IGamePlayer) Proxy.newProxyInstance(cl,new Class[]{IGamePlayer.class},handler);
//登錄
proxy.login("San","123456");
//殺怪
proxy.killBoss();
//升級(jí)
proxy.upgrade();
}
}
動(dòng)態(tài)代理類圖
相關(guān)概念
切面:例如:在用戶注冊(cè)時(shí)候橄仆,上傳用戶頭像、獲取表單數(shù)據(jù)衅斩、權(quán)限驗(yàn)證等就是一個(gè)個(gè)切面
通知:切面中的方法(前置通知盆顾、后置通知、環(huán)繞通知畏梆、最終通知您宪、異常通知等)
切入點(diǎn):只有符合切入點(diǎn),才能讓通知和目標(biāo)方法結(jié)合在一起
織入:形成代理對(duì)象的方法的過程
動(dòng)態(tài)代理通用代碼
/**
* Created by zs on 2017/3/24.
*
* 抽象主題
*/
public interface Subject {
//業(yè)務(wù)處理
public void doSomething(String string);
}
**
* Created by zs on 2017/3/24.
*
* 真實(shí)主題
*/
public class RealSubject implements Subject {
@Override
public void doSomething(String string) {
System.out.println("do something ---> " + string);
}
}
/**
* Created by zs on 2017/3/24.
*
* 動(dòng)態(tài)代理的Handler類
*/
public class MyInvocationHandler implements InvocationHandler {
//被代理的對(duì)象
private Object target = null;
//通過構(gòu)造函數(shù)傳遞一個(gè)對(duì)象
public MyInvocationHandler(Object object){
this.target = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(this.target,args);
}
}
/**
* Created by zs on 2017/3/24.
*
* 通知
*/
public interface IAdvice {
public void execute();
}
/**
* Created by zs on 2017/3/24.
*
* 前置通知
*/
public class BeforeAdvice implements IAdvice {
@Override
public void execute() {
System.out.println("我是前置通知奠涌,我被執(zhí)行了");
}
}
/**
* Created by zs on 2017/3/24.
*
* 動(dòng)態(tài)代理類
*/
public class DynamicProxy<T> {
public static <T> T newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler invocationHandler){
//尋找JoinPoint切入點(diǎn)
if(true){
(new BeforeAdvice()).execute();
}
//執(zhí)行目標(biāo)宪巨,并返回結(jié)果
return (T)Proxy.newProxyInstance(loader,interfaces,invocationHandler);
}
}
/**
* Created by zs on 2017/3/24.
*
* 場景類
*/
public class Client {
public static void main(String[] args) {
Subject subject = new RealSubject();
InvocationHandler handler = new MyInvocationHandler(subject);
Subject proxy = DynamicProxy.newProxyInstance(subject.getClass().getClassLoader(),subject.getClass().getInterfaces(),handler);
proxy.doSomething("Finish");
}
}
代碼賞析
Subject proxy = DynamicProxy.newProxyInstance(subject.getClass().getClassLoader(),subject.getClass().getInterfaces(),handler);
該方法是重新生成了一個(gè)對(duì)象,subject.getClass().getInterfaces():查找到該類的所有接口铣猩,然后實(shí)現(xiàn)接口的所有方法揖铜,最終由new MyInvocationHandler(subject)這個(gè)對(duì)象接管。
代理模式示意圖
jdk動(dòng)態(tài)代理與cglib區(qū)別
Java的動(dòng)態(tài)代理目前只能代理接口达皿,基本的實(shí)現(xiàn)是依靠Java的反射機(jī)制和動(dòng)態(tài)生成class的技術(shù)天吓,來動(dòng)態(tài)生成被代理的接口的實(shí)現(xiàn)對(duì)象贿肩。如果要實(shí)現(xiàn)類的代理,可以使用cglib(一個(gè)開源的Code Generation Library)
效果
- 職責(zé)清晰:真實(shí)的角色不用關(guān)心其他非本職工作的事務(wù)龄寞,通過后期的代理完成一件事務(wù)汰规,附帶的結(jié)果就是編程簡介清晰
- 高擴(kuò)展性:具體主題角色是隨時(shí)都會(huì)變化,只要它實(shí)現(xiàn)了接口物邑,甭管它如何變化溜哮,都逃不開如來佛的手掌(接口)
- 智能化:主要用來做方法的增強(qiáng),讓你可以在不修改源碼的情況下色解,增強(qiáng)一些方法茂嗓,在方法執(zhí)行前后做任何你想做的事情