Lanzamiento de una aplicación Spring Boot mediante programación
Publicado: 2022-03-11Este artículo demostrará cómo iniciar una aplicación Spring Boot desde otro programa Java. Una aplicación Spring Boot generalmente se integra en un solo archivo JAR ejecutable. Contiene todas las dependencias dentro, empaquetadas como JAR anidados.
Del mismo modo, un proyecto de Spring Boot generalmente se crea como un archivo JAR ejecutable mediante un complemento experto proporcionado que hace todo el trabajo sucio. El resultado es un único archivo JAR conveniente que es fácil de compartir con otros, implementar en un servidor, etc.
Iniciar una aplicación Spring Boot es tan fácil como escribir java -jar mySpringProg.jar , y la aplicación imprimirá en la consola algunos mensajes de información con un formato agradable.
Pero, ¿qué pasa si un desarrollador de Spring Boot quiere ejecutar una aplicación desde otro programa Java, sin intervención humana?
Cómo funcionan los JAR anidados
Para empaquetar un programa Java con todas las dependencias en un solo archivo JAR ejecutable, las dependencias que también son archivos JAR deben proporcionarse y almacenarse de alguna manera dentro del archivo JAR ejecutable final.
"Sombreado" es una opción. El sombreado de dependencias es el proceso de incluir y cambiar el nombre de las dependencias, reubicar las clases y reescribir el código de bytes y los recursos afectados para crear una copia que se empaqueta junto con el código propio de una aplicación (proyecto).
El sombreado permite a los usuarios desempaquetar todas las clases y recursos de las dependencias y volver a empaquetarlos en un archivo JAR ejecutable. Esto podría funcionar para escenarios simples, sin embargo, si dos dependencias contienen el mismo archivo de recursos o clase con exactamente el mismo nombre y ruta, se superpondrán y es posible que el programa no funcione.
Spring Boot adopta un enfoque diferente y empaqueta archivos JAR de dependencia dentro de archivos JAR ejecutables, como archivos JAR anidados.
example.jar | +-META-INF | +-MANIFEST.MF +-org | +-springframework | +-boot | +-loader | +-<spring boot loader classes> +-BOOT-INF +-classes | +-mycompany | +-project | +-YourClasses.class +-lib +-dependency1.jar +-dependency2.jar
Un archivo JAR se organiza como un archivo JAR ejecutable en Java estándar. Las clases del cargador Spring Boot se encuentran en org/springframework/boot/loader
, mientras que las clases de usuario y las dependencias se encuentran en BOOT-INF/classes
y BOOT-INF/lib
.
Nota: si es nuevo en Spring, también puede echar un vistazo a nuestro artículo Los 10 errores más comunes de Spring Framework .
Un archivo JAR típico de Spring Boot contiene tres tipos de entradas:
- Clases de proyecto
- Bibliotecas JAR anidadas
- Clases de cargador Spring Boot
Spring Boot Classloader establecerá primero las bibliotecas JAR en el classpath y luego proyectará las clases, lo que hace una ligera diferencia entre ejecutar una aplicación Spring Boot desde IDE (Eclipse, IntelliJ) y desde la consola.
Para obtener información adicional sobre las anulaciones de clases y el cargador de clases, puede consultar este artículo.
Inicio de aplicaciones Spring Boot
Lanzar una aplicación Spring Boot manualmente desde la línea de comando o shell es tan fácil como escribir lo siguiente:
java -jar example.jar
Sin embargo, iniciar una aplicación Spring Boot mediante programación desde otro programa Java requiere más esfuerzo. Es necesario cargar el org/springframework/boot/loader/*.class
, usar un poco de reflexión de Java para instanciar JarFileArchive
, JarLauncher
e invocar el método launch(String[])
.
Echaremos un vistazo más detallado a cómo se logra esto en las siguientes secciones.
Carga de clases del cargador de arranque Spring
Como ya señalamos, un archivo JAR de Spring Boot es como cualquier archivo JAR. Es posible cargar org/springframework/boot/loader/*.class
, crear objetos Class y usarlos para iniciar aplicaciones Spring Boot más adelante.
import java.net.URLClassLoader; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; . . . public static void loadJar(final String pathToJar) throws IOException . . . { // Class name to Class object mapping. final Map<String, Class<?>> classMap = new HashMap<>(); final JarFile jarFile = new JarFile(pathToJar); final Enumeration<JarEntry> jarEntryEnum = jarFile.entries(); final URL[] urls = { new URL("jar:file:" + pathToJar + "!/") }; final URLClassLoader urlClassLoader = URLClassLoader.newInstance(urls);
Aquí podemos ver que classMap
mantendrá los objetos Class asignados a sus respectivos nombres de paquetes, por ejemplo, el valor de cadena org.springframework.boot.loader.JarLauncher
se asignará al objeto JarLauncher.class
.
while (jarEntryEnum.hasMoreElements()) { final JarEntry jarEntry = jarEntryEnum.nextElement(); if (jarEntry.getName().startsWith("org/springframework/boot") && jarEntry.getName().endsWith(".class") == true) { int endIndex = jarEntryName.lastIndexOf(".class"); className = jarEntryName.substring(0, endIndex).replace('/', '.'); try { final Class<?> loadedClass = urlClassLoader.loadClass(className); result.put(loadedClass.getName(), loadedClass); } catch (final ClassNotFoundException ex) { } } } jarFile.close();
El resultado final del ciclo while es un mapa poblado con objetos de clase del cargador Spring Boot.

Automatización del lanzamiento real
Con la carga fuera del camino, podemos proceder a finalizar el inicio automático y usarlo para iniciar nuestra aplicación.
La reflexión de Java permite la creación de objetos a partir de clases cargadas, lo cual es muy útil en el contexto de nuestro tutorial.
El primer paso es crear un objeto JarFileArchive
.
// Create JarFileArchive(File) object, needed for JarLauncher. final Class<?> jarFileArchiveClass = result.get("org.springframework.boot.loader.archive.JarFileArchive"); final Constructor<?> jarFileArchiveConstructor = jarFileArchiveClass.getConstructor(File.class); final Object jarFileArchive = jarFileArchiveConstructor.newInstance(new File(pathToJar));
El constructor del objeto JarFileArchive
toma un objeto File(String)
como argumento, por lo que debe proporcionarse.
El siguiente paso es crear un objeto JarLauncher
, que requiere Archive
en su constructor.
final Class<?> archiveClass = result.get("org.springframework.boot.loader.archive.Archive"); // Create JarLauncher object using JarLauncher(Archive) constructor. final Constructor<?> jarLauncherConstructor = mainClass.getDeclaredConstructor(archiveClass); jarLauncherConstructor.setAccessible(true); final Object jarLauncher = jarLauncherConstructor.newInstance(jarFileArchive);
Para evitar confusiones, tenga en cuenta que Archive
es en realidad una interfaz, mientras que JarFileArchive
es una de las implementaciones.
El último paso del proceso es llamar al método launch(String[])
en nuestro objeto jarLauncher
recién creado. Esto es relativamente sencillo y requiere solo unas pocas líneas de código.
// Invoke JarLauncher#launch(String[]) method. final Class<?> launcherClass = result.get("org.springframework.boot.loader.Launcher"); final Method launchMethod = launcherClass.getDeclaredMethod("launch", String[].class); launchMethod.setAccessible(true); launchMethod.invoke(jarLauncher, new Object[]{new String[0]});
El invoke(jarLauncer, new Object[]{new String[0]})
finalmente iniciará la aplicación Spring Boot. Tenga en cuenta que el subproceso principal se detendrá y esperará aquí a que finalice la aplicación Spring Boot.
Una palabra sobre el cargador de clases Spring Boot
Examinar nuestro archivo JAR de Spring Boot revelará la siguiente estructura:
+--- mySpringApp1-0.0.1-SNAPSHOT.jar +--- META-INF +--- BOOT-INF | +--- classes # 1 - project classes | | | | | +--- com.example.mySpringApp1 | | \--- SpringBootLoaderApplication.class | | | +--- lib # 2 - nested jar libraries | +--- javax.annotation-api-1.3.1 | +--- spring-boot-2.0.0.M7.jar | \--- (...) | +--- org.springframework.boot.loader # 3 - Spring Boot loader classes +--- JarLauncher.class +--- LaunchedURLClassLoader.class \--- (...)
Tenga en cuenta los tres tipos de entradas:
- Clases de proyecto
- Bibliotecas JAR anidadas
- Clases de cargador Spring Boot
Tanto las clases de proyecto ( BOOT-INF/classes
) como los JAR anidados ( BOOT-INF/lib
) son manejados por el mismo cargador de clases LaunchedURLClassLoader
. Este cargador reside en la raíz de la aplicación Spring Boot JAR.
LaunchedURLClassLoader
cargará el contenido de la clase ( BOOT-INF/classes
) después del contenido de la biblioteca ( BOOT-INF/lib
), que es diferente del IDE. Por ejemplo, Eclipse colocará primero el contenido de la clase en el classpath y luego en las bibliotecas (dependencias).
LaunchedURLClassLoader
amplía java.net.URLClassLoader
, que se crea con un conjunto de direcciones URL que se usarán para la carga de clases. La URL puede apuntar a una ubicación como un archivo JAR o una carpeta de clases. Al realizar la carga de clases, todos los recursos especificados por URL se recorrerán en el orden en que se proporcionaron las URL y se utilizará el primer recurso que contenga la clase buscada.
Terminando
Una aplicación Java clásica requiere que todas las dependencias se enumeren en el argumento classpath, lo que hace que el procedimiento de inicio sea algo engorroso y complicado.
Por el contrario, las aplicaciones Spring Boot son prácticas y fáciles de iniciar desde la línea de comandos. Administran todas las dependencias y el usuario final no necesita preocuparse por los detalles.
Sin embargo, iniciar una aplicación Spring Boot desde otro programa Java hace que el procedimiento sea más complicado, ya que requiere cargar las clases del cargador de Spring Boot, crear objetos especializados como JarFileArchive
y JarLauncher
, y luego usar la reflexión de Java para invocar el método de launch
.
En pocas palabras : Spring Boot puede encargarse de muchas tareas menores debajo del capó, lo que permite a los desarrolladores liberar tiempo y concentrarse en trabajos más útiles, como crear nuevas funciones, realizar pruebas, etc.