springboot jar包直接运行,java命令启动springboot jar

  springboot jar包直接运行,java命令启动springboot jar

  

目录
1、maven打包2、罐子包目录结构3、可执行Jar(JarLauncher)4、WarLauncher5、总结

  

1、maven打包

Spring Boot项目的pom.xml文件中默认使用弹簧-启动梅文插件插件进行打包:

  构建插件插件groupIdorg.springframework.boot/groupId artifact id spring-boot-maven-plugin/artifact id/plugin/plugins/build在执行完专家清洁包之后,会生成来个冲突相关文件:

  测试-0 .0 .1-快照.冲突测试-0 .0 .1-快照.jar .原创

  

2、Jar包目录结构

以笔者的测试-0 . 0 . 1-快照. jar为例,来看一下冲突的目录结构,其中都包含哪些目录和文件?

  可以概述为:

  spring-boot-learn-0.0.1-snapshot元INF 清单。MF BOOT-INF 类 应用程序 自由党 第三方依赖jarorgspringframeworkbootloader跳羚启动程序

  其中主要包括三大目录:META-INF、BOOT-INF、org。

  1)META-INF内容

  元信息记录了相关冲突包的基础信息,包括:入口程序。具体内容如下:

  manifest-version : 1.0 Spring-Boot-class path-index : Boot-INF/class path。idx实施-标题: TMS-开始实施-版本: 0。0 .1-快照Spring-Boot-Layers-index : Boot-INF/Layers。idx启动级: com。圣人。startapplicationspring-Boot-class 3: Boot-INF/classes/Spring-Boot-Boot-lib 3: Boot-INF

  Main-Class是org。spring框架。靴子。装载机。罐子发射器,即冲突启动的主要的函数;Start-Class是com.saint.StartApplication,即我们自己跳羚项目的启动类;也是下文提到的项目的引导类http://www . Sina.com/

  引导INF/类目录:存放应用编译后的班级文件源码;引导信息/图书馆目录:存放应用依赖的所有三方冲突包文件;3)组织内容

  (同有机的)有机目录下存放着所有跳羚相关的班级文件,比如:JarLauncher、LaunchedURLClassLoader。

  

3、可执行Jar(JarLauncher)

从冲突包内元INF/清单。中频文件中的主要级别属性值为org。spring框架。靴子。装载机。罐子发射器,可以看出BOOT-INF内容,即:跳靴应用中的妈

  in-class属性指向的class为org.springframework.boot.loader.JarLauncher

  其实吧,主要是 Java官方文档规定:java -jar命令引导的具体启动类必须配置在MANIFEST.MF资源的Main-class属性中;又根据JAR文件规范,MANIFEST.MF资源必须存放在/META-INF/目录下。所以main函数才是JarLauncher

  JarLauncher类继承图如下:

  

  从JarLauncher的类注释我们看出JarLauncher的作用:

  加载内部/BOOT-INF/lib下的所有三方依赖jar;加载内部/BOOT-INF/classes下的所有应用class;1)JarLauncher的运行步骤?

  在解压jar包后的根目录下运行 java org.springframework.boot.loader.JarLauncher。项目引导类(META-INF/MANIFEST.MF文件中的Start-Class属性)被JarLauncher加载并执行。如果直接运行Start-Class(示例的StartApplication)类,会报错ClassNotFoundException。Spring Boot依赖的JAR文件均存放在BOOT-INF/lib目录下。JarLauncher会将这些JAR文件作为Start-Class的类库依赖。这也是为什么JarLauncher能够引导,而直接运行Start-Class却不行。

  2)JarLauncher实现原理?

  

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);}@Overrideprotected boolean isNestedArchive(Archive.Entry entry) {if (entry.isDirectory()) {return entry.getName().equals(BOOT_INF_CLASSES);}return entry.getName().startsWith(BOOT_INF_LIB);}public static void main(String[] args) throws Exception {new JarLauncher().launch(args);}}
JarLauncher#main()中新建了JarLauncher并调用父类Launcher中的launch()方法启动程序;

  BOOT_INF_CLASSES、BOOT_INF_LIB变量对应BOOT-INF/classes和lib路径;isNestedArchive(Archinve.Entry entry)方法用于判断FAT JAR资源的相对路径是否为nestedArchive嵌套文档。进而决定这些FAT JAR是否会被launch。 当方法返回false时,说明FAT JAR被解压至文件目录。1> Archive的概念

  archive即归档文件,这个概念在linux下比较常见;通常就是一个tar/zip格式的压缩包;而jar正是zip格式的。

  SpringBoot抽象了Archive的概念,一个Archive可以是jar(JarFileArchive),也可以是文件目录(ExplodedArchive);这样也就统一了访问资源的逻辑层;

  

public interface Archive extends Iterable<Archive.Entry>, AutoCloseable { ....}
Archive继承自Archive.Entry,Archive.Entry有两种实现:

  JarFileArchive.JarFileEntry --> 基于java.util.jar.JarEntry实现,表示FAT JAR嵌入资源。

  ExplodedArchive.FileEntry --> 基于文件系统实现;

  两者的主要差别是ExplodedArchive相比于JarFileArchive多了一个获取文件的getFile()方法;

  

public File getFile() { return this.file;}
也就是说一个在jar包环境下寻找资源,一个在文件夹目录下寻找资源;

  所以从实现层面证明了JarLauncher支持JAR和文件系统两种启动方式

  当执行java -jar命令时,将调用/META-INF /MANIFEST.MF文件的Main-Class属性的main()方法,实际上调用的是JarLauncher#launch(args)方法;

  3) Launcher#launch(args)方法

  

protected void launch(String[] args) throws Exception { if (!isExploded()) { // phase1:注册jar URL处理器 JarFile.registerUrlProtocolHandler(); } // phase2:创建ClassLoader ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator()); String jarMode = System.getProperty("jarmode"); String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass(); // phase3:调用实际的引导类launch launch(args, launchClass, classLoader);}
launch()方法分三步:

  注册jar URL处理器;为所有的Archive创建可以加载jar in jar目录的ClassLoader;调用实际的引导类(Start-Class);1> phase1 注册jar URL处理器

  

private static final String PROTOCOL_HANDLER = "java.protocol.handler.pkgs";private static final String HANDLERS_PACKAGE = "org.springframework.boot.loader";public static void registerUrlProtocolHandler() { String handlers = System.getProperty(PROTOCOL_HANDLER, ""); System.setProperty(PROTOCOL_HANDLER, ("".equals(handlers) ? HANDLERS_PACKAGE : handlers + "" + HANDLERS_PACKAGE)); // 重置缓存的UrlHandlers; resetCachedUrlHandlers();}private static void resetCachedUrlHandlers() { try { // 由URL类实现:通过URL.setURLStreamHandlerFactory()获得URLStreamHandler。 URL.setURLStreamHandlerFactory(null); } catch (Error ex) { // Ignore }}
JarFile#resetCachedUrlHandlers()方法利用java.net.URLStreamHandler扩展机制,实现由URL#getURLStreamHandler(String)提供。

  URL#getURLStreamHandler(String protocol)方法:

  首先,URL的关联协议(Protocol)对应一种URLStreamHandler实现类。

  JDK内建了一些协议的实现,这些实现均存放在sun.net.www.protocol包下,并且类名必须为Handler,其类全名模式为sun.net.www.protocol.${protocol}.Handler(包名前缀.协议名.Handler),其中${protocol}表示协议名

  如果需要扩展,则必须继承URLStreamHandler类,通过配置Java系统属性java.protocol.handler.pkgs,追加URLStreamHandler实现类的package,多个package以分割。

  所以对于SpringBoot的JarFile,registerURLProtocolHandler()方法将package org.springframework.boot.loader追加到java系统属性java.protocol.handler.pkgs中。

  也就是说,org.springframework.boot.loader包下存在协议对应的Handler类,即org.springframework.boot.loader.jar.Handler;并且按照类名模式,其实现协议为JAR。

  另外:在URL#getURLStreamHandler()方法中,处理器先读取Java系统属性java.protocol.handler.pkgs无论其是否存在,继续读取sun.net.www.protocol包;所以JDK内建URLStreamHandler实现是兜底的

  为什么SpringBoot要选择覆盖URLStreamHandler?

  Spring BOOT FAT JAR除包含传统Java Jar资源之外,还包含依赖的JAR文件;即存在jar in jar的情况;默认情况下,JDK提供的ClassLoader只能识别jar中的class文件以及加载classpath下的其他jar包中的class文件,对于jar in jar的包无法加载;当SpringBoot FAT JAR被java -jar命令引导时,其内部的JAR文件无法被内嵌实现sun.net.www.protocol.jar.Handler当做class Path,故需要定义了一套URLStreamHandler实现类和JarURLConnection实现类,用来加载jar in jar包的class类文件;2> phase2 创建可以加载jar in jar目录的ClassLoader

  获取所有的Archive,然后针对每个Archive分别创建ClassLoader;

  

ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());/** * 获取所有的Archive(包含jar in jar的情况) */protected Iterator<Archive> getClassPathArchivesIterator() throws Exception { return getClassPathArchives().iterator();}/** * 针对每个Archive分别创建ClassLoader */protected ClassLoader createClassLoader(List<Archive> archives) throws Exception { List<URL> urls = new ArrayList<>(archives.size()); for (Archive archive : archives) { urls.add(archive.getUrl()); } return createClassLoader(urls.toArray(new URL[0]));}
3> phase3 调用实际的引导类(Start-Class)

  

// case1: 通过ExecutableArchiveLauncher#getMainClass()获取MainClassString launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();// 2、运行实际的引导类launch(args, launchClass, classLoader);
对于phase3,大致可以分为两步:

  首先通过ExecutableArchiveLauncher#getMainClass()获取mainClass(即:/META-INF/MANIFEST.MF资源中的Start-Class属性);利用反射获取mainClass类中的main(Stirng[])方法并调用;<1> 获取mainClass:

  

  Start-Class属性来自/META_INF/MANIFEST.MF资源中。Launcher的子类JarLauncherWarLauncher没有实现getMainClass()方法。所以无论是Jar还是War,读取的SpringBoot启动类均来自此属性。

  <2> 执行mainClass的main()方法:

  获取mainClass之后,MainMethodRunner#run()方法利用反射获取mainClass类中的main(Stirng[])方法并调用。

  

  运行JarLauncher实际上是在同进程、同线程内调用Start-Class类的main(Stirng[])方法,并且在调用前准备好Class Path。

  

  

4、WarLauncher

WarLauncher是可执行WAR的启动器。

  WarLauncher与JarLauncher的差异很小,主要区别在于项目文件和JAR Class Path路径的不同。

  相比于FAT Jar的目录,WAR增加了WEB-INF/lib-provided,并且该目录仅存放<scope>provided</scope>的JAR文件。传统的Servlet应用的Class Path路径仅关注WEB-INF/classes/和WEB-INF/lib/目录,因此WEB-INF/lib-provided/中的JAR将被Servlet忽略好处:打包后的WAR文件能够在Servlet容器中兼容运行

  所以JarLauncher和WarLauncher并无本质区别。

  

  

5、总结

Spring Boot应用Jar/War的启动流程:

  Spring Boot应用打包之后,生成一个Fat jar,包含了应用依赖的所有三方jar包和SpringBoot Loader相关的类。

  Fat jar的启动Main函数是JarLauncher,它负责创建一个LaunchedURLClassLoader来加载BOOT-INF/classes目录以及/BOOT-INF/lib下面的jar,并利用反射获取mainClass类中的main(Stirng[])方法并调用。即:运行JarLauncher实际上是在同进程、同线程内调用Start-Class类的main(Stirng[])方法,并且在调用前准备好Class Path。

  其他点:

  SpringBoot通过扩展JarFile、JarURLConnection及URLStreamHandler,实现了jar in jar中资源的加载。

  SpringBoot通过扩展URLClassLoader --> LauncherURLClassLoader,实现了jar in jar中class文件的加载。

  WarLauncher相比JarLauncher只是多加载WEB-INF/lib-provided目录下的jar文件。

  到此这篇关于SpringBoot应用jar包启动原理详解的文章就介绍到这了,更多相关SpringBoot jar包启动内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: