天天看點

[SpringBoot系列]基礎過渡與夯實(建立Boot項目的新方式、Boot簡化核心)

👩🏻‍🚀部落格首頁:​​⚠️十八歲讨厭程式設計⚠️​​​ 📖所屬專欄:​​SpringBoot專欄💤​​
🌌寫文目的:記錄學習中的知識點
🛕目前已更新内容涵蓋:🔥【前端】、🔥【後端】、🔥【程式設計語言】、🔥【人工智能】、🔥【資料分析及其可視化】、🔥【網絡爬蟲】、🔥【資料結構與算法】、🔥【PS】、🔥【計算機數學】等幾個大方面
[SpringBoot系列]基礎過渡與夯實(建立Boot項目的新方式、Boot簡化核心)
[SpringBoot系列]基礎過渡與夯實(建立Boot項目的新方式、Boot簡化核心)
此篇文章是對SpringBoot專欄前五篇文章的一個綜合性補充。

文章目錄

  • ​​建立SpringBoot項目的兩種新方式​​
  • ​​使用阿裡雲建立項目​​
  • ​​手工建立項目​​
  • ​​SpringBoot再探索​​
  • ​​parent​​
  • ​​starter​​
  • ​​引導類​​
  • ​​内嵌tomcat​​

建立SpringBoot項目的兩種新方式

使用阿裡雲建立項目

我們在前文講到過兩種建立Boot的方式,但是他們都靠的是通路國外的Spring主站,但是網際網路資訊的通路是可以被限制的,如果一天這個網站你在國内無法通路了,那前面這兩種方式就無法建立SpringBoot工程了,這時候又該怎麼解決這個問題呢?

實作方法:

建立工程時,切換選擇starter服務路徑,然後手工輸入阿裡雲位址即可,位址:​​

​http://start.aliyun.com​

​​或​

​https://start.aliyun.com​

[SpringBoot系列]基礎過渡與夯實(建立Boot項目的新方式、Boot簡化核心)

阿裡為了便于自己公司開發使用,特此在依賴坐标中添加了一些阿裡自主的技術,也是為了推廣自己的技術吧,是以在依賴選擇清單中,你有了更多的選擇。此外,阿裡提供的位址更符合國内開發者的使用習慣,裡面有一些SpringBoot官網上沒有給出的坐标,大家可以好好看一看。

[SpringBoot系列]基礎過渡與夯實(建立Boot項目的新方式、Boot簡化核心)

不過有一點需要說清楚,阿裡雲位址預設建立的SpringBoot工程版本是2.4.1,是以如果你想更換其他的版本,建立項目後在pom檔案中手工修改即可,别忘了重新整理一下,加載新版本資訊。

如果我們還是要建立一個web項目,還是勾選web中的選項:
[SpringBoot系列]基礎過渡與夯實(建立Boot項目的新方式、Boot簡化核心)

注意:阿裡雲提供的工程建立位址初始化完畢後和使用SpringBoot官網建立出來的工程略有差別,主要是在配置檔案的形式上有差別,這個資訊在後面講解SpringBoot程式的執行流程時給大家揭曉。

總結

  1. 選擇start來源為自定義URL
  2. 輸入阿裡雲starter位址
  3. 建立項目

手工建立項目

我們前面介紹的方法都需要聯網,或者說我們需要外網的支援。如果我們的環境不滿足這些條件我們怎麼去建立SpringBoot項目呢?

不能上網,還想建立SpringBoot工程,能不能做呢?能做,但是你要先問問自己聯網和不聯網到底差别是什麼?這個差别找到以後,你就發現,你把聯網要幹的事情都提前準備好,就無需聯網了。

聯網做什麼呢?首先SpringBoot工程也是基于Maven建構的,而Maven工程中如果加載一些工程需要使用又不存在的東西時,就要聯網去下載下傳。其實SpringBoot工程建立的時候就是要去下載下傳一些必要的元件。如果把這些東西提前準備好呢?是的,就是這樣。

下面就手工建立一個SpringBoot工程,如果需要使用的東西提前保障在maven倉庫中存在,整個過程就可以不依賴聯網環境了。

步驟①:建立工程時,選擇建立普通Maven工程。

[SpringBoot系列]基礎過渡與夯實(建立Boot項目的新方式、Boot簡化核心)

步驟②:參照标準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倉庫裡面有才行,如果沒有,還是要去下載下傳的。

總結

  1. 建立普通Maven工程
  2. 繼承spring-boot-starter-parent
  3. 添加依賴spring-boot-starter-web
  4. 制作引導類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>      

總結

  1. 開發SpringBoot程式要繼承spring-boot-starter-parent
  2. spring-boot-starter-parent中定義了若幹個依賴管理
  3. 繼承parent子產品可以避免多個依賴使用相同技術時出現依賴版本沖突
  4. 繼承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-技術名稱      

是以後期見了spring-boot-starter-aaa這樣的名字,這就是SpringBoot官方給出的starter定義。那非官方定義的也有嗎?有的,具體命名方式到整合技術的章節再說。

總結

  1. 開發SpringBoot程式需要導入坐标時通常導入對應的starter
  2. 每個不同的starter根據功能不同,通常包含多個依賴坐标
  3. 使用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。

總結

  1. SpringBoot工程提供引導類用來啟動程式
  2. 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伺服器來研究幾個問題:

  1. 這個伺服器在什麼位置定義的
  2. 這個伺服器是怎麼運作的
  3. 這個伺服器如果想換怎麼換?雖然這個需求很垃圾,搞得開發者會好多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>      
  1. 内嵌Tomcat伺服器是SpringBoot輔助功能之一
  2. 内嵌Tomcat工作原理是将Tomcat伺服器作為對象運作,并将該對象交給Spring容器管理
  3. 變更内嵌伺服器思想是去除現有伺服器,添加全新的伺服器