手写JDK动态代理------动态代理源码学习入门

JDK的动态代理相信大家都使用过,但是大家知道JDK动态代理实现的过程吗?有学习过源码吗?

今天,参照JDK动态代理的源码,仿照写了一个我自己的动态代理(乞丐版,haaaa…),相信看完此篇文章,你对JDK动态代理会有个新的理解。

-----------------------分割线------------------------

首先说明,如果你是对JDK动态代理不熟的同学,请先去复习一下;如果你是想深入学习JDK动态代理源码的大牛,该文章也不适合你哦。

首先,JDK动态代理中最重要的两个类是:
InvocationHandler接口:我们需要写一个类,实现这个接口,重写 public Object invoke(Object proxy, Method method, Object[] args),生成的代理类会调用这个实现类的invoke方法,我们可以在invoke方法里执行目标方法。

Proxy类:调用Proxy类的静态方法public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h),可以获取到我们想要的代理对象。

今天的手写,我们也会写一个自己的InvocationHandler接口和Proxy类,希望在API上,做到高仿。

整体手写思路:根据传进来的实现接口,我们构建一个JAVA类的代码字符串,并将字符串写入一个临时的文件中,动态编译这个文件,并动态加载到JVM中来,最后new一个对象返回给用户。

当然,正版的JDK动态代理不需要我这么麻烦,能跳过前面的步骤,直接生成calss文件的byte[ ],然后将其加载到JVM中来,减少了非常多的IO,性能上会高不少。

下面是业务代码,我需要对UserServiceImpl 进行代理

package cn.java.test;

/**
 * @Classname UserServiceImpl
 * @Description: 这是业务接口的实现类,我们需要对这个类进行代理
 * @date 2020/3/25 8:58
 */
public class UserServiceImpl implements UserService {

	@Override
	public String query() {
		System.out.println("假装查询了数据");
		return "这是数据";
	}

	@Override
	public void add(String str) {
		System.out.println("假装新增了:" + str);
	}
}

UserServiceImpl 实现的接口,

package cn.java.test;

/**
 * @Classname UserService
 * @Description: 这是我们的业务接口
 * @date 2020/3/25 8:57
 */
public interface UserService {
	String query();

	void add(String str);
}

然后仿照正版动态代理,写一个MyInvocationHandler和实现此接口的业务类UserServiceHandler

package cn.java.proxy;

import java.lang.reflect.Method;

/**
 * @author mwl
 * @Classname MyInvocationHandler
 * @Description:
 * @date 2020/3/25 8:54
 */
public interface MyInvocationHandler {
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable;
}

package cn.java.test;

import cn.java.proxy.MyInvocationHandler;

import java.lang.reflect.Method;

/**
 * @author mwl
 * @Classname UserServiceHandler
 * @Description: 被代理后的方法都会调用到此类的invoke方法里,在invoke里可以执行目标方法
 * @date 2020/3/25 9:02
 */
public class UserServiceHandler implements MyInvocationHandler {
	private UserService target;

	public UserServiceHandler(UserService target) {
		this.target = target;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("--->通过代理类执行了此方法UserServiceHandler.invoke()<---");
		return method.invoke(target, args);
	}
}

为了便于我们构建动态的java代码字符串,先手写一个代理类出来,方便参考:

package cn.java.test;

import cn.java.proxy.MyInvocationHandler;
import cn.java.proxy.MyProxy;

import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;

/**
 * 这个是动态生成的代理类示例,可以作为参考,JDK动态代理生成的代理类也会默认继承Proxy,
 * 将InvocationHandler的实现类保存到父类里。这也是为什么JDK动态代理不能基于继承的原因(JAVA是单继承的嘛)。
 */
public class $Proxy extends MyProxy implements UserService {
	private static Method m0;//query
	private static Method m1;//add
	
	public $Proxy(MyInvocationHandler h) {
		super(h);
	}

	static {
		try {
			m0 = Class.forName("cn.java.test.UserService").getMethod("query", (Class[]) null);
			m1 = Class.forName("cn.java.test.UserService").getMethod("add", Class.forName("java.lang.String"));
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}

	}

	public String query() {
		try {
			return (String) super.h.invoke(this, m0, (Object[]) null);
		} catch (Throwable e) {
			//不管业务接口上有没有抛出异常,这里统一捕捉,将Throwable包装为运行时异常抛出
			throw new UndeclaredThrowableException(e);
		}
	}

	public void add(String str) {
		try {
			h.invoke(this, m1, new Object[]{str});
		} catch (Throwable e) {
			//不管业务接口上有没有抛出异常,这里统一捕捉,将Throwable包装为运行时异常抛出
			throw new UndeclaredThrowableException(e);
		}
	}
}

接下来要做的事就简单了,我们依据上面的参考代码,动态的构建出java代码的字符串就好了。创建一个MyProxy类,将生成代码的逻辑放在newProxyInstance方法里

package cn.java.proxy;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;

/**
 * @author mwl
 * @Classname MyProxy
 * @Description:
 * @date 2020/3/25 8:54
 */
public class MyProxy {
	//生成的代理对象的所在包名
	private static final String BASE_PACKAGE = "com.sun";
	//生成的临时文件的所在地址
	private static final String BASE_DIRECTORY = "D:\\\\";
	//记录类的名称,防止重名
	private static Integer ClassNum = 0;
	protected MyInvocationHandler h;

	protected MyProxy(MyInvocationHandler h) {
		this.h = h;
	}

	/**
	 * 此方法原理是根据传入的参数先构建出java代码的文本,再将其写入磁盘,再动态编译,然后加载进JVM,
	 * new 个代理对象出来返回。
	 * 真实的jdk动态代理会跳过前面的步骤,直接生成class文件的byte[],直接加载进JVM,减少了磁盘IO的操作
	 *
	 * @param loader
	 * @param interfaces
	 * @param h
	 * @return
	 * @throws IllegalArgumentException
	 */
	public static Object newProxyInstance(ClassLoader loader,
	                                      Class<?>[] interfaces,
	                                      MyInvocationHandler h)
			throws IllegalArgumentException {
		String className = "$Proxy" + ClassNum++;
		//动态生成代理的类的java代码字符串
		String javaContext = getJavaContext(className, interfaces);
		//将字符串写入临时的java文件中;
		File file = writeToFile(className, javaContext);
		//将java文件编译成class文件
		compilerJavaFile(file);
		//将动态生成的代理类加载进JVM
		Class aClass = loadJavaFile(loader, className);
		//创建代理对象
		return newInstance(aClass, h);
	}

	/**
	 * 动态生成代理的类的java代码字符串
	 *
	 * @param className
	 * @param interfaces
	 * @return
	 */
	private static String getJavaContext(String className, Class<?>[] interfaces) {
		//生成字符串的java代码
		//写出到文件
		String tab = "\t";
		String line = "\n";
		//构建类头部信息
		String s0 = "package " + BASE_PACKAGE + ";" + line;
		String s1 = "import cn.java.proxy.MyInvocationHandler;" + line +
				"import cn.java.proxy.MyProxy;" + line +
				"import java.lang.reflect.Method;" + line +
				"import java.lang.reflect.UndeclaredThrowableException;" + line;
		//构建类定义
		String s2 = "public class " + className + " extends MyProxy";
		if (interfaces.length > 0) {
			s2 += " implements ";
		}
		for (int i = 0; i < interfaces.length; i++) {
			Class<?> anInterface = interfaces[i];
			s2 += anInterface.getTypeName();
			if (i != interfaces.length - 1) {
				s2 += ",";
			}
		}
		s2 += "{";

		//构建成员变量--代理方法
		String s3 = "";
		Integer mNum = 0;
		List<MethodInfo> methodInfos = new ArrayList<>();
		for (Class<?> anInterface : interfaces) {
			Method[] methods = anInterface.getMethods();
			for (int i = 0; i < methods.length; i++) {
				String methodName = "m" + mNum++;
				s3 += tab + "private static Method " + methodName + ";" + line;
				methodInfos.add(new MethodInfo(methodName, methods[i], anInterface.getTypeName()));
			}
		}
		//构建成员变量-InvocationHandler
		String s4 = tab + "public " + className + "(MyInvocationHandler h) {" + line +
				tab + tab + "super(h);" + line + tab + "}";
		//构建静态代码块
		String s5 = line + tab + "static {" + line +
				tab + tab + "try {" + line;
		for (MethodInfo value : methodInfos) {
			s5 += tab + tab + tab + value.getMethodName() + " = Class.forName(\"" + value.getInterfaceName() + "\").getMethod(\"" + value.getMethod().getName() + "\", ";
			Class<?>[] parameterTypes = value.getMethod().getParameterTypes();
			if (parameterTypes == null || parameterTypes.length == 0) {
				s5 += "(Class[]) null";
			} else {
				for (int i = 0; i < parameterTypes.length; i++) {
					s5 += "Class.forName(\"" + parameterTypes[i].getTypeName() + "\")";
					if (i != parameterTypes.length - 1) {
						s5 += ",";
					}
				}
			}
			s5 += ");" + line;
		}
		s5 += tab + tab + "} catch (NoSuchMethodException e) {" + line +
				tab + tab + "} catch (ClassNotFoundException e) {" + line +
				tab + tab + "}" + line +
				tab + "}";
		//构建代理方法方法体
		String s6 = line;
		for (MethodInfo methodInfo : methodInfos) {
			Method method = methodInfo.getMethod();
			s6 += tab + "public " + method.getReturnType().getTypeName() + " " + method.getName() + "(";
			Class<?>[] parameterTypes = method.getParameterTypes();
			String scTemp = "new Object[]{}";
			if (parameterTypes != null && parameterTypes.length > 0) {
				for (int i = 0; i < parameterTypes.length; i++) {
					s6 += parameterTypes[i].getTypeName() + " var" + i;
					scTemp = scTemp.replaceAll("}", "var" + i + "}");
					if (i != parameterTypes.length - 1) {
						scTemp = scTemp.replaceAll("}", ",}");
						s6 += ",";
					}
				}
			} else {
				scTemp = "null";
			}
			s6 += "){" + line +
					tab + tab + "try {" + line +
					tab + tab + tab + (method.getReturnType().getName().equals("void") ? "" : "return (" + method.getReturnType().getTypeName() + ")") + " super.h.invoke(this, " + methodInfo.getMethodName() + "," + scTemp + ");" + line +
					tab + tab + "} catch (Throwable e) {" + line +
					tab + tab + tab + "throw new UndeclaredThrowableException(e);" + line +
					tab + tab + "}" + line +
					tab + "}" + line;
		}
		//构建结尾符号
		String s7 = "}";
		//拼接类文本
		return s0 + line + s1 + line + s2 + line + s3 + line + s4 + line + s5 + line + s6 + line + s7;
	}

	/**
	 * 将字符串写入临时的java文件中
	 *
	 * @param className
	 * @param javaContext
	 * @return
	 */
	private static File writeToFile(String className, String javaContext) {
		String filePath = BASE_DIRECTORY + BASE_PACKAGE.replace(".", "\\") + "\\" + className + ".java";
		File file = new File(filePath);
		if (!file.getParentFile().exists()) {
			file.getParentFile().mkdirs();
		}
		try (FileWriter fileWriter = new FileWriter(file)) {
			fileWriter.write(javaContext);
			fileWriter.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return file;
	}

	/**
	 * 将java文件编译成class文件
	 *
	 * @param file
	 */
	private static void compilerJavaFile(File file) {
		JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
		StandardJavaFileManager sjfm = jc.getStandardFileManager(null, null, null);
		Iterable<? extends JavaFileObject> units = sjfm.getJavaFileObjects(file);
		JavaCompiler.CompilationTask task = jc.getTask(null, sjfm, null, null, null, units);
		task.call();
		try {
			sjfm.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}


	/**
	 * 将动态生成的代理类加载进JVM
	 *
	 * @param loader
	 * @param className
	 */
	private static Class loadJavaFile(ClassLoader loader, String className) {
		try {
			loader = new URLClassLoader(new URL[]{new URL("file:" + BASE_DIRECTORY)});
			Class<?> aClass = loader.loadClass(BASE_PACKAGE + "." + className);
			return aClass;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 创建代理对象
	 *
	 * @param aClass
	 * @return
	 */
	private static Object newInstance(Class aClass, MyInvocationHandler h) {
		try {
			Constructor constructor = aClass.getConstructor(MyInvocationHandler.class);
			return constructor.newInstance(h);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 用来封装代理类信息的内部类
	 */
	private static class MethodInfo {
		private String methodName;
		private Method method;
		private String interfaceName;

		public MethodInfo(String methodName, Method method, String interfaceName) {
			this.methodName = methodName;
			this.method = method;
			this.interfaceName = interfaceName;
		}

		public String getMethodName() {
			return methodName;
		}

		public Method getMethod() {
			return method;
		}

		public String getInterfaceName() {
			return interfaceName;
		}
	}
}

再来写一个测试类:

package cn.java.test;

import cn.java.proxy.MyProxy;

/**
 * @author mwl
 * @Classname TestWindow
 * @Description:
 * @date 2020/3/25 8:57
 */
public class TestWindow {
	public static void main(String[] args) {
		//获取代理类
		UserService proxy = (UserService) MyProxy.newProxyInstance(
				TestWindow.class.getClassLoader(),
				new Class[]{UserService.class},
				new UserServiceHandler(new UserServiceImpl()));
		//测试
		String query = proxy.query();
		System.out.println("----------------分割线---------------");
		proxy.add(query);
	}
}

控制台输出结果:

--->通过代理类执行了此方法UserServiceHandler.invoke()<---
假装查询了数据
----------------分割线---------------
--->通过代理类执行了此方法UserServiceHandler.invoke()<---
假装新增了:这是数据

完美~实现了和正版动态代理一样的效果!当然,正版还会有多很多安全验证之类的代码,这里就给简化掉啦,最后强调一遍,JDK动态代理的主要逻辑和手写版不一样的地方在于,他会直接生成class文件的二进制流,直接加载到JVM中,中间涉及到很多字节码技术和native方法,有兴趣的同学可以去看看正版的源码。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值