返回顶部
首页 > 资讯 > 精选 >SpringBoot是怎么跑起来的
  • 136
分享到

SpringBoot是怎么跑起来的

2023-06-04 08:06:10 136人浏览 独家记忆
摘要

本篇内容主要讲解“SpringBoot是怎么跑起来的”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“springBoot是怎么跑起来的”吧!Hello World首先我们看看 SpringBoot

本篇内容主要讲解“SpringBoot是怎么跑起来的”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习springBoot是怎么跑起来的”吧!

Hello World

首先我们看看 SpringBoot 简单的 Hello World 代码,就两个文件 HelloControll.java 和 Application.java,运行 Application.java 就可以跑起来一个简单的 RESTFul WEB 服务器了。

SpringBoot是怎么跑起来的
SpringBoot是怎么跑起来的
// HelloController.java
package hello;

import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;

@RestController
public class HelloController {

    @RequestMapping("/")
    public String index() {
        return "Greetings from Spring Boot!";
    }

}

// Application.java
package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}


当我打开浏览器看到服务器正常地将输出呈现在浏览器的时候,我不禁大呼 —— SpringBoot 真他妈太简单了。

SpringBoot是怎么跑起来的

但是问题来了,在 Application 的 main 方法里我压根没有任何地方引用 HelloController 类,那么它的代码又是如何被服务器调用起来的呢?这就需要深入到 SpringApplication.run() 方法中看个究竟了。不过即使不看代码,我们也很容易有这样的猜想,SpringBoot 肯定是在某个地方扫描了当前的 package,将带有 RestController 注解的类作为 mvc 层的 Controller 自动注册进了 Tomcat Server。

还有一个让人不爽的地方是 SpringBoot 启动太慢了,一个简单的 Hello World 启动居然还需要长达 5 秒,要是再复杂一些的项目这样龟漫的启动速度那真是不好想象了。

再抱怨一下,这个简单的 HelloWorld 虽然 pom 里只配置了一个 Maven 依赖,但是传递下去,它一共依赖了 36 个 jar 包,其中以 spring 开头的 jar 包有 15 个。说这是依赖地狱真一点不为过。

SpringBoot是怎么跑起来的

批评到这里就差不多了,下面就要正是进入主题了,看看 SpringBoot 的 main 方法到底是如何跑起来的。

SpringBoot 的堆栈

了解 SpringBoot 运行的最简单的方法就是看它的调用堆栈,下面这个启动调用堆栈还不是太深,我没什么可抱怨的。

SpringBoot是怎么跑起来的
public class TomcatServer {

  @Override
  public void start() throws WebServerException {
  ...
  }

}


接下来再看看运行时堆栈,看看一个 Http 请求的调用栈有多深。不看不知道一看吓了一大跳!

SpringBoot是怎么跑起来的


我通过将 IDE 窗口全屏化,并将其它的控制台窗口源码窗口统统最小化,总算勉强一个屏幕装下了整个调用堆栈。

不过转念一想,这也不怪 SpringBoot,绝大多数都是 Tomcat 的调用堆栈,跟 SpringBoot 相关的只有不到 10 层。

探索 ClassLoader

SpringBoot 还有一个特色的地方在于打包时它使用了 FatJar 技术将所有的依赖 jar 包一起放进了最终的 jar 包中的 BOOT-INF/lib 目录中,当前项目的 class 被统一放到了 BOOT-INF/classes 目录中。

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


这不同于我们平时经常使用的 maven shade 插件,将所有的依赖 jar 包中的 class 文件解包出来后再密密麻麻的塞进统一的 jar 包中。下面我们将 springboot 打包的 jar 包解压出来看看它的目录结构。

├── BOOT-INF
│   ├── classes
│   │   └── hello
│   └── lib
│       ├── claSSMate-1.3.4.jar
│       ├── hibernate-validator-6.0.12.Final.jar
│       ├── jackson-annotations-2.9.0.jar
│       ├── jackson-core-2.9.6.jar
│       ├── jackson-databind-2.9.6.jar
│       ├── jackson-datatype-jdk8-2.9.6.jar
│       ├── jackson-datatype-jsr310-2.9.6.jar
│       ├── jackson-module-parameter-names-2.9.6.jar
│       ├── javax.annotation-api-1.3.2.jar
│       ├── jboss-logging-3.3.2.Final.jar
│       ├── jul-to-slf4j-1.7.25.jar
│       ├── log4j-api-2.10.0.jar
│       ├── log4j-to-slf4j-2.10.0.jar
│       ├── logback-classic-1.2.3.jar
│       ├── logback-core-1.2.3.jar
│       ├── slf4j-api-1.7.25.jar
│       ├── snakeyaml-1.19.jar
│       ├── spring-aop-5.0.9.RELEASE.jar
│       ├── spring-beans-5.0.9.RELEASE.jar
│       ├── spring-boot-2.0.5.RELEASE.jar
│       ├── spring-boot-autoconfigure-2.0.5.RELEASE.jar
│       ├── spring-boot-starter-2.0.5.RELEASE.jar
│       ├── spring-boot-starter-JSON-2.0.5.RELEASE.jar
│       ├── spring-boot-starter-logging-2.0.5.RELEASE.jar
│       ├── spring-boot-starter-tomcat-2.0.5.RELEASE.jar
│       ├── spring-boot-starter-web-2.0.5.RELEASE.jar
│       ├── spring-context-5.0.9.RELEASE.jar
│       ├── spring-core-5.0.9.RELEASE.jar
│       ├── spring-expression-5.0.9.RELEASE.jar
│       ├── spring-jcl-5.0.9.RELEASE.jar
│       ├── spring-web-5.0.9.RELEASE.jar
│       ├── spring-webmvc-5.0.9.RELEASE.jar
│       ├── tomcat-embed-core-8.5.34.jar
│       ├── tomcat-embed-el-8.5.34.jar
│       ├── tomcat-embed-websocket-8.5.34.jar
│       └── validation-api-2.0.1.Final.jar
├── META-INF
│   ├── MANIFEST.MF
│   └── maven
│       └── org.springframework
└── org
    └── springframework
        └── boot


这种打包方式的优势在于最终的 jar 包结构很清晰,所有的依赖一目了然。如果使用 maven shade 会将所有的 class 文件混乱堆积在一起,是无法看清其中的依赖。而最终生成的 jar 包在体积上两也者几乎是相等的。

在运行机制上,使用 FatJar 技术运行程序是需要对 jar 包进行改造的,它还需要自定义自己的 ClassLoader 来加载 jar 包里面 lib 目录中嵌套的 jar 包中的类。我们可以对比一下两者的 MANIFEST 文件就可以看出明显差异

// Generated by Maven Shade Plugin
Manifest-Version: 1.0
Implementation-Title: gs-spring-boot
Implementation-Version: 0.1.0
Built-By: qianwp
Implementation-Vendor-Id: org.springframework
Created-By: Apache Maven 3.5.4
Build-Jdk: 1.8.0_191
Implementation-URL: https://projects.spring.io/spring-boot/#/spring-bo
 ot-starter-parent/gs-spring-boot
Main-Class: hello.Application

// Generated by SpringBootLoader Plugin
Manifest-Version: 1.0
Implementation-Title: gs-spring-boot
Implementation-Version: 0.1.0
Built-By: qianwp
Implementation-Vendor-Id: org.springframework
Spring-Boot-Version: 2.0.5.RELEASE
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: hello.Application
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Created-By: Apache Maven 3.5.4
Build-Jdk: 1.8.0_191
Implementation-URL: https://projects.spring.io/spring-boot/#/spring-bo
 ot-starter-parent/gs-spring-boot


SpringBoot 将 jar 包中的 Main-Class 进行了替换,换成了 JarLauncher。还增加了一个 Start-Class 参数,这个参数对应的类才是真正的业务 main 方法入口。我们再看看这个 JarLaucher 具体干了什么

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

  protected void launch(String[] args) {
    try {
      JarFile.reGISterUrlProtocolHandler();
      ClassLoader cl = createClassLoader(getClassPathArcHives());
      launch(args, getMainClass(), cl);
    }
    catch (Exception ex) {
        ex.printStackTrace();
        System.exit(1);
    }
  }

  protected void launch(String[] args, String mcls, ClassLoader cl) {
        Runnable runner = createMainMethodRunner(mcls, args, cl);
        Thread runnerThread = new Thread(runner);
        runnerThread.setContextClassLoader(classLoader);
        runnerThread.setName(Thread.currentThread().getName());
        runnerThread.start();
  }

}

class MainMethodRunner {
  @Override
  public void run() {
    try {
      Thread th = Thread.currentThread();
      ClassLoader cl = th.getContextClassLoader();
      Class<?> mc = cl.loadClass(this.mainClassName);
      Method mm = mc.getDeclaredMethod("main", String[].class);
      if (mm == null) {
        throw new IllegalStateException(this.mainClassName
                        + " does not have a main method");
      }
      mm.invoke(null, new Object[] { this.args });
    } catch (Exception ex) {
      ex.printStackTrace();
      System.exit(1);
    }
  }
}


从源码中可以看出 JarLaucher 创建了一个特殊的 ClassLoader,然后由这个 ClassLoader 来另启一个单独的线程来加载 MainClass 并运行。

又一个问题来了,当 JVM 遇到一个不认识的类,BOOT-INF/lib 目录里又有那么多 jar 包,它是如何知道去哪个 jar 包里加载呢?我们继续看这个特别的 ClassLoader 的源码

class LaunchedURLClassLoader extends URLClassLoader {
  ...
  private Class<?> doLoadClass(String name) {
    if (this.rootClassLoader != null) {
      return this.rootClassLoader.loadClass(name);
    }

    findPackage(name);
    Class<?> cls = findClass(name);
    return cls;
  }

}


这里的 rootClassLoader 就是双亲委派模型里的 ExtensionClassLoader ,JVM 内置的类会优先使用它来加载。如果不是内置的就去查找这个类对应的 Package。

private void findPackage(final String name) {
    int lastDot = name.lastIndexOf('.');
    if (lastDot != -1) {
        String packageName = name.substring(0, lastDot);
        if (getPackage(packageName) == null) {
            try {
                definePackage(name, packageName);
            } catch (Exception ex) {
                // Swallow and continue
            }
        }
    }
}

private final HashMap<String, Package> packages = new HashMap<>();

protected Package getPackage(String name) {
    Package pkg;
    synchronized (packages) {
        pkg = packages.get(name);
    }
    if (pkg == null) {
        if (parent != null) {
            pkg = parent.getPackage(name);
        } else {
            pkg = Package.getSystemPackage(name);
        }
        if (pkg != null) {
            synchronized (packages) {
                Package pkg2 = packages.get(name);
                if (pkg2 == null) {
                    packages.put(name, pkg);
                } else {
                    pkg = pkg2;
                }
            }
        }
    }
    return pkg;
}

private void definePackage(String name, String packageName) {
  String path = name.replace('.', '/').concat(".class");
  for (URL url : getURLs()) {
    try {
      if (url.getContent() instanceof JarFile) {
        JarFile jf= (JarFile) url.getContent();
        if (jf.getJarEntryData(path) != null && jf.getManifest() != null) {
          definePackage(packageName, jf.getManifest(), url);
          return null;
        }
      }
    } catch (IOException ex) {
        // Ignore
    }
  }
  return null;
}


ClassLoader 会在本地缓存包名和 jar包路径的映射关系,如果缓存中找不到对应的包名,就必须去 jar 包中挨个遍历搜寻,这个就比较缓慢了。不过同一个包名只会搜寻一次,下一次就可以直接从缓存中得到对应的内嵌 jar 包路径。

深层 jar 包的内嵌 class 的 URL 路径长下面这样,使用感叹号 ! 分割

jar:file:/workspace/springboot-demo/target/application.jar!/BOOT-INF/lib/snakeyaml-1.19.jar!/org/yaml/snakeyaml/Yaml.class


不过这个定制的 ClassLoader 只会用于打包运行时,在 IDE 开发环境中 main 方法还是直接使用系统类加载器加载运行的。

不得不说,SpringbootLoader 的设计还是很有意思的,它本身很轻量级,代码逻辑很独立没有其它依赖,它也是 SpringBoot 值得欣赏的点之一。

HelloController 自动注册

还剩下最后一个问题,那就是 HelloController 没有被代码引用,它是如何注册到 Tomcat 服务中去的?它靠的是注解传递机制。

SpringBoot 深度依赖注解来完成配置的自动装配工作,它自己发明了几十个注解,确实严重增加了开发者的心智负担,你需要仔细阅读文档才能知道它是用来干嘛的。Java 注解的形式和功能是分离的,它不同于 python 的装饰器是功能性的,Java 的注解就好比代码注释,本身只有属性,没有逻辑,注解相应的功能由散落在其它地方的代码来完成,需要分析被注解的类结构才可以得到相应注解的属性。

那注解是又是如何传递的呢?

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

@ComponentScan
public @interface SpringBootApplication {
...
}

public @interface ComponentScan {
    String[] basePackages() default {};
}

首先 main 方法可以看到的注解是 SpringBootApplication,这个注解又是由ComponentScan 注解来定义的,ComponentScan 注解会定义一个被扫描的包名称,如果没有显示定义那就是当前的包路径。SpringBoot 在遇到 ComponentScan 注解时会扫描对应包路径下面的所有 Class,根据这些 Class 上标注的其它注解继续进行后续处理。当它扫到 HelloController 类时发现它标注了 RestController 注解。

@RestController
public class HelloController {
...
}

@Controller
public @interface RestController {
}


而 RestController 注解又标注了 Controller 注解。SpringBoot 对 Controller 注解进行了特殊处理,它会将 Controller 注解的类当成 URL 处理器注册到 Servlet 的请求处理器中,在创建 Tomcat Server 时,会将请求处理器传递进去。HelloController 就是如此被自动装配进 Tomcat 的。

扫描处理注解是一个非常繁琐肮脏的活计,特别是这种用注解来注解注解(绕口)的高级使用方法,这种方法要少用慎用。SpringBoot 中有大量的注解相关代码,企图理解这些代码是乏味无趣的没有必要的,它只会把你的本来清醒的脑袋搞晕。SpringBoot 对于习惯使用的同学来说它是非常方便的,但是其内部实现代码不要轻易模仿,那绝对算不上模范 Java 代码。

到此,相信大家对“SpringBoot是怎么跑起来的”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

--结束END--

本文标题: SpringBoot是怎么跑起来的

本文链接: https://lsjlt.com/news/237078.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • SpringBoot是怎么跑起来的
    本篇内容主要讲解“SpringBoot是怎么跑起来的”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“SpringBoot是怎么跑起来的”吧!Hello World首先我们看看 SpringBoot...
    99+
    2023-06-04
  • k8s怎么跑起一个wordpress
    本篇内容介绍了“k8s怎么跑起一个wordpress”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!k8s 部署wordpress 官方例子方...
    99+
    2023-06-21
  • hadoop的node manager起不来怎么办
    本篇内容介绍了“hadoop的node manager起不来怎么办”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!现象: node manag...
    99+
    2023-06-03
  • 一起来看看springboot集成redis的使用注解
    目录redis简介:引入依赖:编写application.properties文件编写配置类:springboot的redis注解介绍(1)缓存@Cacheable(2)缓存@Cac...
    99+
    2024-04-02
  • tomcat启动不起来是什么原因
    本篇内容主要讲解“tomcat启动不起来是什么原因”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“tomcat启动不起来是什么原因”吧!目录现象: 端口占用:文件拼写错误:现象:  tomcat安...
    99+
    2023-06-20
  • HTML5是怎么起步的
    这篇文章主要讲解了“HTML5是怎么起步的”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“HTML5是怎么起步的”吧!   HTML5 是如何起步的?  ...
    99+
    2024-04-02
  • Android 11 来袭,一起来看看怎么适配
    我的公众号程序员徐公,四年中大厂工作经验,回复黑马,领取 Android 学习视频一份,回复徐公666,可以获得我精心整理的简历模板,带你走近大厂。 作者:唯鹿 来源:https://jue...
    99+
    2023-10-25
    android
  • vue项目是如何运行起来的
    目录vue项目如何运行涉及到的项目文件index.html,main.js,App.vue 三者关系小结vue踩坑记录:vue项目运行卡在百分之几几几vue项目如何运行 涉及到的项目...
    99+
    2024-04-02
  • 电脑启动不起来怎么办
    如果电脑无法启动,可以尝试以下几个步骤来解决问题:1. 检查电源连接:确保电脑的电源线插头和插座连接正常,电源开关处于打开状态。2....
    99+
    2023-09-07
    电脑
  • 怎么把sql查询的结果拼接起来
    本篇内容介绍了“怎么把sql查询的结果拼接起来”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成! ...
    99+
    2024-04-02
  • 华为云服务器怎么连接起来的
    华为云服务器可以通过以下方法连接: 在华为云服务器的控制面板中,选择管理服务器,进入管理页面后,点击“网络”。 在网络属性中,找到“网络连接”,并点击“本地连接”。 在本地连接页面中,点击右下角的“设置新的连接或网络”。 然后在新建网络...
    99+
    2023-10-26
    华为 服务器
  • DDoS攻击是怎么利用分布式网络来发起攻击的
    DDoS(分布式拒绝服务)攻击利用多个分布在不同地区的计算机或网络设备向目标服务器或网络发送大量的请求,使其无法正常响应其他合法用户...
    99+
    2024-04-15
    DDoS攻击
  • Java是怎么来的
    小编给大家分享一下Java是怎么来的,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!Java的由来对于计算机语言的发展史,业界一般认为:B语言导致了C语言的诞生,C语言演变出了C++语言,而C++语言将让位于Java语言。要...
    99+
    2023-06-03
  • 华为云服务器怎么连接起来的呢
    华为云服务器可以通过以下方法连接: 在华为云服务器的控制面板中,选择【云服务器管理】选项。 选择“云服务器登录”,输入服务器主机名和密码,点击登录。 如果您已经使用华为云服务器,请按照提示进行操作,否则可能需要进行身份验证。 如果是第一...
    99+
    2023-10-26
    华为 服务器
  • 怎么用Python让图片人物动起来
    本篇内容主要讲解“怎么用Python让图片人物动起来”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么用Python让图片人物动起来”吧!其中通过在静止图像中动画对象产生视频有无数的应用跨越的领...
    99+
    2023-06-15
  • 怎么让Ubuntu16.04用起来更得心应手
    本篇内容介绍了“怎么让Ubuntu16.04用起来更得心应手”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!导读要想Ubuntu 16.04用...
    99+
    2023-06-28
  • 华为云服务器怎么连接起来
    华为云服务器可以通过以下方法连接到您的华为云服务器: 从华为云官方网站(https://www.huaweicloud.com/)进行连接。 使用华为云服务器管理平台(https://cloudservermanager.cn/clou...
    99+
    2023-10-26
    华为 服务器
  • weblogic管理节点起不来怎么解决
    如果WebLogic管理节点无法启动,可以尝试以下解决方法: 检查日志:查看WebLogic管理节点的日志文件,通常在域目录下的...
    99+
    2023-10-26
    weblogic
  • java多行字符串怎么连接起来
    在Java中,可以使用加号(+)运算符将多行字符串连接起来。例如: String str1 = "Hello, "...
    99+
    2024-04-02
  • 电脑光标老是乱跑怎么办
    小编给大家分享一下电脑光标老是乱跑怎么办,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!解决方法/步骤:右键选中开始菜单,随后在弹出的隐藏菜单选项中选择计算机管理进...
    99+
    2023-06-27
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作