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. 设置nginx中文件上传的大小限制度

    通过设置nginx的client_max_body_size解决nginx+php上传大文件的问题: 用nginx来做webserver的时,上传大文件时需要特别注意client_max_body_s ...

  2. Java课程作业之动手动脑(五)

    1.请阅读并运行AboutException.java示例. import javax.swing.*; class AboutException { public static void main( ...

  3. HANA私有云解决方案

        在移动互联网时代,不支持在云上的部署一定会落伍的,HANA作为SAP力推的技术,对云的支持也做的很不错,今天我们就来探讨一下HANA私有云解决方案,至于公有云或者混合云,思路也是大同小异了. ...

  4. kvm云主机使用宿主机usb设备

    有些时候KVM客户机还是要使用USB设备,比如USB密钥等 KVM命令行参数 -usb 打开usb驱动程序,启动客户机usb支持-usbdevice devname 为客户机增加usb设备,devna ...

  5. Loadrunner:win10下Vuser 运行脚本通过,Controller执行用户并发报错

    现象:win7安装LR可以正常使用,将win7升级到win10之后,运行场景之后报错:Error (-81024): LR_VUG: The 'QTWeb' type is not supported ...

  6. 29.如何不用 transition 和 animation 也能做网页动画

    原文地址:https://segmentfault.com/a/1190000014964220 感想:动画效果运用了具有滚动效果的marquee标签 HTML代码: 注释:  <!-- mar ...

  7. EXCEL中统计单元格内容出现次数

    参考网站: https://jingyan.baidu.com/article/7c6fb428dfcc9580642c90ae.html 统计单元格内容出现次数是工作中经常会涉及到的问题. 那么,如 ...

  8. Analysis of Web.xml in Hello1project

    一下是hello1  web inf 里的  web.xml <?xml version="1.0" encoding="UTF-8"?><w ...

  9. ubuntu-Linux下如何安装Tensorflow?

    http://wiki.jikexueyuan.com/project/tensorflow-zh/get_started/os_setup.html https://www.cnblogs.com/ ...

  10. 解决 shopnc b2b2c 版权问题 修改路经ULR及目录文件夹思路及教程

    相信各位使用过NC的朋友,多多少少收到过律师函,把一堆人吓尿了,原因你使用了盗版,大哥都要吃饭可以理解#网络那么大,他怎么能快速定位到您的,原因很简单 搜索引擎,NC在开发中定义了URL路由规则,在百 ...