模拟实现Java动态代理底层逻辑

本文详细介绍了Java动态代理的实现过程,从最初的静态实现到动态代理的三个版本,探讨了每个版本的问题和改进。动态代理1.0通过Javassist库生成代理类,但存在代理类和方法名固定的问题。动态代理2.0引入泛型和参数,解决了接口和代理逻辑的灵活性,但仍有局限。最后,动态代理3.0通过InvocationHandler接口实现了动态方法调用,解决了重名和参数列表问题,提高了代码的灵活性和可读性。
摘要由CSDN通过智能技术生成

目录

普通的实现

动态代理1.0

1.0的问题

动态代理2.0

2.0存在的问题

动态代理3.0

关于匿名类的直接理解:


模拟实现Java动态代理底层逻辑,以期更深入理解Java动态代理。Java动态代理能够实现不编写代理类,也能够获得代理类及其实例。下面以实现一个接口TestService为例,逐步深入Java动态代理底层逻辑。

我们都知道java代码要运行需要大略需要进行这样的工作:

这样就实现了一次编译,到处运行。

普通的代理,是通过编写Java源码的方式来实现代理;但是动态代理,则是借助反射,动态编辑.class字节码实现代理,并没有Java源码的产生。由此可见,动态代理底层逻辑的目标就是动态编辑.class字节码:

普通代理和动态代理UML图:

这里借助Javassist,实现.class的编辑。简单介绍Javassist。Javassist是一个用于编写字节码的库,能够做到在JVM加载一个类的时候修改该类,还能做到在运行期间定义一个新类。Javassist提供两个等级的API:源码(source)级和字节码(bytecode)级:源码级API,是根据Java语言设计,可以在没有字节码规范的知识前提下,进行.class文件的编辑;字节码级API就是直接编辑字节码文件。Javassist官方网站:http://www.javassist.org/

下面使用eclipse+jdk1.8,尝试实现动态代理底层逻辑。

普通的实现

先看普通的实现,也就是编写Java源码的实现。为了方便,将所有代码写在一个.java文件中。

public class ProxyFactory {
	public static void main(String[] args) {
		ProxyFactory proxyFactory = new ProxyFactory(); // 外部类对象
		ProxyFactory.TestServiceImpl testServiceImpl = proxyFactory.new TestServiceImpl(); // 使用外部类对象创建内部类对象
		testServiceImpl.sayHello("张三");
	}

	// 普通实现
	class TestServiceImpl implements TestService {
		@Override
		public void sayHello(String name) {
            // 自己编写的代理逻辑。接口/被代理类不关系代理逻辑的实现,由我们自己编写
			System.out.println("hello:" + name);
		}
	}

	// TestService接口
	public interface TestService {
		void sayHello(String name);
	}

}

执行结果:

hello:张三

动态代理1.0

以上可见,普通的实现,需要自己编写源码。接下来借助Javassist,看看写死了实现TestService接口的动态代理。为了方便查看代码,所有的异常均通过throw抛出,实际工作中不建议这么做。

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;

public class ProxyFactory {

	public static void main(String[] args)
			throws InstantiationException, IllegalAccessException, NotFoundException, CannotCompileException {
		TestService proxy = createProxy(); // 根据createProxy()获取代理类对象
		proxy.sayHello("张三"); // 运行代理类对象的方法
	}

	/*
	 *  javasist工具类:生成TestService的代理类对象
	 *  1.创建一个代理类
	 *  2.创建一个方法
	 *  3.实例化该类对象
	 */
	public static TestService createProxy()
			throws NotFoundException, CannotCompileException, InstantiationException, IllegalAccessException {

		ClassPool classPool = new ClassPool(); // 类池
		classPool.appendSystemPath(); // 加载当前类的ClassLoader

		// 1.
		CtClass class1 = classPool.makeClass("TestServiceImpl"); // 创建代理类(此处写死代理了名为TestServiceImpl)
		class1.addInterface(classPool.get(TestService.class.getName())); // 将接口加入代理类

		// 2.
		// 根据信息生成代理(此处为重写)的方法sayHello()
		// make()方法的参数按顺序分别为:代理类返回类型、代理类重写的方法、所重写方法的参数列表、异常、代理逻辑(此处为重写的代码)
        // "{System.out.println(\"hello:\"+$1);}"就是{System.out.println("hello:"+name);}
		CtMethod sayHello = CtNewMethod.make(CtClass.voidType, "sayHello",
				new CtClass[] { classPool.get(String.class.getName()) }, null, "{System.out.println(\"hello:\"+$1);}",
				class1);
		class1.addMethod(sayHello); // 将重写的方法加入代理类中

		// 3.
		Class aClass = classPool.toClass(class1); // 将CtClass转换为Class
		return (TestService) aClass.newInstance(); // 实例化代理类对象并返回(此处只能是TestService)

	}

    // 接口
	public interface TestService {
		void sayHello(String name);
	}

}

 执行结果不变:

hello:张三

1.0的问题

从代码可以看出,这样写出来的代码,不仅只能实现TestService接口的代理,而且代理逻辑也只能在 createProxy() 方法中写,还是写在一个被调用方法的参数列表内,不可改变且十分容易出错。那么,为了实现能够代理任意的类,我们是否可以将 createProxy() 方法的返回类型,由TestService改为为泛型,将被代理类作为方法参数传递?为了能够根据需要改变代理逻辑,能否也将代理逻辑作为方法参数传递?

动态代理2.0

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;

public class ProxyFactory1 {

	public static void main(String[] args)
			throws InstantiationException, IllegalAccessException, NotFoundException, CannotCompileException {
//		TestService proxy = createProxy(TestService.class, "{System.out.println(\"hello:\"+$1);}"); // 利用反射获取接口的信息,下同
//		proxy.sayHello("张三");

		TestService2 proxy2 = createProxy(TestService2.class, "{System.out.println(\"hello:\"+$1);}");
		proxy2.sayHello("李四");

	}

	/*
	 *  javasist工具类:生成代理类对象。方法参数依次为:被代理类/接口,代理逻辑
	 *  1.创建一个代理类
	 *  2.创建一个方法
	 *  3.实例化该类对象
	 */
	public static <T> T createProxy(Class<T> classInterface, String src)
			throws NotFoundException, CannotCompileException, InstantiationException, IllegalAccessException {

		ClassPool classPool = new ClassPool(); // 类池
		classPool.appendSystemPath(); // 加载当前类的ClassLoader

		// 1.
		CtClass class1 = classPool.makeClass("TestServiceImpl"); // 创建一个代理类
		class1.addInterface(classPool.get(classInterface.getName())); // 添加被代理接口/类的方法到代理类中

		// 2.
		// 根据信息生成代理(此处为重写)的方法sayHello()
		// make()方法的参数按顺序分别为:代理类返回类型、代理类重写的方法、所重写方法的参数列表、异常、代理逻辑(此处为重写的代码)
		CtMethod sayHello = CtNewMethod.make(CtClass.voidType, "sayHello",
				new CtClass[] { classPool.get(String.class.getName()) }, null, src, class1);
		class1.addMethod(sayHello); // 添加方法到代理类中

		// 3.
		Class aClass = classPool.toClass(class1); // 将代理类由CtClass转换为Class
		return (T) aClass.newInstance(); // 实例化代理类并返回对象

	}

	// 接口。下同
	public interface TestService {
		void sayHello(String name);
	}

	public interface TestService2 {
		void sayHello(String name);

		void sayHello2(String name);
	}
}

2.0存在的问题

这样就解决1.0的两个问题了。但还有别的问题:

1.代理类一次只能执行一个,否则会编译不通过。因为我们在代码中写死了代理类的类名,会导致代理类名称重复:

CtClass class1 = classPool.makeClass("TestServiceImpl"); // 创建一个代理类,此处代码写死了代
                                                         // 理类的名字为TestServiceImpl
hello:张三
Exception in thread "main" javassist.CannotCompileException: by java.lang.LinkageError: loader (instance of  sun/misc/Launcher$AppClassLoader): attempted  duplicate class definition for name: "TestServiceImpl"
	at javassist.ClassPool.toClass(ClassPool.java:1170)
	at javassist.ClassPool.toClass(ClassPool.java:1113)
	at javassist.ClassPool.toClass(ClassPool.java:1071)
	at lsb.ProxyFactory1.createProxy(ProxyFactory1.java:46)
	at lsb.ProxyFactory1.main(ProxyFactory1.java:17)
Caused by: java.lang.LinkageError: loader (instance of  sun/misc/Launcher$AppClassLoader): attempted  duplicate class definition for name: "TestServiceImpl"
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
	at javassist.ClassPool.toClass2(ClassPool.java:1183)
	at javassist.ClassPool.toClass(ClassPool.java:1164)
	... 4 more

2.代理类中的方法名也是写死为“sayHello”:

// make()方法的参数按顺序分别为:代理类返回类型、代理类重写的方法、所重写方法的参数列表、异常、代理逻辑(此处为重写的代码)
		CtMethod sayHello = CtNewMethod.make(CtClass.voidType, "sayHello",
				new CtClass[] { classPool.get(String.class.getName()) }, null, src, class1);

这就会导致我们无法让代理类重写 TestService2() 接口中除了 sayHello() 以外的方法,因为无法找到 sayHello() 以外的方法:

TestService2 proxy2 = createProxy(TestService2.class, "{System.out.println(\"hello:\"+$1);}");
		proxy2.sayHello2("李四"); // 试图重写TestService2的sayHello2(...)方法
Exception in thread "main" java.lang.AbstractMethodError: TestServiceImpl.sayHello2(Ljava/lang/String;)V
	at lsb.ProxyFactory1.main(ProxyFactory1.java:18)

3.代理类重写方法的参数列表也被写死了,只有一个 String name:

// make()方法的参数按顺序分别为:代理类返回类型、代理类重写的方法、所重写方法的参数列表、异常、代理逻辑(此处为重写的代码)
		CtMethod sayHello = CtNewMethod.make(CtClass.voidType, "sayHello",
				new CtClass[] { classPool.get(String.class.getName()) }, null, src, class1);

其次,由于Java的多态,若更改接口方法的参数列表,sanHello() 方法就是另外一个方法了,此时再运行,将会产生同样的错误:

TestService2 proxy2 = createProxy(TestService2.class, "{System.out.println(\"hello:\"+$1);}");
		proxy2.sayHello2("李四", 1); // 试图重写TestService2的sayHello(String name, int id)
public interface TestService2 {
		void sayHello(String name, int id); // 增加参数int id

		void sayHello2(String name);
	}
Exception in thread "main" java.lang.AbstractMethodError: TestServiceImpl.sayHello2(Ljava/lang/String;)V
	at lsb.ProxyFactory1.main(ProxyFactory1.java:18)

4.代理逻辑的编码和1.0并无区别,效率依然底下且易读性差。

动态代理3.0

为了解决上面的4个问题,将2.0更改为3.0:

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.stream.Collectors;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;

public class ProxyFactory3 {
	static int count = 0; // 作为生成的代理类后缀,避免重名

	public static void main(String[] args) throws InstantiationException, IllegalAccessException, NotFoundException,
			CannotCompileException, IllegalArgumentException, NoSuchFieldException, SecurityException {

		// 匿名类实现 InvokationHandler接口,重写 invoke(...)方法,动态代理实现 TestService 接口
		TestService proxy1 = createProxy(TestService.class, new InvocationHandler() {

			@Override
			public Object invoke(String methodName, Object[] args) {
				// 需要程序员自己写的代理逻辑
				System.out.println("hello:" + args[0]);
				return null;
			}

		});
		proxy1.sayHello("张三");

		// 匿名类实现 InvokationHandler接口,重写 invoke(...)方法,动态代理实现 TestService2 接口
		TestService2 proxy2 = createProxy(TestService2.class, new InvocationHandler() {

			@Override
			public Object invoke(String methodName, Object[] args) {
				System.out.println("hello:" + args[0]);
				return null;
			}

		});
		proxy2.sayHello("李四");
		proxy2.sayHello2("王五");
		proxy2.sayHello3("赵六", 1);

		// 非匿名类实现InvokationHandler接口,重写 invoke(...)方法,动态代理实现 TestService 接口
		ProxyFactory3 proxyFactory3 = new ProxyFactory3();
		Handler handler = proxyFactory3.new Handler();
		TestService proxy3 = createProxy(TestService.class, handler);
		proxy3.sayHello("孙七");

	}

	/*
	 * 创建代理对象:方法参数依次为:代理的方法/接口、handler
	 * 1.创建一个代理类
	 * 2.添加一个InvocationHandler类型的属性,用以保存增强代码
	 * 3.创建接口下的所有方法实现
	 * 4.实例化该代理类对象
	 */
	public static <T> T createProxy(Class<T> classInterface, InvocationHandler handler)
			throws NotFoundException, CannotCompileException, InstantiationException, IllegalAccessException,
			IllegalArgumentException, NoSuchFieldException, SecurityException {

		ClassPool classPool = new ClassPool(); // 类池
		classPool.appendSystemPath(); // 加载当前类的ClassLoader

		// src分为void和return两种。该字符串为代理类最后执行的invoke()方法代码
		String src = "return ($r)this.handler.invoke(\"%s\",$args);";
		String voidSrc = "this.handler.invoke(\"%s\",$args);";

		// 1.创建一个代理类
		CtClass impl = classPool.makeClass("$proxy" + count++); // 代理类impl,加count后缀防止类重名
		impl.addInterface(classPool.get(classInterface.getName())); // 添加被代理接口/类的方法到代理类中

		// 2.添加一个InvocationHandler类型的属性,用以保存增强代码
		// 编译"public lsb.ProxyFactory3.InvocationHandler
		// handler=null;",代码这里用了全权限定名
		// 这样就可以创建一个InvocationHandler接口的空对象handler,并将其加入代理类impl
		// 相当于代理类中声明了一个成员变量 handler:public InvocationHandler handler = null;
		// 这样代理类就可以存储该方法传递过来的参数 handler(createProxy(..., InvocationHandler
		// handler))
		CtField field = CtField.make("public lsb.ProxyFactory3.InvocationHandler handler=null;", impl);
		impl.addField(field); // 将上一句代码创建的属性(变量)加入代理类

		// 3.创建接口下的所有方法实现
		// 循环被代理接口/类的方法,分别获取方法信息,添加到代理类中
		for (Method method : classInterface.getMethods()) {
			CtClass returnType = classPool.get(method.getReturnType().getName()); // 方法类型
			String methodName = method.getName(); // 方法名
			CtClass[] parameters = toCtClass(classPool, method.getParameterTypes()); // 方法参数
			CtClass[] errors = toCtClass(classPool, method.getExceptionTypes()); // 方法的异常(try-catch)

			// 判断代理逻辑是否需要return
			String srcImpl = "";
			if (method.getReturnType().equals(Void.class)) {
				srcImpl = voidSrc;
			} else {
				srcImpl = src;
			}

			// 根据信息生成对应的方法
			CtMethod newMethod = CtNewMethod.make(returnType, methodName, parameters, errors, srcImpl, impl);

			impl.addMethod(newMethod); // 将方法添加到代理类中
		}

		// 4.实例化该代理类对象
		Class clazz = classPool.toClass(impl); // 将CtClass转换为Class。将代理类加载到当前ClassLoader中
		Object object = clazz.newInstance(); // 实例化
		clazz.getField("handler").set(object, handler); // 将handler赋值到代理类中

		return (T) object; // 返回最终获得的代理类对象
	}

	// 接口InvocationHandler
	public interface InvocationHandler {
		// invoke(...)方法,用来接受被代理方法的名称及参数列表
		// 实现该方法时,重写的代码即是代理逻辑。所以该方法用来调用代理之后的方法
		// 例子见下个方法
		Object invoke(String methodName, Object args[]);
	}

	// InvocationHandler接口实现例子(正常实现,在main方法中的实现为匿名类方式)
	public class Handler implements InvocationHandler {
		@Override
		public Object invoke(String methodName, Object[] args) {
			// 自己写的增强代码,InvocationHandler接口不关心增强代码是什么
			System.out.println(args[0] + "执行了增强代码!");

			return null;
		}
	}

	// 工具方法:将Class[]转换为CtClass[]
	private static CtClass[] toCtClass(ClassPool classPool, Class[] classes) {
		return Arrays.stream(classes).map(c -> {
			try {
				return classPool.get(c.getName());
			} catch (NotFoundException e) {
				throw new RuntimeException(e);
			}
		}).collect(Collectors.toList()).toArray(new CtClass[0]);
	}

	// 被代理接口。下同
	public interface TestService {
		void sayHello(String name);
	}

	public interface TestService2 {
		String sayHello(String name);

		void sayHello2(String name);

		void sayHello3(String name, int id);
	}

}

运行结果:

hello:张三
hello:李四
hello:王五
hello:赵六
孙七执行了增强代码!

关于匿名类的直接理解:

可以看到,上面的代码中,在方法 createProxy(Class<T> classInterface, InvocationHandler handler) 中的第二个参数 handler 处,我们直接new了一个 InvocationHandler 对象(其实就是 handler),并且直接在里面写了方法体,则就是匿名类。匿名类的效果可以简单粗暴地理解成这样:

// 接口InvocationHandler
public interface InvocationHandler {
    Object invoke(String methodName, Object args[]);
}

TestService proxy = createProxy(TestService.class, 

    public class handler implements InvocationHandler {
        @Override
        public Object invoke(String methodName, Object[] args) {
            // 自己写的增强代码,InvocationHandler接口不关心增强代码是什么
		    System.out.println("执行了增强代码!");
		    return null;
	    }
    }

);
proxy.sayHello("张三");

当然实际上不能这样说,只不过简单粗暴地如此理解,可以比较快地得到一个感性直观的理解,从而避免在不必要的方面纠结,而耽误了对动态代理的理解。后面会针对内部类、匿名类等专门写一篇文章。

附虚拟代理类简单的类UML关系图:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值