簡介
代理模式屬于行為型模式的一種, 控制對其他對象的通路, 起到中介作用.
代理模式核心角色: 真實角色,代理角色;
按實作方式不同分為靜态代理和動态代理兩種;
意圖
控制對其它對象的通路。
類圖

實作
JDK自帶了Proxy的實作, 下面我們先使用JDK的API來示範代理如何使用, 随後再探究Proxy的實作原理,并自己來實作Proxy.
JDK代理類的使用: ( InvocationHandler
, Proxy
)
InvocationHandler
Proxy
使用JDK實作的代理代碼如下, 先定義業務接口`Car`,然後實作該接口`QQCar`,實作類即為真實角色. 繼續定義代理類`Agent`,代理類需要實作接口`InvocationHandler`的`invoke()`方法, 最後是調用;
注意代理類使用了`Proxy.newProxyInstance()`方法動态生成代理對象, 在稍後手寫代碼時我們将參考本方法定義我們自己的實作方式.
/**
* 汽車接口,定義業務規範
*/
public interface Car {
void sale();
}
/**
* 接口Car的具體實作,是代理模式中的真實角色
*/
public class QQCar implements Car {
public void sale() {
System.out.println("賣了一輛qq");
}
}
/**
* 經紀公司,或者經銷商
*/
public class Agent implements InvocationHandler {
private Car car ;
/**
* 傳回代理對象,接收被代理對象
* @param car
* @return
* @throws Exception
*/
public Object getInstance(Car car) throws Exception {
this.car=car;
Class clazz = car.getClass();
// 看下代理前後的具體類型
System.out.println("代理前對象的類型"+car.getClass().getName());
Object obj = Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
// 看下代理前後的具體類型
System.out.println("代理後對象類型變為"+obj.getClass().getName());
return obj;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Agent find some costumers");
//this.car.sale();
method.invoke(this.car, args);
System.out.println("Agent saled the car");
return null;
}
}
try {
Car car = (Car) new Agent().getInstance(new QQCar());
car.sale();
}catch (Exception e){
e.printStackTrace();
}
總結JDK原理如下:
- 擷取真實角色對象的引用并擷取其接口
- Proxy生成一個代理類,并實作接口的方法
- 擷取被代理對象的引用
- 動态生成代理類的class位元組碼
- 編譯加載
手寫實作Proxy
要自己實作JDK的代理模式,我們首先要搞清楚JDK的Proxy是如何實作的, 通過調試及檢視源碼可以知道JDK生成了一個
$Proxy0
的類型,我們也将該類型名稱列印到了控制台. 如果能動态生成,編譯并将這個類加載到記憶體, 我們就可以自己實作Proxy了.
- 首先拿到
的代碼,供我們後面生成代理類的源碼時參考$Proxy0
//生産接口Car對應的代理類class檔案并儲存到檔案
byte[] data = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{Car.class});
FileOutputStream outputStream = new FileOutputStream("E:\\design-patterns\\src\\main\\java\\com\\xlx\\pattern\\proxy\\jdk\\$Proxy0.class");
outputStream.write(data);
outputStream.close();
生成的$Proxy0.class檔案反編譯後的代碼如下, 可以看到其實作了Car接口.
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import com.xlx.pattern.proxy.jdk.Car;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements Car {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void sale() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.xlx.pattern.proxy.jdk.Car").getMethod("sale");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
- 參考JDK的實作,我們分别定義
MyClassLoader
MyInvocationHandler
,分别對應MyProxy
ClassLoader
InvocationHandler
Proxy
;
其中接口
代碼如下:MyInvocationHandler
/**
* 代理類需要實作該接口
*/
public interface MyInvocationHandler {
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
- 定義代理類
對應我們使用JDK時定義的MyAgent
, 但Agent
方法實作全部改為2中自定義的類,實作2中定義的接口getInstance()
/**
* 代理類
*/
public class MyAgent implements MyInvocationHandler {
private Car car;
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Agent find some costumers");
//this.car.sale();
method.invoke(this.car,args);
System.out.println("Agent saled the car");
return null;
}
public Object getInstance(Car car) throws Exception {
this.car=car;
Class clazz = car.getClass();
Object obj = MyProxy.newProxyInstance(new MyClassLoader(),clazz.getInterfaces(),this);
return obj;
}
}
-
中生成代理對象的方法MyProxy
具體又分為以下幾步:newProxyInstance()
1. 定義動态代理類的源碼 2. 儲存源碼檔案到磁盤 3. 編譯源碼檔案為.class檔案 4. 加載.class位元組碼到記憶體 (具體實作見5.實作MyClassLoader)) 5. 傳回代理對象
/**
* 生成代理對象的代碼, Proxy的具體原理在這裡展現
*/
public class MyProxy {
private static final String ln = "\r\n";
public static Object newProxyInstance(MyClassLoader loader, Class<?>[] interfaces, MyInvocationHandler h) {
File f = null;
try {
// 第一步: 生成源代碼
String src = generateSrc(interfaces[0]);
// 第二步: 儲存生成的源碼檔案
String filePath = MyProxy.class.getResource("").getPath();
f = new File(filePath + "/$Proxy0.java");
FileWriter writer = new FileWriter(f);
writer.write(src);
writer.flush();
writer.close();
// 第三步: 編譯生成.class檔案
JavaCompiler compliler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manager = compliler.getStandardFileManager(null, null, null);
Iterable iterable = manager.getJavaFileObjects(f);
JavaCompiler.CompilationTask task = compliler.getTask(null, manager, null, null, null, iterable);
((JavaCompiler.CompilationTask) task).call();
manager.close();
// 第四步: 加載class位元組碼到記憶體(MyClassLoader類實作)
Class proxyClass = loader.findClass("$Proxy0");
// 第五步: 傳回代理對象
Constructor constructor = proxyClass.getConstructor(MyInvocationHandler.class);
return constructor.newInstance(h);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != f) {
f.delete();
}
}
return null;
}
/**
* 生成源碼的方法
*
* @param interfaces 為了示範,按一個接口處理
* @return
*/
private static String generateSrc(Class<?> interfaces) {
StringBuffer src = new StringBuffer();
src.append("package com.xlx.pattern.proxy.my;" + ln);
src.append("import java.lang.reflect.Method;" + ln);
src.append("public class $Proxy0 extends MyProxy implements " + interfaces.getName() + "{" + ln);
src.append("MyInvocationHandler h;" + ln);
src.append("public $Proxy0(MyInvocationHandler h){" + ln);
src.append("this.h=h;" + ln);
src.append("}" + ln);
// 循環定義方法,與被代理類的方法同名
for (Method m : interfaces.getMethods()) {
src.append("public " + m.getReturnType().getName() + " " + m.getName() + "(){" + ln);
src.append("try{" + ln);
src.append("Method m =" + interfaces.getName() + ".class.getMethod(\"" + m.getName() + "\",new Class[]{});" + ln);
src.append("this.h.invoke(this,m,null);" + ln);
src.append("}catch(Throwable e){e.printStackTrace();}" + ln);
src.append("}" + ln);
}
src.append("}" + ln);
return src.toString();
}
}
-
的MyClassLoader
方法,最終由父類findClass()
方法加載,完成最後拼圖ClassLoader.defineClass()
/**
* 代碼生成,編譯,重新加載到記憶體
* 類加載器, 使用ClassLoader
*/
public class MyClassLoader extends ClassLoader{
File basePath ;
public MyClassLoader(){
String basePath = MyClassLoader.class.getResource("").getPath();
this.basePath = new File(basePath) ;
}
@Override
public Class<?> findClass(String name) throws ClassNotFoundException{
String className = MyClassLoader.class.getPackage().getName()+"."+name;
if (null!=basePath){
File classFile = new File(basePath,name.replaceAll("\\.","/")+".class");
if (classFile.exists()){
FileInputStream in = null;
ByteArrayOutputStream out= null;
try {
in = new FileInputStream(classFile);
out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len=in.read(buffer))!=-1){
out.write(buffer,0,len);
}
return defineClass(className,out.toByteArray(),0,out.size());
}catch (Exception e){
e.printStackTrace();
}finally {
classFile.delete();
if (null!=in){
try{
in.close();
}catch (Exception e){
e.printStackTrace();
}
}
if (null!=out){
try{
out.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
}
return null;
}
}
總結
上面我仿照JDK自帶API用自己的代碼實作了Proxy, 這樣寫了一次之後對Proxy的實作原理加深了很多.Proxy作為一種重要的模式已經大量用在了目前流行的很多架構上, 了解了原理就更有信心去學習架構以及架構的實作思想了.
優點: 1. 職責清晰,真實角色專注實作業務邏輯,代理角色去完成具體的事務,代碼結構簡潔清晰;2. 可擴充
補充
上面研究了JDK動态代理的實作, 首先定義了接口,然後用一個類實作這個接口,這個實作類就是要代理的具體對象;
cglib庫也實作了Proxy模式,與JDK不同的是, cglib不需要定義接口, 而是通過生成被代理類的子類來實作代理模式.這使得代理模式的使用更加簡單. 一般類型均可以作為被代理類型.
大緻的實作如下(原理跟jdk實作差不多,隻不過cglib使用的是類繼承實作):
/**
* 示範 cglib 代理方式
*/
public class CGAgent implements MethodInterceptor {
public Object getInstance(Class clazz) throws Exception{
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("代理開始了....");
methodProxy.invokeSuper(o,objects);
System.out.println("代理結束了....");
return null;
}
}