从JDK源码分析动态代理原理

向右看齐 2023-07-22 07:00 145阅读 0赞
  1. 大家都知道像Spring AOPMyBatis等一些优秀的框架源码里都在大量使用动态代理,动态代理可以在不改变源码结构的情况下使得方法功能可以前置增强以及后置增强。当然代理模式也是23种设计模式之一。但是今天不是讨论动态代理怎么使用,而是动态代理在JDK的底层是怎么实现的?

我们先看一个简单动态代理的例子

1、被代理对象的接口

  1. package com.mzt.proxy;
  2. /**
  3. * @author 马志涛
  4. */
  5. public interface ITest {
  6. public void sayHello();
  7. }

2、被代理对象的接口的实现

  1. package com.mzt.proxy;
  2. /**
  3. * @author 马志涛
  4. */
  5. public class TestImpl implements ITest {
  6. @Override
  7. public void sayHello() {
  8. System.out.println("this is myself");
  9. }
  10. }

3、代理对象辅助类实现InvocationHandler

  1. package com.mzt.proxy;
  2. import java.lang.reflect.InvocationHandler;
  3. import java.lang.reflect.Method;
  4. /**
  5. * @author 马志涛
  6. */
  7. public class TestProxy implements InvocationHandler {
  8. Object obj;
  9. public TestProxy(Object obj){
  10. this.obj = obj;
  11. }
  12. @Override
  13. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  14. System.out.println("前置增强实现...");
  15. method.invoke(obj , args);
  16. System.out.println("后置增强实现...");
  17. return obj;
  18. }
  19. }

4、测试客户端

  1. package com.mzt.proxy;
  2. import java.lang.reflect.Proxy;
  3. /**
  4. * @author 马志涛
  5. */
  6. public class MainTest {
  7. public static void main(String[] args) {
  8. ITest iTest = new TestImpl();
  9. TestProxy testProxy = new TestProxy(iTest);
  10. ITest test = (ITest)Proxy.newProxyInstance(ITest.class.getClassLoader(),new Class[]{ITest.class} , testProxy);
  11. test.sayHello();
  12. }
  13. }

5、运行程序结果

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21hemhpdGFvMTk4Ng_size_16_color_FFFFFF_t_70

6、从第5步可以看到在没有改变sayHello() 方法里代码的情况下,实现了前置增强和后置增强的实现,调试代码看代理对象test是$Proxy0的形式

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21hemhpdGFvMTk4Ng_size_16_color_FFFFFF_t_70 1

思考:

我们知道一个类的完整生命周期是以下几个步骤:

Java源文件(Java文件) ——> Java字节码文件(.class文件) ——> Class对象 ——> 实例对象——- >卸载

那么问题来了

它是怎么样在内存中生成?

生成的class的文件结构是什么样的?

1、动态代理类是事先不存在Java源文件和Java字节码的,那么它是怎么样跳过这两步生成Class对象的呢?我们进入Proxy.newProxyInstance()源码一探究竟。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21hemhpdGFvMTk4Ng_size_16_color_FFFFFF_t_70 2

2、进入getProxyClass0这个方法

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21hemhpdGFvMTk4Ng_size_16_color_FFFFFF_t_70 3

3、进入proxyClassCache.get(loader,interfaces)方法

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21hemhpdGFvMTk4Ng_size_16_color_FFFFFF_t_70 4

4、进入subKeyFactory.apply(key,parameter)方法,是个接口

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21hemhpdGFvMTk4Ng_size_16_color_FFFFFF_t_70 5

5、进入ProxyClassFactory这个实现,可以看出代理类为什么是$Proxy0开头的,继续往下看

20200404223816200.png20200404223832736.png

6、关键的一句代码,看注释Generate the specified proxy class,可以看出来是在这里生成字节码文件的

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21hemhpdGFvMTk4Ng_size_16_color_FFFFFF_t_70 6

7、我们想要查看proxyClassFile这个字节码文件的内容就必须要将它导出一个class文件到磁盘,然后反编译可以查看其内容。以下代码是工具类,入参是代理工具类的全限名(proxyName)和被代理对象的接口(interfaces)

  1. package com.mzt.proxy;
  2. import sun.misc.ProxyGenerator;
  3. import java.io.FileOutputStream;
  4. import java.io.IOException;
  5. /**
  6. * 将动态代理字节码文件输出到磁盘
  7. * 马志涛
  8. */
  9. public class ClassOutUtil {
  10. public void outPutFile(String proxyName, Class interfaces){
  11. String paths = interfaces.getResource(".").getPath() + proxyName + ".class";
  12. /*
  13. * Generate the specified proxy class.
  14. */
  15. byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
  16. proxyName, new Class[]{interfaces});
  17. FileOutputStream fileOutputStream = null;
  18. try{
  19. fileOutputStream = new FileOutputStream(paths);
  20. fileOutputStream.write(proxyClassFile);
  21. fileOutputStream.flush();
  22. }catch (Exception ex){
  23. ex.printStackTrace();
  24. }finally {
  25. try {
  26. fileOutputStream.close();
  27. } catch (IOException e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. }
  32. public static void main(String[] args) {
  33. ClassOutUtil classOutUtil = new ClassOutUtil();
  34. classOutUtil.outPutFile("com.mzt.TestProxy", ITest.class);
  35. }
  36. }

8、我们可以很神奇的看到生成了一个com.mzt.TestProxy.class文件,生成的字节码文件内容如下

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21hemhpdGFvMTk4Ng_size_16_color_FFFFFF_t_70 7

  1. //
  2. // Source code recreated from a .class file by IntelliJ IDEA
  3. // (powered by Fernflower decompiler)
  4. //
  5. package com.mzt;
  6. import com.mzt.proxy.ITest;
  7. import java.lang.reflect.InvocationHandler;
  8. import java.lang.reflect.Method;
  9. import java.lang.reflect.Proxy;
  10. import java.lang.reflect.UndeclaredThrowableException;
  11. public final class TestProxy extends Proxy implements ITest {
  12. private static Method m1;
  13. private static Method m3;
  14. private static Method m2;
  15. private static Method m0;
  16. public TestProxy(InvocationHandler var1) throws {
  17. super(var1);
  18. }
  19. public final boolean equals(Object var1) throws {
  20. try {
  21. return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
  22. } catch (RuntimeException | Error var3) {
  23. throw var3;
  24. } catch (Throwable var4) {
  25. throw new UndeclaredThrowableException(var4);
  26. }
  27. }
  28. public final void sayHello() throws {
  29. try {
  30. super.h.invoke(this, m3, (Object[])null);
  31. } catch (RuntimeException | Error var2) {
  32. throw var2;
  33. } catch (Throwable var3) {
  34. throw new UndeclaredThrowableException(var3);
  35. }
  36. }
  37. public final String toString() throws {
  38. try {
  39. return (String)super.h.invoke(this, m2, (Object[])null);
  40. } catch (RuntimeException | Error var2) {
  41. throw var2;
  42. } catch (Throwable var3) {
  43. throw new UndeclaredThrowableException(var3);
  44. }
  45. }
  46. public final int hashCode() throws {
  47. try {
  48. return (Integer)super.h.invoke(this, m0, (Object[])null);
  49. } catch (RuntimeException | Error var2) {
  50. throw var2;
  51. } catch (Throwable var3) {
  52. throw new UndeclaredThrowableException(var3);
  53. }
  54. }
  55. static {
  56. try {
  57. m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
  58. m3 = Class.forName("com.mzt.proxy.ITest").getMethod("sayHello");
  59. m2 = Class.forName("java.lang.Object").getMethod("toString");
  60. m0 = Class.forName("java.lang.Object").getMethod("hashCode");
  61. } catch (NoSuchMethodException var2) {
  62. throw new NoSuchMethodError(var2.getMessage());
  63. } catch (ClassNotFoundException var3) {
  64. throw new NoClassDefFoundError(var3.getMessage());
  65. }
  66. }
  67. }

9、代理类继承了Proxy类,实现了ITest接口,我们看一下sayHello()方法,原来核心是h.invoke()这个方法

  1. public final void sayHello() throws {
  2. try {
  3. super.h.invoke(this, m3, (Object[])null);
  4. } catch (RuntimeException | Error var2) {
  5. throw var2;
  6. } catch (Throwable var3) {
  7. throw new UndeclaredThrowableException(var3);
  8. }
  9. }

10、代理类中并没有h这个对象,我们查看它的父类Proxy,原来h是父类中的InvocationHandler,也就是说利用代理类调用方法的时候,实际上是调用了辅助类中的Invoke方法,到此为止动态代理原理基本上搞懂了。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21hemhpdGFvMTk4Ng_size_16_color_FFFFFF_t_70 8

发表评论

表情:
评论列表 (有 0 条评论,145人围观)

还没有评论,来说两句吧...

相关阅读

    相关 分析JDK动态代理

    引言 动态代理非常的重要,虽然我们在日常的工作中没有非常底层的 编写过动态代理的代码,但是动态代理却起着非常重要的功能,想一下我们经常使用的框架: 日志框架、AOP等等,

    相关 JDK动态代理分析

    一、代理模式是什么? 代理模式就是给一个对象提供一个代理对象,并由代理对象管理着被代理对象的引用。就像生活中的代理律师,你只需要找好代理律师,剩下的都交给代理律师来打理。