Jarlauncher的实现原理

spring boot的六大特性之一就是开发独立的应用,原因就是在spring boot中使用了内置容器,在开发web应用时不用再采用将应用打包成war包发布到外部容器中,实现了对应用生命周期的完全自主的控制。

开发spring应用

1、创建spring应用

       在开发一个spring应用时由多种创建方式,最简单的就是从https://spring.io/的方式创建一个spring应用模板然后再根据业务需求添加相应的逻辑。

2、根据环境运行spring应用

        最常见的环境分为两类:生产环境和开发环境,在开发环境中通过运行idea中的run方式即可运行该应用,也是在开发过程中最常见的一种方式,也可以采用在启动类的根目录下运行命令函参数java -jar的方式运行,当然第二种方式需要将应用单独打包然后根据jar包中的元信息(MATE-INF/MANIFEST.MF)加载Main-class指定的启动类:jarlauncher去启动start-class指定的spring应用。

1、Jarlauncher的作用        

        JarLauncher作为引导类,当执行java -jar命令去运行一个可执行的spring boot应用时,META -INF/资源的Main-Class属性调用main(String[] args)方法,而实际上调用的是Jarlauncher #launch方法。Jarlauncher作为jar的启动类用于装载项目引导类并添加其依赖,其所在的JAR文件的Maven GAV信息为org.springframework.boot:spring-boot-loader:2.0.2.RELEASE。

      按照java官方文档规定,java -jar命令引导的具体启动类必须配置在MANIFEST.MF中的Main-class属性中。

而打包后的Main-class属性为:org.springframework.boot.loader.JarLauncher

public class JarLauncher extends ExecutableArchiveLauncher {

	private static final String DEFAULT_CLASSPATH_INDEX_LOCATION = "BOOT-INF/classpath.idx";

	static final EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> {
		if (entry.isDirectory()) {
			return entry.getName().equals("BOOT-INF/classes/");
		}
		return entry.getName().startsWith("BOOT-INF/lib/");
	};

	public JarLauncher() {
	}

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

	@Override
	protected ClassPathIndexFile getClassPathIndex(Archive archive) throws IOException {
		// Only needed for exploded archives, regular ones already have a defined order
		if (archive instanceof ExplodedArchive) {
			String location = getClassPathIndexFileLocation(archive);
			return ClassPathIndexFile.loadIfPossible(archive.getUrl(), location);
		}
		return super.getClassPathIndex(archive);
	}

	private String getClassPathIndexFileLocation(Archive archive) throws IOException {
		Manifest manifest = archive.getManifest();
		Attributes attributes = (manifest != null) ? manifest.getMainAttributes() : null;
		String location = (attributes != null) ? attributes.getValue(BOOT_CLASSPATH_INDEX_ATTRIBUTE) : null;
		return (location != null) ? location : DEFAULT_CLASSPATH_INDEX_LOCATION;
	}

	@Override
	protected boolean isPostProcessingClassPathArchives() {
		return false;
	}

	@Override
	protected boolean isSearchCandidate(Archive.Entry entry) {
		return entry.getName().startsWith("BOOT-INF/");
	}

	@Override
	protected boolean isNestedArchive(Archive.Entry entry) {
		return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);
	}

	public static void main(String[] args) throws Exception {
		new JarLauncher().launch(args);
	}

}

通过new一个JarLauncher().launch(args)方式进行启动。

2、JarLauncher启动逻辑分析

2.1Jarlauncher类图

2.2 处理流程图

2.3 代码解读

public class Jarlauncher{
    ...
    private static final String JAR_MODE_LAUNCHER = "org.springframework.boot.loader.jarmode.JarModeLauncher";

    ...
    protected void launch(String[] args) throws Exception {
        //判断是否以一个分解模式的方式运行,如果是则运行,否则只支持规范的jar文件从而选择跳过
		if (!isExploded()) {
			JarFile.registerUrlProtocolHandler();
		}
        //获取类加载器
		ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
		//获取系统中的jar模型
        String jarMode = System.getProperty("jarmode");
        //加载启动引导类
		String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER :             
        getMainClass();
        //启动应用
		launch(args, launchClass, classLoader);
	}
}

 在JDK内部提供了一系列协议的实现用来支持文件(file)、HTTP、JAR等协议,这些实现存放在sun.net.www.protocol包下,类名都是以Handler结尾,而JarFile.registerUrlProtocolHandler();要做的就是用org.springframework.boot.loader.jar.Handler覆盖jdk中内建的对jar协议的实现。

覆盖原因:spring boot fat jar中除了包含传统的java jar中的资源外,还包含依赖的jar包。因此当执行java- jar命令时无法把spring boot fat jar文件当成class path,所以需要用springframework中提供的jar.Handler来进行覆盖。

进入JarFile.registerUrlProtocolHandler()

	public static void registerUrlProtocolHandler() {
		Handler.captureJarContextUrl();
        //获取handers的属性信息
		String handlers = System.getProperty(PROTOCOL_HANDLER, "");
		//如果handlers不存在或者为空则将springframework内部提供的handler加入进去进行,
        //否则用|进行隔离后加入到该属性中,覆盖的方式就是如果存在自定义的handler处理某
        //个协议就不采用内建的实现
        System.setProperty(PROTOCOL_HANDLER,
				((handlers == null || handlers.isEmpty()) ? HANDLERS_PACKAGE : handlers + "|" + HANDLERS_PACKAGE));
        //重置缓存
		resetCachedUrlHandlers();
	}

在将springframework提供的关于jar协议实现加入到Handlers中后,在获取类的加载器。获取的方式是通过迭代的方式从类路径中获取。在Launcher中声明,并在ExecutableAchiveLauncher中实现。

public class ExecutableArchiveLauncher{
    ...
    @Override
	protected Iterator<Archive> getClassPathArchivesIterator() throws Exception {
		Archive.EntryFilter searchFilter = this::isSearchCandidate;
		Iterator<Archive> archives = this.archive.getNestedArchives(searchFilter,
				(entry) -> isNestedArchive(entry) && !isEntryIndexed(entry));
		if (isPostProcessingClassPathArchives()) {
			archives = applyClassPathArchivePostProcessing(archives);
		}
		return archives;
	}
    ...
}

在isNestedAcgive(entry)方法中调用Jarlauncher#isNestedArchive()方法获取类路径和依赖的第三方jar包。

public class JarLauncher extends ExecutableArchiveLauncher {

    ...
	static final EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> {
		if (entry.isDirectory()) {
			return entry.getName().equals("BOOT-INF/classes/");
		}
		return entry.getName().startsWith("BOOT-INF/lib/");
	};
    ...

	@Override
	protected boolean isNestedArchive(Archive.Entry entry) {
		return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);
	}
    ...

}

然后通过属性jarMode是否为空来获取启动引导类:

private static final String JAR_MODE_LAUNCHER = "org.springframework.boot.loader.jarmode.JarModeLauncher";
...
String jarMode = System.getProperty("jarmode");
String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();

public class ExecutableAchiveLauncher{
    ...
	@Override
	protected String getMainClass() throws Exception {
		Manifest manifest = this.archive.getManifest();
		String mainClass = null;
		if (manifest != null) {
            //关联Meta-INF中的start-class属性
			mainClass = manifest.getMainAttributes().getValue(START_CLASS_ATTRIBUTE);
		}
		if (mainClass == null) {
			throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
		}
		return mainClass;
	}
    ...
}

最后启动应用:

public class Launcher{
	protected void launch(String[] args, String launchClass, ClassLoader classLoader) throws Exception {
		Thread.currentThread().setContextClassLoader(classLoader);
		createMainMethodRunner(launchClass, args, classLoader).run();
	}
}

### Java `JarLauncher` 使用方法及常见问题解决方案 #### 什么是 `JarLauncher` `JarLauncher` 是 Spring Boot 应用程序启动的核心组件之一。当通过命令行运行打包好的 JAR 文件时,JVM 将调用此类来初始化应用程序上下文并执行主函数[^1]。 #### 如何确保正确配置 `spring-boot-maven-plugin` 为了使构建工具能够识别并处理可执行 JAR 文件,在项目的 POM 文件中应包含如下所示的 Maven 插件设置: ```xml <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> ``` 上述配置会自动完成以下工作: - 向最终生成的应用包内嵌入必要的引导加载器(`spring-boot-loader`); - 设置 MANIFEST.MF 清单文件中的 `Main-Class` 属性指向 `JarLauncher` 类型入口点[^2]; #### 运行带有 `JarLauncher` 的应用实例 假设已经成功创建了一个基于 Spring Boot 构建的应用项目,并完成了以上提到的相关配置,则可以通过简单的命令行指令轻松部署服务端口监听任务: ```bash java -jar target/myapp.jar ``` 这里假定编译后的目标产物位于默认输出目录 (`target/`) 下面名为 `myapp.jar` 的位置上。 #### 处理与 `JarLauncher` 相关的潜在异常情况 如果尝试启动由 Spring Boot 打包成 JAR 形式的 Web 应用却遭遇失败,可能的原因包括但不限于以下几个方面: - **缺少依赖项**:确认所有必需库都已声明于 POM 或 Gradle 配置文档之中。 - **不兼容版本冲突**:检查是否存在不同模块间相互作用引发的 API 不匹配现象。 - **资源路径错误**:对于静态文件、模板页面等外部资产,请核实其相对地址是否准确无误。 - **环境变量缺失**:某些场景下,特定参数需借助操作系统的预设值才能正常运作。 针对这些问题的具体排查手段可以参照官方文档或是社区论坛上的讨论记录获取更多帮助信息[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值