動態代理學習(一)自己動手模擬JDK動態代理

最近一直在學習Spring的源碼,Spring底層大量使用了動態代理。所以花一些時間對動態代理的知識做一下總結。

  1. 我們自己動手模擬一個動態代理

  2. 對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中

所以我們可以這麼做:

  1. 根據我們需要生成一個.java文件
  2. 動態編譯成一個.class文件
  3. 拿到這個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的方法進行編譯,是不是就解決問題了呢?

實踐:

所以我們現在需要

  1. 拼接字符串,將上面的代碼以字符串的形式拼接出來並寫入到磁盤文件上,並命名爲xxxx.java文件

  2. 編譯.java文件,生成.class文件

  3. 加載這個class文件到JVM內存中(實際上就是方法區中),得到一個class對象

  4. 調用反射方法,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文件可以看到如下內容:

在這裏插入圖片描述

同時控制檯會正常打印:

在這裏插入圖片描述

這樣,我們就完成了一個簡單的代理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章