作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
伊戈尔·德拉克的头像

Igor Delac

Igor是一位热情而熟练的专业人士,在Java开发方面拥有丰富的经验, system administration, and more.

Expertise

Previously At

Ericsson
Share

本文将演示如何启动 Spring Boot 应用程序从另一个Java程序. Spring Boot应用程序通常被构建到单个可执行JAR归档文件中. 它包含了所有的依赖关系,打包成嵌套的jar.

Likewise, Spring Boot项目通常由提供的maven插件构建为可执行的JAR文件,该插件会完成所有繁琐的工作. 其结果是方便, 易于与他人共享的单个JAR文件, deploy on a server, and so on.

启动Spring Boot应用程序就像输入一样简单 java -jar mySpringProg.jar,应用程序将在控制台上打印一些格式良好的信息消息.

But what if a Spring Boot developer 希望在没有人工干预的情况下从另一个Java程序运行应用程序?

How Nested JARs Work

将带有所有依赖项的Java程序打包到单个可运行的JAR文件中, 必须提供同样是JAR文件的依赖项,并以某种方式存储在最终的可运行JAR文件中.

“Shading” is one option. 着色依赖关系是包含和重命名依赖关系的过程, relocating the classes, 并重写受影响的字节码和资源,以便创建与应用程序(项目)自己的代码捆绑在一起的副本.

Shading允许用户从依赖项中解包所有类和资源,并将它们打包回可运行的JAR文件中. 这可能适用于简单的场景, however, 如果两个依赖项包含具有完全相同名称和路径的相同资源文件或类, 它们会重叠,程序可能就不起作用了.

Spring Boot采用不同的方法,将依赖JAR打包到可运行的JAR中,作为嵌套JAR.

example.jar
 |
 +-META-INF
 |  +-MANIFEST.MF
 +-org
 |  +-springframework
 |     +-boot
 |        +-loader
 |           +-
 +-BOOT-INF
    +-classes
    |  +-mycompany
    |     +-project
    |        +-YourClasses.class
    +-lib
       +-dependency1.jar
       +-dependency2.jar

A JAR archive 组织为标准的java可运行JAR文件. Spring引导加载程序类位于 org/springframework/boot/loader 路径,而用户类和依赖项在 BOOT-INF/classes and BOOT-INF/lib.

Note: 如果你是Spring的新手,你可能也想看看我们的 十大最常见的Spring框架错误 article.

典型的Spring Boot JAR文件包含三种类型的条目:

  • Project classes
  • Nested JAR libraries
  • Spring引导加载程序类

Spring Boot Classloader将首先在类路径中设置JAR库,然后再设置项目类, 这使得从IDE运行Spring Boot应用程序(Eclipse, IntelliJ) and from console.

有关类重写和类装入器的其他信息,请参阅 this article.

启动Spring引导应用程序

从命令行或shell中手动启动Spring Boot应用程序很容易,只需输入以下命令:

java -jar example.jar

However, 从另一个Java程序以编程方式启动Spring Boot应用程序需要更多的工作. 有必要加载 org/springframework/boot/loader/ *.class 代码,使用一点Java反射来实例化 JarFileArchive, JarLauncher, and invoke the launch(String[]) method.

我们将在下面的部分中更详细地了解这是如何实现的.

加载Spring引导加载程序类

正如我们已经指出的,Spring Boot JAR文件就像任何JAR归档文件一样. It is possible to load org/springframework/boot/loader/ *.class 条目,创建Class对象,然后使用它们启动Spring Boot应用程序.

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

	loadJar(final String pathToJar)抛出IOException . . . {

		//类名到类对象的映射.
		final Map> classMap = new HashMap<>();

		final JarFile JarFile = new JarFile(pathToJar);
		final Enumeration jarEntryEnum = jarFile.entries();

		final URL[] URL ={新URL("jar:file:" + pathToJar + ")!/") };
		URLClassLoader = URLClassLoader . final.newInstance(urls);

Here we can see classMap 将保存映射到各自包名的类对象,例如.g., String value org.springframework.boot.loader.JarLauncher will be mapped to the JarLauncher.class object.

while (jarEntryEnum.hasMoreElements()) {

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

The end result of the while loop 是一个映射与Spring引导加载程序类对象填充.

自动化实际发射

把装载的东西移开, 我们可以继续完成自动启动并使用它来实际启动我们的应用程序.

Java反射允许从加载的类创建对象, 这在我们的教程中非常有用.

第一步是创建一个 JarFileArchive object.

//创建JarFileArchive(File)对象,用于JarLauncher.
final Class jarFileArchiveClass = 					result.get("org.springframework.boot.loader.archive.JarFileArchive");

final Constructor jarFileArchiveConstructor = 
	jarFileArchiveClass.getConstructor(File.class);

最终对象jarFileArchive = 
		jarFileArchiveConstructor.newInstance(新文件(pathToJar));

The constructor of the JarFileArchive object takes a File(String) 对象作为参数,因此必须提供它.

下一步是创建一个 JarLauncher object, which requires Archive in its constructor.

final Class archiveClass = 	result.get("org.springframework.boot.loader.archive.Archive");
				
//使用JarLauncher(Archive)构造函数创建JarLauncher对象. 
final Constructor jarLauncherConstructor = 		mainClass.getDeclaredConstructor (archiveClass);

jarLauncherConstructor.setAccessible(true);
最终对象jarLauncher = jarLauncherConstructor.newInstance (jarFileArchive);

为了避免混淆,请注意 Archive 实际上是一个界面,而 JarFileArchive 是其中一个实现吗.

过程的最后一步是调用 launch(String[]) 方法在新创建的 jarLauncher object. 这相对简单,只需要几行代码.

//调用JarLauncher#launch(String[])方法.
final Class launcherClass = 	result.get("org.springframework.boot.loader.Launcher");

launchMethod = 
	launcherClass.getDeclaredMethod(“发射”,String [].class);
launchMethod.setAccessible(true);
				
launchMethod.调用(jarLauncher, new Object[]{new String[0]});

The 调用(jarlauncher, new Object[]{new String[0]}) 方法将最终启动Spring Boot应用程序. 注意,主线程将在这里停止并等待Spring Boot应用程序终止.

Spring引导类加载程序简介

检查我们的Spring Boot JAR文件将揭示以下结构:

+--- mySpringApp1-0.0.1-SNAPSHOT.jar
     +--- META-INF
     +--- BOOT-INF
     | +—classes # 1—项目类
     |    |     | 
     |    |     +--- com.example.mySpringApp1
     | | \——SpringBootLoaderApplication.class
     |    |
     | +——lib # 2 -嵌套的jar库
     |          +--- javax.annotation-api-1.3.1
     | +—spring-boot-2.0.0.M7.jar     
     |          \--- (...)
     |
     +--- org.springframework.boot.loader # 3 - Spring引导加载程序类
          +--- JarLauncher.class
          + - - - LaunchedURLClassLoader.class
          \--- (...)

注意条目的三种类型:

  • Project classes
  • Nested JAR libraries
  • Spring引导加载程序类

Both project classes (BOOT-INF/classes) and nested JARs (BOOT-INF/lib)由同一个类装入器处理 LaunchedURLClassLoader. 这个加载程序位于Spring Boot JAR应用程序的根目录中.

The LaunchedURLClassLoader 将加载类内容(BOOT-INF/classes)后的图书馆内容(BOOT-INF/lib),这与IDE不同. For example, Eclipse首先将类内容放在类路径中,然后是库(依赖项)。.

LaunchedURLClassLoader extends java.net.URLClassLoader,它是用一组将用于类加载的url创建的. URL可能指向JAR存档或类文件夹之类的位置. 在执行类加载时, url指定的所有资源都将按照提供url的顺序遍历, 并且将使用包含搜索类的第一个资源.

Wrapping Up

经典Java应用程序要求在classpath参数中枚举所有依赖项, 使启动过程有些繁琐和复杂.

相比之下,Spring Boot应用程序很方便,而且很容易从命令行启动. 它们管理所有的依赖关系,最终用户不需要担心细节.

However, 从另一个Java程序启动Spring Boot应用程序会使这个过程更加复杂, 因为它需要加载Spring Boot的加载器类, 创建专门的对象,例如 JarFileArchive and JarLauncher,然后使用Java反射来调用 launch method.

Bottom lineSpring Boot可以在底层处理很多琐碎的任务, 允许开发人员腾出时间,专注于更有用的工作,如创建新功能, testing, and so on.

了解基本知识

  • 弹簧和弹簧靴有什么区别?

    Spring Boot使独立创建变得很容易, production-grade, 易于运行或部署的基于spring的应用程序, 而Spring框架是一套全面的Java库,用于开发富web, desktop, or mobile applications.

  • 什么是Spring Boot ?它是如何工作的?

    Spring Boot提供maven模板, 内置Tomcat web服务器, 以及一些预定义的配置来简化Spring的使用. 大多数Spring Boot应用程序只需要很少的Spring配置. Spring Boot用于创建可以通过使用Java -jar或更传统的战争部署启动的Java应用程序.

  • 什么是Spring Boot架构?

    Spring Boot架构提供启动器, auto-configuration, 和组件扫描,以便在不需要复杂的XML配置文件的情况下开始使用Spring.

聘请Toptal这方面的专家.
Hire Now
伊戈尔·德拉克的头像
Igor Delac

Located in Split, Croatia

Member since November 3, 2014

About the author

Igor是一位热情而熟练的专业人士,在Java开发方面拥有丰富的经验, system administration, and more.

Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

Expertise

Previously At

Ericsson

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

Toptal Developers

Join the Toptal® community.