springboot项目启动过程

目录

  • springboot项目启动方式
  • 创建springboot可执行jar应用
  • 可执行jar目录结构
  • springboot可执行jar应用启动过程

说起springboot项目的启动大家也许都知道是通过启动类的main方法来启动的,但是启动类的main方法是由谁调用的呢?不要和我说IDE中运行main方法这么low的大家都知道的方式,我说的启动都是服务器上实际生产环境中的启动。这个问题或许大部分人都不知道了吧。

springboot项目的启动方式

首先我们来说说springboot项目启动的方式,springboot项目启动方式大体分为:使用IDE(idea,eclipse,sts)启动;mvn方式启动;jar包或war通过java -jar命令启动。下面说说这三种方式的使用场景

  • 使用IDE启动

通常开发调试过程中我们都是通过集成开发工具eclipse或者IDEA直接执行启动类启动的。

  • 通过maven命令启动

通过maven命令启动,首先进入项目目录,然后通过命令mvn spring-boot:run 启动。因为需要依赖于maven插件并且依托于项目。所以也不适合使用于生产环境。

  • 通过java -jar命令启动

正常的生产环境我们启动springboot项目都是通过java -jar命令来启动项目的,但是不是单纯的将项目打成jar或war包然后直接通过命令java -jar + 应用jar或war包来启动,这种命令窗口下,只要执行Ctrl +c命令项目就停止了,一般我们要求项目以后台进程方式启动。示例如下:

D:\workspace\springboot\target>java -jar springboot.jar

创建springboot可执行jar应用

通过idea创建springboot可执行jar应用,需要一个关键maven插件,就是spring-boot-maven-plugin这个依赖,如果不添加这个依赖,我们就会遇到“没有主清单属性”这个坑,坑详细信息如下:

D:\workspace\springboot\target>java -jar springboot.jar
springboot.jar中没有主清单属性
  • 插件依赖GA如下:

    springboot


    org.springframework.boot
    spring-boot-maven-plugin


  • jar包解压后目录结构如下:

    ├─com
    │ └─jianzhang
    │ └─demo
    └─META-INF
    └─maven
    └─com.jianzhang.demo
    └─springboot

  • MANIFEST.MF文件内容如下(待会和下面章节的可执行jar文件内容比较):
    Manifest-Version: 1.0
    Implementation-Title: springboot
    Implementation-Version: 0.0.1
    Build-Jdk-Spec: 1.8
    Created-By: Maven Archiver 3.4.0

可执行jar目录结构

  • 目录说明

可执行jar目录结构从目录上看分为BOOT-INF、META-INF、org三个目录,详细说明见下表:

目录名称目录内容目录作用
BOOT-INF存放当前应用类字节码文件及应用依赖jar
META-INF存放应用配置文件及MANIFEST.MF文件
org存放应用加载类对应的字节码:org.springframework.boot.loader包下字节码文件
  • MANIFEST.MF文件内容

备注
Manifest-Version1.0文件版本
Implementation-Titlespringboot扩展应用名称
Implementation-Version0.0.1扩展应用版本
Start-Classcom.jianzhang.demo.MySpringbootApplication应用启动类
Spring-Boot-ClassesBOOT-INF/classes/应用编译后的class文件存放目录
Spring-Boot-LibBOOT-INF/lib/依赖jar包目录
Build-Jdk-Spec1.8构建jdk版本
Spring-Boot-Version2.1.5.RELEASEspringboot版本
Created-By:Maven Archiver 3.4.0创建maven版本
Main-Classorg.springframework.boot.loader.JarLauncher入口类

springboot可执行jar应用启动过程

启动命令

应用的启动方式是:java -jar springboot.jar 文件,命令说明如下:

然后从上一个章节我们可以看出springboot可执行jar应用的启动入口类为org.springframework.boot.loader.JarLauncher的main方法,同样改类位于spingboot的loader包中,在springboot项目中为隐藏依赖。依赖GA如下:

org.springframework.boot
spring-boot-loader

启动过程

  • java -jar springboot.jar 启动时从MANIFEST.MF中获取到Main-Class应用启动类JarLauncher;
  • JarLauncher类main方法只是个入口,中通过调用父类ExecutableArchiveLauncher的父类Launcher的launch方法来启动;
  • ExecutableArchiveLauncher类主要读取应用jar包中MANIFEST.MF文件中配置的相关信息,并封装成JarFileArchive对象;
  • Launcher类的launcher方法,获取到MANIFEST.MF配置的启动类信息,设置协议处理,获取类加载器。然后调用应用启动类包装类MainMethodRunner的run方法;
  • MainMethodRunner的run方法中通过类加载器及应用启动类的全限定名获取启动类的Class对象,然后通过反射获取应用启动类的静态main方法;(全限定名有绝对路径的意思,比如一个文件file的存放路径,其绝对路径可能是/usr/local/sbin/file;这个名词也用在其他地方,比如Java类包的定名:com.linux.struct.sort.bubblesort,从最原始最上层的地方援引到具体的对象,这就是全限定名了。)
  • 通过反射执行静态方法,开始springboot项目的启动。

关键类源码

  • JarLauncher源码
package org.springframework.boot.loader;

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

public class JarLauncher extends ExecutableArchiveLauncher {
    //应用编译后class文件目录
    static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";
    //应用依赖jar包目录
    static final String BOOT_INF_LIB = "BOOT-INF/lib/";

    public JarLauncher() {
    }

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

    protected boolean isNestedArchive(Entry entry) {
       //return entry.isDirectory() ? entry.getName().equals("BOOT-INF/classes/") : entry.getName().startsWith("BOOT-INF/lib/");//改写如下
        //判断是否没内嵌架构
        //是目录的话,先判断是否和BOOT_INF_CLASSES(资源文件目录)相同
        //否则判断是否以BOOT_INF_LIB(依赖jar包)开头
        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 {
        //调用父类ExecutableArchiveLauncher的父类Launcher的launcher方法
        (new JarLauncher()).launch(args);
    }
}

  • ExecutableArchiveLauncher源码
package org.springframework.boot.loader;

import java.util.ArrayList;
import java.util.List;
import java.util.jar.Manifest;
import org.springframework.boot.loader.archive.Archive;
import org.springframework.boot.loader.archive.Archive.Entry;

public abstract class ExecutableArchiveLauncher extends Launcher {
    private final Archive archive;

    public ExecutableArchiveLauncher() {
        try {
            //调用父类Launcher的createArchive方法获得JarFile的封装类JarFileArchive其中包含manifest信息
            this.archive = this.createArchive();
        } catch (Exception var2) {
            throw new IllegalStateException(var2);
        }
    }

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

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

    protected String getMainClass() throws Exception {
        //读取MANIFEST.MF文件获取我们的应用启动类MANIFEST.MF中的Start-Class
        Manifest manifest = this.archive.getManifest();
        String mainClass = null;
        if (manifest != null) {
            mainClass = manifest.getMainAttributes().getValue("Start-Class");
        }

        if (mainClass == null) {
            throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
        } else {
            return mainClass;
        }
    }

    protected List<Archive> getClassPathArchives() throws Exception {
        //调用子类JarLauncher的实现isNestedArchive进行判断
        List<Archive> archives = new ArrayList(this.archive.getNestedArchives(this::isNestedArchive));
        this.postProcessClassPathArchives(archives);
        return archives;
    }

    protected abstract boolean isNestedArchive(Entry entry);

    protected void postProcessClassPathArchives(List<Archive> archives) throws Exception {
    }
}

  • Launcher类源码
package org.springframework.boot.loader;

import java.io.File;
import java.net.URI;
import java.net.URL;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.springframework.boot.loader.archive.Archive;
import org.springframework.boot.loader.archive.ExplodedArchive;
import org.springframework.boot.loader.archive.JarFileArchive;
import org.springframework.boot.loader.jar.JarFile;

public abstract class Launcher {
    public Launcher() {
    }

    protected void launch(String[] args) throws Exception {
        //注册协议处理器
        JarFile.registerUrlProtocolHandler();
        //类加载
        ClassLoader classLoader = this.createClassLoader(this.getClassPathArchives());
        //转到launch(args, this.getMainClass(), classLoader)方法
        //调用子类ExecutableArchiveLauncher的实现getMainClass()获取MANIFEST.MF文件中配置的Start-Class属性,其实就是我们springboot应用的启动类
        this.launch(args, this.getMainClass(), classLoader);
    }
    protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
        List<URL> urls = new ArrayList(archives.size());
        Iterator var3 = archives.iterator();

        while(var3.hasNext()) {
            Archive archive = (Archive)var3.next();
            urls.add(archive.getUrl());
        }

        return this.createClassLoader((URL[])urls.toArray(new URL[0]));
    }

    protected ClassLoader createClassLoader(URL[] urls) throws Exception {
        return new LaunchedURLClassLoader(urls, this.getClass().getClassLoader());
    }

    protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {
        //设置线程上下文类加载器
        Thread.currentThread().setContextClassLoader(classLoader);
        //根据mainClass此处的mainClass,就是MANIFEST.MF文件中配置的Start-Class配置的应用启动类
        //转到createMainMethodRunner方法获得MainMethodRunner(包装类),然后调用run方法
        this.createMainMethodRunner(mainClass, args, classLoader).run();
    }

    protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {
        
        return new MainMethodRunner(mainClass, args);
    }

    protected abstract String getMainClass() throws Exception;

    protected abstract List<Archive> getClassPathArchives() throws Exception;

    protected final Archive createArchive() throws Exception {
        //读取META-INF目录下的MANIFEST.MF文件,并将元信息封装到JarFileArchive类中
        ProtectionDomain protectionDomain = this.getClass().getProtectionDomain();
        CodeSource codeSource = protectionDomain.getCodeSource();
        URI location = codeSource != null ? codeSource.getLocation().toURI() : null;
        String path = location != null ? location.getSchemeSpecificPart() : null;
        if (path == null) {
            throw new IllegalStateException("Unable to determine code source archive");
        } else {
            File root = new File(path);
            if (!root.exists()) {
                throw new IllegalStateException("Unable to determine code source archive from " + root);
            } else {
                return (Archive)(root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));
            }
        }
    }
}

  • MainMethodRunner类源码

package org.springframework.boot.loader;

import java.lang.reflect.Method;

public class MainMethodRunner {
    private final String mainClassName;
    private final String[] args;

    public MainMethodRunner(String mainClass, String[] args) {
        //参数初始化
        this.mainClassName = mainClass;
        //参数克隆
        this.args = args != null ? (String[])args.clone() : null;
    }

    public void run() throws Exception {
        //1、通过类加载器及要加载的类的全限定名
        Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
        //2、通过反射获取启动类的静态方法
        Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
        //3、反射调用启动类的静态方法,开始应用启动过程
        mainMethod.invoke((Object)null, this.args);
    }
}

备注:以上是springboot应用启动的过程,后期文章将从启动类的main方法继续展开详细介绍。

如果你喜欢欢迎加我QQ:398354135或者关注我的公众号

在这里插入图片描述

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值