原文同步发表至个人博客【夜月归途】

原文链接:http://www.guitu18.com/se/java/2019-01-03/27.html

作者:夜月归途
出处:http://www.guitu18.com/
本博客中未标明转载的文章归作者夜月归途和博客园所有。
欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

本博客关于Java动态代理相关内容直达链接:

  1. JDK动态代理浅析
  2. Cglib动态代理浅析
  3. JDK动态代理深入理解分析并手写简易JDK动态代理(上)
  4. JDK动态代理深入理解分析并手写简易JDK动态代理(下)

博客真的是好几个月没更了,2019新年第一篇,继续深入动态代理,前两篇简单分析了动态代理的实现原理之后,这次继续深入了解具体的实现方式,并手写一套简易的动态代理已加强理解;
先接上一篇代码,这里先上代码说话,一个简单案列,代理找对象和找工作:

JDK动态代理只能代理有接口的类,定义Persion接口

package com.guitu18.study.proxy;
/**
* @author zhangkuan
* @email xianjian-mail@qq.com
* @Date 2018/12/31 20:30
*/
public interface Persion {
/**
* 找对象
*
* @param condition 对另一半的要求
* @return 表态
*/
String findLove(String condition);
/**
* 找工作
*/
void findWord();
}

实现接口的类:

package com.guitu18.study.proxy;
/**
* @author zhangkuan
* @email xianjian-mail@qq.com
* @Date 2018/12/31 20:13
*/
public class ZhangKuan implements Persion {
@Override
public String findLove(String condition) {
System.out.println(condition);
return "叶青我爱你";
}
@Override
public void findWord() {
System.out.println("我想找月薪15-25k的工作");
}
}

代理类:

package com.guitu18.study.proxy.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author zhangkuan
* @email xianjian-mail@qq.com
* @Date 2018/12/31 20:13
*/
public class JDKProxy {
public static Object getProxyInstance(final Object target) {
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法执行前
System.out.println("开始事务...");
// 执行方法
Object invoke = method.invoke(target, args);
// 方法执行后
System.out.println("提交/回滚事务...");
return invoke;
}
});
}
}

代理测试类:

package com.guitu18.study.proxy.jdk;
import com.guitu18.study.proxy.Persion;
import com.guitu18.study.proxy.ZhangKuan;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
/**
* @author zhangkuan
* @email xianjian-mail@qq.com
* @Date 2018/12/31 20:28
*/
public class JDKProxyTest {
public static void main(String[] args) {
try {
Persion persion = (Persion) JDKProxy.getProxyInstance(new ZhangKuan());
String love = persion.findLove("肤白貌美大长腿");
System.out.println(love); // 叶青我爱你
System.out.println(persion.getClass()); // class com.sun.proxy.$Proxy0
/**
* 上面生成的代理对象字节码 com.sun.proxy.$Proxy0 是在内存中的
* 这里将其对象写到文件中,通过反编译查看
*/
byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Persion.class});
FileOutputStream fos = new FileOutputStream("D://$Proxy0.class");
fos.write(bytes);
System.out.println("文件写入成功");
} catch (Exception e) {
e.printStackTrace();
}
}
}

到这里,代理任务已经可以完成看;此处,我们将JDK动态代理生成的代理类,通过流的形式保存到磁盘中了,现在使用idea自带反编译工具查看:

import com.guitu18.study.proxy.Persion;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy0 extends Proxy implements Persion {
private static Method m1;
private static Method m2;
private static Method m4;
private static Method m3;
private static Method m0; public $Proxy0(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 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 findWord() 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 findLove(String var1) throws {
try {
return (String)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"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m4 = Class.forName("com.guitu18.study.proxy.Persion").getMethod("findWord");
m3 = Class.forName("com.guitu18.study.proxy.Persion").getMethod("findLove", 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());
}
}
}

看完这段代码是不是恍然大悟的感觉,生成的代理类继承了Proxy类并实现了我们的接口,并生成了被代理类的全部方法,包括toString、equals等等所有方法; 这里生成的代理类继承自Proxy,在Proxy中有这么一段代码:

/**
* the invocation handler for this proxy instance.
* @serial
*/
protected InvocationHandler h;

这就是为什么我们在生成代理类的时候,要传入的一个InvocationHandler的实现,我们所有的代理增强代码都写在invoke()方法中,而最终代理类执行代理也是通过调用父类Proxy中的InvocationHandler接口的invoke()方法;
而我们知道Java是单继承的,所以代理类只能去实现我们的接口以供我们可以将其强转;
在我们调用自身方法时,代理类巧妙的通过调用生成的同名方法然后调用super.h.invoke()方法,最终就到了我们实现InvocationHandler时所写的invoke()增强方法;在此时,method.invoke()才真正通过反射调用了我们自身所写的方法;这种极为巧妙无侵入式的方法增强实在是妙,令人叹为观止;

这里推荐一篇 劳夫子 的 [ ProxyGenerator生成代理类的字节码文件解析 ];
分析的是ProxyGenerator.generateClassFile()字节码生成原理,写的非常好,值得好好看一下;

下一篇,通过手写一套简易的JDK动态代理的实现增强理解;

JDK动态代理深入理解分析并手写简易JDK动态代理(上)的更多相关文章

  1. JDK动态代理深入理解分析并手写简易JDK动态代理(下)

    原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2019-01-05/27.html 作者:夜月归途 出处:http://www.guitu ...

  2. 手写简易的Mybatis

    手写简易的Mybatis 此篇文章用来记录今天花个五个小时写出来的简易版mybatis,主要实现了基于注解方式的增删查改,目前支持List,Object类型的查找,参数都是基于Map集合的,可以先看一 ...

  3. 手写简易SpringMVC

    手写简易SpringMVC 手写系列框架代码基于普通Maven构建,因此在手写SpringMVC的过程中,需要手动的集成Tomcat容器 必备知识: Servlet相关理解和使用,Maven,Java ...

  4. 【教程】手写简易web服务器

    package com.littlepage.testjdbc; import java.io.BufferedReader; import java.io.FileReader; import ja ...

  5. python-积卷神经网络全面理解-tensorflow实现手写数字识别

    首先,关于神经网络,其实是一个结合很多知识点的一个算法,关于cnn(积卷神经网络)大家需要了解: 下面给出我之前总结的这两个知识点(基于吴恩达的机器学习) 代价函数: 代价函数 代价函数(Cost F ...

  6. UI进阶之--网易彩票手写plist文件,动态创建控制器与tableViewcell

    点击右上角设置按钮 点击按钮后发生的事件:1. 控制器的跳转,进入新的控制器.view, 2. 跳转的时候对将要跳转的目标控制的子控件进行了布局.---通过手写plist文件的方式加载 为按钮注册单击 ...

  7. 全网最详细最好懂 PyTorch CNN案例分析 识别手写数字

    先来看一下这是什么任务.就是给你手写数组的图片,然后识别这是什么数字: dataset 首先先来看PyTorch的dataset类: 我已经在从零学习pytorch 第2课 Dataset类讲解了什么 ...

  8. mybatis(八)手写简易版mybatis

    一.画出流程图 二.设计核心类 二.V1.0 的实现 创建一个全新的 maven 工程,命名为 mebatis,引入 mysql 的依赖. <dependency> <groupId ...

  9. Java多线程之Executor框架和手写简易的线程池

    目录 Java多线程之一线程及其基本使用 Java多线程之二(Synchronized) Java多线程之三volatile与等待通知机制示例 线程池 什么是线程池 线程池一种线程使用模式,线程池会维 ...

随机推荐

  1. java课程之团队开发冲刺阶段1.6

    一.总结昨天进度 1.依照视频学习了sqlite,但是由于视频的不完整性导致并不知道代码的实际效果怎么样. 二.遇到的问题 1.依据上一条,在date目录下date文件夹中,的确发现了数据库的文件,但 ...

  2. k8s probe

    livenessProbe: httpGet: path: /abc/401 port: 8384 scheme: HTTP

  3. 别以为真懂Openstack: 虚拟机创建的50个步骤和100个知识点(5)

    八.KVM 这一步,像virsh start命令一样,将虚拟机启动起来了.虚拟机启动之后,还有很多的步骤需要完成. 步骤38:从DHCP Server获取IP 有时候往往数据库里面,VM已经有了IP, ...

  4. Python爬虫——Python 岗位分析报告

    前两篇我们分别爬取了糗事百科和妹子图网站,学习了 Requests, Beautiful Soup 的基本使用.不过前两篇都是从静态 HTML 页面中来筛选出我们需要的信息.这一篇我们来学习下如何来获 ...

  5. Android端IM应用中的@人功能实现:仿微博、QQ、微信,零入侵、高可扩展

    本文由“猫爸iYao”原创分享,感谢作者. 1.引言 最近有个需求:评论@人(没错,就是IM聊天或者微博APP里的@人功能),就像下图这样:   ▲ 微信群聊界面里的@人功能    ▲ QQ群聊界面里 ...

  6. [Swift]LeetCode907. 子数组的最小值之和 | Sum of Subarray Minimums

    Given an array of integers A, find the sum of min(B), where B ranges over every (contiguous) subarra ...

  7. Android studio的错误:radle sync failed: Cause: failed to find target android-21 :

    这个错误在Android studio中经常出现,特别是你在编译不同的app的时候,到底是什么原因会导致该错误产生呢? 首先看错误信息,是找不到目标android版本-21导致的,这就很明显了,你的目 ...

  8. VMware修改为静态ip

    选择编辑-虚拟机网路编辑器-NAT模式记录 本机cmd执行命令:ipconfig /all  查看VMnet8的ip地址,跟虚拟机子网ip一个网段 确定. su - root 切换到root用户下 修 ...

  9. IntelliJ IDEA 自定义方法注解模板

    最近没啥事开始正式用Eclipse 转入 idea工具阵营,毕竟有70%的开发者在使用idea开发,所以它的魅力可想而知.刚上手大概有一天,就知道它为啥取名为 intelli(智能化)了,确实很智能, ...

  10. 开启 IPv6 新时代,升级后的 IPv6 厉害在哪?

    IPv6,Internet Protocol Version 6,从字面翻译 “互联网协议第 6 版”,它是IETF设计的用于替代现行版本 IP 协议-IPv4 协议,被称作“下一代互联网协议”.早在 ...