Little Leopard показывает исходный код: динамический прокси JDK

Java исходный код

Следующий исходный код JDK и Javadoc взяты или переведены из реализации версии Java «1.8.0_152».

1 Краткое описание динамических прокси

1.1 Что такое режим прокси

Шаблон прокси — это шаблон прокси, один из 23 наиболее часто используемых шаблонов проектирования для объектно-ориентированного программного обеспечения. Шаблон прокси предоставляет другим объектам прокси для управления доступом к этому объекту. В некоторых случаях объект не подходит или не может напрямую ссылаться на другой объект, а прокси-объект может действовать как посредник между клиентом и целевым объектом. Режим прокси состоит из абстрактных ролей, прокси-ролей и реальных ролей. Абстрактная роль объявляет бизнес-метод, реализованный реальной ролью через интерфейс или абстрактный класс; прокси-роль реализует абстрактную роль, которая является прокси реальной роли, реализует абстрактный метод через метод бизнес-логики реальной роли, и может добавлять свои собственные операции; реальная роль реализует абстракцию Роль, которая определяет бизнес-логику, которая должна быть реализована реальной ролью для вызова прокси-роли. Для получения дополнительной информации см. диаграмму классов режима прокси:

代理模式类图

1.2 Что такое динамический прокси

Динамический прокси — это режим динамического прокси, так называемый динамический, который означает, что абстрактные классы (т.е. абстрактные роли) не определяются во время компиляции, а генерируются во время выполнения. Напротив, поведение абстрактных классов в статических прокси определяется во время компиляции. Динамические прокси — это обычная реализация АОП (аспектно-ориентированного программирования).

1.3 Пример использования динамического прокси

Динамический прокси-сервер Java очень прост в использовании, нам нужно только освоитьProxy.newProxyInstanceметод. Метод Proxy.newProxyInstance определен в JDK следующим образом:

/**
 * 返回一个受调用处理器 (InvocationHandler) 管理,实现了指定接口的代理类的实例
 *
 * @param   loader 声明这个代理类的 ClassLoader
 * @param   interfaces 代理类实现的接口列表
 * @param   h 处理代理类的调用的调用处理器
 * @return  一个受调用处理器 (InvocationHandler) 管理,实现了指定接口的代理类的实例
 * @throws  IllegalArgumentException 违反了 getProxyClass 函数的参数限制条件
 * @throws  SecurityException 如果安全管理器存在并且下面的任意条件满足:
 *               (1) 传入的 loader 是 null 且调用者的类加载器非空,
 *               使用 RuntimePermission("getClassLoader")权限
 *               调用 SecurityManager#checkPermission禁止访问
 *
 *               (2) 对于每一个代理接口,调用者的类加载器与接口类加载器不同或不是其父类,
 *               并且调用 SecurityManager#checkPackageAccess 无权访问接口
 *
 *               (3) 所有传入的代理接口都是非公共的,且调用者类与非公共接口不在同一个包下,
 *               使用 ReflectPermission("newProxyInPackage.{package name}") 调用
 *               SecurityManager#checkPermission 无访问权限
 * @throws  NullPointerException interfaces 数组参数或其中的元素为 null,以及调用处理器 h 为 null
 */
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException;

Из Javadoc мы можем узнать, что нам нужно только передать соответствующий загрузчик класса, интерфейс и вызвать процессор для создания экземпляра прокси. Тогда мы не знакомы с классом InvocationHandler здесь. Давайте посмотрим на код класс InvocationHandler:

package java.lang.reflect;

/**
* InvocationHandler是代理实例的调用处理器实现的接口。
* 每个代理实例都有一个关联的调用处理器。
* 在调用代理实例的方法时,方法调用将被编码并分派给其调用处理程序的 invoke 方法。
*
* @author      Peter Jones
* @see         Proxy
* @since       1.3
*/
public interface InvocationHandler {

   /**
    * 在代理实例上处理方法调用并返回结果。当在与其关联的代理实例上调用
    * 方法时,将调用处理期上的此方法。
    *
    * @param   proxy 该方法被调用的代理实例
    *
    * @param   method Method 对象将是代理接口声明的方法,它可能是代理
    *                 类继承方法的代理接口的超级接口。
    * @param   args 包含在代理实例的方法调用中传递的参数值的对象数组,
    *               如果interface方法不带参数,则为null。基本类型的参
    *               数被封装在适当的基本封装类的实例中,比如
    *               java.lang.Integer 或者 java.lang.Boolean。
    * @return  调用代理实例上的方法获得的返回值。如果接口方法的声明返
    *          回类型是基本类型,则此方法返回的值必须是相应基本包装类
    *          的实例;否则,它必须是转换为声明的返回类型的类型。如果
    *          此方法返回的值为null,并且接口方法的返回类型为原始类型,
    *          则代理实例上的方法调用将引发NullPointerException。如果
    *          此方法返回的值与上面所述的接口方法的声明返回类型不兼容,
    *          则将通过代理实例上的方法调用抛出ClassCastException。
    *
    * @throws  抛出调用代理实例的方法时抛出的异常。异常的类型必须可以
    *          转化为接口方法的 throws 子句中声明的异常类型,也可以分
    *          配给不强制检查的异常类型 java.lang.RuntimeException 或
    *          java.lang.Error。如果这个方法抛出一个强制检查的异常,
    *          这个异常不能转化为接口方法的 throws 子句中声明的异常类
    *          型,那么将会抛出包含这个异常的
    *          UndeclaredThrowableException 异常。
    *
    * @see     UndeclaredThrowableException
    */
   public Object invoke(Object proxy, Method method, Object[] args)
       throws Throwable;
}

Из Javadoc мы знаем, что вызов черезProxy.newProxyInstanceКогда метод в экземпляре прокси, созданном методом, выполняется, входящийInvocationHandler#invokeметод, возвращаемое значение метода в экземпляре проксиInvocationHandler#invokeвозвращаемое значение метода.
Проведем небольшой тест:

package com.example.demo;

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

public class Main {
    /**
     * 代理接口
     */
    interface ITest {
        String test(String val);
    }

    /**
     * 代理实现类
     */
    class Test implements ITest {
        @Override
        public String test(String val) {
            return val + "我是Test";
        }
    }

    /**
     * 调用处理器
     */
    class TestInvocationHandler implements InvocationHandler {

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println(method);
            return args[0] + "我是TestProxy";
        }
    }

    /**
     * 分别对正常实现的 ITest 实现类和动态代理实现类进行调用
     */
    void testProxy() {
        ITest test = new Test();
        ITest testProxy = (ITest) Proxy.newProxyInstance(this.getClass().getClassLoader(),
                new Class[]{ITest.class}, new TestInvocationHandler());
        System.out.println(test.test("Hello,"));
        System.out.println("----------");
        System.out.println(testProxy.test("Hello,"));
    }

    public static void main(String[] args) {
        new Main().testProxy();
    }

}

Результат:

Hello,我是Test
----------
public abstract java.lang.String com.example.demo.Main$ITest.test(java.lang.String)
Hello,我是TestProxy

Из тестового примера мы можем увидеть две характеристики:

  • Класс реализации, реализующий интерфейс ITest, не нужно писать вручную, он генерируется и создается автоматически.
  • Вызовите автоматически сгенерированный экземпляр класса прокси ITest, который вызоветInvocationHandler#invokeметод.

Интересно, если у вас возникнут вопросы при использовании MyBatis, почему вы можете напрямую вызвать интерфейс? Ответ здесь.На самом деле, MyBatis использует аналогичную технологию, чтобы помочь нам реализовать прокси-класс, и все, что мы получаем, это экземпляр класса прокси-класса интерфейса.

2 Принцип реализации динамического прокси в Java

Чтобы выделить ключевые моменты, в следующем коде показан только код, относящийся к теме, а нерелевантное содержимое, такое как защитное программирование и обработка исключений, было опущено.Для полной реализации, пожалуйста, найдите JDK самостоятельно.

Так как же работает динамический прокси-сервер Java? Это возбудило мое любопытство. Итак, перейдем к исходному коду JDK.
ПроверятьProxy.newProxyInstanceРеализация:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
    throws IllegalArgumentException {
    
    final Class<?>[] intfs = interfaces.clone();
    // 通过类加载器和接口使用 getProxyClass0 方法创建实现类
    Class<?> cl = getProxyClass0(loader, intfs);
    // 获得指定构造器
    final Constructor<?> cons = cl.getConstructor(constructorParams);
    // 创建实例
    return cons.newInstance(new Object[]{h});
}

Процесс создания экземпляра в двух предложениях — это обычная операция отражения, которая не будет здесь повторяться.getProxyClass0Как метод создает класс через интерфейс? мы продолжаем следитьgetProxyClass0Реализация метода:

private static Class<?> getProxyClass0(ClassLoader loader,
                                    Class<?>... interfaces) {
    return proxyClassCache.get(loader, interfaces);
}

мы следим заproxyClassCache.getреализации, это должен быть класс, отвечающий за управление кешем:

public V get(K key, P parameter) {
    // Cache 置换、检查等实现均已省略,以下是 Cache 未命中时,创建新实现类的代码
    Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
    V value = supplier.get();
    return value;
}

мы следим заProxyClassFactory#applyРеализация:

public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
    for (Class<?> intf : interfaces) {
        interfaceClass = Class.forName(intf.getName(), false, loader);
        // 对 interfaceClass 进行了系列权限检查,实现略
    }
    // 根据 interfaces、accessFlags 产生名为 proxyName 的代理类字节码
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, 
                                                interfaces, accessFlags);
    // 加载字节码,产生类对象
    return defineClass0(loader, proxyName, proxyClassFile, 
                                                0, proxyClassFile.length);
}

Из кода мы видим:

  • ProxyGenerator.generateProxyClass используется для генерации байт-кода прокси-класса.
  • defineClass0 используется для загрузки байт-кода для создания объекта класса.

Здесь defineClass0 — нативный метод, в него мы вдаваться не будем. ProxyGenerator.generateProxyClass работает с байт-кодом.Поскольку я не очень хорошо понимаю спецификацию байт-кода, нам не очень полезно смотреть на реализацию его кода здесь. Так что делать? Проведем небольшой эксперимент:

public class Main2 {
    /**
     * 代理接口
     */
    interface ITest {
        String test(String val);
    }

    public static void main(String[] args) throws IOException {
        // 通过 ProxyGenerator.generateProxyClass 产生字节码
        byte[] testProxyBytes = ProxyGenerator.generateProxyClass("TestProxy", new Class[]{ITest.class});
        // 将字节码输出到文件,然后我们再反编译它,看看它的内容是什么
        FileOutputStream fileOutputStream = new FileOutputStream("TestProxy.class");
        fileOutputStream.write(testProxyBytes);
        fileOutputStream.flush();
        fileOutputStream.close();
    }
}

Декомпилированный исходный код TestProxy.class:

public final class TestProxy extends Proxy implements ITest {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public TestProxy(InvocationHandler var1) throws  {
        super(var1);
    }

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

    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 String test(String var1) throws  {
        try {
            return (String)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } 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"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.example.demo.Main2$ITest").getMethod("test", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

пройти черезProxyGenerator.generateProxyClassСгенерированный байт-код класса имеет следующие характеристики:

  • Этот класс наследует Proxy и реализует класс входящего интерфейса (ITest).
  • Класс определяет экземпляры метода для всех методов, содержащихся в классе, в статическом блоке кода.
  • В классе есть конструкторTestProxy(InvocationHandler var1)Передайте обработчик вызовов.
  • Все методы этого класса будут выполненыsuper.h.invokeи вернуть его результат.

тогда вотsuper.hЧто это такое, давайте посмотрим на его родительский классProxyкод:

protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
    Objects.requireNonNull(h);
    this.h = h;
}

Меня осенило! здесьsuper.hэтоTestProxy(InvocationHandler var1)h передается в конструкторе.

3 Резюме

  • пользователь черезProxy.newProxyInstanceМетод передает загрузчику класса объект класса интерфейса и вызывает обработчик для создания экземпляра прокси-класса.
  • Прошел в JDKProxyGenerator.generateProxyClassМетод генерирует байт-код прокси-класса в соответствии с входящим объектом класса интерфейса и загружает байт-код для создания объекта прокси-класса.
  • Сгенерированный прокси-класс наследует Proxy для реализации входящих классов интерфейса.
  • Каждый метод этого класса будет выполнять метод вызова вызывающего процессора, передавать соответствующие параметры и возвращать возвращаемое значение метода вызова.