springboot之为什么可以使用java -jar运行springboot 的 jar包

1.如何使spring boot jar通过java -jar xxx.jar 启动

1.1spring boot项目添加spring-boot-maven-plugin插件

<build>
	<plugins>
		<plugin>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
			<executions>
				<execution>
					<id>repackage</id>
					<goals>
						<goal>repackage</goal>
					</goals>
				</execution>
			</executions>
		</plugin>
	</plugins>
</build>

注意:此处需要添加executions才行,网上许多都没有添加,这是不可行的。另外此处的springboot项目是没有依赖spring-boot-starter-parent项目的,如果依赖了spring-boot-starter-parent则不需要添加上述插件,原因是spring-boot-starter-parent中已经添加了。

--下属代码来源于spring-boot-starter-parent.xxx.pom

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <executions>
	<execution>
	  <id>repackage</id>
	  <goals>
		<goal>repackage</goal>
	  </goals>
	</execution>
  </executions>
  <configuration>
	<mainClass>${start-class}</mainClass>
  </configuration>
</plugin>

此时通过maven命令打出的jar包即可通过java -jar命令运行。

2.springboot jar包分析

2.1解压上述步骤1打好的jar包

如:spring-security-1.0-SNAPSHOT.jar

解压后文件夹如下:

其中BOOT-INF是本项目自身代码、配置文件等以及依赖的jar包;META-INF是程序的入口,包含的MANIFEST.MF用于描述jar包的信息;org是spring boot loader相关的代码;

2.2 MANIFEST.MF文件

Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Archiver-Version: Plexus Archiver
Built-By: zhang
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: com.zyf.eflying.security.SpringSecutiryApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Version: 2.4.5
Created-By: Apache Maven 3.6.3
Build-Jdk: 1.8.0_192
Main-Class: org.springframework.boot.loader.JarLauncher

从上述MANIFEST.MF中可以看到Main-Class及Start-Class配置,其中Main-Class指定jar文件的入口类JarLauncher,当使用java -jar执行jar包的时候会调用JarLauncher的main方法,而不是我们编写的SpringSecurityApplication。

2.3 java -jar做了什么

官网对java -jar的解释如下:

If the -jar option is specified, its argument is the name of the JAR file containing class and resource files for the application. The startup class must be indicated by the Main-Class manifest header in its source code.

如果指定了-jar选项,它的参数是包含应用程序的类和资源文件的JAR文件的名称。启动类必须由其源代码中的Main-Class清单头指明。

由此可见:当使用java -jar 启动springboot jar包时是去找Manifast.MF文件中的Main-Class指定的类来启动项目。

有疑问?我们的springboot项目自己编写的启动类是com.zyf.eflying.security.SpringSecutiryApplication,而在ManiFest.MF文件中Start-Class指定的才是我们自己的,那么为什么官方只提到了Main-Class,而没有提及Start-Class,并且java -jar启动类是Main-Class指定的JarLauncher,原理是怎样的的呢。

2.4 JarLauncher的执行流程

org.springframework.boot.loader.JarLauncher#main
    org.springframework.boot.loader.Launcher#launch(java.lang.String[])
        org.springframework.boot.loader.Launcher#launch(java.lang.String[], java.lang.String, java.lang.ClassLoader) 中的第二个参数mainClass-》org.springframework.boot.loader.ExecutableArchiveLauncher#getMainClass
            org.springframework.boot.loader.Launcher#createMainMethodRunner
                org.springframework.boot.loader.MainMethodRunner#run

2.4.1添加spring-boot-loader依赖

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-loader</artifactId>
	<version>2.1.16.RELEASE</version>
</dependency>

2.4.2 源码

结构图:

Main-Class入口类:org.springframework.boot.loader.JarLauncher

package org.springframework.boot.loader;

import org.springframework.boot.loader.archive.Archive;

/**
 * {@link Launcher} for JAR based archives. This launcher assumes that dependency jars are
 * included inside a {@code /BOOT-INF/lib} directory and that application classes are
 * included inside a {@code /BOOT-INF/classes} directory.
 *
 * {@link Launcher}用于基于JAR的档案。该启动程序假设依赖jar包含在{@code BOOT-INFlib}目录中,并且应用程序类包含在{@code BOOT-INFclasses}目录中。
 *
 * @author Phillip Webb
 * @author Andy Wilkinson
 * @since 1.0.0
 */
public class JarLauncher extends ExecutableArchiveLauncher {

	static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";

	static final String BOOT_INF_LIB = "BOOT-INF/lib/";

	public JarLauncher() {
	}

	protected JarLauncher(Archive archive) {
		super(archive);
	}

	@Override
	protected boolean isNestedArchive(Archive.Entry entry) {
		if (entry.isDirectory()) {
			return entry.getName().equals(BOOT_INF_CLASSES);
		}
		return entry.getName().startsWith(BOOT_INF_LIB);
	}
	//main方法入口, 构造JarLauncher,然后调用它的launch方法。参数是控制台传递的
	public static void main(String[] args) throws Exception {
		new JarLauncher().launch(args);
	}

}

 启动类基类:org.springframework.boot.loader.Launcher-》org.springframework.boot.loader.Launcher#launch(java.lang.String[])

/**
 * Base class for launchers that can start an application with a fully configured
 * classpath backed by one or more {@link Archive}s.
 *	启动器的基类,可以用一个或多个{@link Archive}支持的完整配置的类路径启动应用程序。
 * @author Phillip Webb
 * @author Dave Syer
 * @since 1.0.0
 */
public abstract class Launcher {

	/**
	 * Launch the application. This method is the initial entry point that should be
	 * called by a subclass {@code public static void main(String[] args)} method.
	 * 启动应用程序。这个方法是一个初始入口点,它应该被子类{@code public static void main(String[] args)}方法调用。
	 * @param args the incoming arguments
	 * @throws Exception if the application fails to launch
	 */
	protected void launch(String[] args) throws Exception {
		//在系统属性中设置注册了自定义的URL处理器:org.springframework.boot.loader.jar.Handler。如果URL中没有指定处理器,会去系统属性中查询
		JarFile.registerUrlProtocolHandler();
		// getClassPathArchives方法在会去找lib目录下对应的第三方依赖JarFileArchive,同时也会项目自身的JarFileArchive
        // 根据getClassPathArchives得到的JarFileArchive集合去创建类加载器ClassLoader。这里会构造一个LaunchedURLClassLoader类加载器,这个类加载器继承URLClassLoader,并使用这些JarFileArchive集合的URL构造成URLClassPath
        // LaunchedURLClassLoader类加载器的父类加载器是当前执行类JarLauncher的类加载器
		ClassLoader classLoader = createClassLoader(getClassPathArchives());
		// getMainClass方法会去项目自身的Archive中的Manifest中找出key为Start-Class的类
		// 调用重载方法launch
		launch(args, getMainClass(), classLoader);
	}
	......
	......
}

可执行存档启动类的基类:org.springframework.boot.loader.ExecutableArchiveLauncher -》org.springframework.boot.loader.ExecutableArchiveLauncher#getMainClass

/**
 * Base class for executable archive {@link Launcher}s.
 * 可执行存档{@link启动器}的基类。
 * @author Phillip Webb
 * @author Andy Wilkinson
 * @since 1.0.0
 */
public abstract class ExecutableArchiveLauncher extends Launcher {

	private final Archive archive;

	public ExecutableArchiveLauncher() {
		try {
			this.archive = createArchive();
		}
		catch (Exception ex) {
			throw new IllegalStateException(ex);
		}
	}

	protected ExecutableArchiveLauncher(Archive archive) {
		this.archive = archive;
	}

	protected final Archive getArchive() {
		return this.archive;
	}

	@Override
	protected String getMainClass() throws Exception {
		//跟代码this.archive.getManifest()发现对应的是org.springframework.boot.loader.archive.JarFileArchive#getManifest
		//继续跟进入org.springframework.boot.loader.jar.JarFile#getManifest
		//最后org.springframework.boot.loader.jar.JarFile#getManifest对应的是META-INF/MANIFEST.MF文件的内容
		Manifest manifest = this.archive.getManifest();
		String mainClass = null;
		if (manifest != null) {
			//获取META-INF/MANIFEST.MF文件内容中的Start-Class,这才是我们自己写的启动类
			mainClass = manifest.getMainAttributes().getValue("Start-Class");
		}
		if (mainClass == null) {
			throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
		}
		return mainClass;
	}
	......
	......
}

到此可以看出org.springframework.boot.loader.Launcher#launch(java.lang.String[], java.lang.String, java.lang.ClassLoader)中的mainClass参数正是我们自己写的启动类,也就是META-INF/MANIFEST.MF文件中Start-Class指定的类。

Launcher的launch方法:org.springframework.boot.loader.Launcher#launch(java.lang.String[], java.lang.String, java.lang.ClassLoader)

/**
 * Launch the application given the archive file and a fully configured classloader.
 * 启动应用程序,给出归档文件和一个完全配置的类加载器。
 * @param args the incoming arguments
 * @param mainClass the main class to run
 * @param classLoader the classloader
 * @throws Exception if the launch fails
 */
protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {
	Thread.currentThread().setContextClassLoader(classLoader);
	createMainMethodRunner(mainClass, args, classLoader).run();
}

org.springframework.boot.loader.Launcher#createMainMethodRunner

/**
 * Create the {@code MainMethodRunner} used to launch the application.
 * @param mainClass the main class
 * @param args the incoming arguments
 * @param classLoader the classloader
 * @return the main method runner
 */
protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {
	return new MainMethodRunner(mainClass, args);
}

MainMethodRunner:org.springframework.boot.loader.MainMethodRunner-》org.springframework.boot.loader.MainMethodRunner#run 

/**
 * Utility class that is used by {@link Launcher}s to call a main method. The class
 * containing the main method is loaded using the thread context class loader.
 * 被{@link Launcher}用来调用main方法的实用程序类。包含main方法的类是使用线程上下文类装入器装入的。
 * @author Phillip Webb
 * @author Andy Wilkinson
 * @since 1.0.0
 */
public class MainMethodRunner {

	private final String mainClassName;

	private final String[] args;

	/**
	 * Create a new {@link MainMethodRunner} instance.
	 * @param mainClass the main class
	 * @param args incoming arguments
	 */
	public MainMethodRunner(String mainClass, String[] args) {
		this.mainClassName = mainClass;
		this.args = (args != null) ? args.clone() : null;
	}

	public void run() throws Exception {
		Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
		Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
		mainMethod.invoke(null, new Object[] { this.args });
	}

}

上述关键代码执行流程简化如下图:

至此,SpringBoot的jar独立运行的基本原理已然清晰。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值