Java JDK動態代理原理詳解:
參考文章:https://blog.csdn.net/qq_31859365/article/details/82902349
動態代理介紹
Java動態代理與靜態代理相對,靜態代理是在編譯期就已經確定代理類和真實累的關係,並且聲稱代理類的,二動態代理是在運行期利用jvm的反射機制聲稱代理類,這裏是直接生成類的字節碼,然後通過類加載器將字節碼文件加載到Java虛擬機並執行的一種技術。現在主流的Java動態代理有兩種實現方式:一種是jdk自帶的,就是我們所說的jdk動態代理,一種是開源社區的開源項目CGLIB,本文主要講jdk代理的實現原理;
jdk動態代理的實現是在運行時,根據一組接口定義,使用Proxy、InvocationHandler等工具生成一個代理類和代理類實例;
如果還理解不了什麼是動態代理,請看這裏:Java代理模式詳解
JDK動態代理相關API
- Java 編譯API:
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
- InvocationHandler(接口):
該接口是proxy代理實例的調用處理程序實現的一個接口,每個proxy代理實例都有一個關聯的調用處理程序;在代理實例調用方法時,方法調用被編髮分配到調用處理程序的invoke方法; - proxy類:
該類就是用來創建一個代理對象的類,它提供了很多方法,但是我們最常用的是newProxyInstance方法;該方法有三個參數,具體參數介紹如下:
1)、CLassLoader loader:一個ClassLoader對象,定義了由那個classloader對象生成的代理類進行加載;
2)、Class<?> interfaces:一個interfaces對象數組,表示我們將要給我們的代理獨享提供一組什麼樣的接口,如果我們提供了這樣的一個接口對象數組,那麼也就是聲明瞭代理類實現了這些接口,代理類就可以調用接口中聲明的所有方法;
3)、InvocationHandler h:一個InvocationHandler對象,表示的是當動態代理對象調用方法的時候會關聯到哪一個InvocationHandler對象上,並最終由其調用;
JDK動態代理過程
- 定義一個接口:Movable,接口中有方法:move();
- 定義一個方法調用接口InvocationHandler;
- 定義一個方法調用接口實現類,用於調用真實對象的方法的同時添加自己的業務邏輯代碼;
- 實現接口的目標類(它要被代理);
- 定義一個處理器TimeHandler,重載構造方法,重寫invoke函數,添加自己的處理邏輯;
- 代理邏輯實現過程編寫:
1)、定義方法字符串,用於保存方法信息;
2)、接口方法獲取;
3)、生成實現類的對象信息
4)、動態生成 $Proxy.java文件;
5)、加載生成的 $Proxy.class字節碼指令到內存中;
6)、返回生成對象的引用;
代碼詳解
- 定義一個接口:
//定義一個接口;
public interface Moveable {
void move();
}
- 實現接口的目標類
package com.example.springbootdemotest.proxy.principle;
//實現接口的目標類
public class Tank implements Moveable
{
@Override
public void move() {
System.out.println("Tank Move...");
}
}
- 定義一個方法調用接口
package com.example.springbootdemotest.proxy.principle;
import java.lang.reflect.Method;
//定義一個方法調用接口
public interface InvocationHandler {
void invoke(Object o, Method m);
}
- 定義一個調用接口方法實現類
package com.example.springbootdemotest.proxy.principle;
import java.lang.reflect.Method;
public class TimeHandler implements InvocationHandler{
private Object target;
public TimeHandler(Object target){
this.target = target;
}
@Override
public void invoke(Object o, Method m) {
//此處自己編寫執行真實對象方法“前”業務邏輯
long start = System.currentTimeMillis();
System.out.println("StartTime:"+start);
System.out.println(o.getClass().getName());
try {
m.invoke(target);
}catch (Exception e){
e.printStackTrace();
}
//此處自己編寫執行真實對象方法“後”業務邏輯
long end = System.currentTimeMillis();
System.out.println("EndTime:"+end);
System.out.println("time:"+(end-start));
}
}
- 編寫接口實現過程:
package com.example.springbootdemotest.proxy.principle;
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;
import java.net.URL;
import java.net.URLClassLoader;
//代理對象,
public class Proxy {
public static Object newProxyInstance(Class infce, InvocationHandler h) throws Exception {
//1、定義方法字符串,用於保存方法信息;
String methodStr = "";
String rt = "\r\n";
//2、接口方法獲取;
Method[] methods = infce.getMethods();
for (Method m : methods) {
//生成方法信息;
methodStr += "@Override" + rt +
" public void "+ m.getName()+"(){"+rt+
" try{" + rt +
" Method md = " + infce.getName() + ".class.getMethod(\"" + m.getName() + "\");" + rt + //找到具體方法
" h.invoke(this,md);" + rt +
" }catch(Exception e){e.printStackTrace();}" + rt +
"}";
}
//3、生成實現類的對象信息
String src ="package com.example.springbootdemotest.proxy.principle;"+ rt +
"import java.lang.reflect.Method;" +rt +
"public class $Proxy1 implements "+ infce.getName() +"{" +rt +
" public $Proxy1(InvocationHandler h){" +rt +
" this.h = h;" +rt +
" }" +rt +
" com.example.springbootdemotest.proxy.principle.InvocationHandler h;" +rt +
methodStr+
"}";
//4、動態生成$Proxy.java文件;
//D:\software\gitRespository\springbootdemo\src\main\java\com\example\springbootdemotest\proxy\principle\$Proxy1.java
String fileName = System.getProperty("user.dir")+"/springbootdemo-test/src/main/java/com/example/springbootdemotest/proxy/principle/$Proxy1.java";
File f = new File(fileName);
FileWriter fw = new FileWriter(f);
fw.write(src);
fw.flush();
fw.close();
//5、執行動態編譯過程;
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null,null,null);
Iterable units = fileMgr.getJavaFileObjects(fileName);
JavaCompiler.CompilationTask t = compiler.getTask(null,fileMgr,null,null,null,units);
t.call();
fileMgr.close();
//6、加載生成的$Proxy.class到內存中;
//D:\software\gitRespository\springbootdemo\springbootdemo-test\src\main\java\com\example\springbootdemotest\proxy\clibPackage\MainClass.java
//URL[] urls = new URL[]{new URL("file:"+System.getProperty("user.dir")+"/src/main/java")};
URL[] urls = new URL[]{new URL("file:"+"D:/software/gitRespository/springbootdemo/springbootdemo-test/src/main/java")};
URLClassLoader ul = new URLClassLoader(urls);
Class c = ul.loadClass("com.example.springbootdemotest.proxy.principle.$Proxy1");
System.out.println(c);
Constructor ctr = c.getConstructor(InvocationHandler.class);
Object m = ctr.newInstance(h);
//m.move();
return m;
}
public static void main(String[] args) throws Exception{
Tank tank = new Tank();
InvocationHandler h = new TimeHandler(tank);
Moveable m = (Moveable) Proxy.newProxyInstance(Moveable.class,h);
m.move();
}
}
- 執行結果如下:
class com.example.springbootdemotest.proxy.principle.$Proxy1
StartTime:1576233182481
com.example.springbootdemotest.proxy.principle.$Proxy1
Tank Move...
EndTime:1576233182482
time:1
Process finished with exit code 0
- 上述過程完整模擬jdk動態代理過程,且相關類名同jdk動態代理類名相同,如有疑問,請看:Java代理模式詳解