自己实现Java 动态代理 Proxy

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

罗政:Java 动态代理 Proxy源码详解


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

我们先来看下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 文档

引入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架构师修炼

Process finished with exit code 0
支付宝打赏 微信打赏

如果文章对您有帮助,您可以鼓励一下作者