Modo proxy (CGLIB y JDK)

1. Introducción

1. Casos de agencia en la vida

  • Agencia de vivienda: El cliente no tiene información de la vivienda, por lo que necesitamos buscar un agente para obtener la vivienda.
  • Agencia de Compra de Productos: No tenemos productos de bajo costo, por lo que necesitamos encontrar agentes para obtener productos de bajo costo.

Los consumidores solo necesitan preocuparse por sus propios productos a través de agentes de compras y no necesitan prestar atención a cómo obtenerlos, lo que mejora la

2. Aplicación del modo proxy en java

  • Manejo unificado de excepciones
  • Mi zapato
  • Principio de uso de AOP de Spring
  • Marco de registro, etc.

3. ¿Qué es el modo proxy?

El patrón de agente es uno de los 23 patrones de diseño y es un patrón estructural. Significa que un objeto en sí no realiza operaciones reales, sino que obtiene los resultados que desea a través de otros objetos, por lo que el objeto de destino solo necesita preocuparse por los detalles de su propia implementación, y la mejora de funciones a través de objetos proxy puede expandir las funciones. del objeto objetivo. Esto refleja una idea de programación muy importante: no puede modificar el código fuente casualmente. Si necesita modificar el código fuente, solo necesita usar un proxy para expandir la función.
Insertar descripción de la imagen aquí

2. Cómo implementar proxy

1. Introducción

En circunstancias normales, definimos una interfaz, es decir, definimos una especificación y luego usamos una clase para implementar la interfaz, luego el cliente puede usar las funciones básicas llamando a la clase de implementación. En la situación actual, no llamamos directamente a la clase de implementación, sino que implementamos la interfaz a través de una clase proxy y agregamos notificaciones previas y posteriores para mejorar el método de la interfaz, y luego el cliente usa las funciones definidas por la interfaz a través de la clase proxy
Insertar descripción de la imagen aquí

Composición elemental:

  • Interfaz: definir comportamiento y especificaciones.
  • Clase proxy: es el objeto de destino
  • Clase de agente: mejora de funciones

2. Proxy estático

Los proxies estáticos son un patrón de diseño muy común en Java. En este patrón, una clase (clase proxy) proporciona la misma interfaz que otra clase (clase objetivo) y luego reenvía llamadas a la clase objetivo. Tanto la clase proxy como la clase objetivo implementan la misma interfaz. Un uso común de este patrón es agregar comportamiento entre aspectos, como registros, métricas de rendimiento o comprobaciones de seguridad sin modificar el código de la clase de destino.

public interface IService {
    
    
    void doSomething();
}

public class RealService implements IService {
    
    
    public void doSomething() {
    
    
        // 实际业务逻辑
    }
}

public class ProxyService implements IService {
    
    
    private IService realService;

    public ProxyService(IService realService) {
    
    
        this.realService = realService;
    }

    public void doSomething() {
    
    
        // 在调用实际方法前可以执行一些操作
        System.out.println("Before doSomething...");
        realService.doSomething();
        // 在调用实际方法后也可以执行一些操作
        System.out.println("After doSomething...");
    }
}

En este ejemplo, RealService es nuestra clase objetivo y ProxyService es nuestra clase proxy. ProxyService realiza algunas operaciones adicionales antes y después de llamar al método doSomething de RealService. Los proxies estáticos tienen las siguientes características:

  • La clase de proxy y la clase de destino se determinan en tiempo de compilación y son estáticas;
  • Tanto la clase de proxy como la clase de destino implementan la misma interfaz, y la clase de proxy llama a la implementación de la clase de destino a través de los métodos definidos en la interfaz;
  • El usuario llama al método de la clase de destino a través de la clase de proxy, y la clase de proxy puede agregar algún procesamiento adicional antes o después de llamar al método de la clase de destino.

El principal problema del proxy estático es que si la interfaz define muchos métodos, entonces la clase de proxy necesita escribir código de reenvío para cada método, lo que obviamente es muy engorroso. Además, si la interfaz cambia, la clase de proxy también debe modificarse, lo que aumenta la complejidad del mantenimiento. Es por eso que en Java, los servidores proxy dinámicos (como el uso de java.lang.reflect.Proxy de Java o bibliotecas de terceros como CGLIB) se usan a menudo para reemplazar los servidores proxy estáticos.

3. Proxy dinámico

El proxy dinámico en Java se refiere a la tecnología de generar dinámicamente clases de proxy y crear objetos proxy en tiempo de ejecución. La biblioteca central de Java proporciona soporte para servidores proxy dinámicos, principalmente en el paquete java.lang.reflect. El proxy dinámico es una herramienta conveniente y poderosa. Sus principales aplicaciones incluyen interceptores, objetos simulados, conexiones de bases de datos y gestión de transacciones. El uso del proxy dinámico de Java requiere definir un controlador de invocación. El controlador de invocación es una clase que implementa la interfaz java.lang.reflect.InvocationHandler, que solo define un método:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

Este método de invocación se llama cuando se llama a un método en la instancia de proxy. Recibe tres parámetros: el objeto proxy en sí, el objeto del método que se llamará en el objeto proxy y los parámetros pasados ​​al llamar al método. Este método puede agregar lógica de procesamiento adicional antes y después de llamar al método de destino. A continuación se muestra un ejemplo de un proxy dinámico simple:

public interface IService {
    
    
    void doSomething();
}

public class RealService implements IService {
    
    
    public void doSomething() {
    
    
        System.out.println("Do something in RealService");
    }
}

public class MyInvocationHandler implements InvocationHandler {
    
    
    private Object target;

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

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        System.out.println("Before invoke method...");
        Object result = method.invoke(target, args);
        System.out.println("After invoke method...");
        return result;
    }
}

Luego puede crear el objeto proxy utilizando el método newProxyInstance de la clase Proxy:

//一个是在代理对象上调用的方法对象
IService realService = new RealService();
//调用处理器
InvocationHandler handler = new MyInvocationHandler(realService);
//创建代理对象
IService proxyService = (IService) Proxy.newProxyInstance(
    IService.class.getClassLoader(),
    new Class<?>[]{
    
    IService.class},
    handler
);
proxyService.doSomething();  // 调用这个方法时,会触发 handler 的 invoke 方法

En este ejemplo, creamos un proxy para RealService. Cuando llamamos al método doSomething a través del proxy, primero se llamará al método de invocación de MyInvocationHandler. Cabe señalar que el proxy dinámico de Java solo puede crear servidores proxy para interfaces, no clases. Si necesita crear un proxy para una clase, puede utilizar una biblioteca de terceros como CGLIB.

proxy estático proxy dinámico
tiempo de compilación La clase de proxy ya existe en el momento de la compilación. Las clases de proxy se generan dinámicamente en tiempo de ejecución
complejidad del código Debe escribir un código de reenvío para cada método. Si la interfaz tiene muchos métodos, la cantidad de código será grande. Solo es necesario escribir un controlador de invocación y la cantidad de código es relativamente pequeña.
Flexibilidad de código Si la interfaz cambia, la clase de proxy debe modificarse en consecuencia, lo que complica el mantenimiento. El procesador que llama generalmente solo se basa en la interfaz, no en la implementación específica. Si la interfaz cambia, generalmente no es necesario modificar el código del procesador que llama.
Método para realizar Tanto la clase de proxy como la clase de destino implementan la misma interfaz, y la clase de proxy llama directamente al método de la clase de destino. Utilizando la clase Proxy de Java y la interfaz InvocationHandler, la JVM genera dinámicamente el objeto proxy en tiempo de ejecución.
Escenarios de aplicación Cuando necesita crear un proxy para una o varias clases o interfaces específicas, los servidores proxy estáticos son más simples y directos. Los proxies dinámicos son una mejor opción cuando necesita crear proxies para clases o interfaces múltiples o no definidas, o cuando necesita funciones de proxy más flexibles y potentes (como interceptores, AOP)

3. Proxy dinámico en JDK

1. Introducción

package java.lang.reflect;

public interface InvocationHandler {
    
    
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

La interfaz InvocationHandler (utilizada para la interceptación de métodos) define un método: invocar. Este método se llama cuando se llama a un método en la instancia de proxy. El método de invocación acepta tres parámetros:

  1. proxy: instancia de proxy. Esta es la instancia de la clase proxy que llama al método de invocación. (Cree una instancia de proxy a través de newProxyInstance)
  2. método: instancia de método que representa el método de interfaz llamado en la instancia de proxy. Esto se puede utilizar para obtener información sobre el método llamado, como el nombre del método, tipos de parámetros, etc. (ejecutando el método de destino)
  3. args: una matriz de objetos que contiene los argumentos pasados ​​en llamadas a métodos en la instancia de proxy. Si el método de interfaz no toma parámetros, es nulo. (parámetros del método objetivo)

El valor de retorno del método de invocación es el valor de retorno de la llamada al método de instancia de proxy. Es decir, este método en realidad determina el comportamiento del método proxy. Si se lanza una excepción durante una llamada a un método en la instancia de proxy, la excepción será lanzada por el método de invocación. En el uso real, generalmente crea una clase que implementa la interfaz InvocationHandler y escribe su lógica personalizada en el método de invocación, como agregar procesamiento adicional antes y después de la invocación del método.
En Java, InvocationHandler es una interfaz que se utiliza para manejar invocaciones de métodos en instancias de proxy. Es la interfaz central del mecanismo de proxy dinámico de Java.

La clase java.lang.reflect.Proxy de Java es la clase principal para implementar el proxy dinámico. Durante el proceso de trabajo del proxy dinámico, la clase Proxy genera una clase de proxy y su objeto para la interfaz especificada, e implementa la invocación dinámica del método llamando a la interfaz InvocationHandler.

public class Proxy implements java.io.Serializable {
    
    
    private static final long serialVersionUID = -2222568056686623797L;
    
    private InvocationHandler h;
    
    protected Proxy(InvocationHandler h) {
    
    
        Objects.requireNonNull(h);
        this.h = h;
    }

    public static Class<?> getProxyClass(ClassLoader loader,
                                         Class<?>... interfaces)
        throws IllegalArgumentException
    {
    
    
        // ...
    }

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

    public static InvocationHandler getInvocationHandler(Object proxy)
        throws IllegalArgumentException
    {
    
    
        // ...
    }
}

La clase Proxy proporciona los siguientes métodos principales:

  1. getProxyClass (cargador ClassLoader, Class <?>… interfaces): este método se utiliza para generar el objeto Class de la clase proxy de la interfaz especificada. El cargador de parámetros es el cargador de clases, que se utiliza para definir la clase de proxy; las interfaces son un conjunto de interfaces y la clase de proxy implementará estas interfaces.
  2. newProxyInstance (cargador ClassLoader, interfaces Class<?>[], InvocationHandler h): este método se utiliza para generar un objeto proxy para la interfaz especificada. Este método no solo genera el objeto Clase de la clase proxy, sino que también crea una instancia de esta clase. El parámetro h es la implementación de la interfaz InvocationHandler, que define el comportamiento de la invocación del método en el objeto proxy.
  3. getInvocationHandler (Proxy de objeto): este método devuelve el controlador de invocación asociado con el objeto proxy especificado. El parámetro proxy es un objeto proxy.

Cuando se utiliza la clase Proxy para generar un objeto proxy, primero debe proporcionar una clase que implemente la interfaz InvocationHandler. El método de invocación de esta clase define el comportamiento de la invocación del método en el objeto proxy. Luego, puede llamar al método Proxy.newProxyInstance para generar un objeto proxy. Cuando se llama a un método en un objeto proxy, la llamada se reenvía al método de invocación del controlador de invocación.

2. Prueba

Crear clase de estudiante

public class Student {
    
    
    private String name;

    public Student(String name) {
    
    
        this.name = name;
    }
    public Student() {
    
    
    }

    public String getName() {
    
    
        return name;
    }

    @Override
    public String toString() {
    
    
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
}

Crear interfaz de servicio

public interface StudentService {
    
    
    public void save();
    public Student query();
}

Implementar la interfaz de servicio (una clase que necesita un proxy, una clase que necesita ser mejorada)

public class IStudentServiceImpl implements StudentService {
    
    
    public void save() {
    
    
        System.out.println("Save student info");
    }

    public Student query() {
    
    
        System.out.println("查询成功");
        Student student=new Student("赵云");
        return student;
        
    }
}

Implementar clases mejoradas

public class DaoTrascation {
    
    
    public void before(){
    
    
        System.out.println("开启事务操作");
    }
    public void after(){
    
    
        System.out.println("关闭事务操作");
    }
}

Implementar la interfaz InvocationHandler

public class TransactionHandler implements InvocationHandler {
    
    
    //增强类对象
    private DaoTrascation daoTrascation;
    //需要代理的目标对象
    private Object object;
    public TransactionHandler(DaoTrascation daoTrascation,Object object){
    
    
        this.daoTrascation=daoTrascation;
        this.object=object;
    }
    
    //代理对象要执行的方法,实现目标方法执行,和功能增强
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        Object ret=null;
        //判断当前方法是否是save方法,是才做事务操作
        if("save".equals(method.getName())){
    
    
            daoTrascation.before();
            ret=method.invoke(object,args);
            daoTrascation.after();
        }else {
    
    
            method.invoke(object,args);
        }
        return ret;
    }
}

código de prueba

public class MybeanTest {
    
    
    @Test
    public void testMyBean(){
    
    
        //增强类对象
        DaoTrascation trascation=new DaoTrascation();
        //目标类
        StudentService studentService= new IStudentServiceImpl();
        //方法拦截处理器
        TransactionHandler transactionHandler = new TransactionHandler(trascation,studentService);
        //获取代理实例对象
        StudentService studentService1= (StudentService) Proxy.newProxyInstance(
                IStudentServiceImpl.class.getClassLoader(),  //类加载器
                IStudentServiceImpl.class.getInterfaces(),   //目标类所实现的所有接口
                transactionHandler //方法拦截处理器
        );
        studentService1.save();
        studentService1.query();
    }
}

Insertar descripción de la imagen aquí

3. Análisis de principios

Combinado con el código anterior, vea la capa subyacente mediante descompilación

Modificar el código de clase de prueba

 @Test
    public void testMyBean(){
    
    
        //增强类对象
        DaoTrascation trascation=new DaoTrascation();
        //目标类
        StudentService studentService= new IStudentServiceImpl();
        //方法拦截处理器
        TransactionHandler transactionHandler = new TransactionHandler(trascation,studentService);
        //获取代理实例对象
        StudentService studentService1= (StudentService) Proxy.newProxyInstance(
                IStudentServiceImpl.class.getClassLoader(),
                IStudentServiceImpl.class.getInterfaces(),
                transactionHandler
        );
        studentService1.save(new Student());
        Student query = studentService1.query();
        saveProxyClass("/Users/jackchai/Desktop/自学笔记/java项目/SpringTest/Springtest/src");
    }
    //使用生成字节码学习使用
    private void  saveProxyClass(String path) {
    
    
        //生成的代理类的字节码文件
        byte[] $proxy1 = ProxyGenerator.generateProxyClass("$Proxy1", IStudentServiceImpl.class.getInterfaces());
        try(FileOutputStream fileOutputStream = new FileOutputStream(new File(path + "$Proxy1.class"));) {
    
    
            fileOutputStream.write($proxy1);
        }  catch (IOException e) {
    
    
            throw new RuntimeException(e);
        }

    }

Permitir que el código genere archivos de bytes

//代理对象首先实现了StudentService接口
ublic final class $Proxy1 extends Proxy implements StudentService {
    
    
//生成5个代理方法对象(因为Object对象中有一些方法)
    private static Method m1;
    private static Method m4;
    private static Method m2;
    private static Method m3;
    private static Method m0;
    //构造函数
    public $Proxy1(InvocationHandler var1) throws  {
    
    
        super(var1);
    }
    //equals方法(Object方法)
    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);
        }
    }
    //StudentService的query方法
    public final Student query() throws  {
    
    
        try {
    
    
            return (Student)super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
    
    
            throw var2;
        } catch (Throwable var3) {
    
    
            throw new UndeclaredThrowableException(var3);
        }
    }
    //object的tostring方法
    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);
        }
    }
    StudentService的save方法
    public final void save(Student var1) throws  {
    
    
        try {
    
    
            super.h.invoke(this, m3, new Object[]{
    
    var1});
        } catch (RuntimeException | Error var3) {
    
    
            throw var3;
        } catch (Throwable var4) {
    
    
            throw new UndeclaredThrowableException(var4);
        }
    }
    //object的hashcode方法
    public final int hashCode() throws  {
    
    
        try {
    
    
        //invoke方法,代理对象为当前对象,代理方法为m0,参数为null
        //h是Proxy类的InvocationHandeler类的实例
            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"));
            m4 = Class.forName("Service.StudentService").getMethod("query");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("Service.StudentService").getMethod("save", Class.forName("pojo.Student"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
    
    
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
    
    
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

4. Diagrama esquemático

Insertar descripción de la imagen aquí

4. Proxy dinámico en CGLIB

1. Introducción

CGLIB es una biblioteca de generación de código de terceros potente, de alto rendimiento y de alta calidad. Esta biblioteca es ampliamente utilizada por marcos de terceros como Spring, Mybatis e Hibernate para proporcionar operaciones de interceptación de métodos. CGLIB es un proyecto de código abierto, su dirección de código fuente CGLIB . El agente CGLIB introduce principalmente un nivel de direccionamiento indirecto al objeto mediante la operación de código de bytes para controlar el acceso al objeto. CGLIB es más potente que el proxy dinámico JDK basado en la reflexión de Java. Una falla del proxy dinámico JDK es que solo puede representar interfaces y no puede representar clases ordinarias individuales. CGLIB puede resolver este problema. (De hecho, es un complemento del proxy dinámico JDK)

2. Caso

  • Importar paquetes relacionados
 <dependency>
          <groupId>cglib</groupId>
          <artifactId>cglib</artifactId>
          <version>2.2.2</version>
      </dependency>
  • Crear clase objetivo
public class IStudentServiceImpl implements StudentService {
    
    
    public void save(Student student) {
    
    
        System.out.println("Save student info");
    }

    public Student query() {
    
    
        System.out.println("查询成功");
        Student student=new Student("赵云");
        return student;

    }
}
  • Crear operación de transacción
public class DaoTrascation {
    
    
    public void before(){
    
    
        System.out.println("开启事务操作");
    }
    public void after(){
    
    
        System.out.println("关闭事务操作");
    }
}
  • Realizar interceptación de transacciones
public class CglibInterceptor implements MethodInterceptor {
    
    
    DaoTrascation daoTrascation;
    StudentService studentService;

    public CglibInterceptor(DaoTrascation daoTrascation, StudentService studentService) {
    
    
        this.daoTrascation = daoTrascation;
        this.studentService = studentService;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    
    
        daoTrascation.before();
        Object ret=methodProxy.invokeSuper(o,objects);
        daoTrascation.after();
        return ret;
    }
}
  • prueba
  @Test
    public void testMyBean(){
    
    
        DaoTrascation trascation = new DaoTrascation();

        //得到方法拦截器
        CglibInterceptor interceptor=new CglibInterceptor(trascation);
        //使用CGLIB框架生成目标类的子类(代理类)实现增强
        Enhancer enhancer = new Enhancer();
        //设置父类字节码
        enhancer.setSuperclass(IStudentServiceImpl.class);
        //设置拦截处理
        enhancer.setCallback(interceptor);
        StudentService service= (StudentService) enhancer.create();
        service.save(new Student());

    }

Insertar descripción de la imagen aquí

3. Análisis de los principios subyacentes

  • Modificar clase de prueba
public void testMyBean(){
    
    
        //当Spring使用CGLIB创建代理对象时,CGLIB会生成字节码来创建新的类。这个 DebuggingClassWriter.DEBUG_LOCATION_PROPERTY
        // 属性实际上是一个开关,当你设置了这个属性,CGLIB在生成新的类时,会在指定的目录下生成相应的.class文件,这样就可以查看或者进一步
        // 分析这些自动生成的类了。
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"/Users/jackchai/Desktop/自学笔记/java项目/SpringTest/Springtest");
        DaoTrascation trascation = new DaoTrascation();
        //得到方法拦截器
        CglibInterceptor interceptor=new CglibInterceptor(trascation);
        //使用CGLIB框架生成目标类的子类(代理类)实现增强
        Enhancer enhancer = new Enhancer();
        //设置父类字节码
        enhancer.setSuperclass(IStudentServiceImpl.class);
        //设置拦截处理
        enhancer.setCallback(interceptor);
        StudentService service= (StudentService) enhancer.create();
        service.save(new Student());

    }

Insertar descripción de la imagen aquí
El siguiente es el archivo de clase de proxy:

public class IStudentServiceImpl$$EnhancerByCGLIB$$8b9b66bd extends IStudentServiceImpl implements Factory {
    
    
 public final void save(Student var1) {
    
    
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
    
    
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
    
    
            var10000.intercept(this, CGLIB$save$0$Method, new Object[]{
    
    var1}, CGLIB$save$0$Proxy);
        } else {
    
    
            super.save(var1);
        }
    }
    public final Student query() {
    
    
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
    
    
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        return var10000 != null ? (Student)var10000.intercept(this, CGLIB$query$1$Method, CGLIB$emptyArgs, CGLIB$query$1$Proxy) : super.query();
    }
   }
  • La clase proxy hereda el objeto proxy IStudentServiceImpl e implementa sus métodos, por lo que este patrón se implementa mediante herencia.
  • Luego, se llama al método interceptor en el interceptor tanto al guardar como al consultar para lograr la mejora.

Supongo que te gusta

Origin blog.csdn.net/qq_43456605/article/details/131076909
Recomendado
Clasificación