Java設計模式之代理模式
代理模式屬于結構型設計模式,代理模式是為一個類提供一個替身,以控制對這個類的通路,通過代理類通路被代理類。
代理模式的三種形式
(1):靜态代理
(2):動态代理(JDK代理、接口代理)
(3):Cglib代理(動态代理,可以在記憶體中動态的建立對象)
實作靜态代理UML類圖

實作動态代理UML類圖
一、 建立靜态代理的步驟
(1):建立一個operator 接口和實作operator 接口的實體類ChinaTelecom
(2):建立一個代理類PhoneProxy 并實作接口
(1)建立一個operator 接口和實作operator 接口的實體類ChinaTelecom
/**
* @author yly
* @ClassName ChinaTelecom
* @Date 2020/2/20 19:28
* @Version 1.0
**/
public interface operator {
public void accessToTheNetwork();
}
/**
* @author yly
* @ClassName ChinaTelecom
* @Date 2020/2/20 19:30
* @Version 1.0
**/
public class ChinaTelecom implements operator {
@Override
public void accessToTheNetwork() {
System.out.println("中國電信");
}
}
(2)建立一個代理類PhoneProxy 并實作operator 接口
/**
* @author yly
* @ClassName PhoneProxy
* @Date 2020/2/20 19:31
* @Version 1.0
**/
public class PhoneProxy implements operator {
private ChinaTelecom chinaTelecom;
public PhoneProxy(ChinaTelecom chinaTelecom) {
this.chinaTelecom = chinaTelecom;
}
@Override
public void accessToTheNetwork() {
System.out.println("代理開始");
chinaTelecom.accessToTheNetwork();
System.out.println("代理完成");
}
}
(3)使用代理對象通路被代理的類
/**
* @author yly
* @ClassName demo
* @Date 2020/2/20 19:33
* @Version 1.0
**/
public class demo {
public static void main(String[] args) {
PhoneProxy phoneProxy = new PhoneProxy(new ChinaTelecom());
phoneProxy.accessToTheNetwork();
}
}
(4)運作結果
代理開始
中國電信
代理完成
靜态代理優點:
- 不修改目标類功能的前提下,能通過代理對象對目标功能進行擴充。
靜态代理的缺點:
- 代理對象需要與被代理對象同時實作一樣的接口,是以會有很多代理類
- 接口增加方法,代理類與被代理類都需要修改代碼
二、建立動态代理的步驟與靜态代理一緻
(1)建立一個operator 接口和實作operator 接口的實體類ChinaTelecom
/**
* @author yly
* @ClassName ChinaTelecom
* @Date 2020/2/20 19:28
* @Version 1.0
**/
public interface operator {
public void accessToTheNetwork();
public void play(String name);
}
/**
* @author yly
* @ClassName ChinaTelecom
* @Date 2020/2/20 19:30
* @Version 1.0
**/
public class ChinaTelecom implements operator {
@Override
public void accessToTheNetwork() {
System.out.println("中國電信");
}
@Override
public void play(String name) {
System.out.println("玩遊戲:"+name);
}
}
(2)建立一個代理類PhoneProxy ,此處使用JDK動态代理,不需要實作接口
/**
* @author yly
* @ClassName PhoneProxy
* @Date 2020/2/20 19:31
* @Version 1.0
**/
public class PhoneProxy {
private Object object;
public PhoneProxy(Object object) {
this.object = object;
}
/**
* @return
* @CallerSensitive
* public static Object newProxyInstance(ClassLoader loader, //指定目前目标對象使用的類加載器,擷取加載器方法
* Class<?>[] interfaces,//目标對象實作的接口類型,使用泛型方法确認類型
* InvocationHandler h) //事情處理,執行目标對象方法時,觸發事情處理器方法,将目前執行的目标對象方法作為參數傳入
* throws IllegalArgumentException
* {}
*/
public Object getProxyInstance() {
return Proxy.newProxyInstance(object.getClass().getClassLoader(),
object.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK動态代理開始");
Object returnVal = method.invoke(object, args);
System.out.println("JDK動态代理送出");
return returnVal;
}
});
}
}
(3)使用代理對象通路被代理的類
/**
* @author yly
* @ClassName demo
* @Date 2020/2/21 0:49
* @Version 1.0
**/
public class demo {
public static void main(String[] args) {
//建立目标對象
ChinaTelecom chinaTelecom = new ChinaTelecom();
//建立代理對象
operator proxyInstance = (operator)new PhoneProxy(chinaTelecom).getProxyInstance();
//proxyInstance=class com.sun.proxy.$Proxy0記憶體中生成的代理對象
System.out.println("proxyInstance="+proxyInstance.getClass());
proxyInstance.play("王者榮耀");
}
}
(4)運作結果
proxyInstance=class com.sun.proxy.$Proxy0
JDK動态代理開始
玩遊戲:王者榮耀
JDK動态代理送出
三、Cglib代理
Cglib可以使用目标對象子類來進行代理,不需要實作接口,可以彌補靜态代理和JDK動态代理的不足,因為靜态代理和JDK動态代理都需要目标對象實作一個接口,如果目标對象隻是一個單獨的對象,沒有實作接口,就可以使用Cglib代理。Cglib被廣泛的用于aop架構中,例如Spring AOP。
Cglib代理模式注意事項
(1):需要引入cglib的jar檔案
(2):在記憶體中動态建立子類,代理的類不能為final,否則報錯。
(3):目标對象方法如果是final或者static時,不會執行目标對象額外的業務方法
Cglib實作步驟
(1):建立目标對象不需要實作接口
(2):建立代理類
(1)建立目标對象不需要實作接口
/**
* @author yly
* @ClassName ChinaTelecom
* @Date 2020/2/21 10:56
* @Version 1.0
**/
public class ChinaTelecom {
public final String play(String name) {
System.out.println("動态代理cglib,不實作接口");
return "玩遊戲" + name;
}
}
(2)建立代理類,實作MethodInterceptor接口,重寫 intercept()方法
/**
* @author yly
* @ClassName PhoneProxy
* @Date 2020/2/21 10:58
* @Version 1.0
**/
public class PhoneProxy implements MethodInterceptor {
private Object object;
public PhoneProxy(Object object) {
this.object = object;
}
public Object getProxyInstance() {
//建立一個工具類
Enhancer enhancer = new Enhancer();
//設定父類
enhancer.setSuperclass(object.getClass());
//設定回調函數
enhancer.setCallback(this);
return enhancer.create();
}
/**
* 重寫intercept方法,會調用目标對象的方法
* @param o
* @param method
* @param objects
* @param methodProxy
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("Cglib代理開始");
Object invoke = method.invoke(object, objects);
System.out.println("Cglib代理結束");
return invoke;
}
}
(3)使用代理對象通路被代理的類
/**
* @author yly
* @ClassName demo
* @Date 2020/2/21 11:06
* @Version 1.0
**/
public class demo {
public static void main(String[] args) {
ChinaTelecom chinaTelecom = new ChinaTelecom();
ChinaTelecom proxyInstance =(ChinaTelecom) new PhoneProxy(chinaTelecom).getProxyInstance();
String play = proxyInstance.play("王者榮耀");
System.out.println(play);
}
}
(4)運作結果
Cglib代理開始
動态代理cglib,不實作接口
Cglib代理結束
玩遊戲王者榮耀
将目标方法play()用final修飾時,執行結果為
動态代理cglib,不實作接口
玩遊戲王者榮耀
代理模式的使用場景
- 防火牆代理(内網通過代理穿透防火牆,實作對公網的通路)
- 遠端代理(通過遠端代理,可以将遠端對象當本地對象調用)
- 緩存代理(請求資源檔案時,先到緩存拿資料,如果找到資源則傳回,如果找不到資源,再從伺服器拿資源)
使用靜态代理實作緩存代理
(1)建立File接口
/**
* @author yly
* @ClassName Image
* @Date 2020/2/10 19:02
* @Version 1.0
**/
public interface File {
public File resource();
}
(2)建立File的實作類ReadFile
/**
* @author yly
* @ClassName RealImage
* @Date 2020/2/10 19:03
* @Version 1.0
**/
public class ReadFile implements File {
private File file;
private String fileName;
public ReadFile(String fileName) {
this.fileName = fileName;
}
@Override
public File resource() {
System.out.println("緩存檔案: " + fileName);
return file;
}
public File getResource(String fileName){
System.out.println("資源檔案: " + fileName);
return file = new ReadFile(fileName);
}
}
(3)建立代理類ProxyFile
/**
* @author yly
* @ClassName ProxyImage
* @Date 2020/2/10 19:05
* @Version 1.0
**/
public class ProxyFile implements File {
private ReadFile readFile;
private String fileName;
public ProxyImage(String fileName) {
this.fileName = fileName;
}
@Override
public File resource() {
if(readFile == null){
readFile = new ReadFile(fileName);
return readFile.getResource(fileName);
}
return readFile.resource();
}
}
(4)使用代理對象通路被代理的類
/**
* @author yly
* @ClassName ProxyPatternDemo
* @Date 2020/2/10 19:06
* @Version 1.0
**/
public class ProxyPatternDemo {
public static void main(String[] args) {
File file = new ProxyFile("file.jpg");
// 檔案将從磁盤加載
File resource = file.resource();
System.out.println(resource);
System.out.println("");
// 檔案從緩存加載
File resource1 = file.resource();
System.out.println(resource1);
}
}
(5)運作結果
資源檔案: file.jpg
com.java.designmode.impl.ReadFile@16d3586
緩存檔案: file.jpg
com.java.designmode.impl.ReadFile@16d3586