Modo proxy: proxy estático, proxy dinámico (proxy JDK y proxy CGLib)

Visión general

Por algunas razones, es necesario proporcionar un proxy para que un objeto controle el acceso al objeto .

En este momento, el objeto de acceso no es adecuado o no puede referirse directamente al objeto de destino, y el objeto proxy actúa como intermediario entre el objeto de acceso y el objeto de destino .

El proxy en Java se divide en proxy estático y proxy dinámico de acuerdo con los diferentes tiempos de generación de la clase de proxy .

La clase de proxy de proxy estático se genera en tiempo de compilación, mientras que la clase de proxy de proxy dinámico se genera dinámicamente durante el tiempo de ejecución de Java.

Hay dos agentes dinámicos, el agente JDK y el agente CGLib.

estructura

El modo proxy se divide en tres roles:

  • Clase de sujeto abstracto (Subject): Declare métodos de negocio implementados por sujetos reales y objetos proxy a través de interfaces o clases abstractas.
  • Clase Real Subject: Implementa el negocio concreto en el sujeto abstracto, es el objeto real representado por el objeto proxy y el objeto al que eventualmente se hace referencia.
  • Clase Proxy: Proporciona la misma interfaz que el tema real. Contiene referencias al tema real. Puede acceder, controlar o ampliar las funciones del tema real.

Proxy estático

Vamos a tener una idea del proxy estático a través de casos.

[Ejemplo] Venta de billetes en estaciones de tren

Si quieres comprar un billete de tren, debes ir a la estación de tren a comprar un billete, coger un tren hasta la estación de tren, esperar en la fila y esperar una serie de operaciones, que obviamente es más problemático. Y la estación de tren tiene puntos de consignación en muchos lugares, por lo que es mucho más conveniente para nosotros ir al punto de consignación a comprar los billetes.

Este ejemplo es en realidad un modelo de agencia típico: la estación de tren es el objetivo y el agente de ventas es la agencia.

El diagrama de clases es el siguiente:

SellTickets.java

package com.itheima.pattern.proxy.static_proxy;

/**
 * @version v1.0
 * @ClassName: SellTickets
 * @Description: 卖火车票的接口
 * @Author: dym
 */
public interface SellTickets {

    void sell();
}

TrainStation.java

package com.itheima.pattern.proxy.static_proxy;

/**
 * @version v1.0
 * @ClassName: TrainStation
 * @Description: 火车站类
 * @Author: dym
 */
public class TrainStation implements SellTickets {

    public void sell() {
        System.out.println("火车站卖票");
    }
}

ProxyPoint.java

package com.itheima.pattern.proxy.static_proxy;

/**
 * @version v1.0
 * @ClassName: ProxyPoint
 * @Description: 代售点类
 * @Author: dym
 */
public class ProxyPoint implements SellTickets {

    //声明火车站类对象
    private TrainStation trainStation  = new TrainStation();

    public void sell() {
        System.out.println("代售点收取一些服务费用");
        trainStation.sell();
    }

}

Client.java

package com.itheima.pattern.proxy.static_proxy;

/**
 * @version v1.0
 * @ClassName: Client
 * @Description: TODO(一句话描述该类的功能)
 * @Author: dym
 */
public class Client {
    public static void main(String[] args) {
        //创建代售点类对象
        ProxyPoint proxyPoint = new ProxyPoint();
        //调用方法进行买票
        proxyPoint.sell();
    }
}

Se puede ver en el código anterior que la clase de prueba accede directamente al objeto de la clase ProxyPoint,

En otras palabras, ProxyPoint actúa como intermediario entre el objeto de acceso y el objeto de destino.

Al mismo tiempo, se ha mejorado el método de venta (la agencia cobra algunas tarifas de servicio).



Proxy dinámico JDK

A continuación, usamos un proxy dinámico para implementar el caso anterior, hablemos primero del proxy dinámico proporcionado por JDK.

Java proporciona una clase de proxy dinámica Proxy. Proxy no es la clase de objetos de proxy que mencionamos anteriormente.

En su lugar, proporciona un método estático (método newProxyInstance ) para crear un objeto proxy para obtener el objeto proxy.

el código se muestra a continuación:

SellTickets.java

package com.itheima.pattern.proxy.jdk_proxy;

/**
 * @version v1.0
 * @ClassName: SellTickets
 * @Description: 卖火车票的接口
 * @Author: dym
 */
public interface SellTickets {

    void sell();
}

TrainStation.java

package com.itheima.pattern.proxy.jdk_proxy;

/**
 * @version v1.0
 * @ClassName: TrainStation
 * @Description: 火车站类
 * @Author: dym
 */
public class TrainStation implements SellTickets {

    public void sell() {
        System.out.println("火车站卖票");
    }
}

ProxyFactory.java

package com.itheima.pattern.proxy.jdk_proxy;

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

/**
 * @version v1.0
 * @ClassName: ProxyFactory
 * @Description: 获取代理对象的工厂类
 *      代理类也实现了对应的接口
 * @Author: dym
 */
public class ProxyFactory {

    //声明目标对象
    private TrainStation station = new TrainStation();

    //获取代理对象的方法
    public SellTickets getProxyObject() {
        //返回代理对象
        /*
            ClassLoader loader : 类加载器,用于加载代理类。可以通过目标对象获取类加载器
            Class<?>[] interfaces : 代理类实现的接口的字节码对象
            InvocationHandler h : 代理对象的调用处理程序
         */
        SellTickets proxyObject = (SellTickets)Proxy.newProxyInstance(
                station.getClass().getClassLoader(),
                station.getClass().getInterfaces(),
                new InvocationHandler() {

                    /*
                        Object proxy : 代理对象。和proxyObject对象是同一个对象,在invoke方法中基本不用
                        Method method : 对接口中的方法进行封装的method对象
                        Object[] args : 调用方法的实际参数

                        返回值: 方法的返回值。
                     */
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //System.out.println("invoke方法执行了");
                        System.out.println("代售点收取一定的服务费用(jdk动态代理)");
                        //执行目标对象的方法
                        Object obj = method.invoke(station, args);
                        return obj;
                    }
                }
        );
        return proxyObject;
    }
}

Client.java

package com.itheima.pattern.proxy.jdk_proxy;

/**
 * @version v1.0
 * @ClassName: Client
 * @Description: TODO(一句话描述该类的功能)
 * @Author: dym
 */
public class Client {
    public static void main(String[] args) {
        //获取代理对象
        //1,创建代理工厂对象
        ProxyFactory factory = new ProxyFactory();
        //2,使用factory对象的方法获取代理对象
        SellTickets proxyObject = factory.getProxyObject();
        //3,调用卖调用的方法
        proxyObject.sell();

        System.out.println(proxyObject.getClass());


    }
}



Usando agentes dinámicos, pensamos en las siguientes preguntas:

  • ¿ProxyFactory es una clase de proxy?

    ProxyFactory no es la clase de proxy mencionada en el modo de proxy,

  • La clase de proxy es una clase generada dinámicamente en la memoria durante la ejecución del programa .

  • Verifique la estructura de la clase de proxy a través de la herramienta de diagnóstico Java de código abierto de Alibaba (Arthas [Alsace]):

package com.sun.proxy;
​
import com.itheima.proxy.dynamic.jdk.SellTickets;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
​
public final class $Proxy0 extends Proxy implements SellTickets {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;
​
    public $Proxy0(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }
​
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            return;
        }
        catch (NoSuchMethodException noSuchMethodException) {
            throw new NoSuchMethodError(noSuchMethodException.getMessage());
        }
        catch (ClassNotFoundException classNotFoundException) {
            throw new NoClassDefFoundError(classNotFoundException.getMessage());
        }
    }
​
    public final boolean equals(Object object) {
        try {
            return (Boolean)this.h.invoke(this, m1, new Object[]{object});
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
​
    public final String toString() {
        try {
            return (String)this.h.invoke(this, m2, null);
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
​
    public final int hashCode() {
        try {
            return (Integer)this.h.invoke(this, m0, null);
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
​
    public final void sell() {
        try {
            this.h.invoke(this, m3, null);
            return;
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
}
  • De la clase anterior, podemos ver la siguiente información:

    • La clase de proxy ($ Proxy0) implementa SellTickets . Esto también confirma que la clase real y la clase de proxy que dijimos antes implementan la misma interfaz .
    • La clase de proxy ($ Proxy0) pasa el objeto de clase interna anónimo que proporcionamos a la clase principal .
  • ¿Cuál es el proceso de ejecución del agente dinámico?

    El siguiente es el código clave extraído:

//程序运行过程中动态生成的代理类
public final class $Proxy0 extends Proxy implements SellTickets {
    private static Method m3;
​
    public $Proxy0(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }
​
    static {
        m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
    }
​
    public final void sell() {
        this.h.invoke(this, m3, null);
    }
}
​
//Java提供的动态代理相关类
public class Proxy implements java.io.Serializable {
    protected InvocationHandler h;
     
    protected Proxy(InvocationHandler h) {
        this.h = h;
    }
}
​
//代理工厂类
public class ProxyFactory {
​
    private TrainStation station = new TrainStation();
​
    public SellTickets getProxyObject() {
        SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),
                station.getClass().getInterfaces(),
                new InvocationHandler() {
                    
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
​
                        System.out.println("代理点收取一些服务费用(JDK动态代理方式)");
                        Object result = method.invoke(station, args);
                        return result;
                    }
                });
        return sellTickets;
    }
}
​
​
//测试访问类
public class Client {
    public static void main(String[] args) {
        //获取代理对象
        ProxyFactory factory = new ProxyFactory();
        SellTickets proxyObject = factory.getProxyObject();
        proxyObject.sell();
    }
}

El proceso de ejecución es el siguiente:

  1. Llame al método sell () a través del objeto proxy en la clase de prueba
  2. Según las características del polimorfismo, se ejecuta el método sell () en la clase proxy ($ Proxy0)
  3. El método sell () en la clase de proxy ($ Proxy0) llama al método de invocación del objeto de clase de subimplementación de la interfaz InvocationHandler
  4. El método invoke ejecuta el método sell () en la clase a la que pertenece el objeto real (TrainStation) a través de la reflexión.


Agente dinámico CGLIB

Lo mismo es el caso anterior, nuevamente usamos la implementación del proxy CGLIB.

Si la interfaz SellTickets no está definida, solo se define TrainStation (clase de estación de tren).

Obviamente, el proxy JDK no se puede utilizar, porque el proxy dinámico JDK requiere que se defina una interfaz para proxy la interfaz .

CGLIB es un paquete de generación de código potente y de alto rendimiento.

Proporciona un proxy para las clases que no implementan una interfaz y proporciona un buen complemento al proxy dinámico del JDK.

CGLIB es un paquete proporcionado por un tercero, por lo que es necesario introducir las coordenadas del paquete jar:

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

TrainStation.java

package com.itheima.pattern.proxy.cglib_proxy;


/**
 * @version v1.0
 * @ClassName: TrainStation
 * @Description: 火车站类
 * @Author: dym
 */
public class TrainStation {

    public void sell() {
        System.out.println("火车站卖票");
    }
}

ProxyFactory.java

package com.itheima.pattern.proxy.cglib_proxy;

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

import java.lang.reflect.Method;

/**
 * @version v1.0
 * @ClassName: ProxyFactory
 * @Description: 代理对象工厂,用来获取代理对象
 * @Author: dym
 */
public class ProxyFactory implements MethodInterceptor {

    //声明火车站对象
    private TrainStation station = new TrainStation();

    public TrainStation getProxyObject() {
        //创建Enhancer对象,类似于JDK代理中的Proxy类
        Enhancer enhancer = new Enhancer();
        //设置父类的字节码对象。指定父类
        enhancer.setSuperclass(TrainStation.class);
        //设置回调函数
        enhancer.setCallback(this);
        //创建代理对象
        TrainStation proxyObject = (TrainStation) enhancer.create();
        return proxyObject;
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //System.out.println("方法执行了");
        System.out.println("代售点收取一定的服务费用(CGLib代理)");
        //要调用目标对象的方法
        Object obj = method.invoke(station, objects);
        return obj;
    }
}

Client.java

package com.itheima.pattern.proxy.cglib_proxy;

/**
 * @version v1.0
 * @ClassName: Client
 * @Description: TODO(一句话描述该类的功能)
 * @Author: dym
 */
public class Client {
    public static void main(String[] args) {
        //创建代理工厂对象
        ProxyFactory factory = new ProxyFactory();
        //获取代理对象
        TrainStation proxyObject = factory.getProxyObject();
        //调用代理对象中的sell方法卖票
        proxyObject.sell();
    }
}



Comparación de tres agentes

  • agente jdk y agente CGLIB

    Use CGLib para implementar un proxy dinámico. La capa inferior de CGLib usa el marco de generación de código de bytes ASM y la tecnología de código de bytes para generar clases de proxy. Antes de JDK1.6, es más eficiente que usar la reflexión de Java.

Lo único a tener en cuenta es que CGLib no puede representar la clase o el método declarado como final, porque el principio de CGLib es generar dinámicamente subclases de la clase de proxy.

Después de que JDK1.6, JDK1.7 y JDK1.8 hayan optimizado gradualmente el proxy dinámico de JDK, cuando la cantidad de llamadas es pequeña, la eficiencia de proxy de JDK es mayor que la de CGLib. Solo cuando se realiza una gran cantidad de llamadas , JDK1.6 y JDK1.7 son un poco menos eficientes que el proxy CGLib,

Pero en el momento de JDK1.8, la eficiencia del proxy JDK es mayor que la del proxy CGLib. Entonces, si hay una interfaz, use el proxy dinámico JDK, si no hay una interfaz, use el proxy CGLIB.

  • Proxy dinámico y proxy estático

    En comparación con el proxy estático , la mayor ventaja del proxy dinámico es que todos los métodos declarados en la interfaz se transfieren a un método centralizado del controlador de invocación (InvocationHandler.invoke) .

De esta manera, cuando el número de métodos de interfaz es relativamente grande, podemos manejarlo de manera flexible,

No es necesario transferir todos los métodos como un proxy estático. Si una interfaz agrega un método, el modo de proxy estático debe implementar este método además de todas las clases de implementación, y todas las clases de proxy también deben implementar este método . Aumentó la complejidad del mantenimiento del código.

El proxy dinámico no tiene este problema.

 

Pros y contras

ventaja:

  • El modo proxy juega un papel intermediario entre el cliente y el objeto de destino y protege el objeto de destino;
  • El objeto proxy puede ampliar la función del objeto de destino;
  • El modo proxy puede separar al cliente del objeto de destino, lo que reduce el acoplamiento del sistema hasta cierto punto;

Desventajas:

  • Incrementar la complejidad del sistema;

escenas que se utilizarán

  • Proxy remoto (remoto)

    Los servicios locales solicitan servicios remotos a través de la red. Para lograr la comunicación local a remota, necesitamos implementar la comunicación de red para hacer frente a posibles anomalías. Para un buen diseño y mantenimiento del código, ocultamos la parte de comunicación de red y solo exponemos una interfaz al servicio local, a través de la cual se puede acceder a las funciones proporcionadas por el servicio remoto sin prestar demasiada atención a los detalles de la parte de comunicación.

  • Proxy de cortafuegos

    Cuando configura su navegador para usar la función de proxy, el firewall reenviará la solicitud de su navegador a Internet; cuando Internet devuelve una respuesta, el servidor proxy la reenviará a su navegador.

  • Agente de protección (proteger o acceder)

    Controle el acceso a un objeto y, si es necesario, proporcione a diferentes usuarios diferentes niveles de derechos de uso.

Supongo que te gusta

Origin blog.csdn.net/qq_39368007/article/details/113948288
Recomendado
Clasificación