Comprender a fondo el principio ejecutable de SpringBoot jar

Los puntos de conocimiento involucrados incluyen principalmente el ciclo de vida de Maven y los complementos personalizados, las clases de herramientas proporcionadas por JDK para paquetes jar y cómo se extiende Springboot, y finalmente el cargador de clases personalizado.

Complemento Spring-Boot-Maven

El paquete jar ejecutable de SpringBoot, también conocido como jar gordo, es un paquete jar que contiene todas las dependencias de terceros. El paquete jar incorpora todas las dependencias excepto la máquina virtual Java, y es un paquete jar todo en uno.

La diferencia directa entre el paquete generado por el complemento común maven-jar-plugin y el paquete generado por spring-boot-maven-plugin es que hay dos partes principales añadidas al fat jar. La primera parte es el directorio lib, que almacena los archivos jar de los que depende Maven.Archivo de paquete, la segunda parte son las clases relacionadas con el cargador de arranque Spring.

fat jar //estructura de directorio  
├─BOOT-INF  
│ ├─clases  
│ └─lib  
├─META-INF  
│ ├─experto  
│ ├─aplicación.propiedades  
│ ├─MANIFEST.MF     
└─org  
    └─estructura de muelles  
        └─arranque  
            └─cargador
                ├─archivo  
                ├─datos  
                ├─frasco  
                └─util

Es decir, si desea saber cómo se genera el fat jar, debe conocer el mecanismo de funcionamiento de spring-boot-maven-plugin, y spring-boot-maven-plugin es un complemento personalizado, por lo que debemos sepa que el complemento personalizado de Maven cómo funciona

Complemento personalizado de Maven

Maven tiene tres ciclos de vida independientes: limpio, predeterminado y sitio, y cada ciclo de vida contiene algunas fases, las fases son secuenciales y las fases posteriores dependen de las anteriores. La fase de fase del ciclo de vida está ligada al objetivo objetivo del complemento para completar la tarea de construcción real.

<complemento>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-complemento</artifactId>  
    <ejecuciones>  
        <ejecución>  
            <objetivos>  
                <objetivo>reempaquetar</objetivo>  
            </objetivos>  
        </ejecución>  
    </ejecuciones>  
</complemento>

El objetivo de reempaquetado correspondiente se ejecutará en org.springframework.boot.maven.RepackageMojo#execute. La lógica principal de este método es llamar a org.springframework.boot.maven.RepackageMojo#repackage

reempaquetado vacío privado () lanza MojoExecutionException {  
     // Obtenga el jar generado por maven-jar-plugin, el nombre final tendrá el sufijo .original  
   Fuente del artefacto = getSourceArtifact();  
    //El archivo final, el frasco Gordo  
   Destino del archivo = getTargetFile();  
    // Obtenga el reempaquetador y vuelva a empaquetarlo en un archivo jar ejecutable
   Repackager repackager = getRepackager (fuente.getFile());  
    //Buscar y filtrar los jars de los que depende el proyecto en tiempo de ejecución  
   Set<Artefacto> artefactos = filterDependencies(this.project.getArtifacts(),  
         getFilters(getAdditionalFilters()));  
    //Convertir artefactos en bibliotecas  
   Bibliotecas bibliotecas = new ArtifactsLibraries(artefactos, this.requiresUnpack,  
         obtenerRegistro());  
   tratar {  
       // Proporcionar secuencia de comandos de inicio de Spring Boot  
      LaunchScript launchScript = getLaunchScript();
       //Ejecutar la lógica de reempaquetado para generar el fat jar final  
      repackager.repackage(objetivo, bibliotecas, launchScript);  
   }  
   captura (Excepción IOex) {  
      lanza una nueva MojoExecutionException(ex.getMessage(), ex);  
   }  
    //Actualizar fuente al archivo xxx.jar.original  
   updateArtifact(origen, destino, reempaquetador.getBackupFile());  
}

Nos preocupamos por el método org.springframework.boot.maven.RepackageMojo#getRepackager, y sabiendo cómo se genera Repackager, podemos inferir aproximadamente la lógica de empaquetado interna.

Repackager privado getRepackager (fuente del archivo) {  
   Repackager repackager = new Repackager(fuente, this.layoutFactory);  
   repackager.addMainClassTimeoutWarningListener(  
         nuevo LoggingMainClassTimeoutWarningListener());  
    //Establecer el nombre de la clase principal, si no se especifica, encontrará la primera clase que contiene el método principal, y reempaquetar finalmente establecerá org.springframework.boot.loader.JarLauncher  
   repackager.setMainClass(this.mainClass);  
   if (este.diseño!= nulo) {  
      getLog().info("Diseño: " + este.diseño);  
       //Enfóquese en el diseño y finalmente devuelva org.springframework.boot.loader.tools.Layouts.Jar  
      repackager.setLayout(this.layout.layout());  
   }  
   reenvasador de devolución;  
}
/**  
 * Diseño JAR ejecutable.  
 */  
Jar de clase estática pública implementa RepackagingLayout {  
   @Anular  
   cadena pública getLauncherClassName() {  
      devolver "org.springframework.boot.loader.JarLauncher";
   }  
   @Anular
   public String getLibraryDestination(String nombreBiblioteca, alcance de LibraryScope) {  
      devuelve "BOOT-INF/lib/";  
   }  
   @Anular  
   public String getClassesLocation() {  
      devolver "";  
   }  
   @Anular  
   public String getRepackagedClassesLocation() {  
      devuelve "BOOT-INF/clases/";  
   }  
   @Anular  
   booleano público isExecutable() {  
      devolver verdadero;  
   }  
}

diseño Podemos traducirlo en diseño de archivo o diseño de directorio. El código es claro y claro. Al mismo tiempo, debemos prestar atención al objeto org.springframework.boot.loader.JarLauncher. Inferido del nombre, esto es probable para devolver la clase de inicio para archivos jar ejecutables.

Contenido del archivo MANIFEST.MF

Versión del manifiesto: 1.0  
Título de implementación: oneday-auth-server  
Implementación-Versión: 1.0.0-SNAPSHOT  
Archiver-Versión: Plexus Archiver  
Construido por: un día  
Id. de proveedor de implementación: com.oneday  
Spring-Boot-Versión: 2.1.3.RELEASE  
Clase principal: org.springframework.boot.loader.JarLauncher  
Clase de inicio: com.oneday.auth.Application  
Spring-Boot-Classes: BOOT-INF/clases/  
Spring-Boot-Lib: BOOT-INF/lib/  
Creado por: Apache Maven 3.3.9  
Build-Jdk: 1.8.0_171

El archivo MANIFEST.MF generado por repackager es la información anterior, y puede ver dos datos clave: Main-Class y Start-Class. Podemos ir más allá, la entrada de inicio del programa no es la principal definida en nuestro SpringBoot, sino JarLauncher#main, y luego usar la reflexión para llamar al método principal de la clase de inicio definida.

Lanzador de tarros

Introducción a las clases clave

  •  java.util.jar.JarFile lee el archivo jar proporcionado por la clase de herramienta JDK
  • org.springframework.boot.loader.jar.JarFileSpringboot-loader hereda la clase JarFile proporcionada por JDK
  •  entradas del archivo jar proporcionadas por la clase de herramienta java.util.jar.JarEntryDK
  • org.springframework.boot.loader.jar.JarEntry Springboot-loader hereda la clase JarEntry proporcionada por JDK
  • org.springframework.boot.loader.archive.Archive La capa de recursos de acceso unificado abstraída por Springboot
  •  Abstracción de archivo de paquete JarFileArchive jar
  •  Directorio de archivos ExplodedArchive

Aquí nos enfocamos en describir el rol de JarFile, cada JarFileArchive corresponderá a un JarFile. Durante la construcción, se analizará la estructura interna para obtener cada clase de archivo o carpeta en el paquete jar. Podemos echar un vistazo a las anotaciones para esta clase.

/* Variante extendida de {@link java.util.jar.JarFile} que se comporta de la misma manera pero  
* ofrece la siguiente funcionalidad adicional.  
*  
* <li>Un {@link JarFile} anidado se puede {@link #getNestedJarFile(ZipEntry) obtenido} basado en  
* en cualquier entrada del directorio.</li>  
* <li>Se puede {@link #getNestedJarFile(ZipEntry) obtenido} un {@link JarFile} anidado para  
* Archivos JAR incrustados (siempre que su entrada no esté comprimida).</li>  
</ul>  
**/

El separador de recursos en jar es !/. La URL de JarFile proporcionada por JDK solo admite un '!/', y Spring Boot amplía este protocolo para admitir varios '!/', que pueden representar jar en jar, jar en directorio, fat jar recursos.

Mecanismo de carga de clase personalizado

  • El más básico: Bootstrap ClassLoader (carga clases en el directorio /lib del JDK)
  • Sub-básico: Extensión ClassLoader (carga clases en el directorio /lib/ext del JDK)
  • Común: Application ClassLoader (clases en el propio classpath del programa)

En primer lugar, es importante prestar atención al mecanismo de delegación principal. Si el ClassLoader más básico puede cargar una clase, el ClassLoader de alto nivel no puede cargarla. Esto es para introducir una clase que no es JDK con el mismo nombre de clase para el alcance incorrecto.

En segundo lugar, si bajo este mecanismo, los diversos archivos jar de terceros de los que depende el jar gordo no están en la ruta de clase del programa, es decir, si usamos el mecanismo de delegación principal, no podemos obtener el paquete jar del que dependemos. en absoluto. , por lo que debemos modificar el método de búsqueda de clases en el mecanismo de delegación principal y personalizar el mecanismo de carga de clases.

Primero presente brevemente LaunchedURLClassLoader en Springboot2, que hereda java.net.URLClassLoader y reescribe java.lang.ClassLoader#loadClass(java.lang.String, boolean), y luego analizamos cómo modifica el mecanismo de delegación principal.

Mencionamos anteriormente que Spring boot admite múltiples '!/' para representar múltiples jars, y nuestro problema es cómo encontrar estos paquetes de jars múltiples. Echemos un vistazo al constructor de LaunchedURLClassLoader.

public LaunchedURLClassLoader(URL[] urls, padre de ClassLoader) {  
   super(urls, padre);  
}

La anotación urls explica las URL desde las que cargar clases y recursos, es decir, todas las clases y recursos de los que depende el paquete fat jar, pasan el parámetro urls a la clase principal java.net.URLClassLoader y la clase principal java. net.URLClassLoader#findClass Ejecute el método de clase de búsqueda, y la fuente de búsqueda de la clase es el parámetro urls pasado por el constructor.

//Implementación de LaunchedURLClassLoader  
clase protegida <?> loadClass (nombre de cadena, resolución booleana)  
      lanza ClassNotFoundException {  
   Handler.setUseFastConnectionExceptions(verdadero);
   tratar {  
      tratar {  
          //Intente definir el paquete donde se encuentra la clase de acuerdo con el nombre de la clase, es decir, java.lang.Package, para asegurarse de que el manifiesto coincidente en el jar in jar se pueda asociar con el paquete asociado //  
         definePackageIfNecessary(nombre);  
      }  
      captura (IllegalArgumentException ex) {  
         // Tolerar la condición de carrera debido a la capacidad paralela  
         if (getPackage(nombre) == nulo) {  
            // Esto nunca debería suceder como indica IllegalArgumentException  
            // que el paquete ya ha sido definido y, por lo tanto,  
            // getPackage(nombre) no debería devolver un valor nulo.  
            //La excepción aquí muestra que la función del método definePackageIfNecessary es en realidad filtrar previamente los paquetes que no se pueden encontrar  
            throw new AssertionError("Paquete" + nombre + "ya ha sido"  
                  + "definido pero no se pudo encontrar");  
         }  
      }  
      volver super.loadClass(nombre, resolver);  
   }  
   por fin {  
      Handler.setUseFastConnectionExceptions(falso);  
   }  
}

El método super.loadClass(name, resolve) en realidad volverá a java.lang.ClassLoader#loadClass(java.lang.String, boolean), siguiendo el mecanismo de delegación principal para encontrar clases, pero Bootstrap ClassLoader y Extension ClassLoader no podrán para encontrarlas Las clases de las que depende el fat jar eventualmente llegarán a Application ClassLoader y llamarán a java.net.URLClassLoader#findClass

Cómo empezar realmente

La mayor diferencia entre Springboot2 y Springboot1 es que Springboo1 iniciará un nuevo subproceso para ejecutar la lógica de llamada de reflexión correspondiente, mientras que SpringBoot2 elimina el paso de crear un nuevo subproceso.

El método es org.springframework.boot.loader.Launcher#launch(java.lang.String[], java.lang.String, java.lang.ClassLoader) La lógica de llamada de reflexión es relativamente simple, así que no la analizaré aquí, el punto más crítico Sí, antes de llamar al método principal, establezca el cargador de clases de contexto del subproceso actual en LaunchedURLClassLoader

lanzamiento vacío protegido (String[] args, String mainClass, ClassLoader classLoader)  
      lanza una excepción {  
   Thread.currentThread().setContextClassLoader(classLoader);  
   createMainMethodRunner(mainClass, args, classLoader).run();  
}

Manifestación

public static void main(String[] args) lanza ClassNotFoundException, MalformedURLException {  
        JarFile.registerUrlProtocolHandler();  
// Construya el cargador de clases LaunchedURLClassLoader. Aquí se utilizan dos URL, que corresponden a los paquetes dependientes spring-boot-loader y spring-boot en el paquete jar. Están separados por "!/" y requieren org.springframework.boot. loader.jar Procesamiento del controlador  
        LaunchedURLClassLoader classLoader = new LaunchedURLClassLoader(  
                nueva URL[] {  
                        nueva URL("jar:archivo:/E:/IdeaProjects/oneday-auth/oneday-auth-server/target/oneday-auth-server-1.0.0-SNAPSHOT.jar!/BOOT-INF/lib/spring-boot -loader-1.2.3.RELEASE.jar!/")  
                        , nueva URL("jar:archivo:/E:/IdeaProjects/oneday-auth/oneday-auth-server/target/oneday-auth-server-1.0.0-SNAPSHOT.jar!/BOOT-INF/lib/spring- boot-2.1.3.RELEASE.jar!/")  
                },  
                Aplicación.clase.getClassLoader());  
// clase de carga  
// Estas dos clases se encontrarán en el segundo paso de la búsqueda local (método findClass de URLClassLoader)  
        classLoader.loadClass("org.springframework.boot.loader.JarLauncher");  
        classLoader.loadClass("org.springframework.boot.SpringApplication");  
// Usar el orden de carga predeterminado que se encuentra en ApplicationClassLoader en el tercer paso  
   classLoader.loadClass("org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration");  

// SpringApplication.run(Application.class, args);  
    }
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-loader -->  
<dependencia>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>cargador de arranque de resorte</artifactId>  
    <versión>2.1.3.LIBERAR</versión>  
</dependencia>  
<dependencia>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-maven-complemento</artifactId>  
    <versión>2.1.3.LIBERAR</versión>  
</dependencia>

Resumir

Para el análisis del código fuente, la mayor ganancia esta vez es que no puedo intentar comprender la lógica de cada paso del código en el código fuente a la vez, incluso si conozco la función del método. Lo que necesitamos entender es el código clave y los puntos de conocimiento involucrados.

Comencé a rastrear desde el complemento personalizado de Maven y consolidé mi conocimiento de Maven. En el proceso, incluso aprendí que JDK proporciona clases de herramientas correspondientes para leer archivos jar. El último y más importante punto de conocimiento es el cargador de clases personalizado. El código completo no es para decir qué tan bueno es el código, sino para saber por qué es bueno.

Je suppose que tu aimes

Origine blog.csdn.net/Trouvailless/article/details/124383918
conseillé
Classement