读完本篇文章将会了解以下问题

1.代理模式的定义和目的

2.JDK的动态代理的整体流程

3.代理对象帮我们做了什么

4.为什么JDK的动态代理是基于接口的代理(继承为什么不行)

5.生成代理实例化对象方法Proxy.newProxyInstance()的三个参数的作用分别是什么

---------------------------------------------------------------------------------------------------------------------------

在读此篇博文前需要了解:

java类加载机制的原理和作用

java反射的原理和基本用法

1.代理模式的定义和目的

定义:给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用

目的:

  1. 作为中介解耦客户端和真实对象,保护真实对象安全
  2. 通过引入代理对象的方式来间接访问目标对象,防止直接访问目标对象给系统带来的不必要复杂性
  3. 通过代理对象对原有业务进行增强

2.JDK的动态代理的整体流程

举个例子,网红giao哥会唱会跳(把唱和跳理解为两个技能,不论是谁,只要这个人有唱的技能就可以唱,有跳的技能就可以跳)火了,随即他想办一场演唱会,所以找了一个经纪人。原本一切顺利,但演唱会那天giao哥突然肚子疼,上不了场,但演唱会还得继续开,经济人就头疼了,因为经纪人不会唱跳(没有唱、跳这两个技能)。所以经纪人找到了giao哥的弟弟,这位弟弟拥有giao哥的唱和跳的技能书。随后经纪人从弟弟那里获取到了giao哥唱和跳的技能书,在幕后用高科技设备把获取到的技能书转化成了giao哥去唱歌,最终解决了这个问题。

上述例子最后部分有点牵强,因为java拥有反射机制,可以用method.invoke(obj,args)方法来调用obj对象的method方法。

上述例子中对应下文的java名词

Giao哥:被代理对象

经纪人:方法增强类

弟弟:代理对象

唱和跳的技能:被代理对象所实现接口中的方法

唱和跳的技能书:保存在方法区中的模板信息

高科技设备把获取到的技能书转化成了giao哥去唱歌:利用反射调用目标方法

接下来我们用代码来复现这一过程:

Star明星接口:有唱和跳两个方法定义

package user;public interface Star {String sing();String dance();
}

Giao哥类:实现自Star接口,重写接口内唱跳方法

package user;public class BrotherGiao implements Star{@Overridepublic String sing() {System.out.println("Giao哥:一给我里giaogiao");return "giao哥唱完了" ;}@Overridepublic String dance( ) {System.out.println("Giao哥:边唱边giao");return "giao哥跳完了" ;}
}

Agent 经纪人类:实现自InvocationHandler接口,内部保存被代理对象,有invoke和creatProxy方法我们下面说

package user;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class Agent implements InvocationHandler {//目标类,也就是被代理对象private Object object;public void setTarget(Object target){this.object = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable{// 这里可以做增强System.out.println("经纪人:唱歌前帮giao收钱");Object result = method.invoke(object, args);System.out.println("经纪人:唱歌后帮giao打扫战场");return result;}// 生成代理类public Object creatProxy(){return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this);}
}

一个普通的主函数启动类

package run;import user.Agent;
import user.BrotherGiao;
import user.Star;public class Practice001 {public static void main(String args[]){//将动态代理生成的.class文件持久化到磁盘上,老版本JDK用下面这个命令System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");//新版本JDK用下面这个命令//System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");//实例化giao哥对象BrotherGiao brotherGiao = new BrotherGiao();//实例化经纪人对象Agent agent = new Agent();//将giao哥传入经纪人对象内agent.setTarget(brotherGiao);//生成代理类并实例化对象Object obj = agent.creatProxy();//多态,因继承自Star接口,所以可用该接口来接收Star star = (Star) obj;System.out.println(star.sing());}
}

运行结果:

我们看一下主函数里的几行代码,先创建Giao哥对象,再去创建经纪人对象,将Giao哥传入经纪人对象内,经纪人调用createProxy方法生成代理类实例(对应上述例子找弟弟),将代理类实例利用java多态性质用Star接口接收,最后用star.sing()调用方法(接口.方法最终为调用为接口实现类.方法),代码很简单,就不赘余了。

有些小伙伴可能会问,为什么经纪人不直接帮giao哥上去唱啊,还去找弟弟多麻烦。我们看Giao哥类实现Star接口,而经纪人类实现InvocationHandler接口,经纪人类内部根本就没有Star接口内唱和跳的方法(对应上述例子,经纪人不会唱跳),所以经纪人无法直接帮Giao哥去唱,那么jdk动态代理问题的重点就变成了:

1:如何找到一个与被代理类拥有相同方法的代理类并将目标方法的参数传递回增强类,增强类如果可以拿到代理对象的方法、参数,同时其内部还保存着被代理对象的实例,那么就可以通过反射来调用该方法,从而实现代理。

2:动态代理的目的是为了给方法做增强,让方法更灵活,那么假设我们真的找到了一个与被代理类拥有相同方法的类,该如何与增强类建立联系(代理对象调用目标方法的时候怎么能调回增强类中的invoke方法)

想弄清这两个问题就必须跟源码,我们重点来看这两行代码

//目标类,也就是被代理对象
private Object object;
public void setTarget(Object target)
{this.object = target;
}
public Object creatProxy()
{return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this);
}
tatic Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )

可以看到newProxyInstance方法主要有三个参数:

ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的

Class<?>[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型

InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入

我们重点关注第三个this为主方法中的agent对象,因为是agent调用的creatProxy方法。我们传入的是增强类的对象,而增强类恰恰是实现了InvocationHandler这个接口的,那么我们大胆猜测,上述的第2个问题是不是通过这个InvocationHandler h来关联代理类和增强类的呢?

    public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{//检验h不为空,h为空抛异常Objects.requireNonNull(h);//接口的类对象拷贝一份final Class<?>[] intfs = interfaces.clone();//进行安全性检查final SecurityManager sm = System.getSecurityManager();if (sm != null) {checkProxyAccess(Reflection.getCallerClass(), loader, intfs);}/** Look up or generate the designated proxy class.*  查询(在缓存中已经有)或生成指定的代理类的class对象。*/Class<?> cl = getProxyClass0(loader, intfs);/** Invoke its constructor with the designated invocation handler.* 使用指定的调用处理程序调用其构造函数*/try {if (sm != null) {checkNewProxyPermission(Reflection.getCallerClass(), cl);}//得到代理类对象的构造函数,这个构造函数的参数由constructorParams指定final Constructor<?> cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;if (!Modifier.isPublic(cl.getModifiers())) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {cons.setAccessible(true);return null;}});}//这里生成代理对象并返回return cons.newInstance(new Object[]{h});} catch (IllegalAccessException|InstantiationException e) {throw new InternalError(e.toString(), e);} catch (InvocationTargetException e) {Throwable t = e.getCause();if (t instanceof RuntimeException) {throw (RuntimeException) t;} else {throw new InternalError(t.toString(), t);}} catch (NoSuchMethodException e) {throw new InternalError(e.toString(), e);}}

我们看到有生成指定的代理类的class对象这一步:Class<?> cl = getProxyClass0(loader, intfs);主函数中有这样一个方法,可以将动态代理生成的.class文件持久化到磁盘上

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

public final class $Proxy0 extends Proxy implements Star {private static Method m1;private static Method m3;private static Method m2;private static Method m4;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 dance() throws  {try {return (String)super.h.invoke(this, m3, (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 String sing() throws  {try {return (String)super.h.invoke(this, m4, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}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"));m3 = Class.forName("user.Star").getMethod("dance");m2 = Class.forName("java.lang.Object").getMethod("toString");m4 = Class.forName("user.Star").getMethod("sing");m0 = Class.forName("java.lang.Object").getMethod("hashCode");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}
}

我们打开这个$Proxy0.class文件,发现其继承自Proxy类,这也就解释了为什么JDK的动态代理是基于接口的代理,因为java为单继承。然后我们看到这个类实现了Star接口,那我们再大胆猜测一下,这个类中是不是也实现了Star接口中的sing()方法呢?往下一看还真的是,这也就解释了问题1:java不是去找一个与被代理类拥有相同方法的代理类,而是直接创建一个和被代理类实现相同接口并且拥有相同方法的代理类。我们接着看到这个代理类的构造方法传入InvocationHandler然后利用父类构造 ,接下来再看sing()方法,返回值为:return (String)super.h.invoke(this, m4, (Object[])null); super为调用父类,父类的h为增强类的实例对象,也就是我们在调用newProxyInstance方法时的第三个参数 this。这下全理清楚了,super.h.invoke调用的就是增强类中的invoke方法,这也就解释了问题2:代理对象调用目标方法的时候怎么能调回增强类中的invoke方法。然后我们再看参数,this就是当前代理类对象,m4为sing方法,Object[])null为sing的方法参数。最终再看回增强类中的invoke方法

@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable{//这里可以做增强System.out.println("经纪人:唱歌前帮giao收钱");Object result = method.invoke(object, args);System.out.println("经纪人:唱歌后帮giao打扫战场");return result;}

参数一致,至此整个JDK动态代理过程结束,我们可以在增强类中去自定义我们的逻辑了。

实验类类图关系:

具体实现原理:

       1、通过实现InvocationHandler接口创建自己的调用处理器

       2、通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理

       3、通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型

       4、通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入

总结:

1、Jdk动态代理是由Java内部的反射机制来实现的,目标类基于统一接口InvocationHandler。

2、代理对象是在程序运行时产生;

3、对代理对象的所有接口方法调用都会转发到InvocationHandler.invoke()方法,在invoke()方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等;

4、对于从Object中继承的方法,JDK动态代理会把hashCode()、equals()、toString()这三个非接口方法转发给InvocationHandler,其余的Object方法则不会转发。

用通俗易懂的语言去解释JDK的动态代理相关推荐

  1. cglib动态代理jar包_代理模式详解:静态代理+JDK/CGLIB 动态代理实战

    1. 代理模式 代理模式是一种比较好的理解的设计模式.简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标 ...

  2. JDK的动态代理深入解析(Proxy,InvocationHandler)(转)

    一.什么是动态代理 动态代理可以提供对另一个对象的访问,同时隐藏实际对象的具体事实.代理一般会实现它所表示的实际对象的接口.代理可以访问实际对象,但是延迟实现实际对象的部分功能,实际对象实现系统的实际 ...

  3. 4、MyBatis + Log4j日志查看Sql参数、结果集元数据、Mapper代理开发、JDK的动态代理与CGLib代理

    文章目录 MyBatis优化开发 日志 ResultSet如何转换为Java对象 数据库元数据 结果集元数据 Fastjson 基于MyBatis的DAO开发(传统) 推荐mapper代理开发 JDK ...

  4. 代理详解 静态代理+JDK/CGLIB 动态代理实战

    1. 代理模式 代理模式是一种比较好理解的设计模式.简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对 ...

  5. 动态代理(JDK的动态代理)

    UsbSell.java package com.dym.service;//目标接口 public interface UsbSell {float sell(int amount);void pr ...

  6. aop简介-基于jdk的动态代理

    ①目标类接口 public interface TargetInterface {public void method(); } ②目标类 public class Target implements ...

  7. Java-AOP(Hook)实现机制(JDK/cglib动态代理/ASM/Javassist/AspectJ)

    原文: http://www.iteye.com/topic/1116696 1 AOP各种的实现 AOP就是面向切面编程,我们可以从几个层面来实现AOP. AOP实现时有三种方式:生成子类字节码.生 ...

  8. spring18-1:采用jdk的动态代理 proxy。

    接口 public interface UserService {void addUser();void updateUser(); } 接口的实现类 public class UserService ...

  9. Java提高班(六)反射和动态代理(JDK Proxy和Cglib)

    反射和动态代理放有一定的相关性,但单纯的说动态代理是由反射机制实现的,其实是不够全面不准确的,动态代理是一种功能行为,而它的实现方法有很多.要怎么理解以上这句话,请看下文. 一.反射 反射机制是 Ja ...

最新文章

  1. exchange 2010 指定用户邮箱连接CAS服务器
  2. idea部署tomcat项目时,在项目里打断点不能拦截
  3. Hibernate 笔记 缓存
  4. php数组格式化显示,php 打印数组格式化显示
  5. 中文字体其实也可以用在网页上的
  6. python twisted框架_Python 基于Twisted框架的文件夹网络传输源码
  7. 20210116 plecs 版本更新笔记
  8. 求助wpe封包遇到动态验证怎么办
  9. 基于DDPG的智能交通灯控制算法
  10. linux版征途架设教程,魔兽世界-燃烧的征途端完整架设教程有图有真相
  11. 三种技术类型的3D摄像头大体总结 3D人脸识别
  12. matlab-俄罗斯方块小游戏
  13. 自学大数据入门全套学习资料(视频+课程大纲+笔记)
  14. php中或者符号,php中的或运算符号
  15. 原始值(primitive value)
  16. 临界区(临界段)的含义
  17. 人工智能Java SDK:人车非识别
  18. linux打开网络摄像头失败,Opencv没有检测到linux上的firewire网络摄像头
  19. java放麦子_第三届蓝桥杯javaC组_放麦子
  20. hadoop生态圈各产品基本概念梳理

热门文章

  1. Octotree 下载安装
  2. 如何理解beta分布?
  3. [教学] 以远程工作模式教你实战编程经验
  4. 打开viewer.jnlp文件
  5. 数据、数据元素和数据项
  6. python自学视频与excel_小白也能学习的 python pandas excel 处理[视频]
  7. ZJOI2019 麻将
  8. 深入浅出Pairwise 算法
  9. C语言书籍阅读-读书笔记--高质量程序设计指南》--C/C++,林锐
  10. vxe-table 实现复杂的表格功能。