创建 SpringBoot 项目的两种新方式
使用阿里云创建项目
我们在前文讲到过两种创建 Boot 的方式,但是他们都靠的是访问国外的 Spring 主站,但是互联网信息的访问是可以被约束的,如果一天这个网站你在国内无法访问了,那前面这两种方式就无法创建 SpringBoot 工程了,这时候又该怎么解决这个问题呢?
实现方法:
创建工程时,切换选择 starter 服务路径,然后手工输入阿里云地址即可,地址:http://start.aliyun.com
或 https://start.aliyun.com
阿里为了便于自己公司开发使用,特此在依赖坐标中添加了一些阿里自主的技术,也是为了推广自己的技术吧,所以在依赖选择列表中,你有了更多的选择。此外,阿里提供的地址更符合国内开发者的使用习惯,里面有一些 SpringBoot 官网上没有给出的坐标,大家可以好好看一看。
不过有一点需要说清楚,阿里云地址默认创建的 SpringBoot 工程版本是 2.4.1,所以如果你想更换其他的版本,创建项目后在 pom 文件中手工修改即可,别忘了刷新一下,加载新版本信息。
如果我们还是要创建一个 Web 项目,还是勾选 Web 中的选项:
注意:阿里云提供的工程创建地址初始化完毕后和使用 SpringBoot 官网创建出来的工程略有区别,主要是在配置文件的形式上有区别,这个信息在后面讲解 SpringBoot 程序的执行流程时给大家揭晓。
总结
-
选择 start 来源为自定义 URL
-
输入阿里云 starter 地址
-
创建项目
手工创建项目
我们前面介绍的方法都需要联网,或者说我们需要外网的支持。如果我们的环境不满足这些条件我们怎么去创建 SpringBoot 项目呢?
不能上网,还想创建 SpringBoot 工程,能不能做呢?能做,但是你要先问问自己联网和不联网到底差别是什么?这个差别找到以后,你就发现,你把联网要干的事情都提前准备好,就无需联网了。
联网做什么呢?首先 SpringBoot 工程也是基于 Maven 构建的,而 Maven 工程中如果加载一些工程需要使用又不存在的东西时,就要联网去下载。其实 SpringBoot 工程创建的时候就是要去下载一些必要的组件。如果把这些东西提前准备好呢?是的,就是这样。
下面就手工创建一个 SpringBoot 工程,如果需要使用的东西提前保障在 maven 仓库中存在,整个过程就可以不依赖联网环境了。
步骤 ①:创建工程时,选择创建普通 Maven 工程。
步骤 ②:参照标准 SpringBoot 工程的 pom 文件,书写自己的 pom 文件即可。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
</parent>
<groupId>com.itheima</groupId>
<artifactId>springboot_01_04_quickstart</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
用什么写什么,不用的都可以不写。此处我删减了一些目前不是必须的东西,一样能用。核心的内容有两条,一个是继承了一个父工程,另外添加了一个依赖。
步骤 ③:之前运行 SpringBoot 工程需要一个类,这个缺不了,自己手写一个就行了,建议按照之前的目录结构来创建,先别玩花样,先学走后学跑。类名可以自定义,关联的名称同步修改即可。
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
关注:类上面的注解@SpringBootApplication 千万别丢了,这个是核心。
关注:类名可以自定义,只要保障下面代码中使用的类名和你自己定义的名称一样即可,也就是 run 方法中的那个 class 对应的名称。
步骤 ④:下面就可以自己创建一个 Controller 测试一下是否能用了,和之前没有差别的。
看到这里其实应该能够想明白了,通过向导或者网站创建的 SpringBoot 工程其实就是帮你写了一些代码,而现在是自己手写,写的内容都一样,仅此而已。
温馨提示
如果你的计算机上从来没有创建成功过 SpringBoot 工程,自然也就没有下载过 SpringBoot 对应的坐标相关的资源,那用手写创建的方式在不联网的情况下肯定该是不能用的。所谓手写,其实就是自己写别人帮你生成的东西,但是引用的坐标对应的资源必须保障 maven 仓库里面有才行,如果没有,还是要去下载的。
总结
-
创建普通 Maven 工程
-
继承 spring-boot-starter-parent
-
添加依赖 spring-boot-starter-web
-
制作引导类 Application
到这里已经学习了 4 种创建 SpringBoot 工程的方式,其实本质是一样的,都是根据 SpringBoot 工程的文件格式要求,通过不同时方式生成或者手写得到对应的文件,效果完全一样。
SpringBoot 再探索
SpringBoot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化 Spring 应用的初始搭建以及开发过程。
都简化了了哪些东西呢?其实就是针对原始的 Spring 程序制作的两个方面进行了简化:
-
Spring 程序缺点
-
依赖设置繁琐
-
以前写 Spring 程序,使用的技术都要自己一个一个的写,现在不需要了,如果做过原始 SpringMVC 程序的小伙伴应该知道,写 SpringMVC 程序,最基础的 spring-web 和 spring-webmvc 这两个坐标是必须的,就这还不包含你用 JSON 啊等等这些坐标,现在呢?一个坐标搞定了。
-
-
配置繁琐
-
以前写配置类或者配置文件,然后用什么东西就要自己写加载 bean 这些东西,现在呢?什么都没写,照样能用。
-
-
回顾
通过上面两个方面的定位,我们可以产生两个模糊的概念:
1. SpringBoot 开发团队认为原始的 Spring 程序初始搭建的时候可能有些繁琐,这个过程是可以简化的,那原始的 Spring 程序初始搭建过程都包含哪些东西了呢?为什么觉得繁琐呢?最基本的 Spring 程序至少有一个配置文件或配置类,用来描述 Spring 的配置信息,莫非这个文件都可以不写?此外现在企业级开发使用 Spring 大部分情况下是做 Web 开发,如果做 Web 开发的话,还要在加载 Web 环境时加载时加载指定的 Spring 配置,这都是最基本的需求了,不写的话怎么知道加载哪个配置文件/配置类呢?那换了 SpringBoot 技术以后呢,这些还要写吗?
2. SpringBoot 开发团队认为原始的 Spring 程序开发的过程也有些繁琐,这个过程仍然可以简化。开发过程无外乎使用什么技术,导入对应的 jar 包(或坐标)然后将这个技术的核心对象交给 Spring 容器管理,也就是配置成 Spring 容器管控的 bean 就可以了。这都是基本操作啊,难道这些东西 SpringBoot 也能帮我们简化?
再来看看前面提出的两个问题,已经有答案了,都简化了,都不用写了,这就是 SpringBoot 给我们带来的好处。这些简化操作在 SpringBoot 中有专业的用语,也是 SpringBoot 程序的核心功能及优点:
-
起步依赖(简化依赖配置)
-
依赖配置的书写简化就是靠这个起步依赖达成的。
-
-
自动配置(简化常用工程相关配置)
-
配置过于繁琐,使用自动配置就可以做相应的简化,但是内部还是很复杂的,后面具体展开说。
-
-
辅助功能(内置服务器,……)
-
除了上面的功能,其实 SpringBoot 程序还有其他的一些优势,比如我们没有配置 Tomcat 服务器,但是能正常运行,这是 SpringBoot 入门程序中一个可以感知到的功能,也是 SpringBoot 的辅助功能之一。
-
下面结合入门程序来说说这些简化操作都在哪些方面进行体现的,一共分为 4 个方面
-
parent
-
starter
-
引导类
-
内嵌 Tomcat
parent
SpringBoot 关注到开发者在进行开发时,往往对依赖版本的选择具有固定的搭配格式,并且这些依赖版本的选择还不能乱搭配。比如 A 技术的 2.0 版,在与 B 技术进行配合使用时,与 B 技术的 3.5 版可以合作在一起工作,但是和 B 技术的 3.7 版合作开发使用时就有冲突。其实很多开发者都一直想做一件事情,就是将各种各样的技术配合使用的常见依赖版本进行收集整理,制作出了最合理的依赖版本配置方案,这样使用起来就方便多了。
SpringBoot 一看这种情况 so easy 啊,于是将所有的技术版本的常见使用方案都给开发者整理了出来,以后开发者使用时直接用它提供的版本方案,就不用担心冲突问题了,相当于 SpringBoot 做了无数个技术版本搭配的列表,这个技术搭配列表的名字叫做 parent。
parent 自身具有很多个版本,每个 parent 版本中包含有几百个其他技术的版本号,不同的 parent 间使用的各种技术的版本号有可能会发生变化。当开发者使用某些技术时,直接使用 SpringBoot 提供的 parent 就行了,由 parent 帮助开发者统一的进行各种技术的版本管理。
比如你现在要使用 Spring 配合 MyBatis 开发,没有 parent 之前怎么做呢?选个 Spring 的版本,再选个 MyBatis 的版本,再把这些技术使用时关联的其他技术的版本逐一确定下来。当你 Spring 的版本发生变化需要切换时,你的 MyBatis 版本有可能也要跟着切换,关联技术呢?可能都要切换,而且切换后还可能出现其他问题。现在这一切工作都可以交给 parent 来做了。你无需关注这些技术间的版本冲突问题,你只需要关注你用什么技术就行了,冲突问题由 parent 负责处理。
有人可能会提出来,万一 parent 给我导入了一些我不想使用的依赖怎么办?记清楚,这一点很关键,parent 仅仅帮我们进行版本管理,它不负责帮你导入坐标,说白了用什么还是你自己定,只不过版本不需要你管理了。整体上来说,使用 parent 可以帮助开发者进行版本的统一管理。
关注:parent 定义出来以后,并不是直接使用的,仅仅给了开发者一个说明书,但是并没有实际使用,这个一定要确认清楚。
那 SpringBoot 又是如何做到这一点的呢?可以查阅 SpringBoot 的配置源码,看到这些定义。
-
项目中的 pom.xml 中继承了一个坐标
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
</parent>
-
打开后可以查阅到其中又继承了一个坐标
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.5.4</version>
</parent>
-
这个坐标中定义了两组信息
第一组是各式各样的依赖版本号属性,下面列出依赖版本属性的局部,可以看的出来,定义了若干个技术的依赖版本号。
<properties>
<activemq.version>5.16.3</activemq.version>
<aspectj.version>1.9.7</aspectj.version>
<assertj.version>3.19.0</assertj.version>
<commons-codec.version>1.15</commons-codec.version>
<commons-dbcp2.version>2.8.0</commons-dbcp2.version>
<commons-lang3.version>3.12.0</commons-lang3.version>
<commons-pool.version>1.6</commons-pool.version>
<commons-pool2.version>2.9.0</commons-pool2.version>
<h2.version>1.4.200</h2.version>
<hibernate.version>5.4.32.Final</hibernate.version>
<hibernate-validator.version>6.2.0.Final</hibernate-validator.version>
<httpclient.version>4.5.13</httpclient.version>
<jackson-bom.version>2.12.4</jackson-bom.version>
<javax-jms.version>2.0.1</javax-jms.version>
<javax-json.version>1.1.4</javax-json.version>
<javax-websocket.version>1.1</javax-websocket.version>
<jetty-el.version>9.0.48</jetty-el.version>
<junit.version>4.13.2</junit.version>
</properties>
第二组是各式各样的依赖坐标信息,可以看出依赖坐标定义中没有具体的依赖版本号,而是引用了第一组信息中定义的依赖版本属性值.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
关注:上面的依赖坐标定义是出现在标签中的,是对引用坐标的依赖管理,并不是实际使用的坐标。因此当你的项目中继承了这组 parent 信息后,在不使用对应坐标的情况下,前面的这组定义是不会具体导入某个依赖的。
关注:因为在 maven 中继承机会只有一次,上述继承的格式还可以切换成导入的形式进行,并且在阿里云的 starter 创建工程时就使用了此种形式。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
总结
-
开发 SpringBoot 程序要继承 spring-boot-starter-parent
-
spring-boot-starter-parent 中定义了若干个依赖管理
-
继承 parent 模块可以避免多个依赖使用相同技术时出现依赖版本冲突
-
继承 parent 的形式也可以采用引入依赖的形式实现效果
思考
parent 中定义了若干个依赖版本管理,但是也没有使用,那这个设定也就不生效啊,究竟谁在使用这些定义呢?
starter
SpringBoot 关注到实际开发时,开发者对于依赖坐标的使用往往都有一些固定的组合方式,比如使用 spring-webmvc 就一定要使用 spring-web。每次都要固定搭配着写,非常繁琐,而且格式固定,没有任何技术含量。
SpringBoot 一看这种情况,看来需要给开发者带来一些帮助了。安排,把所有的技术使用的固定搭配格式都给开发出来,以后你用某个技术,就不用每次写一堆依赖了,还容易写错,我给你做一个东西,代表一堆东西,开发者使用的时候,直接用我做好的这个东西就好了,对于这样的固定技术搭配,SpringBoot 给它起了个名字叫做 starter。
starter 定义了使用某种技术时对于依赖的固定搭配格式,也是一种最佳解决方案,使用 starter 可以帮助开发者减少依赖配置。
这个东西其实在入门案例里面已经使用过了,入门案例中的 Web 功能就是使用这种方式添加依赖的。可以查阅 SpringBoot 的配置源码,看到这些定义。
-
项目中的 pom.xml 定义了使用 SpringMVC 技术,但是并没有写 SpringMVC 的坐标,而是添加了一个名字中包含 starter 的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
-
在 spring-boot-starter-web 中又定义了若干个具体依赖的坐标
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.5.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.5.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.5.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.9</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.9</version>
<scope>compile</scope>
</dependency>
</dependencies>
之前提到过开发 SpringMVC 程序需要导入 spring-webmvc 的坐标和 Spring 整合 Web 开发的坐标,就是上面这组坐标中的最后两个了。
但是我们发现除了这两个坐标,还有其他的坐标。比如第二个,叫做 spring-boot-starter-json。看名称就知道,这个是与 JSON 有关的坐标了,但是看名字发现和最后两个又不太一样,它的名字中也有 starter,打开看看里面有什么?
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.5.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.9</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
<version>2.12.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.12.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-parameter-names</artifactId>
<version>2.12.4</version>
<scope>compile</scope>
</dependency>
</dependencies>
我们可以发现,这个 starter 中又包含了若干个坐标,其实就是使用 SpringMVC 开发通常都会使用到 JSON,使用 JSON 又离不开这里面定义的这些坐标,看来还真是方便,SpringBoot 把我们开发中使用的东西能用到的都给提前做好了。你仔细看完会发现,里面有一些你没用过的。的确会出现这种过量导入的可能性,没关系,可以通过 maven 中的排除依赖剔除掉一部分。不过你不管它也没事,大不了就是过量导入呗。
到这里基本上得到了一个信息,使用 starter 可以帮开发者快速配置依赖关系。以前写依赖 3 个坐标的,现在写导入一个就搞定了,就是加速依赖配置的。
starter 与 parent 的区别
朦朦胧胧中感觉 starter 与 parent 好像都是帮助我们简化配置的,但是功能又不一样,梳理一下。
starter 是一个坐标中定了若干个坐标,以前写多个的,现在写一个,是用来减少依赖配置的书写量的。
parent 是定义了几百个依赖版本号,以前写依赖需要自己手工控制版本,现在由 SpringBoot 统一管理,这样就不存在版本冲突了,是用来减少依赖冲突的。
实际开发应用方式
-
实际开发中如果需要用什么技术,先去找有没有这个技术对应的 starter
-
如果有对应的 starter,直接写 starter,而且无需指定版本,版本由 parent 提供
-
如果没有对应的 starter,手写坐标即可
-
-
实际开发中如果发现坐标出现了冲突现象,确认你要使用的可行的版本号,使用手工书写的方式添加对应依赖,覆盖 SpringBoot 提供给我们的配置管理
-
方式一:直接写坐标
-
方式二:覆盖中定义的版本号,就是下面这堆东西了,哪个冲突了覆盖哪个就 OK 了
-
<properties>
<activemq.version>5.16.3</activemq.version>
<aspectj.version>1.9.7</aspectj.version>
<assertj.version>3.19.0</assertj.version>
<commons-codec.version>1.15</commons-codec.version>
<commons-dbcp2.version>2.8.0</commons-dbcp2.version>
<commons-lang3.version>3.12.0</commons-lang3.version>
<commons-pool.version>1.6</commons-pool.version>
<commons-pool2.version>2.9.0</commons-pool2.version>
<h2.version>1.4.200</h2.version>
<hibernate.version>5.4.32.Final</hibernate.version>
<hibernate-validator.version>6.2.0.Final</hibernate-validator.version>
<httpclient.version>4.5.13</httpclient.version>
<jackson-bom.version>2.12.4</jackson-bom.version>
<javax-jms.version>2.0.1</javax-jms.version>
<javax-json.version>1.1.4</javax-json.version>
<javax-websocket.version>1.1</javax-websocket.version>
<jetty-el.version>9.0.48</jetty-el.version>
<junit.version>4.13.2</junit.version>
</properties>
温馨提示
SpringBoot 官方给出了好多个 starter 的定义,方便我们使用,而且名称都是如下格式
命名规则:spring-boot-starter-技术名称
1
所以后期见了 spring-boot-starter-aaa 这样的名字,这就是 SpringBoot 官方给出的 starter 定义。那非官方定义的也有吗?有的,具体命名方式到整合技术的章节再说。
总结
-
开发 SpringBoot 程序需要导入坐标时通常导入对应的 starter
-
每个不同的 starter 根据功能不同,通常包含多个依赖坐标
-
使用 starter 可以实现快速配置的效果,达到简化配置的目的
引导类
配置说完了,我们发现 SpringBoot 确实帮助我们减少了很多配置工作,下面说一下程序是如何运行的。目前程序运行的入口就是 SpringBoot 工程创建时自带的那个类,也就是带有 main 方法的那个类,运行这个类就可以启动 SpringBoot 工程的运行。
@SpringBootApplication
public class Springboot0101QuickstartApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot0101QuickstartApplication.class, args);
}
}
SpringBoot 本身是为了加速 Spring 程序的开发的,而 Spring 程序运行的基础是需要创建 Spring 容器对象(IoC 容器)并将所有的对象放置到 Spring 容器中管理,也就是一个一个的 Bean。现在改用 SpringBoot 加速开发 Spring 程序,这个容器还在吗?这个疑问不用说,一定在。其实当前这个类运行后就会产生一个 Spring 容器对象,并且可以将这个对象保存起来,通过容器对象直接操作 Bean。
@SpringBootApplication
public class QuickstartApplication {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(QuickstartApplication.class, args);
BookController bean = ctx.getBean(BookController.class);
System.out.println("bean======>" + bean);
}
}
通过上述操作不难看出,其实 SpringBoot 程序启动还是创建了一个 Spring 容器对象。当前运行的这个类在 SpringBoot 程序中是所有功能的入口,称为引导类。
作为一个引导类最典型的特征就是当前类上方声明了一个注解@SpringBootApplication。
总结
-
SpringBoot 工程提供引导类用来启动程序
-
SpringBoot 工程启动后创建并初始化 Spring 容器
思考
程序现在已经运行了,通过引导类的 main 方法运行了起来。但是运行 Java 程序不应该是执行完就结束了吗?但是我们现在明显是启动了一个 Web 服务器啊,不然网页怎么能正常访问呢?这个服务器是在哪里写的呢?
内嵌 Tomcat
当前我们做的 SpringBoot 入门案例勾选了 Spring-web 的功能,并且导入了对应的 starter。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
SpringBoot 发现,既然你要做 Web 程序,肯定离不开使用 Web 服务器,这样吧,帮人帮到底,送佛送到西,我帮你搞一个 Web 服务器,你要愿意用的,直接使用就好了。SpringBoot 又琢磨,提供一种服务器万一不满足开发者需要呢?干脆我再多给你几种选择,你随便切换。万一你不想用我给你提供的,也行,你可以自己搞。
由于这个功能不属于程序的主体功能,可用可不用,于是乎 SpringBoot 将其定位成辅助功能,别小看这么一个辅助功能,它可是帮我们开发者又减少了好多的设置性工作。
下面就围绕着这个内置的 Web 服务器,也可以说是内置的 Tomcat 服务器来研究几个问题:
-
这个服务器在什么位置定义的
-
这个服务器是怎么运行的
-
这个服务器如果想换怎么换?虽然这个需求很垃圾,搞得开发者会好多 Web 服务器一样,用别人提供好的不香么?非要自己折腾
内嵌 Tomcat 定义位置
说到定义的位置,我们就想,如果我们不开发 Web 程序,用的着 Web 服务器吗?肯定用不着啊。那如果这个东西被加入到你的程序中,伴随着什么技术进来的呢?肯定是 Web 相关的功能啊,没错,就是前面导入的 Web 相关的 starter 做的这件事。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
打开 Web 对应的 starter 查看导入了哪些东西。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.5.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.5.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.5.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.9</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.9</version>
<scope>compile</scope>
</dependency>
</dependencies>
第三个依赖就是 Tomcat 对应的东西了,居然也是一个 starter,再打开看看。
<dependencies>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>1.3.5</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.52</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>tomcat-annotations-api</artifactId>
<groupId>org.apache.tomcat</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>9.0.52</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
<version>9.0.52</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>tomcat-annotations-api</artifactId>
<groupId>org.apache.tomcat</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
这里面有一个核心的坐标,tomcat-embed-core,叫做 Tomcat 内嵌核心。就是这个东西把 Tomcat 功能引入到了我们的程序中的。目前解决了第一个问题,找到根儿了,谁把 Tomcat 引入到程序中的?spring-boot-starter-web 中的 spring-boot-starter-tomcat 做的。之所以你感觉很奇妙的原因就是,这个东西是默认加入到程序中了,所以感觉很神奇,居然什么都不做,就有了 Web 服务器对应的功能。再来说第二个问题,这个服务器是怎么运行的。
内嵌 Tomcat 运行原理
Tomcat 服务器是一款软件,而且是一款使用 Java 语言开发的软件,熟悉 Tomcat 的话应该知道 Tomcat 安装目录中保存有很多 jar 文件。
下面的问题来了,既然是使用 Java 语言开发的,运行的时候肯定符合 Java 程序运行的原理,Java 程序运行靠的是什么?对象呀,一切皆对象,万物皆对象。那 Tomcat 运行起来呢?也是对象啊。
如果是对象,那 Spring 容器是用来管理对象的,这个对象能交给 Spring 容器管理吗?把吗去掉,是个对象都可以交给 Spring 容器管理,行了,这下通了,Tomcat 服务器运行其实是以对象的形式在 Spring 容器中运行的。怪不得我们没有安装这个 Tomcat 但是还能用,闹了白天这东西最后是以一个对象的形式存在,保存在 Spring 容器中悄悄运行的。具体运行的是什么呢?其实就是上前面提到的那个 Tomcat 内嵌核心。
<dependencies>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.52</version>
<scope>compile</scope>
</dependency>
</dependencies>
那既然是个对象,如果把这个对象从 Spring 容器中去掉是不是就没有 Web 服务器的功能呢?是这样的,通过依赖排除可以去掉这个 Web 服务器功能。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
上面对 web-starter 做了一个操作,使用 maven 的排除依赖去掉了使用 Tomcat 的 starter。这下好了,容器中肯定没有这个对象了,重新启动程序可以观察到程序运行了,但是并没有像之前那样运行后是一个一直运行的服务,而是直接停掉了,就是这个原因。
更换内嵌 Tomcat
那根据上面的操作我们思考是否可以换个服务器呢?必须的嘛。根据 SpringBoot 的工作机制,用什么技术,加入什么依赖就行了。SpringBoot 提供了 3 款内置的服务器:
-
tomcat(默认):apache 出品,粉丝多,应用面广,负载了若干较重的组件
-
Jetty:更轻量级,负载性能远不及 Tomcat
-
undertow:负载性能勉强跑赢 Tomcat想用哪个,加个坐标就 OK。前提是把 Tomcat 排除掉,因为 Tomcat 是默认加载的。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
</dependencies>
现在就已经成功替换了 Web 服务器,核心思想就是用什么加入对应坐标就可以了。如果有 starter,优先使用 starter。
总结
-
内嵌 Tomcat 服务器是 SpringBoot 辅助功能之一
-
内嵌 Tomcat 工作原理是将 Tomcat 服务器作为对象运行,并将该对象交给 Spring 容器管理
-
变更内嵌服务器思想是去除现有服务器,添加全新的服务器