动态代理学习(一)本身动手模拟JDK动态代理

最近一直在学习Spring的源码,Spring底层大量使用了动态代理。因此花一些时间对动态代理的知识作一下总结。java

  1. 咱们本身动手模拟一个动态代理web

  2. 对JDK动态代理的源码进行分析jvm

场景:
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对象进行代理。你们能够想一想,咱们如何去生成这个代理对象呢?ide

思路:
分析:
  • 咱们先不考虑须要针对target生成一个代理对象,就单纯的生成一个对象来讲,咱们该怎么办呢?确定是不能new的,由于咱们根本没这个类。svg

  • 因此为了动态的生成这个对象,咱们须要动态的生成一个类,也就是说动态的加载一个类到jvm中学习

因此咱们能够这么作:this

  1. 根据咱们须要生成一个.java文件
  2. 动态编译成一个.class文件
  3. 拿到这个class文件后,咱们经过反射获取一个对象

在这里插入图片描述

如今问题来了,咱们须要生成的java文件该是什么样子呢?咱们能够思考,若是要对这个类作静态代理咱们须要怎么作?url

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的方法进行编译,是否是就解决问题了呢?spa

实践:

因此咱们如今须要代理

  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文件能够看到以下内容:

在这里插入图片描述

同时控制台会正常打印:

在这里插入图片描述

这样,咱们就完成了一个简单的代理。