JVM: cargador de clases; mecanismo de delegación principal; cargador de clases de contexto de subprocesos (ocho)

1. Cargador de clases

El cargador de clases lee el archivo de bytecode (archivo .class) compilado por el compilador de Java según el nombre binario de la clase y lo convierte en una instancia de la clase java.lang.Class.

Cada instancia se usa para representar una clase Java y jvm usa estas instancias para generar objetos Java.

Como un nuevo objeto String, la reflexión genera un objeto String, utilizará el objeto String.class de la clase java.lang.Class.

Básicamente, todos los cargadores de clases son una instancia de la clase java.lang.ClassLoader

Tres categorías de cargadores de clases

cargador de clases clase de carga ilustrar

Iniciar el cargador de clases (Bootstrap ClassLoader)

JAVA_HOME/jre/lib sin acceso directo

Extensión ClassLoader

JAVA_HOME/jre/lib/ext El padre es Bootstrap, mostrando nulo

Cargador de clases de aplicaciones

ruta de clase El superior es Extensión
cargador de clases personalizado personalizar El superior es la aplicación.

  

El orden de carga del cargador de clases es el siguiente

  

El método central del cargador de clases.

nombre del método ilustrar
obtenerPadre() Devuelve el cargador de clases principal de este cargador de clases.
loadClass(nombre de la cadena) Cargue la clase llamada nombre y devuelva una instancia de la clase java.lang.Class
findClass (nombre de la cadena) Busque la clase con el nombre name y el resultado devuelto es una instancia de la clase java.lang.Class
findLoadedClass (nombre de la cadena) Busque la clase llamada nombre que se cargó y el resultado devuelto es una instancia de la clase java.lang.Class
defineClass(String name,byte[] b,int off,int len) De acuerdo con los datos en la matriz de bytes b, se convierte en una clase Java y el resultado devuelto es una instancia de la clase java.lang.Class

Los parámetros de nombre de los métodos anteriores son nombre binario (nombre binario de la clase), como

java.lang.String <nombre del paquete>.<nombre de la clase>

java.concurrent.locks.AbstractQueuedSynchronizer$Node <nombre del paquete>.<nombre de la clase>$<nombre de la clase interna>

java.net.URLClassLoader$1 <nombre del paquete>.<nombre de la clase>.<nombre de la clase interna anónima>

(1) Cargador de clases de inicio

El cargador de clases de inicio es una pieza especial de código C++ incrustado en el jvm para cargar la biblioteca de clases de java core cuando el jvm se está ejecutando.

El cargador de clases de arranque carga el objeto String.class. Los códigos básicos específicos cargados por el cargador de clases de arranque se pueden obtener obteniendo la propiedad del sistema cuyo valor es "sun.boot.class.path".

El cargador de clases de inicio no está escrito en código nativo de Java, por lo que no es una instancia de la clase java.lang.ClassLoader y no tiene el método getParent.

public static void main(String[] args) throws ClassNotFoundException {
        Class<?> aClass = Class.forName("com.mycompany.load.F");
        System.out.println(aClass.getClassLoader()); // AppClassLoader  ExtClassLoader
    }

producción

cd D:\Java\JavaProject\jvm-demo\myjvm\out\production\myjvm

D:\Java\JavaProject\jvm-demo\myjvm\out\production\myjvm>java -Xbootclasspath/a:. com.mycompany.load.Load4
bootstrap F init
null

Imprime nulo, lo que indica que su cargador de clases es Bootstrap ClassLoader 

 -Xbootclasspath significa establecer bootclasspath

Entre ellos, /a: significa agregar el directorio actual a bootclasspath

Reemplace la clase principal con este método

java -Xbootclasspath:<nueva ruta de clase de arranque>

java -Xbootclasspath/a:<ruta adjunta>

java -Xbootclasspath/p:<ruta previa a la adición>

(2) cargador de clase extendida

El cargador de clases extendido se usa para cargar un directorio extendido implementado por jvm, y todas las clases java en este directorio son cargadas por este tipo de cargador.

Esta ruta se puede obtener obteniendo la propiedad del sistema "java.ext.dirs" . El cargador de clases extendido es una instancia de la clase java.lang.ClassLoader, y su método getParent devuelve el cargador de clases de arranque (null se usa para indicar la carga de clases de arranque en la máquina virtual HotSpot)

D:\Java\JavaProject\jvm-demo\myjvm\out\production\myjvm>jar -cvf my.jar com/mycompany/load/F.class
已添加清单
正在添加: com/mycompany/load/F.class(输入 = 481) (输出 = 322)(压缩了 33%)

Copie el paquete jar en JAVA_HOME/jre/lib/ext y ejecute el código nuevamente

(3) cargador de clases de aplicación

El cargador de clases de la aplicación también se denomina cargador de clases del sistema. Los desarrolladores pueden utilizar el método java.lang.ClassLoader.getSystemClassLoader() para obtener una instancia de este tipo de cargador, de ahí el nombre de cargador de clases del sistema. Es el principal responsable de cargar las clases de Java escritas por los propios desarrolladores de programas.

En términos generales, las aplicaciones Java se cargan con este tipo de cargador, y la vía de acceso de clases cargada por el cargador de clases de la aplicación se puede obtener obteniendo la propiedad del sistema de "java.class.path" (es decir, lo que solemos llamar vía de acceso de clases). El cargador de clases de la aplicación es una instancia de la clase java.lang.ClassLoader y su método getParent devuelve el cargador de clases ampliado.

(4) El espacio de nombres de la clase

Durante la ejecución del programa, una clase no se define simplemente por su nombre binario (binary name), sino que se determina conjuntamente por su nombre binario y el espacio de nombres (paquete de tiempo de ejecución) determinado por su cargador de definiciones.

Cuando diferentes cargadores de definiciones cargan una clase con el mismo nombre binario, los objetos Class devueltos no son los mismos, por lo que los objetos creados por diferentes objetos Class tienen tipos diferentes.

Los errores extraños como  Test no se pueden convertir a  Java.lang.ClassCastException de Test son causados ​​por el mismo nombre binario de la clase pero diferentes cargadores de definición en muchos casos

package com.mycompany.load;

import sun.misc.Launcher;

public class Load6 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        ClassLoader classLoader = new Launcher().getClassLoader(); //1 new一个新的类加载器
        System.out.println(classLoader);

        /*
        这是因为 1处获取的应用类加载器a和jvm用来加载Load6.class对象的应用类加载器b不是同一个实例,
        那么构成这两个类的run-time package也就是不同的。所以即使它们的二进制名字相同,
        但是由a定义的Load6类所创建的对象显然不能转化为由b定义的Load6类的实例。
        这种情况下jvm就会抛出ClassCastException
        * */
        Class<?> aClass = classLoader.loadClass("com.mycompany.load.Load6");
        Load6 load6  = (Load6)aClass.newInstance(); //2
        //Exception in thread "main" java.lang.ClassCastException: com.mycompany.load.Load6 cannot be cast to com.mycompany.load.Load6
    }
}

Se informa una excepción:

java.lang.ClassCastException: com.mycompany.load.Load6 cannot be cast to com.mycompany.load.Load6

Esto se debe a que el cargador de clases de aplicaciones a obtenido en 1 y el cargador de clases de aplicaciones b utilizado por jvm para cargar el objeto Load6.class no son la misma instancia, por lo que los paquetes de tiempo de ejecución que constituyen las dos clases también son diferentes.

Entonces, aunque sus nombres binarios son los mismos, el objeto creado por la clase Load6 definida por obviamente no se puede convertir en una instancia de la clase Load6 definida por b. En este caso jvm lanzará ClassCastException

Las clases con el mismo nombre binario se consideran dos clases diferentes si sus cargadores de definición son diferentes

2. Mecanismo de delegación de los padres

El mecanismo de delegación principal Modelo de delegación principal, también conocido como modelo de delegación principal. La llamada delegación de padres se refiere a las reglas para encontrar clases al llamar al método loadClass del cargador de clases (padres, es más apropiado entenderlos como superiores, porque no hay una relación de herencia entre ellos)

(1) Proceso del mecanismo de carga de clases

El compilador de Java compila el archivo fuente de Java en un archivo .class y luego carga el archivo .class en la memoria mediante la JVM.Después de que se carga la JVM, se obtiene un código de bytes de objeto Class. Con un objeto de código de bytes, puede instanciarlo usando

(2) Orden de carga del cargador de clase

  

(3) Proceso del Mecanismo de Designación de los Padres

1. La clase de carga MyClass.class se delega nivel por nivel de nivel bajo a nivel alto. Primero, el cargador de capa de aplicación delega al cargador de clases de extensión, y luego la clase de extensión delega al cargador de clases de inicio.

(1) Si el cargador personalizado está montado en el cargador de clases de aplicación

(2) El cargador de clases de la aplicación delega la solicitud de carga de clases al cargador de clases de extensión

(3) El cargador de clases extendido delega la solicitud de carga de clases al cargador de clases de inicio

2. El cargador de clases de inicio no se carga y luego se carga el cargador de clases de extensión, el cargador de clases de extensión no se carga y finalmente se carga el cargador de clases de aplicación

(1) El cargador de clases de inicio busca y carga el archivo de clase en la ruta de carga. Si no se encuentra el archivo de clase de destino, el cargador de clases de extensión lo cargará.

(2) El cargador de clases de extensión busca y carga el archivo de clase en la ruta de carga. Si no se encuentra el archivo de clase de destino, el cargador de clases de la aplicación lo cargará.

(3) El cargador de clases de la aplicación busca y carga el archivo de clase en la ruta de carga. Si no se encuentra el archivo de clase de destino, el cargador personalizado lo cargará.

(4) Busque y cargue el archivo de clase en el directorio especificado por el usuario en el cargador personalizado. Si el archivo de clase de destino no se encuentra en la ruta de carga personalizada, se generará una excepción ClassNotFoud.

3. Si no se puede encontrar el cargador de clases de la aplicación, se informará una excepción ClassNotFound

(4) Análisis del código fuente

protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // 1. 检查该类是否已经加载
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        // 2. 有上级的话,委派上级 loadClass
                        c = parent.loadClass(name, false);
                    } else {
                        // 3. 如果没有上级了(ExtClassLoader),则委派
                        BootstrapClassLoader
                                c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                }
                if (c == null) {
                    long t1 = System.nanoTime();
                    // 4. 每一层找不到,调用 findClass 方法(每个类加载器自己扩展)来加载
                    c = findClass(name);
                    // 5. 记录耗时
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

El flujo de ejecución es:

1. sun.misc.Launcher$AppClassLoader //1, comience a verificar las clases cargadas, si no

2. sun.misc.Launcher$AppClassLoader // 2 lugares, delegar al superior sun.misc.Launcher$ExtClassLoader.loadClass()

3. sun.misc.Launcher$ExtClassLoader // 1, verifique la clase cargada, si no

4. sun.misc.Launcher$ExtClassLoader // 3 lugares, si no hay un superior, delegue BootstrapClassLoader para buscar

5. BootstrapClassLoader busca clases en JAVA_HOME/jre/lib, no hay

6. sun.misc.Launcher$ExtClassLoader // 4, llame a su propio método findClass para encontrar la clase en JAVA_HOME/jre/lib/ext, obviamente no, vuelva a sun.misc.Launcher$AppClassLoader // 2

7. Continúe con la ejecución hasta sun.misc.Launcher$AppClassLoader // 4, llame a su propio método findClass, busque en classpath y encuéntrelo 

(5) Ventajas y desventajas del mecanismo de delegación de padres

ventaja:

1. Para garantizar la seguridad, la relación jerárquica representa la prioridad, es decir, la carga de todas las clases tiene prioridad para el cargador de clases de inicio, lo que garantiza la clase de la biblioteca de clases principal.

2. Evite la carga repetida de clases. Si el cargador de clases principal lo ha cargado, no es necesario que el cargador de clases secundario lo vuelva a cargar, lo que garantiza la unicidad global de una clase.

defecto:

El proceso de delegación para verificar si una clase está cargada es unidireccional , aunque este método es estructuralmente claro y deja muy claras las responsabilidades de cada ClassLoader, también traerá un problema, es decir, el ClassLoader de nivel superior no puede acceder al clase cargada por el ClassLoader de nivel inferior.

Por lo general, las clases en el cargador de clases de inicio son clases básicas del sistema, incluidas algunas interfaces importantes del sistema, mientras que en el cargador de clases de aplicaciones son clases de aplicaciones. De acuerdo con este modo, no hay problema para que la clase de aplicación acceda a la clase de sistema, pero habrá problemas para que la clase de sistema acceda a la clase de aplicación .

Si se proporciona una interfaz en la clase del sistema, la interfaz debe implementarse en la clase de la aplicación, y la interfaz también está vinculada con un método de fábrica para crear una instancia de la interfaz, y tanto la interfaz como el método de fábrica están en el cargador de clases de inicio. En este momento, parecerá que el método de fábrica no puede crear una instancia de aplicación cargada por el cargador de clases de la aplicación.

3. Cargador de clases de contexto de subprocesos

El cargador de clases de contexto de subprocesos se utiliza para resolver los defectos del modelo de delegación principal de la clase.

En Java, el oficial nos proporciona muchas interfaces SPI , como JDBC, JBI, JNDI, etc. Para este tipo de interfaz SPI, el oficial a menudo solo define la especificación, y la implementación específica la completa un tercero, como JDBC. Diferentes proveedores de bases de datos deben implementarla de acuerdo con la definición de la interfaz JDBC.

Estas interfaces SPI son proporcionadas directamente por la biblioteca central de Java, generalmente ubicada rt.jaren el paquete, y la biblioteca de código específica implementada por el tercero generalmente se coloca debajo de classpathla ruta. Y aquí viene la pregunta:

rt.jarEl cargador de clases Bootstrap carga la interfaz SPI ubicada en el paquete, y el cargador de clases carga la classpathclase de implementación SPI debajo de la rutaApp .

Pero a menudo en la interfaz SPI, el código del implementador se llama a menudo, por lo que generalmente primero debe cargar su propia clase de implementación, pero la clase de implementación no está dentro del rango de carga del cargador de clases Bootstrap. mecanismo de delegación principal. Ya hemos aprendido que el cargador de clases secundario puede delegar la solicitud de carga de clases al cargador de clases principal para la carga, pero este proceso es irreversible . Es decir, el cargador de clases principal no puede delegar solicitudes de carga de clases a su propio cargador de subclases para la carga.

En este punto, surge esta pregunta: ¿cómo cargar la clase de implementación de la interfaz SPI? Eso es romper el modelo de delegación parental.

SPI (Interfaz de proveedor de servicios): el mecanismo SPI de Java es en realidad un mecanismo conectable. En un sistema, a menudo se divide en diferentes módulos, como el módulo de registro, el módulo JDBC, etc., y cada módulo generalmente tiene múltiples soluciones de implementación. Si está directamente codificado en la biblioteca central de Java Para implementar la lógica, si desea reemplazar otra implementación, debe modificar el código de la biblioteca central, lo que viola el principio del mecanismo conectable. Para evitar tales problemas, se necesita un mecanismo de descubrimiento de servicios dinámicos, que pueda detectar dinámicamente al implementador durante el proceso de inicio del programa. El SPI proporciona dicho mecanismo para encontrar un mecanismo de implementación de servicios para una interfaz. como sigue:

Cuando el implementador externo proporciona una implementación de la interfaz de servicio, se crea un archivo con el nombre de la interfaz de servicio en el directorio META-INF/services/ del paquete jar al mismo tiempo, y este archivo es la clase de implementación que implementa la interfaz de servicio. Cuando el programa externo ensambla este módulo, el nombre de la clase de implementación específica se puede encontrar a través del archivo de configuración en el paquete jar META-INF/services/, y se carga e instancia para completar la inyección del módulo.

En base a dicho contrato, la clase de implementación de la interfaz de servicio se puede encontrar muy bien, sin necesidad de formularla en el código.

Al mismo tiempo, JDK proporciona oficialmente una clase de herramienta para encontrar implementadores de servicios: java.util.ServiceLoader

El cargador de clases de contexto de hilo es el destructor del modelo de delegación de padres, que puede romper la relación de la cadena de carga del mecanismo de delegación de padres en el hilo de ejecución, de modo que el programa pueda usar el cargador de clases a la inversa.

(1) Cargador de clases de contexto de subprocesos (Context Classloader)

El cargador de clases de contexto de subprocesos (Context Classloader) se introdujo a partir de JDK 1.2. GetContextClassLoader() y setContextClassLoader(ClassLoader cl) en la clase Thread se utilizan para obtener y configurar el cargador de clases en línea, respectivamente. 

Si no lo establece setContextClassLoader(ClassLoader cl), el subproceso heredará el cargador de clases de contexto de su subproceso principal.
El cargador de clases de contexto para el subproceso inicial del tiempo de ejecución de una aplicación Java es el cargador de clases del sistema. El código que se ejecuta en un subproceso puede cargar clases y recursos a través de este cargador de clases

Puede romper el mecanismo de delegación principal. El ClassLoader principal puede usar el ClassLoader especificado por Thread.currentThread().getContextClassLoader() del hilo actual para cargar la clase, lo que puede cambiar que el ClassLoader principal no pueda usar el ClassLoader secundario u otros ClassLoader. que no tienen una relación padre-hijo directa El caso de las clases cargadas que cambian el modelo de delegado padre

Para SPI, la biblioteca principal de Java proporciona algunas interfaces y el cargador de clases de inicio carga la biblioteca principal de Java, pero la implementación de estas interfaces proviene de diferentes paquetes jar (proporcionados por el fabricante) y el cargador de clases de inicio de Java. No cargará paquetes jar de otras fuentes, por lo que el modelo tradicional de delegación de padres no puede cumplir con los requisitos de SPI. Y al configurar el cargador de clases de contexto para el hilo actual, la carga de la clase de implementación de la interfaz puede realizarse mediante el cargador de clases en línea establecido

Java proporciona la definición de muchas interfaces centrales, estas interfaces se denominan interfaces SPI y, para facilitar la carga de clases de implementación de terceros, SPI proporciona un mecanismo dinámico de descubrimiento de servicios (acuerdo), siempre que el tercero escriba la implementación. clase, cree un nuevo directorio en el proyecto META-INF/services/y cree un archivo con el mismo nombre que la interfaz de servicio en el directorio, luego, cuando se inicie el programa, encontrará todas las clases de implementación que cumplan con las especificaciones según el acuerdo, y luego entregará al cargador de clases de contexto de subprocesos para procesar el procesamiento de carga

mysqlDriver驱动类

Al usar JDBC, es necesario cargar el controlador del controlador, no escriba

Class.forName("com.mysql.jdbc.Driver")
或
Class.forName("com.mysql.cj.jdbc.Driver")

También es posible cargar correctamente com.mysql.jdbc.Driver 

En el paquete jar posterior a MySQL 6.0, se abandona el controlador com.mysql.jdbc.Driver anterior, pero en su lugar se usa com.mysql.cj.jdbc.Driver, porque este último no necesita registrarse Class.forName("com.mysql.jdbc.Driver")manualmente de esta manera Controlador , todo puede ser entregado al mecanismo SPI para su procesamiento

Al usar JDBC, com.mysql.cj.jdbc.Driverla clase de controlador de MySQL usa principalmente una clase central definida por SPI en Java: DriverManager, que se encuentra rt.jaren el paquete y se usa para administrar los controladores implementados por diferentes proveedores de bases de datos en Java. Al mismo tiempo, los Drivercontroladores implementados por estos proveedores Clases, todas heredan de las clases principales de Javajava.sql.Driver

Cargador de clases de DriverManager

System.out.println(DriverManager.class.getClassLoader());

Imprimir nulo significa que su cargador de clases es Bootstrap ClassLoader, y buscará clases en JAVA_HOME/jre/lib, pero obviamente no hay ningún paquete mysql-connector-java-xx.xx.xx.jar en JAVA_HOME/jre/lib 

public class DriverManager {
    // 注册驱动的集合
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers
        = new CopyOnWriteArrayList<>();
    // 初始化驱动
    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
}

método loadInitialDrivers()

private static void loadInitialDrivers() {
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
        // If the driver is packaged as a Service Provider, load it.
        // Get all the drivers through the classloader
        // exposed as a java.sql.Driver.class service.
        // ServiceLoader.load() replaces the sun.misc.Providers()

        // 1、使用 ServiceLoader 机制加载驱动,即 SPI
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {

                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();

                /* Load these drivers, so that they can be instantiated.
                 * It may be the case that the driver class may not be there
                 * i.e. there may be a packaged driver with the service class
                 * as implementation of java.sql.Driver but the actual class
                 * may be missing. In that case a java.util.ServiceConfigurationError
                 * will be thrown at runtime by the VM trying to locate
                 * and load the service.
                 *
                 * Adding a try catch block to catch those runtime errors
                 * if driver not available in classpath but it's
                 * packaged as service and that service is there in classpath.
                 */
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });

        println("DriverManager.initialize: jdbc.drivers = " + drivers);

        // 2、使用 jdbc.drivers 定义的驱动名加载驱动
        if (drivers == null || drivers.equals("")) {
            return;
        }
        String[] driversList = drivers.split(":");
        println("number of Drivers:" + driversList.length);
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
                // 这里的 ClassLoader.getSystemClassLoader() 就是应用程序类加载器
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }

Desde loadInitialDrivers en DriverManager, podemos saber que incluso si no usamos Class.forName("com.mysql.cj.jdbc.Driver"), el controlador mysql también se puede cargar, porque más tarde jdk usa ServiceLoader

2 es usar Class.forName para completar la carga e inicialización de la clase, que está asociada con el cargador de clases de la aplicación, para que la carga de la clase se pueda completar con éxito

1 es la interfaz del proveedor de servicios (SPI). El acuerdo es el siguiente. En el paquete META-INF/services del paquete jar, el archivo lleva el nombre completo de la interfaz y el contenido del archivo es el nombre de la clase de implementación.

(二) Cargador de servicios

ServiceLoader es un mecanismo simple para cargar proveedores de servicios. Normalmente, el proveedor de servicios implementará la interfaz definida en el servicio. El proveedor de servicios se puede instalar en el directorio de extensiones de la plataforma Java en forma de paquete jar extendido y también se puede agregar a la ruta de clase de la aplicación.

1. El proveedor de servicios debe proporcionar un método de construcción sin parámetros

2. El proveedor de servicios está a través del archivo de configuración del proveedor correspondiente en el directorio META-INF/services, y el nombre del archivo de configuración se compone del nombre del paquete de la interfaz de servicio.

3. El archivo de configuración del proveedor contiene la ruta de clase para implementar la interfaz de servicio y cada proveedor de servicio ocupa una línea.

4. ServiceLoader carga e instancia los proveedores a pedido, lo cual es una carga diferida.ServiceLoader también contiene un caché de proveedores de servicios, que almacena los proveedores de servicios cargados.

5. ServiceLoader devolverá un iterador iterador, que devolverá todos los proveedores de servicios cargados

6. ServiceLoader no es seguro para subprocesos

 Usar ServiceLoader

ServiceLoader<接口类型> allImpls = ServiceLoader.load(接口类型.class);
Iterator<接口类型> iter = allImpls.iterator();
while(iter.hasNext()) {
    iter.next();
}

ejemplo:

ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
Iterator<Driver> iterator = loader.iterator();
while (iterator.hasNext()){
   Driver dirver = iterator.next();
   System.out.println(dirver.getClass()+", 类加载器:"+dirver.getClass().getClassLoader());
}
System.out.println("当前线程上线文类加载器:"+Thread.currentThread().getContextClassLoader());
System.out.println("ServiceLoader类加载器:"+loader.getClass().getClassLoader());

Método ServiceLoader.load

public static <S> ServiceLoader<S> load(Class<S> service) {
    // 获取线程上下文类加载器
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

El cargador de clases de contexto del subproceso es el cargador de clases utilizado por el subproceso actual. El valor predeterminado es el cargador de clases de la aplicación. Dentro de él, Class.forName llama al cargador de clases de contexto del subproceso para completar la carga de la clase. El código específico está en la clase interna LazyIterator de ServiceLoader

        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                        "Provider " + cn + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                        "Provider " + cn + " could not be instantiated",
                        x);
            }
            throw new Error(); // This cannot happen
        }

Puede referirse a

 Análisis del subsistema de carga de clases de Java, mecanismo de delegación de padres y cargador de clases de contexto de subprocesos

Explicación del cargador de clases de contexto de subprocesos en Java

4. Cargador de clases personalizado

1. Escenario de cargador de clases personalizado

1. Cargue archivos de clase en rutas arbitrarias que no sean classpath

2. Implementado a través de la interfaz, cuando se desea el desacoplamiento (comúnmente utilizado en el diseño de marcos)

3. Las clases con el mismo nombre en diferentes aplicaciones se pueden cargar sin conflictos, algo común en los contenedores tomcat

2. Pasos del cargador de clases personalizado

1. Heredar la clase principal ClassLoader

2. Cumplir con el mecanismo de delegación principal y reescribir el método findClass

Nota: No es para reescribir el método loadClass, de lo contrario no se utilizará el mecanismo de delegación principal

3. Lea el código de bytes del archivo de clase

4. Llame al método defineClass de la clase principal para cargar la clase

5. El usuario llama al método loadClass del cargador de clases.

public class Load7 {
    public static void main(String[] args) throws Exception {
        MyClassLoader classLoader = new MyClassLoader();
        Class<?> c1 = classLoader.loadClass("TestServiceImpl");
        Class<?> c2 = classLoader.loadClass("TestServiceImpl");
        System.out.println(c1 == c2);//true

        MyClassLoader classLoader2 = new MyClassLoader();
        Class<?> c3 = classLoader2.loadClass("TestServiceImpl");
        //虽然相同类名,但不是同一个类加载器加载的
        System.out.println(c1 == c3);//false

        c1.newInstance();
    }
}

class MyClassLoader extends ClassLoader {

    @Override // name 就是类名称
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String path = "D:\\myclasspath\\" + name + ".class";

        try {
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            Files.copy(Paths.get(path), os);

            // 得到字节数组
            byte[] bytes = os.toByteArray();

            // byte[] -> *.class
            return defineClass(name, bytes, 0, bytes.length);

        } catch (IOException e) {
            e.printStackTrace();
            throw new ClassNotFoundException("类文件未找到", e);
        }
    }
}

Supongo que te gusta

Origin blog.csdn.net/MinggeQingchun/article/details/127230676
Recomendado
Clasificación