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. service和pod通过标签绑定

    service和pod绑定 apiVersion: v1 kind: Service metadata: name: my-haproxy labels: app: my-haproxy spec: ...

  2. docker私有仓库pull/push

    相关条件: 登录 配置秘钥

  3. Python入门-随机漫步

    Python入门-随机漫步,贴代码吧,都在代码里面 代码1 class文件 random_walk.py from random import choice class RandomWalk(): # ...

  4. hadoop distcp 命令& 不同hadoop 版本cp

    # 1 版本相同 hadoop distcp -m 10 -bandwidth 150 hdfs://ns1/user/hive/warehouse/public.db/public_oi_fact ...

  5. hive 索引

    hive 有限的支持索引,不支持主键外键,可以对表添加索引,也可以为某个分区添加索引.维护索引也要额外的存储空间和计算资源. 创建索引需要指定索引处理器 如 as 'org.apache.hadoop ...

  6. mapPartitions

    mapPartitions操作与 map类似,只不过映射的参数由RDD中的每一个元素变成了RDD中每一个分区的迭代器,如果映射过程需要频繁创建额外的对象,使用mapPartitions操作要比map操 ...

  7. DDD领域驱动设计(例子)

    参考:https://tech.meituan.com/DDD_in_%20practice.html

  8. APP-2-Hbuilder开发环境搭建

    1.Hbuilder下载 http://www.dcloud.io/hbuilderx.html 2.夜神模拟器下载 https://www.yeshen.com/ 3.chrome浏览器下载 htt ...

  9. (原)Echarts 报Uncaught Error: Initialize failed: invalid dom 根本解决

    1.循环出的Echarts出现 Uncaught Error: Initialize failed: invalid dom ,附上完美解决方案 setTimeout(function () { co ...

  10. 尚硅谷redis学习8-事务

    是什么? 能干嘛? 常用命令 案例说明 1.正常执行 2.放弃事务 3.全部放弃(全体连坐) 4.只抛弃错误(冤头债主) 5.watch监控 悲观锁 悲观锁(Pessimistic Lock), 顾名 ...