Comprensión personal del agente dinámico de Java

Modo proxy estático

Lo anterior es un diagrama de clases de implementación de proxy estático simple.

Utilice el modo proxy para controlar el acceso del programa al objeto RealSubject . Si se encuentra un acceso anormal, puede limitar directamente el flujo o el retorno, o puede realizar el preprocesamiento y posprocesamiento relacionados antes y después de que se realice el procesamiento comercial para ayudar a la persona que llama superior a proteger la capa inferior Los detalles. Por ejemplo, en el marco de RPC, el proxy puede completar una serie de operaciones como serialización, operaciones de E / S de red, equilibrio de carga, recuperación de fallas y descubrimiento de servicios, mientras que el llamador superior solo percibe una llamada local.

El modo proxy también se puede utilizar para implementar la función de carga diferida . Sabemos que consultar la base de datos es una operación que requiere mucho tiempo y, en ocasiones, el programa no utiliza realmente los datos consultados. La función de carga diferida puede evitar efectivamente este desperdicio. Cuando el sistema accede a la base de datos, primero puede obtener un objeto proxy. En este momento, no se realiza ninguna operación de consulta de la base de datos. Naturalmente, no hay datos reales en el objeto proxy; cuando el el sistema realmente necesita usar los datos Cuando, llame al objeto proxy para completar la consulta de la base de datos y devolver los datos. El principio de carga diferida en marcos ORM comunes (por ejemplo, MyBatis, Hibernate) es aproximadamente el mismo.

Además, el objeto proxy puede coordinar la relación entre el objeto RealSubject real y la persona que llama, y ​​lograr el efecto de desacoplamiento hasta cierto punto.

Proxy dinámico JDK

La implementación del modo proxy descrito anteriormente también se denomina "modo proxy estático", porque es necesario crear una clase Proxy para cada clase RealSubject durante la fase de compilación. Cuando hay muchas clases que necesitan ser proxy, una gran cantidad de Aparecerán Clase de proxy.

En este escenario, podemos usar el proxy dinámico JDK para resolver este problema. El núcleo del proxy dinámico JDK es la interfaz InvocationHandler . Aquí hay una implementación de demostración de InvocationHandler, el código es el siguiente:

public class DemoInvokerHandler implements InvocationHandler {
    private Object target; // 真正的业务对象,也就是RealSubject对象
    public DemoInvokerHandler(Object target) { // 构造方法
        this.target = target;
    }
    public Object invoke(Object proxy, Method method, Object[] args)
             throws Throwable {
        // ...在执行业务方法之前的预处理...
        Object result = method.invoke(target, args);
        // ...在执行业务方法之后的后置处理...
        return result;
    }
    public Object getProxy() {
        // 创建代理对象
        return Proxy.newProxyInstance(Thread.currentThread()
            .getContextClassLoader(),
                target.getClass().getInterfaces(), this);
    }
}

A continuación, podemos crear un método main () para simular la llamada superior, crear y usar un proxy dinámico:

public class Main {
    public static void main(String[] args) {
        Subject subject = new RealSubject();
        DemoInvokerHandler invokerHandler = new DemoInvokerHandler(subject);
        // 获取代理对象
        Subject proxy = (Subject) invokerHandler.getProxy();
        // 调用代理对象的方法,它会调用DemoInvokerHandler.invoke()方法
        proxy.operation();
    }
}

Para las clases de negocios que requieren la misma lógica de proxy, solo necesita proporcionar una clase de implementación de la interfaz InvocationHandler. Durante la ejecución de Java, el JDK generará dinámicamente la clase de proxy correspondiente para cada clase de RealSubject y la cargará en la JVM, luego creará el objeto de instancia de proxy correspondiente y lo devolverá al llamador superior.

El punto de entrada de la implementación del proxy dinámico de JDK es el método estático Proxy.newProxyInstance (). Sus tres parámetros son el cargador de clases para cargar la clase de proxy generada dinámicamente, la interfaz implementada por la clase de negocio y el objeto InvocationHandler presentado anteriormente. La implementación específica del método Proxy.newProxyInstance () es la siguiente:

public static Object newProxyInstance(ClassLoader loader,
     Class[] interfaces, InvocationHandler h) 
         throws IllegalArgumentException {
    final Class<?>[] intfs = interfaces.clone();
    // ...省略权限检查等代码
    Class<?> cl = getProxyClass0(loader, intfs);  // 获取代理类
    // ...省略try/catch代码块和相关异常处理
    // 获取代理类的构造方法
    final Constructor<?> cons = cl.getConstructor(constructorParams);
    final InvocationHandler ih = h;
    return cons.newInstance(new Object[]{h});  // 创建代理对象
}

A través de la implementación del método newProxyInstance (), se puede ver que el proxy dinámico JDK completa la generación y carga de la clase de proxy en el método getProxyClass0 (). La implementación específica del método getProxyClass0 () es la siguiente:

private static Class getProxyClass0 (ClassLoader loader, 
        Class... interfaces) {
    // 边界检查,限制接口数量(略)
    // 如果指定的类加载器中已经创建了实现指定接口的代理类,则查找缓存;
    // 否则通过ProxyClassFactory创建实现指定接口的代理类
    return proxyClassCache.get(loader, interfaces);
}

proxyClassCache es un campo estático definido en la clase Proxy. Se utiliza principalmente para almacenar en caché las clases de proxy que se han creado. La definición es la siguiente:

private static final WeakCache[], Class> proxyClassCache
     = new WeakCache<>(new KeyFactory(), 
           new ProxyClassFactory());

El método WeakCache.get () primero intentará encontrar la clase de proxy en la caché. Si no se puede encontrar, creará un objeto Factory y llamará a su método get () para obtener la clase de proxy. Factory es una clase interna en WeakCache. El método Factory.get () llamará al método ProxyClassFactory.apply () para crear y cargar la clase de proxy.

El método ProxyClassFactory.apply () primero detecta el conjunto de interfaces que la clase de proxy necesita implementar, luego determina el nombre de la clase de proxy, luego crea la clase de proxy y la escribe en el archivo, finalmente carga la clase de proxy y devuelve el objeto de clase correspondiente para uso posterior Cree una instancia del objeto de clase de proxy. La implementación específica de este método es la siguiente:

public Class apply(ClassLoader loader, Class[] interfaces) {
    // ... 对interfaces集合进行一系列检测(略)
    // ... 选择定义代理类的包名(略)
    // 代理类的名称是通过包名、代理类名称前缀以及编号这三项组成的
    long num = nextUniqueNumber.getAndIncrement();
    String proxyName = proxyPkg + proxyClassNamePrefix + num;
    // 生成代理类,并写入文件
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);
    
    // 加载代理类,并返回Class对象
    return defineClass0(loader, proxyName, proxyClassFile, 0, 
      proxyClassFile.length);
}

El método ProxyGenerator.generateProxyClass () generará el código de bytes de la clase de proxy de acuerdo con el nombre especificado y el conjunto de interfaces, y decidirá si guardarlo en el disco de acuerdo con las condiciones. El código específico de este método es el siguiente:

public static byte[] generateProxyClass(final String name,
       Class[] interfaces) {
    ProxyGenerator gen = new ProxyGenerator(name, interfaces);
    // 动态生成代理类的字节码,具体生成过程不再详细介绍,感兴趣的读者可以继续分析
    final byte[] classFile = gen.generateClassFile();
    // 如果saveGeneratedFiles值为true,会将生成的代理类的字节码保存到文件中
    if (saveGeneratedFiles) { 
        java.security.AccessController.doPrivileged(
            new java.security.PrivilegedAction() {
                public Void run() {
                    // 省略try/catch代码块
                    FileOutputStream file = new FileOutputStream(
                        dotToSlash(name) + ".class");
                    file.write(classFile);
                    file.close();
                    return null;
                }
            }
        );
    }
    return classFile; // 返回上面生成的代理类的字节码
}

Finalmente, para ver claramente la definición real de la clase de proxy generada dinámicamente por el JDK, necesitamos descompilar el código de bytes de la clase de proxy generada anteriormente. El ejemplo anterior es la clase de proxy generada por RealSubject, el código obtenido después de la descompilación es el siguiente:

public final class $Proxy37 
      extends Proxy implements Subject {  // 实现了Subject接口
    // 这里省略了从Object类继承下来的相关方法和属性
    private static Method m3;
    static {
        // 省略了try/catch代码块
        // 记录了operation()方法对应的Method对象
        m3 = Class.forName("com.xxx.Subject")
          .getMethod("operation", new Class[0]);
    }
    // 构造方法的参数就是我们在示例中使用的DemoInvokerHandler对象
    public $Proxy11(InvocationHandler var1) throws {
        super(var1); 
    }
    public final void operation() throws {
        // 省略了try/catch代码块
        // 调用DemoInvokerHandler对象的invoke()方法
        // 最终调用RealSubject对象的对应方法
        super.h.invoke(this, m3, (Object[]) null);
    }
}

Hasta ahora, se presentan el uso básico y los principios básicos del proxy dinámico de JDK. Para resumir brevemente, el principio de implementación del proxy dinámico de JDK es crear dinámicamente clases de proxy y cargarlas a través del cargador de clases especificado, y pasar el objeto InvocationHandler como parámetro de construcción al crear el objeto de proxy. Cuando se llama al objeto proxy, se llama al método InvocationHandler.invoke () para ejecutar la lógica del proxy y finalmente llamar al método correspondiente del objeto empresarial real.

CGLib

El proxy dinámico JDK es compatible de forma nativa con Java y no requiere dependencias externas. Sin embargo, como se analizó anteriormente, solo puede usar un proxy basado en interfaces. Para las clases que no heredan ninguna interfaz, el proxy dinámico JDK es inútil .

Si desea utilizar un proxy de una clase que no implementa ninguna interfaz, puede considerar el uso de CGLib.

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 utiliza tecnología de código de bytes para implementar funciones de proxy dinámico. El principio subyacente es generar una subclase para la clase de destino a través de la tecnología de código de bytes y utilizar la interceptación de métodos en la subclase para interceptar todas las llamadas de método de clase principal para lograr la función de proxy.

Debido a que CGLib implementa el proxy dinámico mediante la generación de subclases, no puede utilizar métodos proxy modificados por la palabra clave final (porque los métodos finales no se pueden anular). De esta manera, el proxy dinámico CGLib y JDK pueden complementarse: cuando la clase de destino implementa la interfaz, use el proxy dinámico JDK para crear el objeto proxy; cuando la clase de destino no implementa la interfaz, use CGLib para implementar la función de el proxy dinámico . En una variedad de marcos de código abierto como Spring y MyBatis, puede ver el uso combinado de proxy dinámico JDK y CGLib.

La implementación de CGLib consta de dos miembros importantes.

  • Enhancer : especifique el objeto de destino que se va a transferir y el objeto que realmente maneja la lógica del proxy. Finalmente, el objeto de proxy se obtiene llamando al método create (). Todas las llamadas al método no final de este objeto se reenviarán a MethodInterceptor para su procesamiento. .

  • MethodInterceptor : las llamadas al método del objeto proxy dinámico se reenviarán al método de intercepción para su mejora.

El uso de estos dos componentes es similar al Proxy e InvocationHandler en el proxy dinámico JDK.

p.ej:

public class CglibProxy implements MethodInterceptor {
    // 初始化Enhancer对象
    private Enhancer enhancer = new Enhancer(); 
    
    public Object getProxy(Class clazz) {
        enhancer.setSuperclass(clazz); // 指定生成的代理类的父类
        enhancer.setCallback(this); // 设置Callback对象
        return enhancer.create(); // 通过ASM字节码技术动态创建子类实例
    }
    
    // 实现MethodInterceptor接口的intercept()方法
    public Object intercept(Object obj, Method method, Object[] args,
                   MethodProxy proxy) throws Throwable {
        System.out.println("前置处理");
        Object result = proxy.invokeSuper(obj, args); // 调用父类中的方法
        System.out.println("后置处理");
        return result;
    }
}  

// 这里是测试的类
public class CGLibTest { // 目标类
    public String method(String str) {   // 目标方法
        System.out.println(str);
        return "CGLibTest.method():" + str;
    }
    
    public static void main(String[] args) {
        CglibProxy proxy = new CglibProxy();
        // 生成CBLibTest的代理对象
        CGLibTest proxyImp = (CGLibTest) 
            proxy.getProxy(CGLibTest.class);
        // 调用代理对象的method()方法
        String result = proxyImp.method("test");
        System.out.println(result);
    }
}

Javassist

Javassist es una biblioteca de clases de código abierto que genera código de bytes de Java . Su principal ventaja es que es simple y rápido. Puede usar directamente la API de Java proporcionada por Javassist para modificar dinámicamente la estructura de la clase o generar dinámicamente la clase.

El uso de Javassist es relativamente simple, primero veamos cómo usar la API de Java proporcionada por Javassist para crear clases dinámicamente. El código de muestra es el siguiente:

public class JavassistMain {
    public static void main(String[] args) throws Exception {
        ClassPool cp = ClassPool.getDefault(); // 创建ClassPool
        // 要生成的类名称为com.test.JavassistDemo
        CtClass clazz = cp.makeClass("com.test.JavassistDemo");
    
        StringBuffer body = null;
        // 创建字段,指定了字段类型、字段名称、字段所属的类
        CtField field = new CtField(cp.get("java.lang.String"), 
            "prop", clazz);
        // 指定该字段使用private修饰
        field.setModifiers(Modifier.PRIVATE);
    
        // 设置prop字段的getter/setter方法
        clazz.addMethod(CtNewMethod.setter("getProp", field));
        clazz.addMethod(CtNewMethod.getter("setProp", field));
        // 设置prop字段的初始化值,并将prop字段添加到clazz中
        clazz.addField(field, CtField.Initializer.constant("MyName"));
    
        // 创建构造方法,指定了构造方法的参数类型和构造方法所属的类
        CtConstructor ctConstructor = new CtConstructor(
            new CtClass[]{}, clazz);
        // 设置方法体
        body = new StringBuffer();
        body.append("{\n prop=\"MyName\";\n}");
        ctConstructor.setBody(body.toString());
        clazz.addConstructor(ctConstructor); // 将构造方法添加到clazz中
    
        // 创建execute()方法,指定了方法返回值、方法名称、方法参数列表以及
        // 方法所属的类
        CtMethod ctMethod = new CtMethod(CtClass.voidType, "execute",
                new CtClass[]{}, clazz);
        // 指定该方法使用public修饰
        ctMethod.setModifiers(Modifier.PUBLIC);
        // 设置方法体
        body = new StringBuffer();
        body.append("{\n System.out.println(\"execute():\" " +
                "+ this.prop);");
        body.append("\n}");
        ctMethod.setBody(body.toString());
        clazz.addMethod(ctMethod); // 将execute()方法添加到clazz中
        // 将上面定义的JavassistDemo类保存到指定的目录
        clazz.writeFile("/Users/xxx/"); 
        // 加载clazz类,并创建对象
        Class<?> c = clazz.toClass();
        Object o = c.newInstance();
        // 调用execute()方法
        Method method = o.getClass().getMethod("execute", 
          new Class[]{});
        method.invoke(o, new Object[]{});
    }
}

Después de ejecutar el código anterior, puede encontrar el archivo JavassistDemo.class generado en el directorio especificado, descompilarlo y obtener el código JavassistDemo de la siguiente manera:

public class JavassistDemo {
    private String prop = "MyName";
    public JavassistDemo() {
        prop = "MyName";
    }
    public void setProp(String paramString) {
        this.prop = paramString;
    }
    public String getProp() {
        return this.prop;
    }
    public void execute() {
        System.out.println("execute():" + this.prop);
    }
}

Javassist también puede implementar funciones de proxy dinámicas, y los principios subyacentes también se implementan mediante la creación de subclases de la clase de destino. Aquí usamos Javassist para crear un objeto proxy para el JavassitDemo generado anteriormente. La implementación específica es la siguiente:

public class JavassitMain2 {
    public static void main(String[] args) throws Exception {
        ProxyFactory factory = new ProxyFactory();
        // 指定父类,ProxyFactory会动态生成继承该父类的子类
        factory.setSuperclass(JavassistDemo.class);
        // 设置过滤器,判断哪些方法调用需要被拦截
        factory.setFilter(new MethodFilter() {
            public boolean isHandled(Method m) {
                if (m.getName().equals("execute")) {
                    return true;
                }
                return false;
            }
        });
        // 设置拦截处理
        factory.setHandler(new MethodHandler() {
            @Override
            public Object invoke(Object self, Method thisMethod, 
                Method proceed, Object[] args) throws Throwable {
                System.out.println("前置处理");
                Object result = proceed.invoke(self, args);
                System.out.println("执行结果:" + result);
                System.out.println("后置处理");
                return result;
            }
        });
        // 创建JavassistDemo的代理类,并创建代理对象
        Class<?> c = factory.createClass();
        JavassistDemo JavassistDemo = (JavassistDemo) c.newInstance();
        JavassistDemo.execute(); // 执行execute()方法,会被拦截
        System.out.println(JavassistDemo.getProp());
    }
}

Aquí se presenta el conocimiento básico de Javassist. Javassist puede usar directamente cadenas de lenguaje Java para generar clases, lo cual es relativamente fácil de usar. Javassist también tiene un mejor rendimiento, que es el método de generación de proxy predeterminado de Dubbo .

 

Supongo que te gusta

Origin blog.csdn.net/qq_32323239/article/details/109428092
Recomendado
Clasificación