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

先看一个Demo:

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方法:

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对象:

可以得出以下几个结论:

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

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

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

 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动态代理的学习!喜欢的同学点个赞吧~~~~

动态代理学习(二)JDK动态代理源码分析的更多相关文章

  1. java 1.8 动态代理源码分析

    JDK8动态代理源码分析 动态代理的基本使用就不详细介绍了: 例子: class proxyed implements pro{ @Override public void text() { Syst ...

  2. 动态代理以及对应Spring中AOP源码分析

    AOP(面向切面编程)在Spring中是被广泛应用的(例如日志,事务,权限等),而它的基本原理便是动态代理. 我们知道动态代理有两种:基于JDK的动态代理以及基于CGlib动态代理.以下是两种动态代理 ...

  3. 设计模式之JDK动态代理源码分析

    这里查看JDK1.8.0_65的源码,通过debug学习JDK动态代理的实现原理 大概流程 1.为接口创建代理类的字节码文件 2.使用ClassLoader将字节码文件加载到JVM 3.创建代理类实例 ...

  4. jdk 动态代理源码分析

    闲来无事,撸撸源码 使用方法 直接看代码吧.. package com.test.demo.proxy; import java.lang.reflect.InvocationHandler; imp ...

  5. JDK动态代理源码分析

    先抛出一个问题,JDK的动态代理为什么不支持对实现类的代理,只支持接口的代理??? 首先来看一下如何使用JDK动态代理.JDK提供了Java.lang.reflect.Proxy类来实现动态代理的,可 ...

  6. JDK7动态代理源码分析

    IObject proxy = (IObject) Proxy.newProxyInstance(IObject.class.getClassLoader(), new Class[]{IObject ...

  7. JDK动态代理[2]----JDK动态代理的底层实现之Proxy源码分析

    在上一篇里为大家简单介绍了什么是代理模式?为什么要使用代理模式?并用例子演示了一下静态代理和动态代理的实现,分析了静态代理和动态代理各自的优缺点.在这一篇中笔者打算深入源码为大家剖析JDK动态代理实现 ...

  8. Spring的两种代理方式:JDK动态代理和CGLIB动态代理

    代理模式 代理模式的英文叫做Proxy或Surrogate,中文都可译为”代理“,所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动.在一些情况下,一个客户不想或者不能够直接引用一个对 ...

  9. java动态代理源码解析

    众所周知,java动态代理同反射原理一直是许多框架的底层实现,之前一直没有时间来分析动态代理的底层源码,现结合源码分析一下动态代理的底层实现 类和接口 java动态代理的主要类和接口有:java.la ...

随机推荐

  1. HashMap实现原理(JDK1.8)

    概述HashMap在底层数据结构上采用了数组+链表+红黑树,通过散列映射来存储键值对数据因为在查询上使用散列码(通过键生成一个数字作为数组下标,这个数字就是hash code)所以在查询上的访问速度比 ...

  2. Trie(字典树、前缀树)

    目录 什么是Trie? 创建一棵Trie 向Trie中添加元素 Trie的查询操作 对比二分搜索树和Trie的性能 leetcode上的问题 什么是Trie?   Trie是一个多叉树,Trie专门为 ...

  3. SpringBoot连接Redis服务出现DENIED Redis is running in protected mode because protected mode is enabled

    修改redis.conf,yes 改为 no

  4. 【08NOIP提高组】笨小猴

    笨 小 猴 来自08年NOIP提高组的第一题 1.题目描述 [题目描述] 笨小猴的词汇量很小,所以每次做英语选择题的时候都很头痛.经实验证明,用这种方法去选择选项的时候选对的几率非常大!这种方法的具体 ...

  5. C#集合ArrayList、泛型集合List(3)

    数组的制约:局限性.有多少放多少,要想追加,就必须重新再定义一个数组,这就造成了资源的极大浪费而且性能消耗也比较大.因此此操作不太推荐.所以集合就来了. ,,,} 创建集合: ArrayList li ...

  6. 高性能的JavaScript,这是一个高级程序员必备的技能

    不知道大家有没有看过高性能JavaScript,这个书是一本好书,推荐有JavaScript的基础的同学可以看一看这本书. 下面是我根据这本书整理出来的知识: 1.将经常使用的对象成员.数组项.和域外 ...

  7. python入门学习之Python爬取最新笔趣阁小说

    Python爬取新笔趣阁小说,并保存到TXT文件中      我写的这篇文章,是利用Python爬取小说编写的程序,这是我学习Python爬虫当中自己独立写的第一个程序,中途也遇到了一些困难,但是最后 ...

  8. 面试官:你对Redis缓存了解吗?面对这11道面试题你是否有很多问号?

    前言 关于Redis的知识,总结了一个脑图分享给大家 1.在项目中缓存是如何使用的?为什么要用缓存?缓存使用不当会造成什么后果? 面试官心理分析 这个问题,互联网公司必问,要是一个人连缓存都不太清楚, ...

  9. SpringBoot项目集成Redis

    一.在pom文件中添加依赖 <!-- 集成redis --> <dependency> <groupId>org.springframework.boot</ ...

  10. 微信网页授权报code been used, hints: [ req_id: XYv1Ha07042046 ]

    先贴上代码: public function index() { $code = input('get.code'); $tool = new Wxtool(); if (empty($code)) ...