一、打包

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. PHP 之循环创建文件夹

    /** * 循环创建文件夹 * @param string $dir 需要创建的文件夹路径 * @param integer $mode 文件夹权限 * @return bool 返回创建是否成功 * ...

  2. 【CSP模拟赛】Freda的迷宫(桥)

    题目描述 Freda是一个迷宫爱好者,她利用业余时间建造了许多迷宫.每个迷宫都是由若干房间和走廊构成的,每条走廊都连接着两个不同的房间,两个房间之间最多只有一条走廊直接相连,走廊都是双向通过.  黄昏 ...

  3. OpenFOAM——具有压差且平行平板间具有相对运动流动

    本算例翻译整理自:http://the-foam-house5.webnode.es/products/chapter-1-plane-parallel-plates-case/ 这个算例中两平板间具 ...

  4. Kubernetes Pod概述

    Pod简介 Pod是Kubernetes创建或部署的最小/最简单的基本单位,一个Pod代表集群上正在运行的一个进程. 一个Pod封装一个应用容器,Pod代表部署的一个单位. Pods提供两种共享资源: ...

  5. CTF CMS(转)

    CTF--CMS漏洞总结 海洋CMS 6.28 海洋CMS6.28命令执行漏洞 6.45-6.54 漏洞预警 | 海洋CMS(SEACMS)0day漏洞预警 8.8(未验证) 海洋cms前台到后台的g ...

  6. tar加密码

    tar -zcvf - *** | openssl des3 -salt -k pass | dd of=.his dd if=.his | openssl des3 -d -k pass| tar ...

  7. mysql-connector-java(6.0以上)的时差问题

    一.背景 通过mybatis日志观察插入数据库的时间为当前时间,但是打开数据库表发现时间滞后了8个小时. 二.推论及解决 很容易猜到这是时区的问题. 三.最后找到的问题点如下: jdbc:mysql: ...

  8. Oracle系列八 高级子查询

    子查询 子查询 (内查询) 在主查询执行之前执行 主查询(外查询)使用子查询的结果 多列子查询 主查询与子查询返回的多个列进行比较 多列子查询中的比较分为两种: 成对比较 问题:查询与141号或174 ...

  9. EasyDSS高性能RTMP/FLV/HLS(m3u8)/RTSP流媒体服务器技术的HTTP QueryString URL的C++实现方案

    EasyDSS支持HTTP GET接口访问,我们需要获取url的各种参数信息 比如 http://ip:port/action?a=1&b=2&c=3 我们需要知道对应的a.b.c的值 ...

  10. smb文件共享

    一.服务端: #安装 yum install samba samba-common samba-client -y systemctl start smb ##开启samba服务 systemctl ...