JDK动态代理的简单实现

JDK动态代理的简单实现

前置说明

首先说明一下就是, jdk动态代理,为什么是动态,是因为其在运行时帮你生成了代理类,这里其实本质上还是生成一个静态代理类, 然后通过InvocationHandler的invoke(反射的方式)来调用真正的对象, 所以我的理解动态代理就是运行时生成+反射调用.(其实不局限于运行时生成,也可以是类加载生成或者类编译时生成).
这次我会用两种方式来实现动态代理.

动手实现

实现方式一

第一种实现方式的思路就是, 按照我们平常的编写代码逻辑,也就是先编写java文件, 然后编译成class文件,然后加载到jvm中去,所以其核心逻辑就是拼接java文件的内容

各位应该记得jdk的动态代理的api吧, 如下

IService instance = (IService) Proxy.newProxyInstance(new MyClassLoader(Thread.currentThread().getContextClassLoader()), //这里我随便定义了classloader, 因为可以自定义生成的类路径, 所以可以通过自定义类加载器去加载class,并在加载后可以删除文件
				new Class[]{IService.class}, //需要实现的接口
				(proxy, method, args1) -> {
					System.out.println("before");
					return method.invoke(service, args1); //真正的执行
				});

那么先看一下我们要生成的java模板吧

//首先模板要有声明包
package com.cdy.demo.repeatedWheels.myproxy;
// import 都给他省略了,因为下面都是使用全限定名

//动态代理类的名称这里简化为$Proxy+递增的数字, 然后其继承传进来的接口
public class $Proxy1 implements com.cdy.demo.repeatedWheels.myproxy.IService{
// 这里是真正的委托执行器
com.cdy.demo.repeatedWheels.myproxy.InvocationHandler h;
public $Proxy1(com.cdy.demo.repeatedWheels.myproxy.InvocationHandler h) {
		this.h = h;
}
public java.lang.String doService(java.lang.String arg0){
		try{
		// 其实为了提供性能, 可以写一个static,提前把所有的method都准备好,这样不用再执行时再去查找,提高新=性能
			java.lang.reflect.Method m = com.cdy.demo.repeatedWheels.myproxy.IService.class.getMethod("doService",new java.lang.Class[]{java.lang.String.class});
			// 调用委托类
			return (java.lang.String)this.h.invoke(this,m,new java.lang.Object[]{arg0});
		}catch(java.lang.Throwable e){throw new java.lang.RuntimeException(e);}
}
}

具体的模板拼接代码如下, 也没啥好看的, 就是动态生成所有的对应方法,生成的结果就是上面的代码模板

String ln = "\r\n"; //换行 定义成static就可以
AtomicInteger integer = new AtomicInteger(0); //递增数 也定义从全局的就可以了

StringBuilder src = new StringBuilder();
		String name = MyClassLoader.class.getPackage().getName();
		src.append("package ").append(name).append(";").append(ln);
		src.append("public class ").append(className).append(" implements ").append(interfaces.getName()).append("{").append(ln);
		
		src.append(InvocationHandler.class.getName()).append(" h;").append(ln);
		
		src.append("public ").append(className).append("(").append(InvocationHandler.class.getName()).append(" h) {").append(ln);
		src.append("this.h = h;").append(ln);
		src.append("}").append(ln);
		
		for (Method m : interfaces.getMethods()) {
			StringBuilder params = new StringBuilder();
			StringBuilder params2 = new StringBuilder();
			StringBuilder classes = new StringBuilder();
			for (Parameter parameter : m.getParameters()) {
				params.append(parameter.getType().getName()).append(" ").append(parameter.getName()).append(",");
				classes.append(parameter.getType().getName()).append(".class").append(",");
				params2.append(parameter.getName()).append(",");
			}
			params.deleteCharAt(params.lastIndexOf(","));
			params2.deleteCharAt(params2.lastIndexOf(","));
			classes.deleteCharAt(classes.lastIndexOf(","));
			String returnType = m.getReturnType().getName();
			src.append("public ").append(returnType).append(" ")
					.append(m.getName()).append("(").append(params.toString()).append("){").append(ln);
			
			src.append("try{").append(ln);
			src.append("java.lang.reflect.Method m = ").append(interfaces.getName()).append(".class.getMethod(\"")
					.append(m.getName()).append("\",new java.lang.Class[]{").append(classes).append("});").append(ln);
			if (!returnType.contains("void")) {
				src.append("return ").append("(").append(returnType).append(")");
			}
			src.append("this.h.invoke(this,m,").append("new java.lang.Object[]{").append(params2).append("}").append(");").append(ln);
			src.append("}catch(java.lang.Throwable e){throw new java.lang.RuntimeException(e);}").append(ln);
			src.append("}").append(ln);
		}
		
		src.append("}");
		
		String s = src.toString();
		System.out.println(s);
		return s;

接下来就是重头戏了, 也就是jdk动态代理api的实现模板了

			//1 生成类文件
			int num = integer.incrementAndGet();
			
			String className ="$Proxy" + num ;
			String proxySrc = generateSrc(interfaces[0], className);
			
			//2 加载类文件
			String filePath = Proxy.class.getResource("").getPath();
			File f = new File(filePath + className + ".java");
			f.deleteOnExit();
			File classFile = new File(filePath + className + ".class");
			classFile.deleteOnExit();
			
			FileWriter fw = new FileWriter(f);
			fw.write(proxySrc);
			fw.flush();
			fw.close();
		
			//3. 通过java api提供的编译器进行编译
			JavaCompiler  compiler = ToolProvider.getSystemJavaCompiler();
			StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
			Iterable iterable = manager.getJavaFileObjects(f);
			CompilationTask task = compiler.getTask(null, manager, null, null, null, iterable);
			task.call();
			manager.close();
			
			//4. 类加载器加载类
			Class proxyClass = classLoader.findClass(className);
			Constructor c = proxyClass.getConstructor(InvocationHandler.class);
			f.delete();
			
			//5. 返回动态代理类的实例
			return c.newInstance(h);

最后的执行结果就是输出一个before,来表明走了我们的代理新增逻辑了.(这里和装饰器模式的概念有点冲, 因为新增功能是装饰器模式的概念, 而代理模式仅表明是原实例的代理转发功能,不过也不影响我们的理解动态的思路)

实现方式二

各位应该发现了,我们上面的步骤其实多了一步,就是生产java文件, 因为jvm认识的是class,我们直接给他class就好了,为什么还多生成一个java文件, 这又不是我们平常写代码,写完代码统一用maven编译,这可是已经在运行中了啊, 所以我们应该直接生成class文件才对.
所以这里就用到字节码生成器 javassist了, 这个包的底层实现用的ASM(但是直接写字节码太复杂了).

然后就是具体的实现, 具体的实现和上面基本大同小异, 主要还是我不能直接写出全部的字节码文件(又难又累,倒是可以先生成字节码,然后在改成通用的,但是也麻烦啊), 所以主要还是将之前的java文件内容交给javassist去翻译为class文件,然后直接加载到jvm中去,可以提升很大的性能.
具体的实现如下


        try {
            int num = integer.incrementAndGet();
            String className = "$Proxy" + num ;
        	// 创建类
            CtClass ctClass = mPool.makeClass(className);
            //添加接口
            ctClass.addInterface(mPool.get(interfaces[0].getName()));
			//添加成员变量
			ctClass.addField(CtField.make("com.cdy.demo.repeatedWheels.myproxy.InvocationHandler h;", ctClass));
        
            String constructor = "public " + className + "(" + InvocationHandler.class.getName() + " h) {" + ln +
                    "this.h = h;" + ln +
                    "}" + ln;
            //添加构造函数
            ctClass.addConstructor(CtNewConstructor.make(constructor,ctClass));
        
            for (Method m : interfaces[0].getMethods()) {
                StringBuilder method = new StringBuilder();
                StringBuilder params = new StringBuilder();
                StringBuilder params2 = new StringBuilder();
                StringBuilder classes = new StringBuilder();
                for (Parameter parameter : m.getParameters()) {
                    params.append(parameter.getType().getName()).append(" ").append(parameter.getName()).append(",");
                    classes.append(parameter.getType().getName()).append(".class").append(",");
                    params2.append(parameter.getName()).append(",");
                }
                params.deleteCharAt(params.lastIndexOf(","));
                params2.deleteCharAt(params2.lastIndexOf(","));
                classes.deleteCharAt(classes.lastIndexOf(","));
                String returnType = m.getReturnType().getName();
                method.append("public ").append(returnType).append(" ")
                        .append(m.getName()).append("(").append(params.toString()).append("){").append(ln);
        
                method.append("try{").append(ln);
                method.append("java.lang.reflect.Method m = ").append(interfaces[0].getName()).append(".class.getMethod(\"")
                        .append(m.getName()).append("\",new java.lang.Class[]{").append(classes).append("});").append(ln);
                if (!returnType.contains("void")) {
                    method.append("return ").append("(").append(returnType).append(")");
                }
                method.append("this.h.invoke(this,m,").append("new java.lang.Object[]{").append(params2).append("}").append(");").append(ln);
                method.append("}catch(java.lang.Throwable e){throw new java.lang.RuntimeException(e);}").append(ln);
                method.append("}").append(ln);
                // 添加方法
                ctClass.addMethod(CtMethod.make(method.toString(), ctClass));
            }
            //生成类
            Class clazz = ctClass.toClass(classLoader, JavasisstProxy.class.getProtectionDomain());
            Constructor c = clazz.getConstructor(InvocationHandler.class);
         	// 实例化
            return c.newInstance(h);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    

总的来说呢, javassist的方案的性能会比方案一高很多, 而且这个框架被很多其他的开源所用到比如dubbo的动态代理实现和spi的扩展实现都是有javassist的身影.

最后

最后说一下就是其实动态代理除了jdk的动态代理还有cglib的动态代理, jdk动态代理主要是根据接口去生成代理类进行委托, 而cglib是通过直接继承对应的实现类来实现. 早期的时候cglib的性能会高出jdk动态代理很多, cglib使用的FastMethod可以一定程度提升性能, 不过后来经过jdk各个版本的优化后,反射性能不会差太多了,所以不用太纠结性能. 具体的cglib和反射的性能差异可以Java反射的效率测试-优先使用FastMethod看这篇文章的demo测试,不过这个demo测试有个不好的地方在于, 在调用方法那里,所有的参数都指向同一个实例, 在这个情况下cglib 的速度会快很多, 于是我改成都是new 出来的数组, 得出的速度如下

请无视那个反射优化, 那个操作并不是优化操作, 截了2张, 大部分情况下cglib会快一点,但差不了太多
反射和cglib的速度差异

差异而

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值