java动态代理,轻轻松松从入门到精通

动态代理、动态代理、动态代理,首先它是动态的,然后他能实现代理模式。

“动态”、“代理”,这两个词将贯穿全文。

一、什么是动态代理

java的动态代理,首先它是用来实现**“代理模式**”的。

然后他是动态的可以灵活的代理**“任何类”**【当然这个类需要有接口】。

我们先来看看动态代理能实现的效果。

java动态代理,轻轻松松从入门到精通_第1张图片

如上示意图,类TestA、TestB想调用TargetObject0、TargetObject1……等目标对象的任何方法,都需要经过InvocationHandler的invoke方法。

注意这是说的是,TargetObject对象的任何方法

注意这是说的是,TargetObject对象的任何方法

注意这是说的是,TargetObject对象的任何方法

为什么说他是动态的呢?

首先,InvocationHandler类可以代理几乎任一类的几乎任一方法【构造方法不行】

然后,java是通过动态创建代理类,来实现上述代理功能。

没错,动态创建类,java在进程运行时动态创建类,不是编译期动态创建类。

没错,动态创建类,java在进程运行时动态创建类,不是编译期动态创建类。

没错,动态创建类,java在进程运行时动态创建类,不是编译期动态创建类。

文章后面,我们再细聊,java如何通过,动态创建代理类,来实现任一类代理功能。

二、如何实现一个最简单的动态代理:

直接上代码,代码不难。我们自顶向下,先看main方法。

public class DynamicProxyMain {
     

  public static void main(String[] args) {
     
    //1)创建目标对象
    TargetObject targetObject = new TargetObject();

    //2)创建代理对象
    ITarget proxyObject = (ITarget) Proxy.newProxyInstance(
      TargetObject.class.getClassLoader(),
      TargetObject.class.getInterfaces(),
      new DynamicProxyHandler(targetObject)//注意DynamicProxyHandler在这
    );

    //3)通过代理对象proxyObject,调用目标对象targetObject的方法getVoid()
    proxyObject.getVoid();// ITarget接口方法的调用,直接代理到DynamicProxyHandler.invoke()方法

  }
}

class DynamicProxyHandler implements InvocationHandler {
     
  private Object target;

  public DynamicProxyHandler(Object target) {
     
    this.target = target;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     
    System.out.println("----调用开始,对象:" + target + "; 方法:" + method.getName());
    //通过反射调用目标对象的方法,当然也可以不调用目标对象的方法,完全由你决定
    Object ret = method.invoke(target, args);
    System.out.println("----调用结束,对象:" + target + "; 方法:" + method.getName() + ",返回值" + ret+ "\n");
    return ret;
  }
}

目标类的定义

interface ITarget {
     
    void getVoid();
}

class TargetObject implements ITarget {
     //注意一定要实现接口ITarget
    public void getVoid() {
     
        System.out.print("TargetObject.getVoid();\n");
    }
}

Demo日志输出:

----调用开始,对象:com.java.study.base.proxy.TargetObject@6f94fa3e; 方法:getVoid
TargetObject.getVoid();
----调用结束,对象:com.java.study.base.proxy.TargetObject@6f94fa3e; 方法:getVoid,返回值null

注意,这里需要强调下:

被代理的目标类TargetObject必须有接口:也就是ITarget接口。

三、动态代理的原理

那么,Proxy.newProxyInstance()方法创建了一个什么对象?

接着我们稍微该下main方法:

public class DynamicProxyMain {
     

  public static void main(String[] args) {
     
    Object proxyObject = Proxy.newProxyInstance(TargetObject.class.getClassLoader(), TargetObject.class.getInterfaces(), new DynamicProxyHandler(new TargetObject()));

    //我们一起来看看,动态代理proxyObject的类是什么?
    //下面的日志将会输出什么?
    System.out.println("\n\nproxyObject的类=" + proxyObject.getClass().getSimpleName());
    System.out.println("proxyObject实现的接口=" + Arrays.toString(proxyObject.getClass().getInterfaces()));

  }
}

上面main函数中的日志将会输出什么?

上面main函数中的日志将会输出什么?

上面main函数中的日志将会输出什么?

public class DynamicProxyMain {
     

  public static void main(String[] args) {
     
    Object proxyObject = Proxy.newProxyInstance(TargetObject.class.getClassLoader(), TargetObject.class.getInterfaces(), new DynamicProxyHandler(new TargetObject()));

    //我们一起来看看,动态代理proxyObject的类是什么?
    System.out.println("\n\nproxyObject的类=" + proxyObject.getClass().getSimpleName());
		//日志输出:proxyObject的类=$Proxy0
    
    System.out.println("proxyObject实现的接口=" + Arrays.toString(proxyObject.getClass().getInterfaces()));
    //日志输出:proxyObject实现的接口=[interface com.java.study.base.proxy.ITarget]

  }
}

从日志我们可看到,Proxy.newProxyInstance()返回的对象的类是 $Proxy0,并且这个对象实现了接口ITarget。

P r o x y 0 ∗ ∗ 类 就 是 我 们 前 面 提 到 的 , j a v a 动 态 创 建 的 类 。 ∗ ∗ Proxy0**类就是我们前面提到的,java动态创建的类。** Proxy0javaProxy0类就是真正的代理类,通过这个对象调用 的方法都会代理到 DynamicProxyHandler的invoke方法。

DynamicProxyHandler的invoke方法,可以决定是否调用TargetObject的对应方法,也可以在调用之前和调用之后做些特殊处理。

我们再来梳理下Proxy.newProxyInstance()方法:

public static Object newProxyInstance(
  ClassLoader loader, //动态创建的代理类$Proxy0,会加载到loader中
  Class<?>[] interfaces,//动态创建的代理类$Proxy0,将implement所有interfaces中的接口
  InvocationHandler h)//创建的代理对象的方法调用都将代理到InvocationHandler.invoke()方法

我们继续梳理一下上面demo的类图

java动态代理,轻轻松松从入门到精通_第2张图片

那么大叔要问一个问题了:

上面的demo,代理类,$Proxy0,只能代理ITarget接口定义的方法吗?

代理类,$Proxy0,只能代理ITarget接口定义的方法吗?

代理类,$Proxy0,只能代理ITarget接口定义的方法吗?

代理类,$Proxy0,只能代理ITarget接口定义的方法吗?

如果调用Object.equals()是否能代理到DynamicProxyHandler.invoke()方法呢?

如下demo:

public static void main(String[] args) {
     
    Object proxyObject = Proxy.newProxyInstance(TargetObject.class.getClassLoader(), TargetObject.class.getInterfaces(), new DynamicProxyHandler(new TargetObject()));
    );

    proxyObject.equals(proxyObject);// 这个equals方法是否会代理到DynamicProxyHandler.invoke()方法?

}

答案是肯定的。proxyObject.equals()、proxyObject.hashCode()、proxyObject.toString()等都会代理到DynamicProxyHandler.invoke()方法。

四、java是静态语言,$Proxy0是怎么动态生成的呢?

我们来跟下java8的源码。

注意:java8的源码;不同版本的java源码,可能存在差异:https://github.com/JetBrains/jdk8u_jdk

注意:java8的源码;不同版本的java源码,可能存在差异:https://github.com/JetBrains/jdk8u_jdk

注意:java8的源码;不同版本的java源码,可能存在差异:https://github.com/JetBrains/jdk8u_jdk

public class Proxy implements java.io.Serializable {
     


  private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>>{
     

    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
     
      //...
      //1)生成$Proxy0类的字节码,存储在byte数组中[proxyClassFile]
      byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
        proxyName, interfaces, accessFlags);
      try {
     
        //2)将$Proxy0类的字节码解析成Class对象返回,并将$Proxy0类加载到ClassLoader中。
        return defineClass0(loader, proxyName,
                            proxyClassFile, 0, proxyClassFile.length);
      } catch (ClassFormatError e) {
     
        /*
                 * A ClassFormatError here means that (barring bugs in the
                 * proxy class generation code) there was some other
                 * invalid aspect of the arguments supplied to the proxy
                 * class creation (such as virtual machine limitations
                 * exceeded).
                 */
        throw new IllegalArgumentException(e.toString());
      }
    }
  }
}
我们先看下:$Proxy0的字节码是怎么生成的?

于是我们找到ProxyGenerator类的源码。https://github.com/JetBrains/jdk8u_jdk/blob/master/src/share/classes/sun/misc/ProxyGenerator.java

public class ProxyGenerator {
     
  public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
     
    ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
    final byte[] var4 = var3.generateClassFile();
    //...
    return var4;
  }

  
  /**
  *  重点在generateClassFile()函数;
  *  重点在generateClassFile()函数;
  *  重点在generateClassFile()函数;
  *  我们可以看到:
  *  以流的方式[DataOutputStream]一个个字节写入,字节码。
  *  然后导出,DataOutputStream.toByteArray()导出byte[]。
  **/
  private byte[] generateClassFile() {
     
    //...
    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    DataOutputStream dout = new DataOutputStream(bout);
    //开始将字节码写入dout流 ------ 如果想细究java的字节码文件格式的同学可以谷歌下:java字节码文件
    dout.writeInt(0xCAFEBABE);//0xCAFEBABE,看这里看这里,先写了4个字节:字节码的魔术
    dout.writeShort(CLASSFILE_MINOR_VERSION);//继续又写2个字节:minor version次版本
    dout.writeShort(CLASSFILE_MAJOR_VERSION);//继续又写2个字节:major version 主版本
    cp.write(dout);
    dout.writeShort(accessFlags);
    dout.writeShort(cp.getClass(dotToSlash(className)));
    dout.writeShort(cp.getClass(superclassName));
    dout.writeShort(interfaces.length);
    //...
    //
    return bout.toByteArray();
  }
}
我们继续看下,有了byte[]形式的字节码之后,我们如何将他解析成class呢?

我们继续找到Proxy的defineClass0()函数的源码

public class Proxy implements java.io.Serializable {
     

	//返现defineClass0()函数是native的
  private static native Class<?> defineClass0(ClassLoader loader, String name,
                                              byte[] b, int off, int len);
}

靠,Proxy.defineClass0()方法是c写的。

靠,Proxy.defineClass0()方法是c写的。

靠,Proxy.defineClass0()方法是c写的。

不慌不忙,继续找到他的c源码。

我们拿出大学时代学习的c语言知识。

接着看:Proxy.c

#include 
#include 

#include "jvm.h"
#include "io_util.h"


JNIEXPORT jclass JNICALL
  Java_java_lang_reflect_Proxy_defineClass0(JNIEnv *env,
                                            jclass ignore,
                                            jobject loader,
                                            jstring name,
                                            jbyteArray data,
                                            jint offset,
                                            jint length)
{
     
  //...
  jbyte *body;
  char *utfName;
  body = (jbyte *)malloc(length);
  
	//...
  //1)将字节码data数组,拷贝到body数组;
  //原则上java虚拟机,数组的内存分配在堆上,由java的GC统一释放,所以c要访问java的数组一般都需要做下处理下。
  (*env)->GetByteArrayRegion(env, data, offset, length, body);

	//...
  
  //2)下面可以简单理解为:将java的字符串name,拷贝到c的字符串指针上utfName。
  if (name != NULL) {
     
    jsize len = (*env)->GetStringUTFLength(env, name);
    jsize unicode_len = (*env)->GetStringLength(env, name);
    if (len >= (jsize)sizeof(buf)) {
     
      utfName = malloc(len + 1);
      if (utfName == NULL) {
     
        JNU_ThrowOutOfMemoryError(env, NULL);
        goto free_body;
      }
    } else {
     
      utfName = buf;
    }
    (*env)->GetStringUTFRegion(env, name, 0, unicode_len, utfName);
    VerifyFixClassname(utfName);
  } else {
     
    utfName = NULL;
  }

  //3)关键方法在这里
  //关键方法在这里
  //关键方法在这里
  //  DefineClass()函数,构建:代理类。并返回。
  //  这个方法是jni的标准api,好了JNIEnv的源码我们就不深挖了。
  result = (*env)->DefineClass(env, utfName, loader, body, length);

  //...
  
  return result;
}

五、总结

最后我们用一句话,总结一下:

java的动态代理的核心是,通过Proxy.newProxyInstance(classLoader,interfaces,invocationHandler)动态创建代理,将所有函数调用代理到invocationHandler里。

动态代理有一定的局限性,就是代理的对象必须有接口。

动态代理的使用场景非常少,正常开发基本很少用到动态代理,尤其android这种客户端开发就更少了。

大叔一直很迷惑,为什么有些面试官很喜欢问动态代理,理解java动态代理,能说明什么呢?




如果可以的话,大叔希望动态代理了,您的点赞功能。

可惜不行,那就主动点个赞 再走吧,老铁。



原文链接https://juejin.im/post/6874624518329991176

更多精彩原文:
  • kotlin如何解决java开发痛点,让程序员happier
  • google为何选择kotlin?kotlin如何解决java开发痛点【续】?
  • 深入浅出,kotin Any类
  • 一分钟入门kotiln协程,线程切换

  • 打破你的认知,java,除以0一定会崩溃吗?
  • 把断言(Assert)用的淋漓精致,提高代码的健壮性
  • 时间戳总结:System.nanoTime(),System.currentTimeMillis(),SystemClock
  • 突破Android O 系统对Service的限制
  • 详解:android O 对Service的限制【Background Execution Limits】

  • 技巧1《不在线查看 android 开发文档 && 离线查看android 开发文档》
  • 技巧2《adb root安卓模拟器,协助:问题跟进、android系统分析、竞品分析》
  • 技巧3《android开发,通过main方法,写单元测试》
  • 技巧4《android源码阅读及下载》

你可能感兴趣的:(java,android,java,android,jvm,jdk)