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

原文引用https://www.dazhuanlan.com/2019/08/26/5d6300df6f20f/

博客真的是好几个月没更了,2019新年第一篇,继续深入动态代理,前两篇简单分析了动态代理的实现原理之后,这次继续深入了解具体的实现方式,并手写一套简易的动态代理已加强理解;

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

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

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

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

12345678910111213141516171819
package com.guitu18.study.proxy;

* @email [email protected] * @Date 2018/12/31 20:30 */public interface  {    /**     * 找对象     *     * @param condition 对另一半的要求     * @return 表态     */    String findLove(String condition);    /**     * 找工作     */    void findWord();}

实现接口的类:

1234567891011121314151617
package com.guitu18.study.proxy;/** * @author zhangkuan * @email [email protected] * @Date 2018/12/31 20:13 */public class ZhangKuan implements Persion {

public String findLove(String condition) {        System.out.println(condition);        return "叶青我爱你";    }    @Override    public void findWord() {        System.out.println("我想找月薪15-25k的工作");    }}

代理类:

1234567891011121314151617181920212223242526
package com.guitu18.study.proxy.jdk;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;/** * @author zhangkuan * @email [email protected] * @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;                    }                });    }}

代理测试类:

123456789101112131415161718192021222324252627282930
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 [email protected] * @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自带反编译工具查看:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
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中有这么一段代码:

12345
/** * 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动态代理的实现增强理解;

原文地址:https://www.cnblogs.com/petewell/p/11410473.html

时间: 2024-11-05 14:37:58

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

手写实现JDK的动态代理

Person接口 package com.zhoucong.proxy.jdk; public interface Person { // 寻找真爱 void findlove(); } 人物实现类 package com.zhoucong.proxy.jdk; public class Zhangsan implements Person{ @Override public void findlove() { System.out.println("我叫张三,性别女,我找对象的要求如下:\n&

深入理解Redux之手写React-Redux

React-Redux主要由两部分组成,一是Provider(提供者),顾名思义作用就是提供状态数据. 另一部分是connect函数,它的作用是把UI组件和状态数据“连接”起来,实现了Model和View的分离,也就是UI组件并不直接管理状态数据,而是只负责界面的展示. 通过connect函数可以获得一个容器组件的创建器,进而创建出容器组件,而容器组件又包含了UI组件,由于容器组件是可以获取到状态数据的,获取到之后再传给UI组件就可以实现UI组件和状态数据的连接. 下面我们先来实现第一部分——P

java反射与动态代理的理解

一.什么是反射机制? 反射的官方定义是这样的:在运行状态中,对于任意的一个类,都能够知道这个类的所有属性和方法,对任意一个对象都能够通过反射机制调用一个类的任意方法,这种动态获取类信息及动态调用类对象方法的功能称为java的反射机制. 讲的通俗一点的话就是,对于jvm来说,.java文件必须要先编译为.class文件才能够被jvm执行,所以在编译为.class文件的过程中,对象的类型都会被指定好,比如说 User user.那么如果说我想在代码运行的过程中获取到对象的类型呢?或者说程序在运行过程

WinCE 调试某手写输入法时遇到的加载手写库失败的问题

调试某手写输入法时遇到的加载手写库失败的问题 在 WinCE6.0 下使用此手写输入法 SDK 做了一个单独的手写输入程序A(MFC Dialog 框架).正常情况下,可以正常使用. 此 A 程序,采用 LIB 方式加载此的手写 SDK. 在运行某一带手写输入的程序 B 后,A 程序无法运行.首先怀疑 B 程序中也使用了此手写 SDK,导致加载冲突. 但仔细想想程序 B 和程序 A 应该运行在不同的进程空间,且最后确定程序 B 并未使用此手写. 进一步测试发现,如果程序 A 先运行,再程序 B 

提高Android应用手写流畅度(基础篇)

在使用android类的手写应用时,整体上都有这样一个印象:android的手写不流畅.不自然,和苹果应用比起来相差太远.本文结合作者亲身经历,介绍一下有效提高手写流畅度的几种方法: 1.未做任何处理的手写效果: 这是一个自定义的view,通过在onTouchEvent时间中捕获系统回调的触摸点信息,然后再onDraw方法里面刷新,可以明显地感觉到线条很生硬,并且在手写的过程中跟随感很差,反应迟钝,具体代码如下: package com.mingy.paint.view; import andr

手写数字识别的k-近邻算法实现

(本文为原创,请勿在未经允许的情况下转载) 前言 手写字符识别是机器学习的入门问题,k-近邻算法(kNN算法)是机器学习的入门算法.本文将介绍k-近邻算法的原理.手写字符识别问题分析.手写字符识别的kNN实现.测试. kNN算法原理 kNN算法是一种分类算法,即如何判定一组输入数据属于哪一类别的算法.kNN属于监督学习算法,必须给定训练样本,样本包括输入样本和输出样本.而无监督学习则不需要训练样本. 那么最简单的分类方法就是将输入数据与样本一一比对,并将相似性最强的前k个样本选出,这k个样本中的

苹果没放弃手写笔 这样的iPad你想要吗?

12 月 31 日,美国专利与商标局(The U.S. Patent and Trademark Office)当地时间周四批准了一项来自苹果的专利申请,该专利主要描述的是一种可以通过陀螺仪.无线通讯装置和内置储存芯片将手写在白板.平面上内容 同步传输到电子设备屏幕上的触控技术. 据美国科技媒体 Apple Insider 透露,苹果此次的专利申请代号为 8922530,其内容其实就是一个可以通过陀螺仪.无线通讯装置和内置储存芯片将手写内容传输到电子设备屏幕上的“通讯手写笔” (communic

(5)C#之ADO.NET 手写SqlHelp.cs 以及改进

为了更快捷的反问数据库,以及更加符合面向对象封装的原则,我们应该有一个自己的数据库操作类,SqlHelper.cs  这样我们在访问数据库的时候就不必每一次都连接.赋值.执行了,可以比较方便的一步到位. 首先书写一个比较简易的SqlHelp.cs,分开写ExecuteNonQuery.ExecuteScalar.ExecuteReader. ◇首先配置连接变量(上一节中有说到) private static string connStr = ConfigurationManager.Connec

跟我一起造轮子 手写springmvc

作为java程序员,项目中使用到的主流框架多多少少和spring有关联,在面试的过程难免会问一些spring springmvc spring boot的东西,比如设计模式的使用. 怎么实现springioc 怎么实现springmvc诸如此类的问题,今天我们就来探寻spring mvc的实现,然后自己实现一个简单的spring mvc 一. 了解spring mvc的基本运行流程 ps: 网上一大堆关于springmvc的详细讲解,在这里就不累赘了 小结:spring mvc的核心是Dispa