Comprensión profunda de JavaGuid del modo proxy

1. Modelo de agencia

Reimpreso en JavaGuid

El modelo de agente es un patrón de diseño mejor entendido. En términos simples que utilizamos para acceder al objeto proxy en lugar del objeto real (objeto real), para que pueda sin modificar el objeto objetivo original es la premisa de proporcionar una operación de funcionalidad adicional, la expansión de las características del objeto objetivo.

La función principal del modo proxy es expandir las funciones del objeto de destino. Por ejemplo, puede agregar algunas operaciones personalizadas antes y después de que se ejecute un determinado método del objeto de destino.

Por ejemplo: el tuyo le pidió a Xiaohong que te ayudara a interrogar, Xiaohong lo consideró mi agente y el comportamiento (método) del agente fue el interrogatorio.

Inserte la descripción de la imagen aquí

El modo proxy tiene dos métodos de implementación: proxy estático y proxy dinámico Veamos primero la implementación del modo proxy estático.

2. Proxy estático

En el proxy estático, la mejora de cada método del objeto de destino se realiza manualmente (el código se demostrará en detalle más adelante ), lo cual es muy inflexible (por ejemplo, una vez que se agrega un nuevo método a la interfaz, el objeto de destino y el objeto de proxy deben modificarse ) y problemático (Es necesario escribir una clase de proxy separada para cada clase de destino ). Los escenarios de aplicación reales son muy, muy pocos, y el uso de agentes estáticos es casi invisible en el desarrollo diario.

Por encima de nosotros somos agentes estáticos desde la perspectiva de la implementación y la aplicación. Desde la perspectiva de la JVM, los agentes estáticos convierten las 编译时interfaces, las clases de implementación y las clases de proxy en archivos de clases reales.

Pasos de implementación de proxy estático:

  1. Definir una interfaz y su clase de implementación;
  2. Cree una clase de proxy para implementar también esta interfaz
  3. Inyecte el objeto de destino en la clase de proxy y luego llame al método correspondiente en la clase de destino en el método correspondiente de la clase de proxy. En este caso, podemos bloquear el acceso al objeto de destino a través de la clase de proxy y podemos hacer lo que queramos antes y después de que se ejecute el método de destino.

¡Muéstralo a través del siguiente código!

1. Defina la interfaz para enviar SMS

public interface SmsService {
    
    
    String send(String message);
}

2. Implementar la interfaz para enviar SMS

public class SmsServiceImpl implements SmsService {
    
    
    public String send(String message) {
    
    
        System.out.println("send message:" + message);
        return message;
    }
}

3. Cree una clase de proxy y también implemente la interfaz para enviar SMS

public class SmsProxy implements SmsService {
    
    

    private final SmsService smsService;

    public SmsProxy(SmsService smsService) {
    
    
        this.smsService = smsService;
    }

    @Override
    public String send(String message) {
    
    
        //调用方法之前,我们可以添加自己的操作
        System.out.println("before method send()");
        smsService.send(message);
        //调用方法之后,我们同样可以添加自己的操作
        System.out.println("after method send()");
        return null;
    }
}

4. Uso real

public class Main {
    
    
    public static void main(String[] args) {
    
    
        SmsService smsService = new SmsServiceImpl();
        SmsProxy smsProxy = new SmsProxy(smsService);
        smsProxy.send("java");
    }
}

Después de ejecutar el código anterior, la consola imprime:

before method send()
send message:java
after method send()

Se puede ver la salida, hemos aumentado SmsServiceImplel send()método.

3. Agente dinámico

En comparación con los agentes estáticos, los agentes dinámicos son más flexibles. No necesitamos crear una clase de proxy separada para cada clase de destino, y no necesitamos implementar una interfaz, podemos proxy directamente la clase de implementación ( mecanismo de proxy dinámico CGLIB ).

Desde la perspectiva de JVM, el proxy 运行时dinámico genera dinámicamente un código de bytes de clase y lo carga en JVM.

Hablando de proxy dinámico, Spring AOP y el marco RPC deben ser dos cosas que se deben mencionar, su implementación se basa en un proxy dinámico.

El agente dinámico es relativamente pequeño en nuestro desarrollo diario, pero es casi una tecnología necesaria en el marco. Después de aprender los agentes dinámicos, también es muy útil para nosotros comprender y aprender los principios de varios marcos.

En lo que se refiere a Java, hay muchas maneras de implementar proxy dinámico, como JDK proxy dinámico , proxy dinámico CGLIB y así sucesivamente.

Guide-rpc-framework usa un proxy dinámico JDK, echemos un vistazo al uso del proxy dinámico JDK.

Además, aunque el guide-rpc-framework no utiliza el proxy dinámico CGLIB, aquí presentaremos brevemente su uso y comparación con el proxy dinámico JDK.

3.1. Mecanismo de proxy dinámico de JDK

3.1.1. Introducción

En la InvocationHandlerinterfaz del mecanismo de proxy dinámico de Java y la Proxyclase es el núcleo.

ProxyEl método más utilizado en la clase es: newProxyInstance()Este método se utiliza principalmente para generar un objeto proxy.

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

Este método tiene 3 parámetros:

  1. cargador : cargador de clases, utilizado para cargar objetos proxy.
  2. interfaces : algunas interfaces implementadas por la clase proxy;
  3. h : InvocationHandleun objeto que implementa la interfaz r;

Si desea implementar un proxy dinámico, también debe implementar la InvocationHandlerlógica de procesamiento personalizada. Cuando nuestro objeto proxy dinámico llame a un método al llamar a este método, se reenviará a la realización InvocationHandlerdel invokemétodo de clase de interfaz para invocar.

public interface InvocationHandler {
    
    

    /**
     * 当你使用代理对象调用方法的时候实际会调用到这个方法
     */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

invoke() El método tiene los siguientes tres parámetros:

  1. proxy : clase de proxy generada dinámicamente
  2. método : corresponde al método llamado por el objeto de clase proxy
  3. args : los parámetros del método método actual

En otras palabras: pasa Proxyla clase newProxyInstance()al llamar al método, las llamadas reales para lograr el objeto proxy creado en InvocationHandlerlos invoke()métodos de la interfaz de clase . Puede invoke()personalizar los métodos lógicos de procesamiento, tales métodos hacen algo antes y después de la ejecución.

3.1.2. Pasos de uso de la clase de proxy dinámico JDK

  1. Definir una interfaz y su clase de implementación;
  2. Personalice InvocationHandlery anule el invokemétodo, invokeel método llamaremos al método nativo (el método de la clase proxy) y personalizaremos alguna lógica de procesamiento;
  3. Al Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)crear un método de objeto proxy;

3.1.3. Ejemplo de código

Esto puede ser un poco vacío y difícil de entender. ¡Echemos un vistazo a mi último ejemplo!

1. Defina la interfaz para enviar SMS

public interface SmsService {
    
    
    String send(String message);
}

2. Implementar la interfaz para enviar SMS

public class SmsServiceImpl implements SmsService {
    
    
    public String send(String message) {
    
    
        System.out.println("send message:" + message);
        return message;
    }
}

3. Defina una clase de proxy dinámico JDK

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

/**
 * @author shuang.kou
 * @createTime 2020年05月11日 11:23:00
 */
public class DebugInvocationHandler implements InvocationHandler {
    
    
    /**
     * 代理类中的真实对象
     */
    private final Object target;

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


    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
    
    
        //调用方法之前,我们可以添加自己的操作
        System.out.println("before method " + method.getName());
        Object result = method.invoke(target, args);
        //调用方法之后,我们同样可以添加自己的操作
        System.out.println("after method " + method.getName());
        return result;
    }
}

invoke()Métodos: cuando nuestro objeto proxy dinámico llama al método nativo, llama al hecho de que el invoke()método final , entonces invoke()el método en nuestro lugar para llamar a métodos nativos son objetos proxy.

4. Obtenga la clase de fábrica del objeto proxy

public class JdkProxyFactory {
    
    
    public static Object getProxy(Object target) {
    
    
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(), // 目标类的类加载
                target.getClass().getInterfaces(),  // 代理需要实现的接口,可指定多个
                new DebugInvocationHandler(target)   // 代理对象对应的自定义 InvocationHandler
        );
    }
}

getProxy(): Proxy.newProxyInstance()Obtener principalmente el objeto proxy de una determinada clase a través de métodos

5. Uso real

SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
smsService.send("java");

Después de ejecutar el código anterior, la consola imprime:

before method send
send message:java
after method send

3.2. Mecanismo de proxy dinámico CGLIB

3.2.1. Introducción

Uno de los problemas más graves con el proxy dinámico JDK es que solo puede utilizar clases de proxy que implementan interfaces.

Para resolver este problema, podemos utilizar el mecanismo de proxy dinámico CGLIB para evitarlo.

CGLIB ( Code Generation Library ) es una biblioteca de generación de códigos de bytes basada en ASM , que nos permite modificar y generar códigos de bytes de forma dinámica en tiempo de ejecución. CGLIB implementa el proxy a través de la herencia. Muchos marcos de código abierto conocidos usan CGLIB , como el módulo AOP en Spring: si el objeto de destino implementa la interfaz, el proxy dinámico JDK se usa de forma predeterminada; de lo contrario, se usa el proxy dinámico CGLIB.

En la MethodInterceptorinterfaz del mecanismo de proxy dinámico CGLIB y la Enhancerclase es el núcleo.

Necesita personalizar MethodInterceptory anular los interceptmétodos interceptutilizados para interceptar el método de mejora es la clase de proxy.

public interface MethodInterceptor
extends Callback{
    
    
    // 拦截被代理类中的方法
    public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
                               MethodProxy proxy) throws Throwable;
}

  1. obj : el objeto que se está transfiriendo (el objeto que necesita ser mejorado)
  2. método : el método interceptado (el método que debe mejorarse)
  3. args : parámetro de método
  4. methodProxy : usado para llamar al método original

Puede Enhancerobtener la clase de proxy dinámica a la clase, cuando la clase de proxy llama al método cuando la llamada real está MethodInterceptoren el interceptmétodo.

3.2.2. Pasos para utilizar la clase de proxy dinámico CGLIB

  1. Definir una clase;
  2. Personalizar MethodInterceptory anular el interceptmétodo, interceptun método para mejorar la clase de proxy de interceptación, y el invokemétodo de proxy dinámico JDK análogo;
  3. Por Enhancerclase create()para crear una clase de proxy;

3.2.3. Ejemplo de código

A diferencia del proxy dinámico JDK, no se requieren dependencias adicionales. CGLIB ( Biblioteca de generación de código ) en realidad pertenece a un proyecto de código abierto, si desea usarlo, debe agregar manualmente las dependencias relacionadas.

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>

1. Implementar una clase que use Alibaba Cloud para enviar SMS

package github.javaguide.dynamicProxy.cglibDynamicProxy;

public class AliSmsService {
    
    
    public String send(String message) {
    
    
        System.out.println("send message:" + message);
        return message;
    }
}

2. Personalizado MethodInterceptor(método interceptor)

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

import java.lang.reflect.Method;

/**
 * 自定义MethodInterceptor
 */
public class DebugMethodInterceptor implements MethodInterceptor {
    
    


    /**
     * @param o           被代理的对象(需要增强的对象)
     * @param method      被拦截的方法(需要增强的方法)
     * @param args        方法入参
     * @param methodProxy 用于调用原始方法
     */
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    
    
        //调用方法之前,我们可以添加自己的操作
        System.out.println("before method " + method.getName());
        Object object = methodProxy.invokeSuper(o, args);
        //调用方法之后,我们同样可以添加自己的操作
        System.out.println("after method " + method.getName());
        return object;
    }

}

3. Obtén la clase de proxy

import net.sf.cglib.proxy.Enhancer;

public class CglibProxyFactory {
    
    

    public static Object getProxy(Class<?> clazz) {
    
    
        // 创建动态代理增强类
        Enhancer enhancer = new Enhancer();
        // 设置类加载器
        enhancer.setClassLoader(clazz.getClassLoader());
        // 设置被代理类
        enhancer.setSuperclass(clazz);
        // 设置方法拦截器
        enhancer.setCallback(new DebugMethodInterceptor());
        // 创建代理类
        return enhancer.create();
    }
}

4. Uso real

AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
aliSmsService.send("java");

Después de ejecutar el código anterior, la consola imprime:

before method send
send message:java
after method send

3.3. Comparación de proxy dinámico JDK y proxy dinámico CGLIB

  1. El proxy dinámico JDK solo puede usar clases de proxy que implementan interfaces, mientras que CGLIB puede usar clases de proxy que no implementan ninguna interfaz. Además, el proxy dinámico CGLIB intercepta las llamadas a métodos de la clase de proxy generando una subclase de la clase de proxy, por lo que no puede usar clases de proxy y métodos declarados como finales.
  2. En términos de eficiencia de los dos, en la mayoría de los casos, el proxy dinámico JDK es mejor, pero con la actualización de la versión JDK, esta ventaja se vuelve más obvia.

4. Comparación de proxy estático y proxy dinámico

  1. Flexibilidad : el proxy dinámico es más flexible, sin tener que implementar una interfaz, puede proxy directamente la clase de implementación y no es necesario crear una clase de proxy para cada clase de destino. Además, en un proxy estático, una vez que se agrega un nuevo método a la interfaz, el objeto de destino y el objeto de proxy deben modificarse, lo cual es muy problemático.
  2. Nivel de JVM : el proxy estático convierte la interfaz, la clase de implementación y la clase de proxy en archivos de clase reales en el momento de la compilación. El agente dinámico genera dinámicamente un código de bytes de clase en tiempo de ejecución y lo carga en la JVM.

5. Resumen

Este artículo presenta principalmente dos implementaciones del modo proxy: proxy estático y proxy dinámico. Cubre el combate real entre agentes estáticos y dinámicos, la diferencia entre agentes estáticos y dinámicos, y la diferencia entre agentes dinámicos JDK y agentes dinámicos Cglib.

Todo el código fuente involucrado en el artículo, puede encontrarlo aquí: https://github.com/Snailclimb/guide-rpc-framework-learning/tree/master/src/main/java/github/javaguide/proxy .

Supongo que te gusta

Origin blog.csdn.net/qianzhitu/article/details/108472805
Recomendado
Clasificación