自己实现Java 动态代理 Proxy

jdk Proxy的源码解析在下面这篇文章。建议看完在看今天的自己实现,否则可能会看不懂。

罗政:Java 动态代理 Proxy源码详解3 赞同 · 1 评论文章

代码是程序之根基,我们只了解理论是学不好程序的,必须要实实在在的敲代码,今天我们就来实操代码来实现自己的动态代理。代码不多,但一定要手敲

我们先来看下JDK是怎么用动态代理的:

public class Main { // 相亲代理机构 static class FindWomanProxy implements InvocationHandler{ // 被代理的对象 private FindWoman woman; public FindWomanProxy(FindWoman woman) { this.woman = woman ; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //在真实的对象执行之前我们可以添加自己的操作 System.out.println("相亲代理正在筛选A杯的女士"); // 筛选 完成 交给 程序员去消费相亲 return method.invoke(woman, args); } } // 相亲 接口 static interface FindWoman { public void find(); } // java 程序员相亲 static class JavaFindWoman implements FindWoman{ @Override public void find() { System.err.println("java程序员相亲 "); } } public static void main(String[] args) { // 构造相亲代理 , 把java程序员传进去 FindWomanProxy proxy = new FindWomanProxy(new JavaFindWoman()); // 机构产出的相亲代理对象,并非传入的 JavaFindWoman 。 FindWoman findWoman = (FindWoman)Proxy.newProxyInstance(Main.class.getClassLoader(), JavaFindWoman.class.getInterfaces(), proxy); findWoman.find(); } }

我们可以提取出以下信息:

Proxy有个静态方法newProxyInstance用来动态生成接口的实现。InvocationHandler 为代理机构要实现的接口,invoke方法就是回调真正代理类方法的途径。

开始动手

首先新建InvocationHandler接口,替换jdk的。

package debug_jdk8; import java.lang.reflect.Method; public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }

新建Proxy类,也替换JDK的Proxy。Proxy需要干下面事情:

动态生成一个Class ,这个Class必须要实现客户端接口(例子中的FindWoman接口)。重写这个find方法,应该转到调用InvocationHandler的invoke方法,这也是代理的核心。将利用反射构造Class实例返回。public class Proxy { // 类的名称 自增 计数 final static AtomicLong atomicLong = new AtomicLong(); // 利用 Javassist 作为底层实现 , 你也可以用cglib , asm ,javax.tools.JavaCompiler 等 final static JavassistClassGenerator classGenerator = new JavassistClassGenerator(); // 通过接口的全限定名(com.a.b.FindWoman) 截取包名:com.a.b private static String getProxyClassPckName(Class<?> interClazz) { // 接口名 比如 : com.a.b.A String name = interClazz.getName(); int n = name.lastIndexOf(.); // 截取包名 : com.a.b return ((n == -1) ? "" : name.substring(0, n)); } public static Object newProxyInstance(Class<?> interClazz, InvocationHandler handler) { try { // 生成类 Class<?> clazz = classGenerator.createClass(getProxyClassPckName(interClazz), "$Proxy" + atomicLong.getAndIncrement(), interClazz); return clazz.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } }

代码还是很容易懂的,主要就是三步曲

截取接口包名,也就是我们动态生成class的包名。class的类名称为:$Proxy0 , $Proxy1 , $Proxy2 ....交给JavassistClassGenerator 生成器去动态产生类。利用反射newInstance 去实例化Class对象。

关键就是我们的JavassistClassGenerator 组件。我们生成的class应该是这样的:

public class $Proxy0 implements FindWoman { private InvocationHandler invocationHandler; public $Proxy(InvocationHandler paramInvocationHandler) { this.invocationHandler = paramInvocationHandler; } public void find(int paramInt) { Method method = Class.forName("debug_jdk8.JavassistClassGenerator$FindWoman").getMethod("find", new Class[] { int.class }); this.invocationHandler.invoke(this, method, new Object[] { paramInt }); } }

客户端FindWoman接口调用find方法,也就是$Proxy0实例调用find方法,会先通过反射找到find的Method,然后执行InvocationHandler的invoke,是不是就执行到了客户端的代理机构FindWomanProxy了。

Javassist为一个动态生成类的框架,文档在这:

罗政:Javassist 文档0 赞同 · 0 评论文章

引入Javassist maven:

<dependency> <groupId>javassist</groupId> <artifactId>javassist</artifactId> <version>3.12.1.GA</version> </dependency>

​JavassistClassGenerator 的实现:

public class JavassistClassGenerator { public Class<?> createClass(String proxyClassPckName, String className, Class<?> interClazz) throws Exception { // ClassPool:CtClass对象的容器 ClassPool pool = ClassPool.getDefault(); // 通过ClassPool生成一个public新类 CtClass ctClass = pool.makeClass(proxyClassPckName + "." + className); // 为类 添加接口 ctClass.addInterface(pool.get(interClazz.getName())); // 添加 InvocationHandler 成员变量 => private InvocationHandler invocationHandler; String invocationHandlerName = InvocationHandler.class.getName(); CtField enameField = new CtField(pool.getCtClass(invocationHandlerName), "invocationHandler", ctClass); enameField.setModifiers(Modifier.PRIVATE); ctClass.addField(enameField); // 添加构造函数 => this.invocationHandler = invocationHandler; CtConstructor cons = new CtConstructor(new CtClass[] { pool.get(invocationHandlerName) }, ctClass); // $0=this / $1,$2,$3... 代表方法参数 cons.setBody("{$0.invocationHandler = $1;}");// this.invocationHandler = invocationHandler; ctClass.addConstructor(cons); // 添加方法 ,有多个,还包括 toStrng ,hashcode等Object的方法。 for (Method method : interClazz.getDeclaredMethods()) { // 返回值 CtClass returnType = pool.get(method.getReturnType().getName()); if (hasObjectMethod(method)) { // 如果是Object里面的方法,就直接返回调用Object类的 continue; } // 构造方法参数(根据接口method的参数来) CtClass[] parameters = convertParameters(pool, method.getParameterTypes()); CtMethod ctMethod = new CtMethod(returnType, method.getName(), parameters, ctClass); // 添加方法的 代码 ctMethod.setBody(getMethodBody(method, interClazz)); // 添加方法 ctClass.addMethod(ctMethod); } System.out.println("生成的class文件路径:" + this.getClass().getResource("").getPath()); ctClass.writeFile(this.getClass().getResource("").getPath()); return ctClass.toClass(); } private String getMethodBody(Method method, Class<?> interClazz) { StringBuilder sb = new StringBuilder(); sb.append("{").append("\r\n"); // 获取客户端接口的 class sb.append("java.lang.Class clazz = Class.forName(\"" + interClazz.getName() + "\");").append("\r\n"); // 获取对应方法 // java.lang.reflect.Method curMethod = interClazz.getDeclaredMethod("",new Class[] {}); // 获取参数 String typeNames = getParameterTypeNames(method.getParameterTypes()); if(typeNames.equals("")) { //将方法参数 (int a,String b) 变成 int.class,String.class sb.append("java.lang.reflect.Method curMethod = clazz.getDeclaredMethod(\"" + method.getName() + "\", null);").append("\r\n"); }else { sb.append("java.lang.reflect.Method curMethod = clazz.getDeclaredMethod(\"" + method.getName() + "\", new Class[] {" + typeNames + "});").append("\r\n"); } sb.append("invocationHandler.invoke(this,curMethod, $args);").append("\r\n"); sb.append("}"); System.out.println("生成的" + method.getName() +" 方法体如下:"); System.out.println(sb.toString()); return sb.toString(); } // 将方法参数 (int a,String b) 变成 int.class,String.class private String getParameterTypeNames(Class<?>[] jdkParameters) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < jdkParameters.length; i++) { sb.append(jdkParameters[i].getName()).append(","); } if (sb.length() > 0) { sb.deleteCharAt(sb.length() - 1); // 删除最后一个字符 , } return sb.toString(); } // 将jdk的参数class数组。转成 Javassist的CtClass数组 private CtClass[] convertParameters(ClassPool pool, Class<?>[] jdkParameters) throws NotFoundException { if (jdkParameters == null || jdkParameters.length == 0) { return null; } CtClass[] pCtClasses = new CtClass[jdkParameters.length]; for (int i = 0; i < jdkParameters.length; i++) { pCtClasses[i] = pool.get(jdkParameters[i].getName()); } return pCtClasses; } // 是不是Object里面的方法 :toStrng ,hashcode等 private boolean hasObjectMethod(Method method) { for (Method objMethd : Object.class.getDeclaredMethods()) { if (method.getName().equals(objMethd.getName())) { return true; } } return false; } }

其实就是 Javassist 用法。就是为了拼装生成这样一个class:

public class $Proxy0 implements FindWoman { private InvocationHandler invocationHandler; public $Proxy(InvocationHandler paramInvocationHandler) { this.invocationHandler = paramInvocationHandler; } public void find(int paramInt) { Method method = Class.forName("debug_jdk8.JavassistClassGenerator$FindWoman").getMethod("find", new Class[] { int.class }); this.invocationHandler.invoke(this, method, new Object[] { paramInt }); } }

客户端使用

public class Main { // 相亲代理 public static class FindWomanProxy implements InvocationHandler{ // 被代理的对象 private FindWoman woman; public FindWomanProxy(FindWoman woman) { this.woman = woman ; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //在真实的对象执行之前我们可以添加自己的操作 System.out.println("相亲代理正在筛选A照杯的女士"); // 筛选 完成 交给 程序员去消费相亲 return method.invoke(woman, args); } } // 相亲 接口 public static interface FindWoman { public void find(); } // java 程序员相亲 public static class JavaFindWoman implements FindWoman{ @Override public void find() { System.err.println("java程序员相亲 "); } } public static void main(String[] args) throws IOException { // 构造相亲代理 , 把java程序员传进去 FindWomanProxy proxy = new FindWomanProxy(new JavaFindWoman()); FindWoman findWoman = (FindWoman) Proxy.newProxyInstance(FindWoman.class, proxy); findWoman.find(); } }

启动输出

生成的find 方法体如下: { java.lang.Class clazz = Class.forName("com.hadluo.test.proxy.FindWoman"); java.lang.reflect.Method curMethod = clazz.getDeclaredMethod("find", null); invocationHandler.invoke(this,curMethod, $args); } 生成的class文件路径:/D:/hadluo/java_project/hadluo-test/target/classes/com/hadluo/test/proxy/ 相亲代理正在筛选A杯的女士 java程序员相亲

找到class文件,用jd-gui 工具打开:

package com.hadluo.test.proxy; import java.lang.reflect.Method; public class $Proxy0 implements FindWoman { private InvocationHandler invocationHandler; public $Proxy0(InvocationHandler paramInvocationHandler) { this.invocationHandler = paramInvocationHandler; } public void find() { Class<?> clazz = Class.forName("com.hadluo.test.proxy.FindWoman"); Method method = clazz.getDeclaredMethod("find", null); this.invocationHandler.invoke(this, method, new Object[0]); } }

强烈推荐一个java进阶架构师的博客

Java架构师修炼​githubs.xyz/