代理模式
??代理模式的定義:給某一個對象提供一個代理颠猴,并由代理對象控制對原對象的引用。
??代理(Proxy)是一種設(shè)計模式小染,提供了對目標對象另外的訪問方式翘瓮;即通過代理對象訪問目標對象。這樣做的好處是:可以在目標對象實現(xiàn)的基礎(chǔ)上裤翩,增強額外的功能操作资盅,即擴展目標對象的功能。
??這里使用到編程中的一個思想:不要隨意去修改別人已經(jīng)寫好的代碼或者方法岛都,如果需改修改律姨,可以通過代理的方式來擴展該方法。
??舉個例子來說明代理的作用:假設(shè)我們想邀請一位明星臼疫,那么并不是直接聯(lián)系明星择份,而是聯(lián)系明星的經(jīng)紀人,來達到同樣的目的烫堤。明星就是一個目標對象荣赶,他只要負責活動中的節(jié)目凤价,而其他瑣碎的事情就交給他的代理人(經(jīng)紀人)來解決。這就是代理思想在現(xiàn)實中的一個例子拔创。
??代理模式的關(guān)鍵點是:代理對象與目標對象利诺。代理對象是對目標對象的擴展,并會調(diào)用目標對象剩燥。
??代理模式包含如下角色:
??ISubject:抽象主題角色慢逾,是一個接口。該接口是對象和它的代理共用的接口灭红。
??RealSubject:真實主題角色侣滩,是實現(xiàn)抽象主題接口的類。
??Proxy:代理角色变擒,內(nèi)部含有對真實對象RealSubject的引用君珠,從而可以操作真實對象。代理對象提供與真實對象相同的接口娇斑,以便在任何時刻都能代替真實對象策添。同時,代理對象可以在執(zhí)行真實對象操作時毫缆,附加其他的操作唯竹,相當于對真實對象進行封裝。
??代理模式的應(yīng)用:
??遠程代理:也就是為一個對象在不同的地址空間提供局部代表苦丁。這樣可以隱藏一個對象存在于不同地址空間的事實摩窃。
??虛擬代理:是根據(jù)需要創(chuàng)建開銷很大的對象。通過它來存放實例化需要很長時間的真實對象芬骄。
??安全代理:用來控制真實對象訪問時的權(quán)限。
??智能代理:是指當調(diào)用真實的對象時鹦聪,代理處理一些另外的事情账阻。
??一般將代理分類的話,可分為靜態(tài)代理和動態(tài)代理兩種泽本。
靜態(tài)代理
??靜態(tài)代理比較簡單淘太,是由程序員編寫的代理類,并在程序運行前就編譯好的规丽,而不是由程序動態(tài)產(chǎn)生代理類蒲牧,這就是所謂的靜態(tài)。
??考慮這樣的場景赌莺,管理員在網(wǎng)站上執(zhí)行操作冰抢,在生成操作結(jié)果的同時需要記錄操作日志,這是很常見的艘狭。此時就可以使用代理模式挎扰,代理模式可以通過聚合和繼承兩種方式實現(xiàn):
/**
* @Description: 抽象主題接口
* @author: zxt
* @time: 2018年7月7日 下午2:29:46
*/
public interface Manager {
public void doSomething();
}
/**
* @Description: 真實的主題類
* @author: zxt
* @time: 2018年7月7日 下午2:31:21
*/
public class Admin implements Manager {
@Override
public void doSomething() {
System.out.println("這是真實的主題類:Admin doSomething!!!");
}
}
/**
* @Description: 以聚合的方式實現(xiàn)代理主題
* @author: zxt
* @time: 2018年7月7日 下午2:37:08
*/
public class AdminPoly implements Manager {
// 真實主題類的引用
private Admin admin;
public AdminPoly(Admin admin) {
this.admin = admin;
}
@Override
public void doSomething() {
System.out.println("聚合方式實現(xiàn)代理:Admin操作開始4涠!");
admin.doSomething();
System.out.println("聚合方式實現(xiàn)代理:Admin操作結(jié)束W窬搿尽超!");
}
}
/**
* @Description: 繼承方式實現(xiàn)代理
* @author: zxt
* @time: 2018年7月7日 下午2:40:39
*/
public class AdminProxy extends Admin {
@Override
public void doSomething() {
System.out.println("繼承方式實現(xiàn)代理:Admin操作開始!梧躺!");
super.doSomething();
System.out.println("繼承方式實現(xiàn)代理:Admin操作結(jié)束K扑!");
}
}
public static void main(String[] args) {
// 1掠哥、聚合方式的測試
Admin admin = new Admin();
Manager manager = new AdminPoly(admin);
manager.doSomething();
System.out.println("============================");
// 2巩踏、繼承方式的測試
AdminProxy proxy = new AdminProxy();
proxy.doSomething();
}
??聚合實現(xiàn)方式中代理類聚合了被代理類,且代理類及被代理類都實現(xiàn)了同一個接口龙致,可實現(xiàn)靈活多變蛀缝。繼承式的實現(xiàn)方式則不夠靈活。
??比如目代,在管理員操作的同時需要進行權(quán)限的處理屈梁,操作內(nèi)容的日志記錄,操作后數(shù)據(jù)的變化三個功能榛了。三個功能的排列組合有6種在讶,也就是說使用繼承要編寫6個繼承了Admin的代理類,而使用聚合霜大,僅需要針對權(quán)限的處理构哺、日志記錄和數(shù)據(jù)變化三個功能編寫代理類,在業(yè)務(wù)邏輯中根據(jù)具體需求改變代碼順序即可战坤。
??缺點:
??1)曙强、代理類和委托類實現(xiàn)了相同的接口,代理類通過委托類實現(xiàn)了相同的方法途茫。這樣就出現(xiàn)了大量的代碼重復(fù)碟嘴。如果接口增加一個方法,除了所有實現(xiàn)類需要實現(xiàn)這個方法外囊卜,所有代理類也需要實現(xiàn)此方法娜扇。增加了代碼維護的復(fù)雜度。
??2)栅组、代理對象只服務(wù)于一種類型的對象雀瓢,如果要服務(wù)多類型的對象。勢必要為每一種對象都進行代理玉掸,靜態(tài)代理在程序規(guī)模稍大時就無法勝任了刃麸。
動態(tài)代理
??實現(xiàn)動態(tài)代理的關(guān)鍵技術(shù)是反射。
??一般來說排截,對代理模式而言嫌蚤,一個主題類與一個代理類一一對應(yīng)辐益,這也是靜態(tài)代理模式的特點。
??但是脱吱,也存在這樣的情況智政,有n各主題類,但是代理類中的“前處理箱蝠、后處理”都是一樣的续捂,僅調(diào)用主題不同。也就是說宦搬,多個主題類對應(yīng)一個代理類牙瓢,共享“前處理,后處理”功能间校,動態(tài)調(diào)用所需主題矾克,大大減小了程序規(guī)模,這就是動態(tài)代理模式的特點憔足。動態(tài)代理主要有兩種:JDK自帶的動態(tài)代理和CGLIB動態(tài)代理胁附。
??首先是另一個靜態(tài)代理的實例:
1、一個可移動接口
public interface Moveable {
public void move();
}
2滓彰、一個實現(xiàn)了該接口的Car類
public class Car implements Moveable {
@Override
public void move() {
try {
Thread.sleep(new Random().nextInt(1000));
System.out.println("汽車行駛中----");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3控妻、現(xiàn)在需要有一個代理類來記錄Car的運行時間:
public class CarTimeProxy implements Moveable {
private Moveable m;
public CarTimeProxy(Moveable m) {
super();
this.m = m;
}
@Override
public void move() {
long startTime = System.currentTimeMillis();
System.out.println("汽車行駛前----");
m.move();
long endTime = System.currentTimeMillis();
System.out.println("汽車行駛結(jié)束----行駛時間為:" + (endTime - startTime) + "毫秒!");
}
}
4揭绑、另一個代理類記錄Car的日志:
public class CarLogProxy implements Moveable {
private Moveable m;
public CarLogProxy(Moveable m) {
super();
this.m = m;
}
@Override
public void move() {
System.out.println("日志開始");
m.move();
System.out.println("日志結(jié)束");
}
}
5弓候、客戶端的調(diào)用:
public class CarTest {
public static void main(String[] args) {
Car car = new Car();
// 先寫日志,再計時
CarTimeProxy ctp = new CarTimeProxy(car);
CarLogProxy clp = new CarLogProxy(ctp);
clp.move();
System.out.println();
// 先計時他匪,再寫日志
CarLogProxy clp1 = new CarLogProxy(car);
CarTimeProxy ctp1 = new CarTimeProxy(clp1);
ctp1.move();
}
}
JDK的動態(tài)代理
在java的動態(tài)代理機制中菇存,有兩個重要的類或接口,一個是InvocationHandler(Interface)邦蜜、另一個則是 Proxy(Class)撰筷,這一個類和接口是實現(xiàn)我們動態(tài)代理所必須用到的。
??JDK動態(tài)代理的實現(xiàn)
??1畦徘、創(chuàng)建一個實現(xiàn)接口InvocationHandler的類,它必須實現(xiàn)invoke方法抬闯。
??使用JDK動態(tài)代理類時井辆,需要實現(xiàn)InvocationHandler接口,所有動態(tài)代理類的方法調(diào)用溶握,都會交由InvocationHandler接口實現(xiàn)類里的invoke()方法去處理杯缺。這是動態(tài)代理的關(guān)鍵所在。
??2睡榆、創(chuàng)建被代理的類以及接口萍肆。
??3袍榆、調(diào)用Proxy的靜態(tài)方法,創(chuàng)建代理類塘揣。
??newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h);
??4包雀、通過代理調(diào)用方法。
??使用JDK動態(tài)代理的方式實現(xiàn)上面Car的時間代理:
1亲铡、首先是InvocationHandler接口的實現(xiàn)類:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TimeHandler implements InvocationHandler {
// 被傳遞過來的要被代理的對象
private Object object;
public TimeHandler(Object object) {
super();
this.object = object;
}
/**
* proxy:被代理的對象
* method:被代理的方法
* args:被代理方法的參數(shù)
*
* 函數(shù)返回:method的返回
*
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
System.out.println("汽車行駛前----");
method.invoke(object, args);
long endTime = System.currentTimeMillis();
System.out.println("汽車行駛結(jié)束----行駛時間為:" + (endTime - startTime) + "毫秒才写!");
return null;
}
}
2、創(chuàng)建動態(tài)代理類:
/**
* @Description: JDK動態(tài)代理的測試類
* @author: zxt
* @time: 2019年3月1日 下午7:59:29
*/
public class TimeHandlerTest {
public static void main(String[] args) {
// 需要被代理的對象
Car car = new Car();
InvocationHandler h = new TimeHandler(car);
Class<?> clazz = car.getClass();
/**
* 參數(shù)一:類加載器
* 參數(shù)二:被代理類實現(xiàn)的接口
* 參數(shù)三:InvocationHandler實例
*
* 函數(shù)返回:返回由InvocationHandler接口接收的被代理類的一個動態(tài)代理類對象
*/
Moveable m = (Moveable) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), h);
m.move();
}
}
cglib動態(tài)代理
??JDK動態(tài)代理可以在運行時動態(tài)生成字節(jié)碼奖蔓,主要使用到了一個接口InvocationHandler與Proxy.newProxyInstance靜態(tài)方法赞草。使用內(nèi)置的Proxy實現(xiàn)動態(tài)代理有一個問題:被代理的類必須要實現(xiàn)某接口,未實現(xiàn)接口則沒辦法完成動態(tài)代理吆鹤。
??如果項目中有些類沒有實現(xiàn)接口厨疙,則不應(yīng)該為了實現(xiàn)動態(tài)代理而刻意去抽象出一些沒有實際意義的接口,通過cglib可以解決該問題疑务。
??CGLIB(Code Generation Library)是一個開源項目沾凄,是一個強大的,高性能暑始,高質(zhì)量的Code生成類庫搭独,它可以在運行期擴展Java類與實現(xiàn)Java接口,通俗地說cglib可以在運行時動態(tài)生成字節(jié)碼廊镜。
??使用cglib完成動態(tài)代理牙肝,大概的原理是:cglib繼承被代理的類,重寫方法嗤朴,織入通知配椭,動態(tài)生成字節(jié)碼并運行。對指定目標類產(chǎn)生一個子類雹姊,通過方法攔截技術(shù)攔截所有父類的方法調(diào)用股缸,因為是繼承實現(xiàn)所以final類是沒有辦法動態(tài)代理的。
CGLIB動態(tài)代理實例:
import java.util.Random;
// 不實現(xiàn)接口的被代理類
public class Train {
public void move() {
try {
Thread.sleep(new Random().nextInt(1000));
System.out.println("火車行駛中----");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
// 得到代理類的方法
public Object getProxy(Class<?> clazz) {
// 設(shè)置創(chuàng)建子類的類 (即我們需要為哪個類產(chǎn)生代理類)
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
/**
* 攔截所有目標類方法的調(diào)用
*
* object:目標類的實例
* method:目標類的目標方法的反射實例
* args:目標方法的參數(shù)
* proxy:代理類的實例
*/
@Override
public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable {
long startTime = System.currentTimeMillis();
System.out.println("火車行駛前----");
// 代理類調(diào)用父類的方法 (由于Cglib動態(tài)代理的實現(xiàn)是通過繼承被代理類吱雏,因此代理類這里需要調(diào)用父類的方法)
proxy.invokeSuper(object, args);
long endTime = System.currentTimeMillis();
System.out.println("火車行駛結(jié)束----行駛時間為:" + (endTime - startTime) + "毫秒敦姻!");
return null;
}
}
public class CglibProxyTest {
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
Train train = (Train) cglibProxy.getProxy(Train.class);
train.move();
}
}
JDK動態(tài)代理的模擬實現(xiàn)
模擬JDK動態(tài)代理的實現(xiàn),根據(jù)Java源代碼動態(tài)生成代理類歧杏。
package com.zxt.jdkproxy;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import org.apache.commons.io.FileUtils;
import com.zxt.staticproxy.Car;
/**
*
* @Description: 模擬JDK動態(tài)代理的實現(xiàn)
* 動態(tài)代理的實現(xiàn)思路:
* 實現(xiàn)功能:通過自定義的Proxy的newProxyInstance方法返回代理對象
* 1镰惦、聲明一段源碼(動態(tài)產(chǎn)生代理)
* 2、編譯源碼(JDK Compiler API)犬绒,產(chǎn)生新的類(代理類)
* 3旺入、將這個類load到內(nèi)存當中,產(chǎn)生一個新的對象(代理對象)
* 4、return 代理對象
*
* @author: zxt
*
* @time: 2019年4月18日 下午3:44:58
*
*/
public class MyProxy {
@SuppressWarnings({ "rawtypes", "unchecked" })
public static Object newProxyInstance(Class<?> inteface) throws Exception {
// 1茵瘾、聲明一段源碼(動態(tài)產(chǎn)生代理)
String rt = "\r\n";
String methodStr = "";
for(Method m : inteface.getMethods()) {
methodStr += " @Override" + rt
+ " public void " + m.getName() + "() {" + rt
+ " System.out.println(\"日志開始\");" + rt
+ " m." + m.getName() + "();" + rt
+ " System.out.println(\"日志結(jié)束\");" + rt
+ " }";
}
String code =
"package com.zxt.jdkproxy;" + rt + "\n"
+ "import com.zxt.staticproxy.Moveable;" + rt + "\n"
+ "public class $MyProxy0 implements " + inteface.getSimpleName() + " {" + rt + "\n"
+ " private " + inteface.getSimpleName() + " m;" + rt + "\n"
+ " public $MyProxy0(" + inteface.getSimpleName() + " m) {" + rt
+ " super();" + rt
+ " this.m = m;" + rt
+ " }" + rt + "\n"
+ methodStr + rt + "\n"
+ "}";
// 由源代碼生成java類文件
String filename = System.getProperty("user.dir") + "/bin/com/zxt/jdkproxy/$MyProxy0.java";
File file = new File(filename);
// 使用commons-io里面的簡便的工具類來寫文件
FileUtils.writeStringToFile(file, code, "UTF-8");
// 2礼华、編譯源碼(JDK Compiler API),產(chǎn)生新的類(代理類)
// 拿到編譯器
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// 文件管理者
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
// 獲取文件
Iterable units = fileManager.getJavaFileObjects(filename);
// 獲取編譯任務(wù)
CompilationTask task = compiler.getTask(null, fileManager, null, null, null, units);
// 編譯
task.call();
fileManager.close();
// 3拗秘、加載到內(nèi)存
ClassLoader cl = ClassLoader.getSystemClassLoader();
Class c = cl.loadClass("com.zxt.jdkproxy.$MyProxy0");
// 4圣絮、返回代理類
Constructor ctr = c.getConstructor(inteface);
return ctr.newInstance(new Car());
}
public static void main(String[] args) {
}
}
public class MyProxyTest {
public static void main(String[] args) throws Exception {
Moveable m = (Moveable) MyProxy.newProxyInstance(Moveable.class);
m.move();
}
}
??可以發(fā)現(xiàn)上述實現(xiàn)中的源代碼是寫死在類中的,因此無法對任意類進行動態(tài)代理聘殖,所以仿照InvocationHandler接口晨雳,定義自己的InvocationHandler接口從而實現(xiàn)對不同的類進行動態(tài)代理。
import java.lang.reflect.Method;
public interface MyInvocationHandler {
public void invoke(Object o, Method m);
}
實現(xiàn)該接口的類
import java.lang.reflect.Method;
public class MyLogHandler implements MyInvocationHandler {
// 需要被代理的對象
private Object target;
public MyLogHandler(Object target) {
super();
this.target = target;
}
@Override
public void invoke(Object o, Method m) {
try {
System.out.println("日志開始");
m.invoke(target);
System.out.println("日志結(jié)束");
} catch (Exception e) {
e.printStackTrace();
}
}
}
對動態(tài)代理MyProxy類進行改進
/**
*
* @Description: 模擬JDK動態(tài)代理的實現(xiàn)
* 動態(tài)代理的實現(xiàn)思路:
* 實現(xiàn)功能:通過自定義的Proxy的newProxyInstance方法返回代理對象
* 1奸腺、聲明一段源碼(動態(tài)產(chǎn)生代理)
* 2餐禁、編譯源碼(JDK Compiler API),產(chǎn)生新的類(代理類)
* 3突照、將這個類load到內(nèi)存當中帮非,產(chǎn)生一個新的對象(代理對象)
* 4、return 代理對象
*
* @author: zxt
*
* @time: 2019年4月18日 下午3:44:58
*
*/
public class MyProxy {
@SuppressWarnings({ "rawtypes", "unchecked" })
public static Object newProxyInstance(Class<?> inteface, MyInvocationHandler h) throws Exception {
// 1讹蘑、聲明一段源碼(動態(tài)產(chǎn)生代理)
String rt = "\r\n";
String methodStr = "";
for(Method m : inteface.getMethods()) {
methodStr += " @Override" + rt
+ " public void " + m.getName() + "() {" + rt
+ " try { " + rt
+ " Method md = " + inteface.getSimpleName() + ".class.getMethod(\""
+ m.getName() + "\");" + rt
+ " h.invoke(this, md);" + rt
+ " } catch (Exception e) { " + rt
+ " e.printStackTrace();" + rt
+ " }" + rt
+ " }";
}
String code =
"package com.zxt.jdkproxy;" + rt + "\n"
+ "import java.lang.reflect.Method;" + rt
+ "import com.zxt.staticproxy.Moveable;" + rt + "\n"
+ "public class $MyProxy0 implements " + inteface.getSimpleName() + " {" + rt + "\n"
+ " private MyInvocationHandler h;" + rt + "\n"
+ " public $MyProxy0( MyInvocationHandler h ) {" + rt
+ " this.h = h;" + rt
+ " }" + rt + "\n"
+ methodStr + rt + "\n"
+ "}";
// 由源代碼生成java類文件
String filename = System.getProperty("user.dir") + "/bin/com/zxt/jdkproxy/$MyProxy0.java";
File file = new File(filename);
// 使用commons-io里面的簡便的工具類來寫文件
FileUtils.writeStringToFile(file, code, "UTF-8");
// 2末盔、編譯源碼(JDK Compiler API),產(chǎn)生新的類(代理類)
// 拿到編譯器
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// 文件管理者
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
// 獲取文件
Iterable units = fileManager.getJavaFileObjects(filename);
// 獲取編譯任務(wù)
CompilationTask task = compiler.getTask(null, fileManager, null, null, null, units);
// 編譯
task.call();
fileManager.close();
// 3座慰、加載到內(nèi)存
ClassLoader cl = ClassLoader.getSystemClassLoader();
Class c = cl.loadClass("com.zxt.jdkproxy.$MyProxy0");
// 4陨舱、返回代理類
Constructor ctr = c.getConstructor(MyInvocationHandler.class);
return ctr.newInstance(h);
}
}
測試類:
public class MyProxyTest {
public static void main(String[] args) throws Exception {
// 需要被代理的對象
Car car = new Car();
MyInvocationHandler h = new MyLogHandler(car);
Moveable m = (Moveable) MyProxy.newProxyInstance(Moveable.class, h);
m.move();
}
}