JDK动态代理机制分析

 
环境:jdk 1.8.0_201

引言

         本来是准备研究 Spring 源码,其中 AOP 部分对于普通接口类是通过 JDK 动态代理来实现的,所以顺便研究了动态代理的运行机制和特点,发现网上文章搜出来讲的都不太明白,所以本文结合原理和代码分析,对动态代理进行阐述。 通过阅读本文,读者将会对 Java 动态代理机制有更加深入的理解。
        Java 动态代理机制的出现,使得 Java 开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类。代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过程中,开发人员还可以按需调整委托类对象及其功能,这是一套非常灵活有弹性的代理框架。

代理:设计模式

        代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

图 1. 代理模式

        图1是通用的代理模式,Subject是需要实现的接口,ProxySubject是代理类,RealSubject是最终执行接口方法的委托类。为了保持行为的一致性,代理类和委托类通常会实现相同的接口,代理类中拥有委托类的实例,并在代理类的接口实现中通过该实例的接口实现,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。用户调用流程如下:
        用户 - 》ProxySubject -》 RealSubject 
图 2. JDK动态代理
    图2是 Java动态代理的实现,该实现 以巧妙的方式近乎完美地实践了代理模式的设计理念。 跟图1的区别在于,动态代理实现中 增加了调用处理器接口 InvocationHandler,用户需要实现自己的调用处理器类( ProxyInvocationHandler)。JDK用调用处理器去除了代理类ProxySubject和委托类RealSubject的耦合, 代理类拥有 调用处理器的实例,调用处理器中拥有委托类的实例。用户调用流程如下:
      用户 - 》ProxySubject -》 ProxyInvocationHandler -》 RealSubject 
    这样设计是为了保证动态代理实现的灵活性和通用性,不需要开发人员为每一个委托类定义一个代理类。JDK的动态代理类ProxySubject是动态生成的,无法开发者自定义。对委托类的调用和不同控制策略的实现都在调用处理器中通过反射的方式完成。
 
实例:惯例,我们先上个例子
1、定义好接口(jdk动态代理只能对接口进行代理)
package jdkproxy;

interface Subject {
   void doSomeThing();
}

 

2、定义好委托类(委托类实现了Subject接口)
package jdkproxy;

public class RealSubject implements Subject{

   @Override
   public void doSomeThing() {
      System.out.println("this is RealSubject do SomeThing");
   }

}

 

 
3、定义好调用处理器类
package jdkproxy;


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

public class JDKDynamicProxyHandler implements InvocationHandler {

   // 委托类,通过构造函数传入,可以是任意类型
   private Object target;

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

   /**
    * @description 获取代理类的实例对象
    */
   public <T> T getProxy() {
      return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
   }

   /**
    * @description invoke方法,代理类通过调用处理器类中的该方法,实现对委托类的代理
    */    
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      System.out.println("Do something in Proxy before");//委托类方法调用前处理
      Object result = method.invoke(target, args);//委托类方法调用
      System.out.println("Do something in Proxy after");//委托类方法调用后处理
      return result;
   }
}

 

 
4、测试
package jdkproxy;


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


public class TestJDKDynamicProxy {
   public static void main(String[] args){
      // 保存生成的代理类的字节码文件
      System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
      // jdk动态代理测试
      RealSubject realSubject = new RealSubject();
      Subject subject = new JDKDynamicProxyHandler(realSubject).getProxy();
      subject.doSomeThing();
   }
}

 

输出结果如下:
 

相关的类和接口

要了解 Java 动态代理的机制,首先需要了解以下相关的类或接口:
java.lang.reflect.Proxy:这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。

清单 1. Proxy 的静态方法

// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
static InvocationHandler getInvocationHandler(Object proxy)
 
// 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
 
// 方法 3:该方法用于判断指定类对象是否是一个动态代理类
static boolean isProxyClass(Class<?> cl)
 
// 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,
InvocationHandler h)
java.lang.reflect.InvocationHandler:这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。

清单 2. InvocationHandler 的核心方法

// 该方法负责集中处理动态代理类上的所有方法调用。第一个参数是动态代理类实例,第二个参数是被调用的方法对象
// 第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行
Object invoke(Object proxy, Method method, Object[] args)
每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象(参见 Proxy 静态方法 4 的第三个参数)。
java.lang.ClassLoader:这是类装载器类,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。
Proxy 静态方法生成的动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。
每次生成动态代理类对象时都需要指定一个类装载器对象(参见 Proxy 静态方法 4 的第一个参数),这个对象一般是通过委托类实例获取。

代理机制及其特点

首先让我们来了解一下如何使用 Java 动态代理。具体有如下四步骤:
通过实现 InvocationHandler 接口创建自己的调用处理器;
通过 为 Proxy 类指定 ClassLoader 对象、一组 interface 来创建动态代理类;
通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数传入。
 
然后,我们结合前面的代码来对这四个步骤从源码上进行分析。
 
第1步,通过实现 InvocationHandler 接口创建自己的调用处理器JDKDynamicProxyHandler,并将委托类作为调用处理器构造函数的唯一参数传入
public class JDKDynamicProxyHandler implements InvocationHandler {
   private Object target;
   public JDKDynamicProxyHandler(Object target) {
      this.target = target;
   }
   /**
    * @description 获取被代理类的实例对象
    */
   public <T> T getProxy() {
      return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
   }
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      System.out.println("Do something in Proxy before");
      Object result = method.invoke(target, args);
      System.out.println("Do something in Proxy after");
      return result;
   }
}

 

第2-4步,被JDK动态代理封装到了一个方法里面,传入的三个参数依次是委托类的classloader,委托类实现的接口,调用处理器实例
Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);

我们深入newProxyInstance方法,看看2-4步是如何实现的。
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);
    }


    /*
     * 第2步,创建动态代理类,该类默认会创建在com.sun.proxy的包下面,如果委托类的接口是非public的,则会创建在委托类接口的同级包下
     */
    Class<?> cl = getProxyClass0(loader, intfs);


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


        // 第3步,获取动态代理类构造函数,参数是InvocationHandler类,private static final Class<?>[] constructorParams = { InvocationHandler.class };
        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;
                }
            });
        }
        // 第4步,构造函数创建动态代理类实例,构造时调用处理器对象作为参数传入
        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);
    }
}

 

 
    接下来让我们来了解一下 Java 动态代理机制的一些特点。 jdk是通过 getProxyClass0获取到代理类的,所以分析的所有代理类的特点,都可以从这个方法里面找到。所以下来看下这个方法的源码:
/**
* 代理类的缓存
*/
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
    proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

/**
* 生成动态代理类
*/
private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }

    // 如果指定ClassLoader和接口的代理类存在, proxyClassCache将从缓存中获取;
    // 否则proxyClassCache将通过ProxyClassFactory创建代理类
    return proxyClassCache.get(loader, interfaces);
}

//proxyClassCache.get方法的定义
public V get(K key, P parameter)

 

    从上面可以看到,proxyClassCache实际上是一个WeakCache对象,传入的参数有KeyFactory和ProxyClassFactory实例(这两个Factory类都集成的BiFunction类,实现了apply方法)。这里只说下WeakCache的功能,具体源码分析(怎么使用弱引用做的缓存,怎么生成的代理类)可以看链接“ 基础-JDK动态代理-WeakCache解析”,这里WeakCache的get功能总结起来是:
  •     利用KeyFactory的apply方法,根据传入的loader和interfaces生成一个二级key(传入的interfaces为一级key)
  •     判断二级key对应的代理类是否存在,如果不存在,则调用ProxyClassFactory的apply方法创建代理类
  •    使用弱引用做一级key,解决内存泄露问题
    所以,要看动态类具体的创建过程,需要看ProxyClassFactory中apply的实现。后面结合特点和对应的实现来分析。
 
首先是动态生成的代理类本身的一些特点。
1)包:如果所代理的接口都是 public 的,那么它将被定义在顶层包(即包路径为空),如果所代理的接口中有非 public 的接口(因为接口不能被定义为 protect 或 private,所以除 public 之外就是默认的 package 访问级别),那么它将被定义在该接口所在包(假设代理了 com.ibm.developerworks 包中的某非 public 接口 A,那么新生成的代理类所在的包就是 com.ibm.developerworks),这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问,源码位于Proxy的 ProxyClassFactory工厂( 该工厂可以根据提供的classloader和接口数组,生成、定义和返回代理类 )实现中, 如下:
for (Class<?> intf : interfaces) {
    int flags = intf.getModifiers();
    // 判断接口是否public
    if (!Modifier.isPublic(flags)) {
        accessFlags = Modifier.FINAL;
        String name = intf.getName();
        int n = name.lastIndexOf('.');
        String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
        // 如果接口不是public,则设置代理类位于该接口的同级包下
        if (proxyPkg == null) {
            proxyPkg = pkg;
        } else if (!pkg.equals(proxyPkg)) {
            // 如果存在多个非public接口,且位于不同包下,则会抛异常
            throw new IllegalArgumentException(
                "non-public interfaces from different packages");
        }
    }
}

 

2)类修饰符:该代理类具有 final 和 public 修饰符,意味着它可以被所有的类访问,但是不能被再度继承,但如果委托类的接口是非public的,则代理类的修饰符会被修改为final, 源码同样位于Proxy的 ProxyClassFactory工厂实现 中, 如下
// 定义代理类修饰符为final 和 public
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

for (Class<?> intf : interfaces) {
    int flags = intf.getModifiers();
    // 判断接口是否public
    if (!Modifier.isPublic(flags)) {
        // 如果委托类的接口是非public的,代理类的修饰符则被修改为 final的
        accessFlags = Modifier.FINAL;
        String name = intf.getName();
        int n = name.lastIndexOf('.');
        String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
        // 如果接口不是public,则设置代理类位于该接口的同级包下
        if (proxyPkg == null) {
            proxyPkg = pkg;
        } else if (!pkg.equals(proxyPkg)) {
            // 如果存在多个非public接口,且位于不同包下,则会抛异常
            throw new IllegalArgumentException(
                "non-public interfaces from different packages");
        }
    }
}

 

3)类名:格式是”$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。 源码同样位于Proxy的 ProxyClassFactory工厂实现 中,如下
// 所有的代理类都使用$Proxy前缀
private static final String proxyClassNamePrefix = "$Proxy";

// 定义了一个原子类型,记录当前生成代理类的编号
private static final AtomicLong nextUniqueNumber = new AtomicLong();

// 拼接代理的最终包+名称
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;

 

4)类继承关系:该类的继承关系如图:
        由图可见,Proxy 类是它的父类,这个规则适用于所有由 Proxy 创建的动态代理类。而且该类还实现了其所代理的一组接口,这就是为什么它能够被安全地类型转换到其所代理的某接口的根本原因。这个源码可以通过idea反编译代理类的class文件来看到。
        接下来让我们了解一下代理类实例的一些特点。每个实例都会关联一个调用处理器对象,可以通过 Proxy 提供的静态方法 getInvocationHandler 去获得代理类实例的调用处理器对象。在代理类实例上调用其代理的接口中所声明的方法时,这些方法最终都会由调用处理器的 invoke 方法执行,此外,值得注意的是,代理类的根类 java.lang.Object 中有三个方法也同样会被分派到调用处理器的 invoke 方法执行,它们是 hashCode,equals 和 toString,可能的原因有:一是因为这些方法为 public 且非 final 类型,能够被代理类覆盖;二是因为这些方法往往呈现出一个类的某种特征属性,具有一定的区分度,所以为了保证代理类与委托类对外的一致性,这三个方法也应该被分派到委托类执行。当代理的一组接口有重复声明的方法且该方法被调用时,代理类总是从排在最前面的接口中获取方法对象并分派给调用处理器,而无论代理类实例是否正在以该接口(或继承于该接口的某子接口)的形式被外部引用,因为在代理类内部无法区分其当前的被引用类型。
接着来了解一下被代理的一组接口有哪些限制 , 这些限制都可以在Proxy类中,如下:
首先,接口类的数目不能超过 65535
private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {
    /*
     * 验证接口数目是否满足条件
     */
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }

    // 如果代理类已经被类装载器装载了,直接返回代理类,反之则通过ProxyClassFactory创建代理类;
    return proxyClassCache.get(loader, interfaces);
}

 

 
其次,需被代理的所有非 public 的接口类必须在同一个包中,否则代理类生成也会失败
for (Class<?> intf : interfaces) {
    int flags = intf.getModifiers();
    if (!Modifier.isPublic(flags)) {
        accessFlags = Modifier.FINAL;
        String name = intf.getName();
        int n = name.lastIndexOf('.');
        String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
        /*
         * 如果接口是非Public的,则代理会创建在跟该接口同一个包中,因为非public的接口只有同一个包下面的类才能访问。proxyPkg = pkg即是将接口的包名赋值给代理类作为代理类的包名。!pkg.equals(proxyPkg判断存在多个非public接口类的情况下,非public接口类的包名是否一致,如果不一致,则抛出异常。
         */
        if (proxyPkg == null) {
            proxyPkg = pkg;
        } else if (!pkg.equals(proxyPkg)) {
            throw new IllegalArgumentException(
                "non-public interfaces from different packages");
        }
    }
}

 

 
再次,接口对于类装载器必须可见
最后,不能有重复的接口,以避免动态代理类代码生成时的编译错误
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
    /*
     * 验证接口对于类装载器是否可见
     */
    Class<?> interfaceClass = null;
    try {
        interfaceClass = Class.forName(intf.getName(), false, loader);
    } catch (ClassNotFoundException e) {
    }
    if (interfaceClass != intf) {
        throw new IllegalArgumentException(
            intf + " is not visible from class loader");
    }
    /*
     * 验证传入的类是不是接口类
     */
    if (!interfaceClass.isInterface()) {
        throw new IllegalArgumentException(
            interfaceClass.getName() + " is not an interface");
    }
    /*
     * 验证接口类是否重复
     */
    if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
        throw new IllegalArgumentException(
            "repeated interface: " + interfaceClass.getName());
    }
}

 

        最后再来了解一下异常处理方面的特点。从调用处理器接口声明的方法中可以看到理论上它能够抛出任何类型的异常,因为所有的异常都继承于 Throwable 接口,但事实是否如此呢?答案是否定的,原因是我们必须遵守一个继承原则:即子类覆盖父类或实现父接口的方法时,抛出的异常必须在原方法支持的异常列表之内。所以虽然调用处理器理论上讲能够,但实际上往往受限制,除非父接口中的方法支持抛 Throwable 异常。那么如果在 invoke 方法中的确产生了接口方法声明中不支持的异常,那将如何呢?放心,Java 动态代理类已经为我们设计好了解决方法:它将会抛出 UndeclaredThrowableException 异常。这个异常是一个 RuntimeException 类型,所以不会引起编译错误。通过该异常的 getCause 方法,还可以获得原来那个不受支持的异常对象,以便于错误诊断。

美中不足

        诚然,Proxy 已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫 Proxy。Java 的继承机制注定了这些动态代理类们无法实现对 class 的动态代理,原因是多继承在 Java 中本质上就行不通。
有很多条理由,人们可以否定对 class 代理的必要性,但是同样有一些理由,相信支持 class 动态代理会更美好。接口和类的划分,本就不是很明显,只是到了 Java 中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。此外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。
但是,不完美并不等于不伟大,伟大是一种本质,Java 动态代理就是佐例。
 
 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值