如何手寫JDK動態代理
阿新 • • 發佈:2018-12-16
1、關於靜態代理和動態代理的區別
靜態代理:
由程式設計師事先生成原始碼再對其編譯。在程式執行前,代理類的.class檔案就已經存在了。
動態代理類:
在程式執行時,運用 Java 反射機制動態建立而成。
比較:
- 靜態代理通常只代理一個類,動態代理是代理一個介面下的多個實現類。
- 靜態代理事先知道要代理的是什麼,而動態代理不知道要代理什麼東西,只有在執行時才知道。
- 動態代理是實現JDK裡的InvocationHandler介面的invoke方法,但注意的是代理的是介面,也就是你的業務類必須要實現介面,通過Proxy裡的newProxyInstance得到代理物件。
- 還有一種動態代理CGLIB,代理的是類,不需要業務類繼承介面,通過派生的子類來實現代理。通過在執行時,動態修改位元組碼達到修改類的目的。
2、JDK 動態代理的實現
- Person.java 介面類
package com.leitan.architect.pattern.proxy.sta;
/**
* @Author: tan.lei
* @Date: 2018-10-17 14:45
*/
public interface Person {
void findPartner();
void findHouse();
void findJob();
void shopping();
}
- Xiaoming.java 被代理類
package com.leitan.architect. pattern.proxy.jdk;
import com.leitan.architect.pattern.proxy.sta.Person;
/**
* @Author: tan.lei
* @Date: 2018-10-17 14:52
*/
public class Xiaoming implements Person {
@Override
public void findPartner() {
System.out.println("需要膚白、貌美、大長腿的伴侶....");
}
@Override
public void findHouse() {
System.out.println("需要環境安靜的房子");
}
@Override
public void findJob() {
System.out.println("需要高薪的工作");
}
@Override
public void shopping() {
System.out.println("需要160m²的海景別墅");
}
}
- MyInvocationHandler.java 處理介面,類似參考 JDK 的 InvocationHandler
package com.leitan.architect.pattern.proxy.mine;
import java.lang.reflect.Method;
/**
* @Author: tan.lei
* @Date: 2018-10-17 16:49
*/
public interface MyInvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
- MyMeipo.java 媒婆的實現類
package com.leitan.architect.pattern.proxy.mine;
import com.leitan.architect.pattern.proxy.sta.Person;
import java.lang.reflect.Method;
/**
* @Author: tan.lei
* @Date: 2018-10-18 10:09
*/
public class MyMeipo implements MyInvocationHandler {
private Person target;// 被代理的物件,把引用儲存下來
public Object getInstance(Person target) throws Exception {
this.target = target;
Class<?> clazz = target.getClass();
// 用來生成一個新物件(位元組碼重組來實現)
return MyProxy.newProxyInstance(new MyClassLoader(), clazz.getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("請給我你的需求,開始幫你物色...");
method.invoke(this.target, args);// 目標類的方法呼叫,需要找媳婦的條件
System.out.println("如果找到符合要求的就安排給你!");
return null;
}
}
根據媒婆的要求,目前還需要
MyProxy.java
需要動態生成的代理類
MyClassLoader.java
自定義的類載入器
- MyProxy.java 代理類
package com.leitan.architect.pattern.proxy.mine;
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
* @Author: tan.lei
* @Date: 2018-10-17 16:48
*/
public class MyProxy {
private static final String LN = "\r\n";// 換行
/**
* 獲取動態代理類
*
* @param classLoader 自定義的類載入器
* @param interfaces 被代理的目標介面
* @param handler 實際的業務處理實現
* @return
*/
public static Object newProxyInstance(MyClassLoader classLoader, Class<?>[] interfaces, MyInvocationHandler handler) {
try {
// 1.動態生成原始碼.java檔案
String src = generateSrc(interfaces);
// 2.Java檔案輸出到磁碟
String filePath = MyProxy.class.getResource("").getPath();
System.out.println("FilePath = " + filePath);
File f = new File(filePath + "$Proxy0.java");
FileWriter fw = new FileWriter(f);
fw.write(src);
fw.flush();
fw.close();
// 3.把.java檔案編譯成.class檔案
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
Iterable iterable = manager.getJavaFileObjects(f);
JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, iterable);
task.call();
manager.close();
// 4.把.class載入到JVM中
Class proxyClass = classLoader.findClass("$Proxy0");
Constructor constructor = proxyClass.getConstructor(MyInvocationHandler.class);
f.delete();// 編譯完成首刪除.java原檔案
// 5.返回位元組碼重組後新生成的物件
return constructor.newInstance(handler);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 拼接需要動態生成代理類的字串內容
*
* @param interfaces 被代理類的介面
* @return
*/
private static String generateSrc(Class<?>[] interfaces) {
StringBuffer sb = new StringBuffer();
sb.append("package com.leitan.architect.pattern.proxy.mine;" + LN);
sb.append(LN);
sb.append("import " + interfaces[0].getName() + ";" + LN);
sb.append("import java.lang.reflect.Method;" + LN);
sb.append(LN);
sb.append("public class $Proxy0 implements " + interfaces[0].getSimpleName() + " {" + LN);
sb.append(LN);
sb.append("public MyInvocationHandler handler;" + LN);
sb.append(LN);
sb.append("public $Proxy0(MyInvocationHandler handler) {" + LN);
sb.append("this.handler = handler;" + LN);
sb.append("}" + LN);
sb.append(LN);
for (Method method : interfaces[0].getMethods()) {
sb.append("public " + method.getReturnType().getName() + " " + method.getName() + "() {" + LN);
sb.append("try {" + LN);
sb.append("Method m = " + interfaces[0].getSimpleName() + ".class.getMethod(\"" + method.getName() + "\", new Class[]{});" + LN);
sb.append("this.handler.invoke(this, m, null);" + LN);
sb.append("} catch(Throwable e) { " + LN);
sb.append("e.printStackTrace();" + LN);
sb.append("}" + LN);
sb.append("}" + LN);
sb.append(LN);
}
sb.append("}" + LN);
return sb.toString();
}
}
- MyClassLoader.java 自定義類載入器
package com.leitan.architect.pattern.proxy.mine;
import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
/**
* @Author: tan.lei
* @Date: 2018-10-17 16:50
*/
public class MyClassLoader extends ClassLoader {
private File classPathFile;// class的存放路徑
public MyClassLoader() {
String classPath = MyClassLoader.class.getResource("").getPath();
this.classPathFile = new File(classPath);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> resClass = null;
if (classPathFile != null) {
File classFile = new File(classPathFile, name.replaceAll("\\.", "/") + ".class");
if (classFile.exists()) {
FileInputStream in = null;
ByteOutputStream out = null;// 把class轉換成位元組碼寫到JVM中
try {
in = new FileInputStream(classFile);
out = new ByteOutputStream();
byte[] buff = new byte[1024];
int len;
while ((len = in.read(buff)) != -1) out.write(buff, 0, len);
String className = MyClassLoader.class.getPackage().getName() + "." + name;
resClass = defineClass(className, out.getBytes(), 0, out.size());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (in != null) in.close();
if (out != null) out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return resClass;
}
}
-
MyProxyTest.java 測試類
package com.leitan.architect.pattern.proxy.mine; import com.leitan.architect.pattern.proxy.jdk.Xiaoming; import com.leitan.architect.pattern.proxy.sta.Person; /** * @Author: tan.lei * @Date: 2018-10-18 10:07 */ public class MyProxyTest { public static void main(String[] args) { try { Person obj = (Person) new MyMeipo().getInstance(new Xiaoming()); System.out.println(obj.getClass());// 這個類是位元組碼動態生成的 // 你在這裡看似呼叫目標類的方法,實際上是呼叫動態生成的代理類方法,目標類的方法已經經過了動態類的邏輯包裝 obj.findPartner(); //obj.findHouse(); //obj.findJob(); //obj.shopping(); } catch (Exception e) { e.printStackTrace(); } } }
測試結果如下:
FilePath = /Users/leitan/Worker/IdeaSpace/architect/target/classes/com/leitan/architect/pattern/proxy/mine/ class com.leitan.architect.pattern.proxy.mine.$Proxy0 媒婆:請給我你的需求,開始幫你物色… 需要膚白、貌美、大長腿的伴侶… 如果找到符合要求的就安排給你!
Process finished with exit code 0
-
根據上面的介面已經實現了找媳婦的動態代理