动态代理学习(二)JDK动态代理源码分析

上篇文章咱们学习了如何本身实现一个动态代理,这篇文章咱们从源码角度来分析下JDK的动态代理java

先看一个Demo:web

public class MyInvocationHandler implements InvocationHandler {

	private MyService target;

	public MyInvocationHandler(MyService target) {
		this.target = target;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object invoke = method.invoke(target, args);
		System.out.println("proxy invoke");
		if (method.getReturnType().equals(Void.TYPE)) {
			return null;
		} else {
			System.out.println(invoke);
			return invoke+"proxy";
		}
	}
}

public interface MyService {
	void test01();
	void test02(String s);
}

public class MyServiceImpl implements MyService {

	@Override
	public void test01() {
		System.out.println("test01");
	}

	@Override
	public void test02(String s) {
		System.out.println(s);
	}
}

main方法:spring

public class Main {
	public static void main(String[] args) {
		MyServiceImpl target = new MyServiceImpl();
		Class<? extends MyServiceImpl> clazz = target.getClass();
		MyService proxyInstance = (MyService) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces()
				, new MyInvocationHandler(target));
		proxyInstance.test01();
		proxyInstance.test02("test02");
	}
}

咱们运行Debug观察下生成的proxyInstance对象:app

在这里插入图片描述

能够得出如下几个结论:框架

  1. 生成的代理类的类名是$Proxy0
  2. 代理类持有咱们的MyInvocationHandler对象

这里咱们越过不重要的代码,直接端点到java.lang.reflect.Proxy.ProxyClassFactory#apply这个方法,ide

咱们分段分析这个方法的代码(简单的代码咱们就直接跳过了):svg

Class<?> interfaceClass = null;
                try {
                    interfaceClass = Class.forName(intf.getName(), false, loader);
                } catch (ClassNotFoundException e) {
                }
                if (interfaceClass != intf) {
                    throw new IllegalArgumentException(
                        intf + " is not visible from class loader");
                }

这段代码主要是为了确保类加载器对这个class文件解析后获得的是同一个对象。若是咱们要确保两个对象相等的话,那么它们的类加载器一定是同样的。函数

for (Class<?> intf : interfaces) {
    int flags = intf.getModifiers();
    if (!Modifier.isPublic(flags)) {
        accessFlags = Modifier.FINAL;
        String name = intf.getName();
        int n = name.lastIndexOf('.');
        String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
        if (proxyPkg == null) {
            proxyPkg = pkg;
        } else if (!pkg.equals(proxyPkg)) {
            throw new IllegalArgumentException(
                "non-public interfaces from different packages");
        }
    }
}

 if (proxyPkg == null) {
                // if no non-public proxy interfaces, use com.sun.proxy package
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }

这段代码主要是在判断接口是不是public的,若是不是public的那么须要将代理类生成在接口同名的包下。不然生成的代理类在com.sun.proxy包下。学习

这里咱们能够作一个验证:测试

  1. 咱们测试接口若是不是public的,代理类会生成在接口的同一个包下,在这种状况下,咱们能够在接口的同名包下新建一个类,类名为$Proxy0,以下:
// 接口换为包访问权限
interface MyService {
	void test01();
	void test02(String s);
}

新建一个类,类名为$Proxy0

在这里插入图片描述

  1. main函数进行测试
public static void main(String[] args) {
		MyServiceImpl target = new MyServiceImpl();
		Class<? extends MyServiceImpl> clazz = target.getClass();
        // 加载这个类到JVM中
		$Proxy0 proxy0 = new $Proxy0();
		MyService proxyInstance = (MyService) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces()
				, new MyInvocationHandler(target));
		proxyInstance.test01();
		proxyInstance.test02("test02");
		}

运行后发现报错:显示有重复的类定义

Exception in thread "main" java.lang.LinkageError: loader (instance of  sun/misc/Launcher$AppClassLoader): attempted  duplicate class definition for name: "com/dmz/proxy/target/$Proxy0"
	at java.lang.reflect.Proxy.defineClass0(Native Method)
	at java.lang.reflect.Proxy.access$300(Proxy.java:228)
	at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:642)
	at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:557)
	at java.lang.reflect.WeakCache$Factory.get(WeakCache.java:230)
	at java.lang.reflect.WeakCache.get(WeakCache.java:127)
	at java.lang.reflect.Proxy.getProxyClass0(Proxy.java:419)
	at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:719)
	at com.dmz.proxy.target.Main.main(Main.java:14)

从上面咱们就验证了,若是不是public的那么须要将代理类生成在接口同名的包下

接下来咱们验证,正常状况下,代理类会被生成在com.sun.proxy包下

  1. 同理,咱们能够建立一个类,全类名为com.sun.proxy.$Proxy0

在这里插入图片描述

  1. 同时咱们将接口改成public的,一样的咱们会发现会报同一个错。

至此,证实完毕。咱们继续看代码

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
                // 省略部分代码......

咱们能够看到,经过ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags)生成一个字节流后,直接调用了defineClass0(…)方法,并且咱们跟踪这个方法能够发现,这是一个本地方法。而且它直接返回了一个Class对象。

private static native Class<?> defineClass0(ClassLoader loader, String name,
                                            byte[] b, int off, int len);

回顾咱们上一篇文章的实现思路:

在这里插入图片描述

对比后咱们能够发现,咱们本身实现时,是经过生成java文件,而后进行编译生成class文件,再将其加载到JVM中获得class对象。而对于jdk动态代理,直接经过一个字节流调用本地方法后直接生成class对象。

咱们再回过头去看下jdk是如何给咱们生成这个字节流的,这里咱们主要关注sun.misc.ProxyGenerator#generateClassFile这个方法,这里我就不贴代码了。由于也是一些字符串的拼接动做,而后写入到一个字节流中,咱们关注下最后生成的这个字节流是什么样子的,咱们将其写入到一个文件中:

MyServiceImpl target = new MyServiceImpl();
		Class<? extends MyServiceImpl> clazz = target.getClass();
		MyService proxyInstance = (MyService) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces()
				, new MyInvocationHandler(target));

		byte[] bytes = ProxyGenerator.generateProxyClass("proxy", clazz.getInterfaces());

		File file = new File("G:\\com\\dmz\\proxy\\proxy.class");
		FileOutputStream outputStream = new FileOutputStream(file);
		outputStream.write(bytes);
		proxyInstance.test01();
		proxyInstance.test02("test02");

咱们将获得的这个class文件放入idea反编译:

public final class proxy extends Proxy implements MyService {
    private static Method m1;
    private static Method m4;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public proxy(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void test01() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void test02(String var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m4 = Class.forName("com.dmz.proxy.target.MyService").getMethod("test01");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.dmz.proxy.target.MyService").getMethod("test02", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

观察上面代码,咱们能够发现如下几点:

  1. 代理类继承了Proxy这个类,正由于如此,因此jdk动态代理只能实现基于接口的代理,而不能实现对整个类进行代理,由于java是单继承的。那么为何代理类必定要继承Proxy这个类呢?咱们能够发现代理类并无使用Proxy中的什么属性或者方法(虽然使用了InvocationHandler对象,可是也能够在生成class之初就将InvocationHandler放入到代理类中)。因此实际上不进行继承也是没有任何关系的。查了不少资料后发现,找到一个比较合理的解释以下:

    JDK的动态代理只容许动态代理接口是设计使然,由于动态代理一个类存在一些问题。在代理模式中代理类只作一些额外的拦截处理,实际处理是转发到原始类作的。这里存在两个对象,代理对象跟原始对象。若是容许动态代理一个类,那么代理对象也会继承类的字段,而这些字段是其实是没有使用的,对内存空间是一个浪费。由于代理对象只作转发处理,对象的字段存取都是在原始对象上处理。更为致命的是若是代理的类中有final的方法,动态生成的类是无法覆盖这个方法的,无法代理,并且存取的字段是代理对象上的字段,这显然不是咱们但愿的结果。spring aop框架就是这种模式。

    总结起来主要两点

    • 咱们在进行代理时,实际的方法执行逻辑仍然是交给目标类处理,这个时候代理类持有目标类中的字段只不过是对内存空间的一种浪费,其他没有任何做用。

    • 即便咱们能接受对内存空间的浪费,然而若是咱们在代理对象中操做代理对象中的字段,目标对象的字段不受任何影响,这显然也是不合理的。

    • 若是是基于继承实现代理,那么有final的方法的状况下,没法完成对final方法的代理。

  2. 代理类实现了咱们目标对象实现的接口,因此说JDK动态代理是基于接口实现的。

  3. 代理对象不只仅是对接口中的方法进行了代理,还对hashCode,equals,toString三个方法进行了代理,这也是为了覆盖目标类中的全部方法

至此,咱们就完成对JDK动态代理的学习!喜欢的同窗点个赞吧~~~~