Принцип и применение динамического агента

Java задняя часть Spring Hibernate

Введение в динамические агенты

Динамический прокси — это механизм, который динамически создает прокси-объекты во время выполнения и динамически обрабатывает вызовы прокси-метода.

Фактически это прокси-механизм. Прокси можно рассматривать как инкапсуляцию вызывающего таргета, а обращение к целевому коду осуществляется напрямую через прокси

Сравнение со статическими прокси

статический прокси

Заранее напишите прокси-класс, каждому бизнес-классу должен соответствовать прокси-класс, который негибок.

  • ISubject, интерфейс — это посетитель или посещаемый объект
  • SubjectImpl, конкретный класс реализации посетителя
  • SubjectProxy, класс реализации прокси доступного или доступного ресурса
  • Клиент: представляет абстрактную роль посетителя.Клиент будет обращаться к объекту или ресурсу типа Isubject и обращаться к нему через прокси-класс.
  • И SubjectImpl, и SubjectProxy здесь реализуют интерфейс ISubject. SubjectProxy перенаправляет запрос в SubjectImpl, внутри которого находится объект SubjectImpl, и может добавлять некоторые ограничения.

Приведенная выше диаграмма по сути является объяснением режима прокси, который подходит для статического прокси и динамического прокси.Разница заключается в том, когда и как генерируются прокси-объекты и прокси-методы..

Динамический прокси предназначен для автоматического создания прокси-объектов во время выполнения. Одним из недостатков является то, что для создания прокси-объектов и вызова прокси-методов требуется время.

Как реализовать динамический прокси

Существует два основных способа реализации динамического прокси: один — динамический прокси JDK, другой — механизм байт-кода CGLIB и, конечно же, библиотека Javassist или ASM, эти два способа представлены вместе в CGLIB.

Динамический прокси JDK

Говоря о динамическом прокси JDK, нельзя не упомянуть о механизме отражения: динамический прокси JDK реализуется через механизм отражения.

механизм отражения

Отражение — это получение информации о классе во время выполнения через класс Class и библиотеку классов java.lang.reflect. Например, через классы Field, Method и Constructor в библиотеке классов java.lang.reflect можно получить соответствующую информацию о классе.

Реализация динамического прокси JDK

Ниже приведен простой пример реализации динамического прокси JDK с помощью механизма отражения.

/**
 * 动态代理的实现
 *
 * @author pjmike
 * @create 2018-08-04 17:42
 */
public class DynamicProxy {
    public static void main(String[] args) {
        IHelloImpl hello = new IHelloImpl();
        MyInvocationHandler handler = new MyInvocationHandler(hello);
        //获取目标用户的代理对象
        IHello proxyHello = (IHello) Proxy.newProxyInstance(IHelloImpl.class.getClassLoader(), IHelloImpl.class.getInterfaces(), handler);
        //调用代理方法
        proxyHello.sayHello();
    }
}

/**
 * 被访问者接口
 */
interface IHello{
    void sayHello();
}

/**
 * 被访问者的具体实现类
 */
class IHelloImpl implements IHello {

    @Override
    public void sayHello() {
        System.out.println("Hello World");
    }
}

class MyInvocationHandler implements InvocationHandler {
    private Object target;

    /**
     *
     * @param target 被代理的目标对象
     */
    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    /**
     * 执行目标对象的方法
     *
     * @param proxy 代理对象
     * @param method 代理方法
     * @param args 方法参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("invoke method");
        System.out.println("Method name : "+method.getName());
        Object result = method.invoke(target, args);
        return result;
    }
}

Как видно из приведенного выше примера, генерация прокси-объекта выполняется с помощьюProxy.newProxyInstance()завершить

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

newProxyInstance()Метод в основном имеет следующие три параметра

  • Загрузчик классов (ClassLoader) используется для загрузки динамических прокси-классов.
  • Массив для реализации интерфейса, его видно с этой точки, если хотитеЧтобы использовать динамический прокси JDK, должен быть класс интерфейса
  • Реализация интерфейса InvocactionHandler.

proxy

Динамические прокси могут перенаправлять все вызовы вызывающему обработчику, поэтому обычно ссылка на «реальный» объект передается конструктору вызывающего обработчика, что позволяет обработчику запрашивать переадресацию при выполнении задач.

Реализация динамического прокси с CGLIB

cglib— это основанная на ASM библиотека генерации байт-кода для создания и преобразования байт-кода Java.

ASM — это легкий, но высокопроизводительный фреймворк для работы с байт-кодом. cglib — это приложение верхнего уровня, основанное на ASM.Для классов, которые не реализуют интерфейсы, cglib очень полезен.

cglib

Простой пример динамического прокси CGLIB

По сути, для класса, который необходимо проксировать, он просто динамически генерирует подкласс для переопределения нефинального метода и привязывает хук для обратного вызова пользовательского перехватчика.

Добавить зависимость CGLIB

 <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>3.2.4</version>
 /dependency>
package com.pjmike.proxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * CGLIB动态代理实现
 *
 * @author pjmike
 * @create 2018-08-06 16:55
 */
public class CglibProxy {
    public static void main(String[] args) {
        //Enhancer是CGLIB的核心工具类,是一个字节码增强器,它可以方便的对类进行扩展
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(PersonService.class);
        //设置回调所需的拦截器
        enhancer.setCallback(new MyMethodInterceptor());
        //通过enhancer.create()方法获取代理对象
        //对代理对象所有非final的方法调用都会转发给MethodInterceptor.intercept方法,
        //作用跟JDK动态代理的InvocationHandler类似
        PersonService personService = (PersonService) enhancer.create();
        System.out.println(personService.sayHello("pjmike"));
    }
}

class PersonService {
    public String sayHello(String name) {
        return "Hello, " + name;
    }
}

class MyMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        return methodProxy.invokeSuper(obj, args);
    }
}

Что касается упомянутого выше javassist, то он также должен напрямую манипулировать байт-кодом, что похоже на ASM, поэтому порог для использования этих двух относительно высок, и он обычно используется для базовой реализации фреймворка. Например, hibernate использует javassist и cglib внизу.

javassist

javassist — это библиотека компиляции во время выполнения, которая может динамически генерировать или изменять байт-код класса. Он имеет две схемы реализации динамического прокси: интерфейс динамического прокси, предоставляемый javassist, и байт-код javassist.

Поскольку javassist предоставляет динамический прокси-интерфейс, он относительно медленный, поэтому здесь мы в основном анализируем байт-код javassist, не рассматривая его динамический прокси-интерфейс. Что касается сравнения производительности этих прокси-схем, см.Сравнение производительности динамических прокси-решений.

javassist в основном состоит из CtClass, CtMethod и CtField, которые аналогичны Class, Method и Field в отражении JDK.

Простой в использовании

добавить зависимости

<dependency>
      <groupId>org.javassist</groupId>
      <artifactId>javassist</artifactId>
      <version>3.21.0-GA</version>
</dependency>

код

package com.pjmike.proxy;

import javassist.*;

/**
 * javassist字节码
 *
 * @author pjmike
 * @create 2018-08-07 0:07
 */
public class JavassistByteCode {
    public static void main(String[] args) throws IllegalAccessException, CannotCompileException, InstantiationException, NotFoundException {
        ByteCodeAPI byteCodeApi = createJavassistBycodeDynamicProxy();
        System.out.println(byteCodeApi.sayHello());
    }
    public static ByteCodeAPI createJavassistBycodeDynamicProxy() throws CannotCompileException, IllegalAccessException, InstantiationException, NotFoundException {
        //获取运行时类的上下文
        ClassPool pool = ClassPool.getDefault();
        //动态创建类
        CtClass cc = pool.makeClass(ByteCodeAPI.class.getName()+"demo");
        cc.addInterface(pool.get(ByteCodeAPI.class.getName()));
        //创建属性
        CtField field = CtField.make("private String a;", cc);
        cc.addField(field);
        //创建方法
        CtMethod method = CtMethod.make("public String sayHello() {return \"hello\";}", cc);
        cc.addMethod(method);
        //添加构造器
        cc.addConstructor(CtNewConstructor.defaultConstructor(cc));
        Class<?> pc = cc.toClass();
        ByteCodeAPI byteCodeApi = (ByteCodeAPI) pc.newInstance();
        return byteCodeApi;
    }
}
interface ByteCodeAPI {
    public String sayHello();
}

//结果:输出 hello

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

Динамический прокси на самом деле имеет множество применений, таких как реализация spring aop, реализация rpc framework, внутреннее использование некоторых сторонних библиотек инструментов и так далее. Вот краткое введение в применение динамического прокси в Spring AOP и RPC framework.

Приложение 1: реализация динамического прокси-сервера Spring AOP

Существует два основных способа реализации динамического прокси в Spring AOP: динамический прокси JDK и генерация байт-кода CGLIB.

По умолчанию, если Spring AOP находит целевой объект и реализует соответствующий интерфейс, он использует механизм динамического прокси JDK для создания для него прокси-объекта. Если интерфейс не найден, используйте CGLIB для создания экземпляра динамического прокси-объекта для целевого объекта.

Приложение 2: Приложение в RPC Framework

RPC — это удаленный вызов процедур. В его реализации используется динамический прокси. Конкретный принцип RPC см.Принципы RPC, которые вы должны знатьдругие статьи

rpc

На самом деле, одна из проблем, которую должен решить фреймворк RPC, это: как вызывать чужие удаленные службы? Вызов удаленной службы, как если бы это была локальная служба.

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

резюме

В реальной разработке мы часто используем готовые фреймворки и библиотеки с открытым исходным кодом, в том числе динамические прокси-приложения. Только по-настоящему поняв знания о динамических агентах, мы сможем лучше понять их дизайн в фреймворке, и это также способствует лучшему использованию фреймворка.

использованная литература