一、打包

Springboot打包的时候,需要配置一个maven插件[spring-boot-maven-plugin]

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

这个插件提供了5个功能模块,包括:

  • build-info:生成项目的构建信息文件build-info.properties
  • repackage:默认goal。在mvn package执行之后,再次打包生成可执行的jar/war,同时重命名mvn package生成的jar/war为 ***.origin
  • run:这个可以用来运行Spring Boot应用
  • start:这个在 mvn integration-test 阶段,进行 Spring Boot 应用生命周期的管理
  • stop:这个在 mvn integration-test 阶段,进行 Spring Boot 应用生命周期的管理

如果想mvn package打的包不被重命名,可以配置classifier,这样Springboot打包生成的可执行jar就是XXX-executable.jar了。

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<classifier>executable</classifier>
</configuration>
</plugin>
</plugins>
</build>

二、区别

Springboot打的包和Maven打的包区别在哪里呢?

把maven package打的包 a.jar.original 重命名成a-original.jar;然后和Springboot打的包a.jar比较一下,发现:

Springboot打的包的MANIFEST.MF文件多了如下几行:

...
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.xxx.XxxApplication
...

这里的Start-Class就是我们自己写的代码的main入口类了;而Main-Class是Springboot给我们添加的启动类;

三、源码Debug

要了解Springboot可执行jar的执行过程,最好的途径就是debug一下。下面就先配置一下。

  1. 设置执行jar的命令

    一般执行jar使用的命令是 java -jar xxx.jar

    debug需要开调试端口,命令是 java -agentlib:jdwp=transport=dt_socket,server=y,address=5005,suspend=y -jar xxx.jar

  2. 在idea中配置remote debug,设置端口号为5005,Host为localhost

  3. 在项目的pom.xml中,增加spring-boot-loader的maven依赖

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-loader</artifactId>
    <version>1.5.10.RELEASE</version>
    </dependency>
  4. 完成上述配置之后,先debug启动程序,在打开idea的调试

四、源码解析

  1. 先看看前面提说到的Main-Class

    即org.springframework.boot.loader.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);
    } @Override
    protected 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);
    } }
  2. 跟着进父抽象类Launcher的launch方法

    在这个方法里面,通过getClassPathArchives()把jar包里的jar抽象成Archive对象列表。

    在Springboot loader中,抽象出了Archive概念。

    一个archive可以是一个jar(JarFileArchive),也可以是一个文件目录(ExplodedArchive)。这些都可以理解为Springboot抽象出来的统一访问资源的层。

    此List包括:

    • \BOOT-INF\classes (项目的class)
    • \BOOT-INF\lib 目录下的所有jar
  3. 遍历List,获得每个Archive的URL,组成List

  4. 创建一个自定义的类加载器LaunchedURLClassLoader。这个类加载器继承自jdk自带的java.net.URLClassLoader

  5. 加载,创建MainMethodRunner

    看下Launcher的方法,逻辑很清晰

    public abstract class Launcher {
    
        /**
    * 1、获取List<Archive>
    * 2、创建LaunchedURLClassLoader
    * 3、找到main class
    * 4、加载
    */
    protected void launch(String[] args) throws Exception {
    JarFile.registerUrlProtocolHandler();
    ClassLoader classLoader = createClassLoader(getClassPathArchives());
    launch(args, getMainClass(), classLoader);
    } /**
    * 创建classloader
    */
    protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
    List<URL> urls = new ArrayList<URL>(archives.size());
    for (Archive archive : archives) {
    urls.add(archive.getUrl());
    }
    return createClassLoader(urls.toArray(new URL[urls.size()]));
    } /**
    * 创建LaunchedURLClassLoader
    */
    protected ClassLoader createClassLoader(URL[] urls) throws Exception {
    return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
    } /**
    * 通过指定的classloader,加载main class
    */
    protected void launch(String[] args, String mainClass, ClassLoader classLoader)
    throws Exception {
    Thread.currentThread().setContextClassLoader(classLoader);
    createMainMethodRunner(mainClass, args, classLoader).run();
    } /**
    * 创建MainMethodRunner
    */
    protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {
    return new MainMethodRunner(mainClass, args);
    } /**
    * 抽象方法,用来获取main class
    */
    protected abstract String getMainClass() throws Exception; /**
    * 抽象方法,用来获取List<Archive>
    */
    protected abstract List<Archive> getClassPathArchives() throws Exception; protected final Archive createArchive() throws Exception {
    ProtectionDomain protectionDomain = getClass().getProtectionDomain();
    CodeSource codeSource = protectionDomain.getCodeSource();
    URI location = (codeSource == null ? null : codeSource.getLocation().toURI());
    String path = (location == null ? null : location.getSchemeSpecificPart());
    if (path == null) {
    throw new IllegalStateException("Unable to determine code source archive");
    }
    File root = new File(path);
    if (!root.exists()) {
    throw new IllegalStateException(
    "Unable to determine code source archive from " + root);
    }
    return (root.isDirectory() ? new ExplodedArchive(root)
    : new JarFileArchive(root));
    }
    }
  6. 执行MainMethodRunner的run方法,启动主类的main

    public class MainMethodRunner {
    
        private final String mainClassName;
    
        private final String[] args;
    
        public MainMethodRunner(String mainClass, String[] args) {
    this.mainClassName = mainClass;
    this.args = (args == null ? null : args.clone());
    } /**
    * 通过反射找到main方法,然后invoke
    */
    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打包执行源码解析的更多相关文章

  1. SpringBoot源码学习1——SpringBoot自动装配源码解析+Spring如何处理配置类的

    系列文章目录和关于我 一丶什么是SpringBoot自动装配 SpringBoot通过SPI的机制,在我们程序员引入一些starter之后,扫描外部引用 jar 包中的META-INF/spring. ...

  2. springboot自动配置源码解析

    springboot版本:2.1.6.RELEASE SpringBoot 自动配置主要通过 @EnableAutoConfiguration, @Conditional, @EnableConfig ...

  3. SpringBoot自动装配源码解析

    序:众所周知spring-boot入门容易精通难,说到底spring-boot是对spring已有的各种技术的整合封装,因为封装了所以使用简单,也因为封装了所以越来越多的"拿来主义" ...

  4. SpringBoot Profile使用详解及配置源码解析

    在实践的过程中我们经常会遇到不同的环境需要不同配置文件的情况,如果每换一个环境重新修改配置文件或重新打包一次会比较麻烦,Spring Boot为此提供了Profile配置来解决此问题. Profile ...

  5. 了解腾讯开源的多渠道打包技术 VasDolly源码解析

    一.概要 大家应该都清楚,大家上线app,需要上线各种平台,比如:小米,华为,百度等等等等,我们多数称之为渠道,如果发的渠道多,可能有上百个渠道. 针对每个渠道,我们希望可以获取各个渠道的一些独立的统 ...

  6. SpringBoot 2.0.3 源码解析

    前言 用SpringBoot也有很长一段时间了,一直是底层使用者,没有研究过其到底是怎么运行的,借此机会今天试着将源码读一下,在此记录...我这里使用的SpringBoot 版本是  2.0.3.RE ...

  7. SpringBoot之DispatcherServlet详解及源码解析

    在使用SpringBoot之后,我们表面上已经无法直接看到DispatcherServlet的使用了.本篇文章,带大家从最初DispatcherServlet的使用开始到SpringBoot源码中Di ...

  8. SpringBoot的条件注解源码解析

    SpringBoot的条件注解源码解析 @ConditionalOnBean.@ConditionalOnMissingBean 启动项目 会在ConfigurationClassBeanDefini ...

  9. springboot源码解析-管中窥豹系列之Runner(三)

    一.前言 Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去. 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot ...

随机推荐

  1. Comparison of SIFT Encoded and Deep Learning Features for the Classification and Detection of Esca Disease in Bordeaux Vineyards(分类MobileNet,目标检测 RetinaNet)

    识别葡萄的一种虫害,比较了传统SIFT和深度学习分类,最后还做了目标检测 分类用的 MobileNet,目标检测 RetinaNet MobileNet 是将传统深度可分离卷积分成了两步,深度卷积和逐 ...

  2. iis启动 服务无法在此时接受控制信息。 (异常来自 HRESULT:0x80070425)

    问题描述:每隔一段时间应用程序池就会自动停止. 再次启动就报错:服务无法在此时接受控制信息. (异常来自 HRESULT:0x80070425) 处理办法:同时按下Win+R,运行“services. ...

  3. Hadoop运行原理总结(详细)

    本编随笔是小编个人参照个人的笔记.官方文档以及网上的资料等后对HDFS的概念以及运行原理进行系统性地归纳,说起来真的惭愧呀,自学了很长一段时间也没有对Hadoop知识点进行归纳,有时候在实战中或者与别 ...

  4. pdf 翻译

    某某狗 https://www.fanyigou.com/tslg/share/4DO875ON.htm

  5. 游标_oracle

    https://blog.csdn.net/weixin_41367660/article/details/80449032

  6. Python将print输出内容保存到指定文件中

    #!/usr/bin/python # -*- coding: utf- -*- import sys import os class Logger(object): def __init__(sel ...

  7. Redis 操作帮助类

    首先从Nuget中添加StackExchange.Redis包 1.Redis连接对象管理帮助类 using Mvc.Base; using Mvc.Base.Log; using StackExch ...

  8. Blob/DataURL/canvas/image的相互转换

    函数都比较简单,直接看就ok了 /*-----------------------------------------------------------------------*/ // canva ...

  9. Python爬虫笔记安装篇

    目录 爬虫三步 请求库 Requests:阻塞式请求库 Requests是什么 Requests安装 selenium:浏览器自动化测试 selenium安装 PhantomJS:隐藏浏览器窗口 Ph ...

  10. [LeetCode] 274. H-Index H指数

    Given an array of citations (each citation is a non-negative integer) of a researcher, write a funct ...