最近一直在學習Spring的源碼,Spring底層大量使用了動態代理。所以花一些時間對動態代理的知識做一下總結。
我們自己動手模擬一個動態代理
對JDK動態代理的源碼進行分析
場景:
public interface MyService {
void test01();
void test02(String s);
}
public class MyServiceImpl implements MyService {
@Override
public void test01() {
System.out.println("test01");
}
@Override
public void test02(String s) {
System.out.println(s);
}
}
public class Main {
public static void main(String[] args) {
MyServiceImpl target = new MyServiceImpl();
}
}
我們現在要對target
對象進行代理。大家可以想想,我們如何去生成這個代理對象呢?
思路:
分析:
-
我們先不考慮需要針對target生成一個代理對象,就單純的生成一個對象來說,我們該怎麼辦呢?肯定是不能new的,因爲我們根本沒這個類。
-
所以爲了動態的生成這個對象,我們需要動態的生成一個類,也就是說動態的加載一個類到jvm中
所以我們可以這麼做:
- 根據我們需要生成一個.java文件
- 動態編譯成一個.class文件
- 拿到這個class文件後,我們通過反射獲取一個對象
現在問題來了,我們需要生成的java文件該是什麼樣子呢?我們可以思考,如果要對這個類做靜態代理我們需要怎麼做?
package com.dmz.proxy;
import com.dmz.proxy.target.MyService;
/**
* 靜態代理
*/
public class StaticProxy implements MyService {
private MyService target;
public StaticProxy(MyService target) {
this.target = target;
}
@Override
public void test01() {
System.out.println("proxy print log for test01");
target.test01();
}
@Override
public void test02(String s) {
System.out.println("proxy print log for test02");
target.test02(s);
}
}
上面就是靜態代理的代碼,如果我們可以動態的生成這樣的一個.java文件,然後調用jdk的方法進行編譯,是不是就解決問題了呢?
實踐:
所以我們現在需要
-
拼接字符串,將上面的代碼以字符串的形式拼接出來並寫入到磁盤文件上,並命名爲xxxx.java文件
-
編譯.java文件,生成.class文件
-
加載這個class文件到JVM內存中(實際上就是方法區中),得到一個class對象
-
調用反射方法,class.newInstanc(…)
代碼如下:
public class ProxyUtil {
/**
* @param target 目標對象
* @return 代理對象
*/
public static Object newInstance(Object target) {
Object proxy = null;
// 開始拼接字符串
Class targetInf = target.getClass().getInterfaces()[0];
Method[] methods = targetInf.getDeclaredMethods();
String line = System.lineSeparator();
String tab = "\t";
String infName = targetInf.getSimpleName();
String content = "";
String packageContent = "package com.dmz.proxy;" + line;
String importContent = "import " + targetInf.getName() + ";" + line;
String clazzFirstLineContent = "public class $Proxy implements " + infName + "{" + line;
String filedContent = tab + "private " + infName + " target;" + line;
String constructorContent = tab + "public $Proxy (" + infName + " target){" + line
+ tab + tab + "this.target =target;"
+ line + tab + "}" + line;
String methodContent = "";
for (Method method : methods) {
String returnTypeName = method.getReturnType().getSimpleName();
String methodName = method.getName();
// Sting.class String.class
Class args[] = method.getParameterTypes();
String argsContent = "";
String paramsContent = "";
int flag = 0;
for (Class arg : args) {
String temp = arg.getSimpleName();
//String
//String p0,Sting p1,
argsContent += temp + " p" + flag + ",";
paramsContent += "p" + flag + ",";
flag++;
}
if (argsContent.length() > 0) {
argsContent = argsContent.substring(0, argsContent.lastIndexOf(",") - 1);
paramsContent = paramsContent.substring(0, paramsContent.lastIndexOf(",") - 1);
}
methodContent += tab + "public " + returnTypeName + " " + methodName + "(" + argsContent + ") {" + line
+ tab + tab + "System.out.println(\"proxy print log for " + methodName + "\");" + line
+ tab + tab + "target." + methodName + "(" + paramsContent + ");" + line
+ tab + "}" + line;
}
content = packageContent + importContent + clazzFirstLineContent + filedContent + constructorContent + methodContent + "}";
//字符串拼接結束
// 開始生成.java文件
File file = new File("g:\\com\\dmz\\proxy\\$Proxy.java");
try {
if (!file.exists()) {
file.createNewFile();
}
FileWriter fw = new FileWriter(file);
fw.write(content);
fw.flush();
fw.close();
// .java文件生成結束
// 開始進行編譯
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
Iterable units = fileMgr.getJavaFileObjects(file);
JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
t.call();
fileMgr.close();
// 編譯結束,生成.class文件
// 從G盤中加載class文件
URL[] urls = new URL[]{new URL("file:G:\\\\")};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
// 加載
Class clazz = urlClassLoader.loadClass("com.dmz.proxy.$Proxy");
// 加載結束
// 構造代理對象
Constructor constructor = clazz.getConstructor(targetInf);
proxy = constructor.newInstance(target);
} catch (Exception e) {
e.printStackTrace();
}
return proxy;
}
}
我們調用這個方法:
public class Main {
public static void main(String[] args) {
MyServiceImpl target = new MyServiceImpl();
MyService o = ((MyService) ProxyUtil.newInstance(target));
o.test01();
o.test02("test02");
}
}
會在我們的G盤中生成文件:
打開.java文件可以看到如下內容:
同時控制檯會正常打印:
這樣,我們就完成了一個簡單的代理。