1. 代理基本概念:

  以下是代理概念的百度解释:代理(百度百科)

总之一句话:三个元素,数据--->代理对象--->真实对象;复杂一点的可以理解为五个元素:输入数据--->代理对象--->真实对象--->代理对象--->输出数据。

2. JDK的动态代理概念:

  JDK的动态代理和正常的代理逻辑有些区别。

  首先先明确一下术语:类 class ,接口 interface。

  JDK动态代理是基于 interface 创建的,而不是真正的对象;也就是说,即使没有真正的对象,JDK依然可以创建代理对象。下面用代码来解释:

public class JDKProxy implements InvocationHandler{
public Object getObject(TestInterface ref){
return Proxy.newProxyInstance(getClass().getClassLoader(), ref.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
}

  当然,实际中使用的情况是有真正的对象的,像下面这样: 

public class JDKProxy implements InvocationHandler{
TestInterface ref;
public Object getObject(TestInterface ref){
this.ref = ref;
return Proxy.newProxyInstance(getClass().getClassLoader(), ref.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("doBefore");
Object o = method.invoke(ref, args);
System.out.println("doAfter");
return o;
}
}

  那么,和正常的代理逻辑区别就在这里了,JDK的动态代理多依赖一个元素,就是被代理对象ref所实现的接口。如果ref对象没有实现任何接口,那么这个对象是无法被代理的。

  那么问题来了: 为什么Java自带的动态代理 选择 要基于接口 ?基于什么考虑,或者说Java如果 选择 直接 代理真正的对象会有什么问题?

3. 进入正题:JDK动态代理是如何实现的?(基于JDK1.8)

  Java中涉及到的关键先生:InvocationHandler , Proxy

  3.1 使用方法及参数详细解释

    代码使用方法:

     

//代理类,实现InvocationHandler
public class JDKProxy implements InvocationHandler{
private UserService userServiceRef;
   //获取代理对象
public UserService getProxy(UserService userServiceRef){
this.userServiceRef = userServiceRef;
     //Proxy生成代理对象
return (UserService) Proxy.newProxyInstance(getClass().getClassLoader(), userServiceRef.getClass().getInterfaces(), this);
}
   //代理对象做的事
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("someone is logining");
Object returnObject = method.invoke(userServiceRef, args);
System.out.println("someone login success");
return returnObject;
}
}  

    下面是InvocationHandler的接口描述:

   package java.lang.reflect;
   public interface InvocationHandler {
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
   }

    其中的参数:

      proxy: 代理对象本身,也就是 getProxy(UserService userServiceRef) 获取到的对象。大家思考一下,为什么要把这个代理对象作为参数传进来?

              我个人觉得这是个完全没有必要的参数。

      method:userServiceRef中的方法。

      args: method方法的参数。

    再来看一下Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)方法参数:

      loader:类加载器,用来加载代理类的,即Proxy.newProxyInstance()的返回结果的类字节码。

      interfaces:代理对象所实现的接口。 这里接口是数组参数,通常被代理只实现一个接口。那实现多个接口时使用代理对象有什么问题?其实也没问题,就是调用不同接口的方法前需要先强转为对应的接口类,麻烦。

      h:实现InvocationHandler的类,也就是示例代码中的JDKProxy类 。

    测试代码:

    public static void main(String[] args) {
  UserService userService = new UserServiceImpl();
  JDKProxy proxy = new JDKProxy();
  UserService userServiceProxy = proxy.getProxy(userService);
  userServiceProxy.login();
  }

  3.2 实现的原理与细节:

      1.代理对象的创建过程:

    创建代理对象的方法:Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

    通过这个方法的参数其实可以看到一些眉目,loader用来加载代理类字节码,interfaces作为代理类实现的接口,h为代理对象实际调用的方法(即invoke方法)。

    创建过程大致分为几步:

    • 从缓存中获取代理对象,获取到则直接返回;

      缓存由WeakCache中的ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map来存储;

      为什么是两级Map? 想一想,Java类的唯一性由ClassLoader+Class决定,所以Key Object是ClassLoader,Key Object是所有接口组成的对象().

       WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

      其中KeyFactory的作用就是将interfaces转换为Key Object 。

    • 生成代理对象的类名proxyName;

      由 private static final class ProxyClassFactory 来完成.

      private static final String proxyClassNamePrefix = "$Proxy"; //ProxyClassFactory
      // 每次使用时 自增1
      private static final AtomicLong nextUniqueNumber = new AtomicLong();//ProxyClassFactory
      ReflectUtil.PROXY_PACKAGE = "com.sun.proxy";//RelectionUtil

      proxyName = PROXY_PACKAGE + proxyClassNamePrefix nextUniqueNumber

    • 生成proxyName类的字节码;

      由 sun.misc.ProxyGenerator.generateProxyClass(String proxyName, Class<?>[] interfaces, int accessFlags) ,方法返回二进制字节码。

      参数: 类名,需要实现的接口,访问标志。

    • 将字节码加载到虚拟机中,即方法区内存(jdk1.7之前是永久代,jdk8之后的元数据区);

      由 java.lang.reflect.Proxy.defineClass0(ClassLoader loader, String proxyName, byte[] proxyClass, int offset, int length) 完成。

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

      这是一个本地方法,通过JNI调用,返回代理的Class对象。

    • 生成代理Class对象的实例;构造器实例化

      生成代理对象的三个参数中的 interfaces, classLoader都使用过了,还有一个InvocationHandler 没有使用。

      通过反射获取代理Class的参数为InvocationHandler的构造器,通过 Constructor.newInstance(new Object[]{h}); 返回最后的代理实例对象。

    创建代理的过程就完成了。

      2. 代理对象的字节码分析:

    还是以上面3.1的例子分析,测试代码稍作修改,如下:

   public static void main(String[] args) {
     System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); //生成的代理对象字节码保存到.class文件中。
  JDKProxy proxy = new JDKProxy();
  proxy.getProxy(new UserServiceImpl()); //生成代理对象
}

    运行测试代码之后,user.dir目录下会多出一个目录:com/sun/proxy,打开后可以看到$Proxy0.class文件。

    jd-gui反编译该class文件:为方便阅读,我把方法里的try catch全部移掉了。

   package com.sun.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import myproxy.UserService;
public final class $Proxy0 extends Proxy implements UserService {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0; public $Proxy0(InvocationHandler paramInvocationHandler){
super(paramInvocationHandler); //h引用在父类Proxy中
}
public final boolean equals(Object paramObject){
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
}
public final String toString(){
return (String)this.h.invoke(this, m2, null);
}
public final boolean login(){
return ((Boolean)this.h.invoke(this, m3, null)).booleanValue(); //boolean存在包装和解包装
}
public final int hashCode(){
return ((Integer)this.h.invoke(this, m0, null)).intValue();
}
static{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("myproxy.UserService").getMethod("login", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
}

    一目了然。主要五部分内容:

    代理对象继承了Proxy类,并实现了目标接口。

    生成了以InvocationHandler为参数的构造器,实例化时将我们的自定JDKProxy(实现了InvocationHandler,并持有被代理对象)传递进去;

    生成了Object中的三个方法:equals, hashCode, toString;

    生成了接口中的所有方法,全部调用InvocationHandler对象的invoke方法;

    生成了对应方法的Method对象属性,传递给invoke方法。

  3 .提示细节:

    • 代理对象的父类java.lang.reflect.Proxy类是实现了java.io.Serializable接口的,所以代理对象都是可序列化的;
    • 对于有参和无参方法,都是通过invoke方法调用,无参方法会直接传入null,所以在invoke方法中使用args参数时一定要先进行null的判断;
    • 对于原始数据类型(int,boolean等8种),代理对象的方法中参数和返回值都进行了包装和解包装。
    • 代理对象生成过程中用到了反射,生成字节码时,反射Object对象方法和反射接口方法;生成实例时,反射获取代理对象的构造器;代理对象方法调用过程中是没有使用反射的。
    • 有没有感觉跟 装饰器模式 有一些 异曲同工 呢?

4. 以上就是个人总结分享的JDK动态代理的内容,原创内容,转载请注明出处。

个人水平有限,有误的地方欢迎评论中指正。

  

    

  

    

  

byte[]

Java,JDK动态代理的原理分析的更多相关文章

  1. 解析JDK动态代理实现原理

    JDK动态代理使用实例 代理模式的类图如上.关于静态代理的示例网上有很多,在这里就不讲了. 因为本篇讲述要点是JDK动态代理的实现原理,直接从JDK动态代理实例开始. 首先是Subject接口类. p ...

  2. java jdk动态代理模式举例浅析

    代理模式概述 代理模式是为了提供额外或不同的操作,而插入的用来替代”实际”对象的对象,这些操作涉及到与”实际”对象的通信,因此代理通常充当中间人角色. java中常用的动态代理模式为jdk动态代理和c ...

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

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

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

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

  5. java jdk动态代理学习记录

    转载自: https://www.jianshu.com/p/3616c70cb37b JDK自带的动态代理主要是指,实现了InvocationHandler接口的类,会继承一个invoke方法,通过 ...

  6. Java JDK 动态代理使用及实现原理分析

    转载:http://blog.csdn.net/jiankunking   一.什么是代理? 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问.代理类负责为委托类预处理 ...

  7. Java JDK 动态代理实现和代码分析

    JDK 动态代理 内容 一.动态代理解析 1. 代理模式 2. 为什么要使用动态代理 3. JDK 动态代理简单结构图 4. JDK 动态代理实现步骤 5. JDK 动态代理 API 5.1 java ...

  8. JDK 动态代理实现原理

    一.引言 Java动态代理机制的出现,使得Java开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象便能动态生成代理类.代理类会负责将所有方法的调用分派到委托对象上反射执行,在分派执行的过 ...

  9. JDK动态代理实现原理--转载

    之前虽然会用JDK的动态代理,但是有些问题却一直没有搞明白.比如说:InvocationHandler的invoke方法是由谁来调用的,代理对象是怎么生成的,直到前几个星期才把这些问题全部搞明白了.  ...

随机推荐

  1. linux 下的常用操作命令

    一,Linux的介绍 略. 二,文件类的常用命令 三,权限类的常用命令

  2. [Unity插件]Lua行为树(十):通用行为和通用条件节点

    在行为树中,需要扩展的主要是行为节点和条件节点.一般来说,每当要创建一个节点时,就要新建一个节点文件.而对于一些简单的行为节点和条件节点,为了去掉新建文件的过程,可以写一个通用版本的行为节点和条件节点 ...

  3. (二)apache atlas配置和运行

    上一篇文章,我们已经构建出了altas的安装包,所以我们继续使用安装包配置和运行atlas 首先解压atlas压缩包,授予bin目录下的执行权限 1.默认启动atlas cd atlas/bin/ p ...

  4. File Input Features

    文件输入功能 1.该插件将将一个简单的 HTML 文件输入转换为高级文件选取器控件.将有助于对不支持 JQuery 或 Javascript 的浏览器的正常 HTML 文件输入进行回退. 2.文件输入 ...

  5. Running a jupyter notebook server

    你也许需要服务器运行jupyter notebook 阿里云: https://yq.aliyun.com/articles/98527 关于更安全的证书访问: http://jupyter-note ...

  6. CPU UsageTimes Profile (cpu=times)

    HPROF工具能搜集CPU使用信息通过注入代码到每个方法进入点和退出点.因此能够统计方法真实调用次数和花费的时间. 它使用BCI(Byte Code Injection),所以比cpu=samples ...

  7. 【学习】Python解决汉诺塔问题

    参考文章:http://www.cnblogs.com/dmego/p/5965835.html   一句话:学程序不是目的,理解就好:写代码也不是必然,省事最好:拿也好,查也好,解决问题就好!   ...

  8. webform(复合控件)

    一.组合单选 RadioButtonList 单选按钮与简单控件不同,可理解为在集合中放置多对象 例: <asp:RadioButtonList ID="RadioButtonList ...

  9. Others-大数据平台Lambda架构浅析(全量计算+增量计算)

    大数据平台Lambda架构浅析(全量计算+增量计算) 2016年12月23日 22:50:53 scuter_victor 阅读数:1642 标签: spark大数据lambda 更多 个人分类: 造 ...

  10. 配置maven访问nexus,配置项目pom.xml以发布maven项目到nexus中

    maven访问nexus有三种配置方法,分别为: 项目pom.xml,优先级最高: user的settings.xml,优先级中,未在pom.xml中配置repository标签,则使用这个配置: m ...