"Comprensión en profundidad de la máquina virtual Java", notas de lectura (ocho), caso del subsistema de carga y ejecución de clases (carga de clases Tomcat, OSGI, proxy dinámico)

Uno, arquitectura de cargador de clases de Tomcat

Como servidor web, es necesario resolver los siguientes problemas:

  • Las bibliotecas de clases Java utilizadas por las aplicaciones web implementadas en el mismo servidor se pueden aislar unas de otras.
  • Las bibliotecas de clases de Java utilizadas por dos aplicaciones web implementadas en el mismo servidor se pueden compartir entre sí.
  • El servidor debe asegurarse de que su propia seguridad no se vea afectada por la aplicación web implementada tanto como sea posible.
  • JSP debe admitir actualizaciones en caliente

Debido a los problemas anteriores, una sola ruta de clases no puede satisfacer la demanda, por lo que el servidor web proporciona varias rutas de clases para que los usuarios almacenen bibliotecas de clases de terceros. En la estructura de directorios de Tomcat, hay 3 conjuntos de directorios ("/ common / *", "/ server / *" y "/ shared / *"), más el directorio propio de la aplicación web "/ WEB-INF / *".

  • / directorio común: Tomcat y todas las aplicaciones web pueden utilizar la biblioteca de clases.

  • / directorio del servidor: Tomcat puede utilizar la biblioteca de clases y es invisible para todas las aplicaciones web.

  • / directorio compartido: la biblioteca de clases puede ser utilizada por todas las aplicaciones web, pero no es visible para el propio Tomcat.

  • Directorio / webapp / WEB-INF: la biblioteca de clases solo puede ser utilizada por esta micro aplicación web y no es visible para Tomcat y otras aplicaciones web.

Tomcat ha personalizado varios cargadores de clases, que se implementan de acuerdo con el modelo clásico de delegación parental. La relación se muestra en la figura:

Estructura del cargador de clases de Tomcat 5.x (diagrama de red)

Entre ellos, el cargador de clases de inicio de nivel superior, el cargador de clases extendido y el cargador de clases de aplicación son todos los cargadores de clases proporcionados por el JDK de forma predeterminada. Otro CommonClassLoader es responsable de cargar la biblioteca de clases Java en "/ common / *", CatalinaClassLoader es responsable de cargar la biblioteca de clases Java en "/ server / *", SharedClassLoader es responsable de cargar la biblioteca de clases Java en "/ shared / * ", y WebAppClassLoader es responsable de cargar las respectivas bibliotecas de clases de Java en" / WebApp / WEB-INF / * ", y finalmente cada archivo jsp corresponde a un cargador de clases.

Nota: Después de la versión 6.x de Tomcat, common, server y shared se fusionan en el directorio lib de forma predeterminada. La biblioteca de clases en este directorio es equivalente a la función que desempeña la biblioteca de clases en el directorio común anterior. Si la configuración predeterminada no puede cumplir con los requisitos, el usuario puede volver a habilitar la estructura del cargador 5.x modificando el archivo de configuración para especificar server.loader y share.loader

Como se puede ver en la figura anterior, las clases que CommonClassLoader puede cargar pueden ser cargadas por los cargadores de clases CatalinaClassLoader y SharedClassLoader, mientras que las clases que CatalinaClassLoader y SharedClassloader pueden cargar están aisladas entre sí. WebClassLoader puede usar las clases cargadas por SharedClassLoader (incluido el cargador de clases superiores), pero cada instancia de WebAppClassLoader está aislada entre sí. El rango de carga de JasperLoader es solo la clase compilada por este archivo jsp. Cuando el servidor detecta que el archivo jsp ha sido modificado, reemplazará la instancia actual de JasperLoader y creará una nueva clase jsp para cargar para lograr la función de actualización en caliente de jsp ( acerca de JSP puede referirse a cómo JSP se compila en un servlet y proporciona servicios ).

Con respecto a la violación de la delegación de los padres

Como se mencionó anteriormente, para los contenedores web, existe un problema importante que debe resolverse: para diferentes aplicaciones web, es posible que los paquetes jar en sus respectivos directorios deban aislarse entre sí. Un ejemplo simple es que diferentes aplicaciones implementadas en el mismo contenedor pueden depender de diferentes versiones del mismo paquete jar, por lo que deben aislarse entre sí. Conseguir el aislamiento también es muy simple: cada aplicación tiene un cargador de clases correspondiente a su propio directorio, y estos paquetes jar se almacenan en su propio directorio de aplicaciones y se cargan en su respectivo cargador de clases para lograr un aislamiento mutuo. Pero a partir de esta descripción, en realidad no viola el modelo de delegación parental, depende principalmente de cómo lograrlo.

Para lograr este objetivo, existen dos soluciones en términos de realización:

  • Si un cargador de clases padre común y un cargador de clases específico de la aplicación pueden cargar estos paquetes jar, entonces el cargador de clases respectivo de la aplicación de control no se entregará al cargador de clases padre para que se cargue primero de acuerdo con las recomendaciones de la delegación de los padres, pero directamente primero. Cargado por mí mismo

  • Siguiendo el modelo de delegación parental, el cargador de clases específico de la aplicación aún confía al cargador de clases padre que cargue primero, pero es necesario asegurarse de que todos sus cargadores de clases padre comunes no puedan cargar estos paquetes jar, de modo que, de acuerdo con el retroceso de la delegación parental, todavía se cargará solo

二 、 OSGI

OSGI (Open Service Gateway Initiative) es una especificación modular dinámica basada en el lenguaje Java formulado por OSGI Alliance. Cada módulo en OSGI (llamado Bundle) puede declarar el paquete Java del que depende (descrito por Import-Package), o puede declarar que permite exportar el Java-Package publicado (descrito por Export-Package), y cada módulo comienza de En apariencia, parece ser una dependencia entre pares. Una de las grandes ventajas de OSGI es que es probable que los programas basados ​​en OSGI puedan lograr funciones de conexión en caliente a nivel de módulo, gracias a su arquitectura flexible de carga de clases.

El cargador de clases Bundle de OSGI solo tenía reglas antes y no había una relación de delegación fija. Por ejemplo, un Bundle declara un paquete del que depende. Si otros Bundles declaran liberar este paquete, todas las acciones de carga de clases para este paquete se delegarán al cargador de clases Bundle que lo liberó para completar. Cuando un paquete específico no está involucrado, cada cargador de paquetes tiene una relación de nivel. Solo cuando se utilizan un paquete y una clase específicos, la delegación y las dependencias entre los paquetes se construirán de acuerdo con las definiciones de importación y exportación de paquetes.

Suponga que hay tres módulos, BundleA, BundleB y BundleC, y sus dependencias definidas son las siguientes:

  • BundleA: Release packageA, depende de java. *

  • BundleB: depende de packageA, packageC y java. *

  • BundleC: release packageC, depende del paqueteA

Luego, la relación de carga de clases entre estos tres paquetes se muestra en la siguiente figura:

Arquitectura del cargador de clases de OSGI

 

Puede verse que la relación entre los cargadores en OSGI ya no es la estructura de árbol del modelo de delegación principal, sino que se ha convertido en una estructura de red más compleja que solo se puede determinar en tiempo de ejecución . En los primeros días, este tipo de arquitectura de cargador de clases no estructurada en árbol era propensa al punto muerto debido a la interdependencia. Tome OSGI como ejemplo. Si BundleA depende del paquete B del BundleB, BundleB depende del paquete A del BundleA: cuando BundleA carga el paquete B, primero bloquee el objeto de instancia del cargador de clases actual (ClassLoader.loadClass () es un método sincronizado), y luego delegar la solicitud al cargador de clases de BundleB, pero si BundleB también solo quiere cargar la clase de packageA, primero debe bloquear su propio cargador de clases y luego solicitar al cargador de BundleA que lo procese, de modo que ambos cargadores estén esperando que el otro procese sus propias solicitudes, pero cada uno tiene su propio bloqueo, lo que provoca un estado de punto muerto.

En JDK1.7, se realizó una actualización especial para la arquitectura del cargador de clases de herencia sin árbol. Consulte: Bloqueo de control de simultaneidad para carga de clases (ClassLoadingLock)

3. Tecnología de generación de códigos de bytes y proxy dinámico

En comparación con el proxy dinámico, la implementación del proxy estático es relativamente simple, simplemente deje que la clase de proxy reemplace directamente a la clase de proxy para ejecutar el método, por supuesto, la premisa es que se implementa la misma interfaz o se hereda la misma clase principal. A continuación, se muestra un ejemplo sencillo:

public class StaticProxyTest {
    interface Runnable {
        void run();
    }

    class Dog implements Runnable {
        @Override
        public void run() {
            System.out.println("dog run.");
        }
    }

    static class StaticProxy implements Runnable {
        Runnable originObj;

        public Runnable bind(Runnable obj) {
            this.originObj = obj;
            return this;
        }

        @Override
        public void run() {
            System.out.println("before run.");
            originObj.run();
        }
    }

    public static void main(String[] args) {
        new StaticProxy().bind(new StaticProxyTest().new Dog()).run();
    }
}

Se puede ver que la implementación del proxy estático es muy simple y no hay intrusión en el objeto del proxy, pero se limita a este escenario simple. El proxy estático requiere que la clase y la interfaz originales se conozcan en tiempo de compilación y no es dinámico, y si la clase de proxy implementa múltiples interfaces, no es tan fácil de mantener. La implementación del proxy dinámico en Java puede determinar el comportamiento del proxy de la clase de proxy cuando aún no se conocen la clase y la interfaz originales. La clase de proxy se separa directamente de la clase original y se puede reutilizar de forma flexible en diferentes escenarios de aplicación. La implementación correspondiente también es relativamente simple, y se basa principalmente en dos clases: Proxy e InvocationHandler, el siguiente es un ejemplo simple:

public class DynamicProxyTest {
    interface Speakable {
        void speak();
    }

    interface Runnable {
        void run();
    }

    class Dog implements Runnable, Speakable {

        @Override
        public void run() {
            System.out.println("dog run.");
        }

        @Override
        public void speak() {
            System.out.println("dog speak.");
        }
    }

    static class DynamicProxy implements InvocationHandler {
        //原始对象
        Object originObj;

        public Object bind(Object obj) {
            this.originObj = obj;
            //生成代理对象
            return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("before method:" + method.getName());
            return method.invoke(originObj);
        }
    }

    public static void main(String[] args) {
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        Object proxy = (new DynamicProxy().bind(new DynamicProxyTest().new Dog()));
        ((Runnable) proxy).run();
        ((Speakable) proxy).speak();
    }
}

La lógica del proxy dinámico anterior es muy clara. Lo único que no está claro es el método Proxy.newProxyInstance. ¿Cómo devuelve este método el objeto proxy? Todos sabemos que el principio del proxy dinámico es crear una nueva clase de proxy (código de bytes) basada en la interfaz de proxy y la instancia de InvocationHandler que proporcionamos. Dado que el análisis del código fuente no es el tema central de esta serie de artículos, aquí solo se publica una parte de la lógica central:

El método Proxy.newProxyInstance eventualmente generará un código de bytes de clase de proxy a través de un método de clase interna estática ProxyClassFactory.apply (classLoader, interfaces) de la clase java.lang.reflect.Proxy y un análisis de código de bytes completo (parte del código):

private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
    {
        // prefix for all proxy class names
        private static final String proxyClassNamePrefix = "$Proxy";

        // next number to use for generation of unique proxy class names
        private static final AtomicLong nextUniqueNumber = new AtomicLong();

        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
            .......
           /*
             * Generate the specified proxy class.
             * 这里生成代理类字节码内容
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                /*
                 * A ClassFormatError here means that (barring bugs in the
                 * proxy class generation code) there was some other
                 * invalid aspect of the arguments supplied to the proxy
                 * class creation (such as virtual machine limitations
                 * exceeded).
                 */
                throw new IllegalArgumentException(e.toString());
            }
            ......
        }
    }

Mientras tanto, se llamará a ProxyGenerator.generateProxyClass para generar una clase de proxy. Agregue una declaración en el método principal:

System.getProperties (). Put ("sun.misc.ProxyGenerator.saveGeneratedFiles", "true") es para generar la clase de proxy generada automáticamente. Después de ejecutar, se encuentra que hay una clase $ Proxy0 en el directorio:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.demo.aop;

import com.demo.aop.DynamicProxyTest.Runnable;
import com.demo.aop.DynamicProxyTest.Speakable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

final class $Proxy0 extends Proxy implements Runnable, Speakable {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m4;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

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

    public final void run() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

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

    public final void speak() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            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"));
            m3 = Class.forName("com.demo.aop.DynamicProxyTest$Runnable").getMethod("run");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("com.demo.aop.DynamicProxyTest$Speakable").getMethod("speak");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

Como puede ver, la clase de proxy generada automáticamente hereda java.lang.reflect.Proxy de forma predeterminada, implementa la interfaz implementada por la clase de proxy y llama al método de invocación para finalmente llamar al método de invocación que implementamos. Dado que Java es de herencia única, el proxy dinámico proporcionado por Java requiere que la clase de proxy implemente la interfaz, por lo que en el aop de primavera, InvocationHandler y cglib se proporcionan en dos modos de proxy.

Supongo que te gusta

Origin blog.csdn.net/huangzhilin2015/article/details/114554579
Recomendado
Clasificación