9 minutos para que comprenda el modo proxy, proxy estático, proxy dinámico JDK + CGLIB

Tabla de contenido:

  • 1. Modelo de agencia
  • 2. Proxy estático
  • 3. Agente dinámico
    • 3.1. Mecanismo de proxy dinámico de JDK
    • 3.2. Mecanismo de proxy dinámico CGLIB
    • 3.3. Comparación de proxy dinámico JDK y proxy dinámico CGLIB
  • 4. Comparación de proxy estático y proxy dinámico
  • 5. Resumen

1. Modelo de agencia

El modelo de agente es un patrón de diseño mejor entendido. En pocas palabras,  usamos objetos proxy para reemplazar el acceso a objetos reales, de modo que podamos proporcionar operaciones funcionales adicionales y ampliar la funcionalidad del objeto de destino sin modificar el objeto de destino original.

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.

9 minutos para que comprenda el modo proxy, proxy estático, proxy dinámico JDK + CGLIB

 

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.

Arriba 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 en tiempo de compilación.

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. De esta manera, 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.

¡El siguiente código muestra!

1. Defina la interfaz para enviar SMS

9 minutos para que comprenda el modo proxy, proxy estático, proxy dinámico JDK + CGLIB

 

2. Implementar la interfaz para enviar SMS

9 minutos para que comprenda el modo proxy, proxy estático, proxy dinámico JDK + CGLIB

 

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

9 minutos para que comprenda el modo proxy, proxy estático, proxy dinámico JDK + CGLIB

 

4. Uso real

9 minutos para que comprenda el modo proxy, proxy estático, proxy dinámico JDK + CGLIB

 

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

9 minutos para que comprenda el modo proxy, proxy estático, proxy dinámico JDK + CGLIB

 

Como puede ver en la salida, hemos agregado el método send () de SmsServiceImpl.

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 la JVM, el agente dinámico genera dinámicamente el código de bytes de la clase en tiempo de ejecución y lo carga en la JVM.

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

El agente dinámico es relativamente pequeño en nuestro desarrollo diario, pero es una tecnología casi 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 [1]  usa un proxy dinámico JDK, primero echemos un vistazo al uso del proxy dinámico JDK.

Además, aunque  guide-rpc-framework [2]  no usa el  proxy dinámico CGLIB, 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 el mecanismo de proxy dinámico de Java, la   interfaz InvocationHandler y la   clase Proxy son el núcleo.

El método más utilizado en la clase Proxy es: newProxyInstance (), este método se usa principalmente para generar un objeto proxy.

9 minutos para que comprenda el modo proxy, proxy estático, proxy dinámico JDK + CGLIB

 

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  : un objeto que implementa la interfaz InvocationHandler;

Para implementar el proxy dinámico, también debe implementar InvocationHandler para personalizar la lógica de procesamiento. Cuando nuestro objeto de proxy dinámico llama a un método, la invocación de este método se reenviará al método de invocación que implementa la clase de interfaz InvocationHandler para la invocación.

9 minutos para que comprenda el modo proxy, proxy estático, proxy dinámico JDK + CGLIB

 

El método invoke () 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

Es decir: cuando se  llama al método del objeto proxy creado por  el newProxyInstance () del proxy de clase  , en realidad va a llamar a  la invoke () método de la clase que implementa el InvocationHandler interfaz  .  Puede personalizar la lógica de procesamiento en el método invoke (), como qué hacer antes y después de que se ejecute el método.

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 InvocationHandler y anule el método de invocación. En el método de invocación, llamaremos al método nativo (método de la clase proxy) y personalizaremos alguna lógica de procesamiento;
  3. Cree un objeto proxy mediante el método Proxy.newProxyInstance (cargador ClassLoader, interfaces Class <?> [], InvocationHandler h);

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

9 minutos para que comprenda el modo proxy, proxy estático, proxy dinámico JDK + CGLIB

 

2. Implementar la interfaz para enviar SMS

9 minutos para que comprenda el modo proxy, proxy estático, proxy dinámico JDK + CGLIB

 

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;
    }
}

Método invoke (): cuando nuestro objeto proxy dinámico llama a un método nativo, en realidad llama al método invoke (), y luego el método invoke () nos reemplaza para llamar al método nativo del objeto 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 (): obtiene principalmente el objeto proxy de una determinada clase a través del método Proxy.newProxyInstance ()

5. Uso real

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

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

9 minutos para que comprenda el modo proxy, proxy estático, proxy dinámico JDK + CGLIB

 

3.2. Mecanismo de proxy dinámico CGLIB

3.2.1. Introducción

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

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

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

La  interfaz MethodInterceptor y la   clase Enhancer son el núcleo del mecanismo de proxy dinámico CGLIB  .

Debe personalizar MethodInterceptor y anular el método de intercepción. La intercepción se utiliza para interceptar métodos que mejoran 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 obtener dinámicamente la clase de proxy a través de la clase Enhancer.Cuando la clase de proxy llama a un método, en realidad se llama al método de intercepción en MethodInterceptor.

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

  1. Definir una clase;
  2. Personalice MethodInterceptor y anule el método de intercepción. El método de intercepción se utiliza para interceptar y mejorar el método de clase de proxy, que es similar al método de invocación en el proxy dinámico de JDK;
  3. Cree una clase de proxy a través de create () de la clase Enhancer;

3.2.3. Ejemplo de código

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

9 minutos para que comprenda el modo proxy, proxy estático, proxy dinámico JDK + CGLIB

 

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

9 minutos para que comprenda el modo proxy, proxy estático, proxy dinámico JDK + CGLIB

 

2. MethodInterceptor personalizado  (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

9 minutos para que comprenda el modo proxy, proxy estático, proxy dinámico JDK + CGLIB

 

4. Uso real

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

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

9 minutos para que comprenda el modo proxy, proxy estático, proxy dinámico JDK + CGLIB

 

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.

Lectura recomendada para artículos de alta calidad:

Preguntas avanzadas de la entrevista de Alibaba (primer número, 136 preguntas de alta frecuencia, incluidas las respuestas)

https://blog.csdn.net/weixin_45132238/article/details/107251285

Las 4 guías de entrevistas de bajo nivel de GitHub Biaoxing 20w (capa inferior de la computadora + sistema operativo + algoritmo), titulares de entrevistas / ¡Tencent tiene razón!

https://blog.csdn.net/weixin_45132238/article/details/108640805

Combate de arquitectura interna de Alibaba: SpringBoot / SpringCloud / Docker / Nginx / distribuido

https://blog.csdn.net/weixin_45132238/article/details/108666255

Supongo que te gusta

Origin blog.csdn.net/weixin_45132238/article/details/108736629
Recomendado
Clasificación