深入理解系列之JAVA动态代理机制

代理的作用,就是生成代理对象使得真实对象的某些方法执行被代理对象拦截,从而在真实方法执行前、执行后添加额外的“动作”!动态代理则是指不需要修改原来的对象方法,在程序运行的过程中动态的生成代理对象,从而动态的生成这些“额外的”动作,主要从两个方面来深入理解动态代理机制!

问题一、动态代理的基本实现是什么?

动态代理本质上还是java中的“代理设计模式”,所以启UML图如下所示
这里写图片描述
真正实现的时候分为以下几个步骤:

1、创建被代理对象的接口

interface Subject{
      public void run();
    }

2、实现被代理的真实对象

class RealSubject implements Subject{
      @Override
      public void run(){
        System.out.println("真实对象正在运行");
      }
    }

3、创建调用处理器:

class SubjectInvocation implements InvocationHandler{

      private Subject subject;

      public SubjectInvocation(Subject subject){
        this.subject = subject;
      }

      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("将要代理");
        Object result = method.invoke(subject,args);
        System.out.println("代理结束");
        return null;
      }
    }

调用处理器对象很关键,我们可以看到调用处理器对象持有被代理对象的接口,当invoke()执行的时候,通过method.invoke()执行实际对象的具体方法,这样就相当于拦截了真实对象的执行,从而在实际方法执行的时候添加额外的动作!

4、生成代理对象:

public class ProxyTest {

      public static void main(String[] args){

        SubjectInvocation subjectInvocation = new SubjectInvocation(new RealSubject());
        Subject proxySubject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(),
                new Class[]{Subject.class},subjectInvocation);
        proxySubject.run();
        }
    }

重点关注代码:

Subject proxySubject = (Subject)Proxy.newProxyInstance(Subject.class.getClassLoader(),
                new Class[]{Subject.class},
                subjectInvocation);

我们发现,代理对象的类型是强制转换为的被代理的接口类型,其中代理对象的生成是调用Proxy类的静态方法实现的,

public static Object newProxyInstance(
ClassLoader loader, 
Class<?>[] interfaces, 
InvocationHandler h) 
throws IllegalArgumentException{…}

参数的含义如下:
loader:定义了代理类的ClassLoder,通常是interface.class.getClassLoader获得;
interfaces:代理类实现的接口列表
h:调用处理器,也就是我们上面定义的实现了InvocationHandler接口的类实例
最后当调用proxySubject.run()时,实际上被拦截进而执行invoke()函数。我们运行一下,看看我们的动态代理是否能正常工作。我这里运行后的输出为:

将要代理
真实对象正在运行
代理结束

问题二、为什么执行proxySubject.run()之后,会被拦截从而执行invoke()函数呢?

我们看到,invoke()函数其实是调用处理器里的函数,而ProxySubject是Subject类型的,他们之间有什么关系,为什么执行run之后会跳到invoke函数呢?解开这个谜底的第一步是去看 Proxy.newProxyInstance()方法的源码!
第一步:

public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException
        {
            Objects.requireNonNull(h);

            final Class<?>[] intfs = interfaces.clone();
            final SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
            }

            /*
             * Look up or generate the designated proxy class.
             */
            Class<?> cl = getProxyClass0(loader, intfs);

            /*
             * Invoke its constructor with the designated invocation handler.
             */
            try {
                if (sm != null) {
                    checkNewProxyPermission(Reflection.getCallerClass(), cl);
                }

                final Constructor<?> cons = cl.getConstructor(constructorParams);
                final InvocationHandler ih = h;
                if (!Modifier.isPublic(cl.getModifiers())) {
                    AccessController.doPrivileged(new PrivilegedAction<Void>() {
                        public Void run() {
                            cons.setAccessible(true);
                            return null;
                        }
                    });
                }
                return cons.newInstance(new Object[]{h});
            } catch (IllegalAccessException|InstantiationException e) {
                throw new InternalError(e.toString(), e);
            } catch (InvocationTargetException e) {
                Throwable t = e.getCause();
                if (t instanceof RuntimeException) {
                    throw (RuntimeException) t;
                } else {
                    throw new InternalError(t.toString(), t);
                }
            } catch (NoSuchMethodException e) {
                throw new InternalError(e.toString(), e);
            }
        }

我们把多余的异常处理等代码去除,只看核心代码

public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException
        {

            final Class<?>[] intfs = interfaces.clone();
            Class<?> cl = getProxyClass0(loader, intfs);
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            return cons.newInstance(new Object[]{h});
         }

我们可以看到,代理对象的生成实际上是利用反射获取构造函数,通过加载构造函数生成的,其中生成的对象持有调用处理器h。这里有个很隐藏的问题:return cons.newInstance(new Object[]{h});到底返回的是什么类型的对象?Subject?Proxy?其实都不是,我们可以打印出来该类的名称来验证一下,所以代码修改成这样:

public class ProxyTest {

      public static void main(String[] args){
        SubjectInvocation subjectInvocation = new SubjectInvocation(new RealSubject());
        Subject proxySubject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(),
                new Class[]{Subject.class},subjectInvocation);
        System.out.println(proxySubject.getClass().getName());
        proxySubject.run();
        }
    }

运行输出为:

proxySubject的类型是:$Proxy0
正在代理
真实对象正在运行
代理结束

OK,我们看到了一个意想不到的结果:Proxy0这个是什么类型?还记得文章开始所说的代理类是动态生成的吗?其中proxySubject就是代理类的实例对象,所以这个Proxy0就是动态生成的代理类,proxySubject就是根据这个类创建的动态代理对象!那么这个类和Proxy、Subject又有什么关系呢?为什么它持有run方法而且能调用invoke方法呢?解开谜底的第二步就是研究Proxy0!
第二步:

按理说,生成的动态代理类是直接加载到内存中我们无法获取的,直观的方法可以通过反射还原这个类的内容,但实在复杂!但是好在JDK提供了代理类的生成函数,所以代码改成这样:

public class ProxyTest {
    public static void main(String[] args){
//添加这一句
    System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
    SubjectInvocation subjectInvocation = new SubjectInvocation(new RealSubject());
    Subject proxySubject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(),
                new Class[]{Subject.class},subjectInvocation);
    System.out.println(proxySubject.getClass().getName());
    proxySubject.run();
} 

System.getProperties().put(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”) 这句话执行后再进行动态代理生成就会在项目的根目录下生成代理类的字节码文件(实际路径是包名+类名)然后再借用IDE的反编译功能就能看到这个动态生成类的庐山真面目:

import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;

    final class $Proxy0 extends Proxy implements Subject {
      private static Method m1;
      private static Method m3;
      private static Method m2;
      private static Method m0;

      public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
      }

      public final boolean equals(Object var1) throws  {
        try {
          return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
        } catch (RuntimeException | Error var3) {
          throw var3;
        } catch (Throwable var4) {
          throw new UndeclaredThrowableException(var4);
        }
      }

      public final void run() throws  {
        try {
          super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
          throw var2;
        } catch (Throwable var3) {
          throw new UndeclaredThrowableException(var3);
        }
      }

      public final String toString() throws  {
        try {
          return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
          throw var2;
        } catch (Throwable var3) {
          throw new UndeclaredThrowableException(var3);
        }
      }

      public final int hashCode() throws  {
        try {
          return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
          throw var2;
        } catch (Throwable var3) {
          throw new UndeclaredThrowableException(var3);
        }
      }

      static {
        try {
          m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
          m3 = Class.forName("proxy.Subject").getMethod("run");
          m2 = Class.forName("java.lang.Object").getMethod("toString");
          m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
          throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
          throw new NoClassDefFoundError(var3.getMessage());
        }
      }
    }

我们很明显的看到, Proxy0继承了Proxy实现了Subject!这样,因为继承Proxy,所以Proxy0就持有了调用处理器对象,因为实现Subject,所以Proxy0就可以拥有run()方法。这样我们继续看代码就可以轻而易举的找出run()方法时如何联系上调用处理器的invoke()方法的了!

首先看到,$Proxy0类声明了m0~m3几个Method类型的变量:

      private static Method m1;
      private static Method m3;
      private static Method m2;
      private static Method m0;

然后看最后的静态代码块(类加载的时候,静态代码块首先执行):

static {
        try {
          m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
          m3 = Class.forName("Subject").getMethod("run");
          m2 = Class.forName("java.lang.Object").getMethod("toString");
          m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
          throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
          throw new NoClassDefFoundError(var3.getMessage());
        }
      }

可以看到,这些变量实际上是通过反射机制利用Class.forName().getMethod()动态加载的指向特定类型下的方法引用变量,这里我们只看run()方法:

public final void run() throws  {
        try {
          super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
          throw var2;
        } catch (Throwable var3) {
          throw new UndeclaredThrowableException(var3);
        }
      }

“很巧合”的是,$Proxy0中“竟然”也有一个run()方法(实际上是因为继承了Subject罢了),而且里面传入的方法引用便是m3,而执行run方法后,竟然调用的是h.invoke()!

到这里基本可以说是真相大白了: ProxySubject是$Proxy0类型对象,而$Proxy0类因为继承Proxy实现了Subject,所以持有了调用处理器h和run()方法,而且重写了run方法:执行run()方法后实际执行的是invoke方法,而invoke方法因为传入了由反射机制生成的方法引用,所以执行invoke方法后再执行method.invoke(),自然而然的执行了method也就是run方法!

读者应该也注意到,$Proxy0除了包含run方法外还有hashcode、equals、toString方法,这是因为所有的类包括 Subject类都是继承的Object类,$Proxy0自然也包含这些方法! 这么说来,JDK的动态代理机制的UML图就变成了这样:
这里写图片描述

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值