Iniciando um aplicativo Spring Boot programaticamente

Publicados: 2022-03-11

Este artigo demonstrará como iniciar um aplicativo Spring Boot de outro programa Java. Um aplicativo Spring Boot normalmente é construído em um único arquivo JAR executável. Ele contém todas as dependências internas, empacotadas como JARs aninhados.

Da mesma forma, um projeto Spring Boot geralmente é construído como um arquivo JAR executável por um plug-in maven fornecido que faz todo o trabalho sujo. O resultado é um arquivo JAR único e conveniente que é fácil de compartilhar com outras pessoas, implantar em um servidor e assim por diante.

Iniciar um aplicativo Spring Boot é tão fácil quanto digitar java -jar mySpringProg.jar , e o aplicativo imprimirá no console algumas mensagens informativas bem formatadas.

Mas e se um desenvolvedor Spring Boot quiser executar um aplicativo de outro programa Java, sem intervenção humana?

Como funcionam os JARs aninhados

Para empacotar um programa Java com todas as dependências em um único arquivo JAR executável, as dependências que também são arquivos JAR devem ser fornecidas e armazenadas de alguma forma dentro do arquivo JAR executável final.

“Sombreamento” é uma opção. Sombrear dependências é o processo de incluir e renomear dependências, realocar as classes e reescrever bytecode e recursos afetados para criar uma cópia que é empacotada junto com o próprio código de um aplicativo (projeto).

O sombreamento permite aos usuários descompactar todas as classes e recursos das dependências e embalá-los de volta em um arquivo JAR executável. Isso pode funcionar para cenários simples, no entanto, se duas dependências contiverem o mesmo arquivo ou classe de recurso com exatamente o mesmo nome e caminho, elas se sobreporão e o programa poderá não funcionar.

O Spring Boot adota uma abordagem diferente e empacota JARs de dependência dentro de JAR executável, como JARs aninhados.

 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

Um arquivo JAR é organizado como um arquivo JAR executável em Java padrão. As classes do carregador Spring Boot estão localizadas em org/springframework/boot/loader path, enquanto as classes de usuário e dependências estão em BOOT-INF/classes e BOOT-INF/lib .

Observação: se você é novo no Spring, também pode dar uma olhada no nosso artigo Top 10 Most Common Spring Framework Mistakes .

Um arquivo JAR típico do Spring Boot contém três tipos de entradas:

  • Aulas de projeto
  • Bibliotecas JAR aninhadas
  • Classes do carregador Spring Boot

O Spring Boot Classloader definirá primeiro as bibliotecas JAR no caminho de classe e, em seguida, as classes de projeto, o que faz uma pequena diferença entre executar um aplicativo Spring Boot do IDE (Eclipse, IntelliJ) e do console.

Para obter informações adicionais sobre substituições de classe e o carregador de classe, você pode consultar este artigo.

Iniciando aplicativos Spring Boot

Iniciar um aplicativo Spring Boot manualmente a partir da linha de comando ou do shell é fácil, basta digitar o seguinte:

 java -jar example.jar

No entanto, iniciar um aplicativo Spring Boot programaticamente a partir de outro programa Java requer mais esforço. É necessário carregar o org/springframework/boot/loader/*.class , usar um pouco de reflexão Java para instanciar JarFileArchive , JarLauncher e invocar o método launch(String[]) .

Veremos mais detalhadamente como isso é feito nas seções a seguir.

Carregando classes do carregador de inicialização Spring

Como já apontamos, um arquivo JAR do Spring Boot é como qualquer arquivo JAR. É possível carregar org/springframework/boot/loader/*.class , criar objetos Class e usá-los para iniciar aplicativos Spring Boot posteriormente.

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

Aqui podemos ver que classMap conterá objetos Class mapeados para seus respectivos nomes de pacote, por exemplo, o valor String org.springframework.boot.loader.JarLauncher será mapeado para o 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();

O resultado final do loop while é um mapa preenchido com objetos de classe do carregador Spring Boot.

Automatizando o lançamento real

Com o carregamento fora do caminho, podemos finalizar o lançamento automático e usá-lo para realmente iniciar nosso aplicativo.

A reflexão Java permite a criação de objetos a partir de classes carregadas, o que é bastante útil no contexto do nosso tutorial.

O primeiro passo é criar um 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));

O construtor do objeto JarFileArchive recebe um objeto File(String) como argumento, portanto, deve ser fornecido.

A próxima etapa é criar um objeto JarLauncher , que requer Archive em seu construtor.

 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 confusão, observe que Archive é na verdade uma interface, enquanto JarFileArchive é uma das implementações.

A última etapa do processo é chamar o método launch(String[]) em nosso objeto jarLauncher recém-criado. Isso é relativamente simples e requer apenas algumas linhas 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]});

O invoke(jarLauncer, new Object[]{new String[0]}) finalmente iniciará o aplicativo Spring Boot. Observe que o thread principal irá parar e aguardar aqui até que o aplicativo Spring Boot seja encerrado.

Uma palavra sobre o carregador de classe Spring Boot

Examinar nosso arquivo Spring Boot JAR revelará a seguinte estrutura:

 +--- 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 \--- (...)

Observe os três tipos de entradas:

  • Aulas de projeto
  • Bibliotecas JAR aninhadas
  • Classes do carregador Spring Boot

Ambas as classes de projeto ( BOOT-INF/classes ) e JARs aninhados ( BOOT-INF/lib ) são manipulados pelo mesmo carregador de classes LaunchedURLClassLoader . Esse carregador reside na raiz do aplicativo Spring Boot JAR.

O LaunchedURLClassLoader carregará o conteúdo da classe ( BOOT-INF/classes ) após o conteúdo da biblioteca ( BOOT-INF/lib ), que é diferente do IDE. Por exemplo, o Eclipse colocará primeiro o conteúdo da classe no caminho de classe e depois as bibliotecas (dependências).

LaunchedURLClassLoader estende java.net.URLClassLoader , que é criado com um conjunto de URLs que serão usados ​​para carregamento de classe. A URL pode apontar para um local como um arquivo JAR ou uma pasta de classes. Ao realizar o carregamento de classe, todos os recursos especificados por URLs serão percorridos na ordem em que os URLs foram fornecidos e será usado o primeiro recurso que contém a classe pesquisada.

Empacotando

Um aplicativo Java clássico requer que todas as dependências sejam enumeradas no argumento classpath, tornando o procedimento de inicialização um tanto incômodo e complicado.

Em contraste, os aplicativos Spring Boot são úteis e fáceis de iniciar a partir da linha de comando. Eles gerenciam todas as dependências e o usuário final não precisa se preocupar com os detalhes.

No entanto, iniciar um aplicativo Spring Boot a partir de outro programa Java torna o procedimento mais complicado, pois requer carregar as classes do carregador do Spring Boot, criar objetos especializados como JarFileArchive e JarLauncher e, em seguida, usar a reflexão Java para invocar o método de launch .

Conclusão : o Spring Boot pode cuidar de muitas tarefas domésticas sob o capô, permitindo que os desenvolvedores liberem tempo e se concentrem em trabalhos mais úteis, como criar novos recursos, testar e assim por diante.