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 del directorio   
├─BOOT-INF   
│ ├─classes   
│ └─lib   
├─META-INF   
│ ├─maven   
│ ├─app.properties   
│ ├─MANIFEST.MF      
└─org   
    └─springframework   
        └─boot   
            └─boot ─cargador 
                ├─archivo   
                ├─datos   
                ├─jar   
                └─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.

<plugin> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-maven-plugin</artifactId>   
    <ejecuciones>   
        <ejecución>   
            <objetivos>   
                <objetivo>reempaquetado</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

private void repackage() lanza MojoExecutionException {   
     //Obtenga el jar generado por maven-jar-plugin, el nombre final agregará el sufijo .orignal   
   Artifact source = getSourceArtifact();   
    //Archivo final, a saber, Fat jar   
   File target = getTargetFile( ) ;   
    //Obtenga el reempaquetador y vuelva a empaquetarlo en un archivo jar ejecutable 
   Repackager repackager = getRepackager(source.getFile());   
    //Encuentre y filtre 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   
   Libraries library = new ArtifactsLibraries(artifacts, this.requiresUnpack,   
         getLog());   
   try {   
       //Proporcionar script de inicio de Spring Boot  
      LaunchScript launchScript = getLaunchScript(); 
       //Ejecute la lógica de reempaquetado para generar el fat jar final   
      repackager.repackage(objetivo, bibliotecas, launchScript);   
   }   
   catch (IOException ex) {   
      throw new MojoExecutionException(ex.getMessage(), ex);   
   }   
    //Actualizar fuente a xxx.jar.archivo original   
   updateArtifact(fuente, destino, repackager.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.

private Repackager getRepackager(File source) {   
   Repackager repackager = new Repackager(source, this.layoutFactory);   
   repackager.addMainClassTimeoutWarningListener(   
         new LoggingMainClassTimeoutWarningListener());   
    //Establezca el nombre de la clase principal, si no se especifica, buscará el primero que contiene la clase del método principal, repacke finalmente establecerá org.springframework.boot.loader.JarLauncher   
   repackager.setMainClass(this.mainClass);   
   if (this.layout != null) {   
      getLog().info("Layout: " + this .layout);   
       //Finalmente preocúpese por el diseño y finalmente devuelva org.springframework.boot.loader.tools.Layouts.Jar   
      repackager.setLayout(this.layout.layout());   
   }   
   return repackager;   
}
/**   
 * Diseño JAR ejecutable.  
 */   
public static class Jar implementa RepackagingLayout {   
   @Override   
   public String getLauncherClassName() {   
      return "org.springframework.boot.loader.JarLauncher"; 
   }   
   @Override 
   public String getLibraryDestination(String libraryName, LibraryScope scope) {   
      return "BOOT-INF/lib/";  
   }   
   @Override   
   public String getClassesLocation() {   
      return "";  
   }   
   @Override   
   public String getRepackagedClassesLocation() {   
      return "BOOT-INF/classes/";  
   }   
   @Anular  
   public boolean isExecutable() {   
      return true;  
   }   
}

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 la implementación: oneday-auth-server   
Versión de la implementación: 1.0.0-SNAPSHOT   
Versión del archivador: Plexus Archiver   
Construido por: oneday   
Id. del proveedor de la implementación: com.oneday   
Spring-Boot-Version: 2.1 .3.LIBERAR   
Clase principal: org.springframework.boot.loader.JarLauncher   
Clase de inicio: com.oneday.auth.Application   
Spring-Boot-Classes: BOOT-INF/classes/   
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.  
* <ul>   
* <li>Un {@link JarFile} anidado se puede {@link #getNestedJarFile(ZipEntry) obtenido} basado   
en * cualquier entrada de directorio.</li>   
* <li>Un {@link JarFile} anidado puede ser {@link #getNestedJarFile(ZipEntry) obtenido} 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.

//La implementación de LaunchedURLClassLoader   
protected Class<?> loadClass(String name, boolean resolve)   
      throws ClassNotFoundException {   
   Handler.setUseFastConnectionExceptions(true); 
   try {   
      try {   
          //Intente definir el paquete donde se encuentra la clase de acuerdo con el nombre de la clase , a saber, java.lang. Package, asegúrese de que el manifiesto coincidente en jar in jar se pueda asociar con el paquete asociado //   
         definePackageIfNecessary(name);   
      }   
      catch (IllegalArgumentException ex) {   
         // Tolera la condición de carrera debido a que tiene capacidad paralela   
         si ( getPackage(name) == null ) {    
            // Esto nunca debería suceder como indica IllegalArgumentException  
            // que el paquete ya ha sido definido y, por lo tanto,   
            // getPackage(name) no debe devolver null.   
            // la excepción aquí indica que la función del método definePackageIfNecessary es en realidad filtrar previamente los paquetes que no se pueden encontrar   
            throw new AssertionError("Package " + name + " has ya se "   
                  + "definió pero no se pudo encontrar");   
         }   
      }   
      return super.loadClass(name, resolve);   
   }   
   finalmente {   
      Handler.setUseFastConnectionExceptions(false);   
   }   
}

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)   
      arroja una excepción {   
   Thread.currentThread () .setContextClassLoader (classLoader);  
   createMainMethodRunner(mainClass, args, classLoader).run();  
}

Manifestación

public static void main(String[] args) throws ClassNotFoundException, MalformedURLException {   
        JarFile.registerUrlProtocolHandler();   
// Construya el cargador de clases LaunchedURLClassLoader. Aquí se usan dos URL, correspondientes a los paquetes de dependencia spring-boot-loader y spring in the jar paquete respectivamente -boot, separados por "!/", requiere el controlador org.springframework.boot.loader.jar.Handler para manejar   
        LaunchedURLClassLoader classLoader = new LaunchedURLClassLoader(   
                new URL[] {   
// Cargar la clase   
// Estas dos clases se encontrarán en el segundo paso de la búsqueda local (el método findClass de URLClassLoader)   
                        new URL("jar:file:/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!/")   
                },   
                Application.class.getClassLoader());  
        classLoader.loadClass("org.springframework.boot.loader.JarLauncher");   
        classLoader.loadClass("org.springframework.boot.SpringApplication");   // 
   classLoader  
se encuentra en ApplicationClassLoader usando el orden de carga predeterminado en el tercer paso   
// SpringApplication.run(Application.class, args);   
    }

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-loader -->   
<dependency>   
    <groupId>org.springframework.boot</groupId>   
    <artifactId>spring-boot- cargador</artifactId>   
    <version>2.1.3.RELEASE</version>   
</dependency>   
<dependency>   
    <groupId>org.springframework.boot</groupId>   
    <artifactId>spring-boot-maven-plugin</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.

Supongo que te gusta

Origin blog.csdn.net/Trouvailless/article/details/124383918
Recomendado
Clasificación