1. 程式人生 > 程式設計 >【SpringBoot-In-Action】一、Spring Boot快速入門

【SpringBoot-In-Action】一、Spring Boot快速入門

本系列教程根據本人實際學習使用 SpringBoot2.x 過程總結整理而來。

1、Spring Boot 簡介

Spring Boot 用來簡化 Spring 應用開發,約定大於配置,刪繁就簡,just run 就能建立一個獨立的、產品級的應用

  • 出現背景:

    J2EE(例如 Spring ) 笨重的開發、繁多的配置、低下的開發效率、負責的部署流程和第三方技術整合難度大。

  • 解決方案:

    “Spring 全家桶” 時代:

    Spring Boot → J2EE 一站式解決方案

    Spring Cloud → 分散式整體解決方案

  • 優點:

    快速建立獨立執行的 Spring 專案以及與主流框架整合

    使用嵌入式的 Servlet 容器,應用無需打成 war包

    starters 自動依賴於版本控制

    大量的自動配置,簡化開發,也可以修改預設屬性值

    開箱即用,無需配置 xml,無程式碼生成

    生產環境的執行時應用監控

    與雲端計算天然整合

總結:

  • 簡化Spring應用開發的一個框架;
  • 整個Spring技術棧的一個大整合;
  • J2EE開發的一站式解決方案;

1-20

2、微服務

  • 2014由martin fowler提出
  • 微服務:架構風格(服務微化)
  • 一個應用應該是一組小型服務;可以通過HTTP的方式進行互通;
  • 單體應用:ALL IN ONE
  • 微服務:每一個功能元素最終都是一個可獨立替換和獨立升級的軟體單元;
  • 詳細參照微服務檔案

單體應用:

1-21

微服務:

1-22

1-23

3、環境準備

開發環境:

  • jdk1.8:Spring Boot 推薦jdk1.7及以上;java version "1.8.0_221"
  • maven3.x:maven 3.3以上版本;apache-maven-3.6.1
  • IntelliJIDEA2019:IntelliJ IDEA 2019.1.3 x64
  • SpringBoot 2.2.1.RELEASE:2.2.1;

開發配置:

1、MAVEN設定:

給 maven 的 settings.xml 配置檔案的 profiles 標籤新增下面的配置,設定 maven 的建立專案時的預設編譯版本使用 jdk8

<profile>
<id>jdk-1.8</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
</profile>
複製程式碼

2、IDEA設定:

Idea 整合 Maven:

1-1

1-2

4、建立 Spring Boot HelloWorld

1、快速開始

首先我們先建立一個統一的 maven 工程,用來管理我們之後學習中所有的專案:

1、新建專案

1-3

2、選擇Maven專案
1-4

3、填寫GroupId、ArtifactId

GroupId:com.demo.springboot

ArtifactId:SpringBoot-In-Action

1-5

5、填寫專案名和專案路徑
1-6

6、建立一個空的maven專案完成,選擇允許自動匯入
1-7

7、刪除多餘的目錄

只保留一個pom.xml檔案即可,同時在pom檔案中新增springboot依賴和打包型別

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.1.RELEASE</version>
</parent>
<packaging>pom</packaging>
複製程式碼

1-8

2、現在開始我們的 Spring Boot HelloWorld 專案:

一個功能:瀏覽器傳送hello請求,伺服器接受請求並處理,響應Hello World字串

1、建立一個maven工程;(jar)

在SpringBoot-In-Action專案中新建一個module,選擇maven工程,命名為 spring-boot-01-helloworld

1-9

2、匯入spring boot相關的依賴

因為springboot的依賴已經在父pom中匯入

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.1.RELEASE</version>
</parent>
複製程式碼

所以在當前的pom中只需要匯入spring-boot-starter-web的依賴即可:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
複製程式碼

完整 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">
    <parent>
        <artifactId>SpringBoot-In-Action</artifactId>
        <groupId>com.demo.springboot</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-boot-01-helloworld</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

</project>
複製程式碼

3、編寫一個主程式;啟動Spring Boot應用

1-10

/**
 * @SpringBootApplication 來標註一個主程式類,說明這是一個Spring Boot應用
 */
@SpringBootApplication
public class HelloWorldMainApplication {
    public static void main(String[] args) {
        // Spring應用啟動起來
        SpringApplication.run(HelloWorldMainApplication.class,args);
    }
}
複製程式碼

4、編寫相關的Controller、Service

為了演示,我在這裡只建立了 Controller 類,省略了 Service

@Controller
public class HelloController {

    @ResponseBody
    @RequestMapping("/hello")
    public String hello() {
        return "Hello World!";
    }
}
複製程式碼

5、執行主程式測試效果

選中 HelloWorldMainApplication 右鍵單擊選擇 Run ... ,或者開啟 HelloWorldMainApplication 按下快捷鍵 Ctrl+Shift+F10 執行程式,可以看到專案在 8080 埠啟動:

1-11

在瀏覽器中訪問 http://localhost:8080/hello 可以看到正常返回了 Hello World

1-12

至此,我們的第一個 Spring Boot 專案算是建立完了。

6、補充:簡化部署

在當前專案,即 spring-boot-01-helloworldpom 檔案中新增下面的配置

<!-- 這個外掛,可以將應用打包成一個可執行的jar包;-->
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>
複製程式碼

然後選中當前 pom 檔案,右鍵單擊,選擇 Run Mavenpackage ,將這個應用打成 jar 包,可以看到 target 資料夾下生成一個名為 spring-boot-01-helloworld-1.0-SNAPSHOT.jarjar 檔案,我們可以直接在命令列使用 java -jar xxx.jar 命令進行執行,啟動後在瀏覽器中訪問 http://localhost:8080/hello 可以看到正常返回了 Hello World 。若要結束程式,關閉命令列視窗即可。

1-13

思考:為什麼我們打包後的程式可以直接在命令列使用 java jar 命令就可以部署?

帶著疑問我們來分析一下 spring-boot-01-helloworld-1.0-SNAPSHOT.jar ,我們使用壓縮檔案開啟 spring-boot-01-helloworld-1.0-SNAPSHOT.jar,可以看到如下所示的目錄結構:

1-14

我們開啟 BOOT-INF 資料夾看到有 classeslib 兩個資料夾,我們寫的程式碼的二進位制位元組碼就在 classes 目錄下,而 lib 下可以看到是我們專案依賴的所有 jar 包,最關鍵的,我們的專案是一個 web 專案,可以看到所依賴的內嵌的 tomcat jar包,這是我們專案能獨立部署的關鍵:

1-15

其他目錄 META-INF 存放專案的 pom 檔案,org 目錄下存放的是 springframework 相關程式碼。

5、Hello World 探究

1、POM檔案

1、父專案

依賴路徑入下圖所示:

1-16

spring-boot-01-helloworld 的 pom:

<parent>
    <artifactId>SpringBoot-In-Action</artifactId>
    <groupId>com.demo.springboot</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>
複製程式碼

它的父專案是 SpringBoot-In-Action 中的 pom:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.1.RELEASE</version>
</parent>

<packaging>pom</packaging>

<groupId>com.demo.springboot</groupId>
<artifactId>SpringBoot-In-Action</artifactId>
<version>1.0-SNAPSHOT</version>

<modules>
    <module>spring-boot-01-helloworld</module>
</modules>
複製程式碼

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.1.RELEASE</version>
</parent>
複製程式碼

的父 pom 是 spring-boot-starter-parent-2.2.1.RELEASE.pom:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.2.1.RELEASE</version>
    <relativePath>../../spring-boot-dependencies</relativePath>
</parent>
<artifactId>spring-boot-starter-parent</artifactId>
複製程式碼

而它的父 pom 是 spring-boot-dependencies-2.2.1.RELEASE.pom:

<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.1.RELEASE</version>
<packaging>pom</packaging>

在這個pom裡邊可以看到,它是用來真正管理Spring Boot應用裡面的所有依賴版本,定義了每一個依賴的版本;
複製程式碼

1-17

spring-boot-dependencies-2.x.RELEASE.pom 可以成稱為 Spring Boot的版本仲裁中心

以後我們匯入其他依賴預設是不需要寫版本;(當然沒有在dependencies裡面管理的依賴自然需要宣告版本號)

2、匯入的其他依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
複製程式碼

spring-boot-starter-web :我們可以將其拆分為 spring-boot-starterweb 兩部分

  • spring-boot-starter:spring-boot 場景啟動器;spring-boot-starter-web 幫我們匯入了 web 模組正常執行所依賴的元件。

我們按下 Ctrl 點選 spring-boot-starter-web 進入 spring-boot-starter-web-2.2.1.RELEASE.pom,可以看到它匯入了很多 web 模組正常執行需要的相關依賴:

1-18

Spring Boot 為我們提供了簡化企業級開發的絕大多數場景的 starter pom(啟動器),只要引入了相應場景的 starter pom ,相關技術的絕大部分配置將會消除(即自動配置),從而簡化我們的開發。很多業務場景中我們就會使用到 Spring Boot 為我們自動配置的 bean,同時 Spring Boot 對這些場景依賴的 jar 也做了嚴格的測試與版本控制,我們可以不必擔心 jar 版本的適配問題。

在 Spring Boot 中還有很多這樣的啟動器,我們開啟官方檔案可以看到關於啟動器的描述:

1-19

Spring Boot 將所有的功能場景都抽取出來,做成一個個的 starters(啟動器),只需要在專案裡面引入這些starter 相關場景的所有依賴都會匯入進來。要用什麼功能就匯入什麼場景的啟動器。而我們一般的開發也是圍繞著這些 starter 來展開。

2、主程式類(主入口類)

/**
 *  @SpringBootApplication 來標註一個主程式類,說明這是一個Spring Boot應用,沒有這個註解專案無法啟動
 */
@SpringBootApplication
public class HelloWorldMainApplication {

    public static void main(String[] args) {

        // Spring應用啟動起來
        SpringApplication.run(HelloWorldMainApplication.class,args);
    }
}
複製程式碼

1、@SpringBootApplication

@SpringBootApplication: Spring Boot應用標註在某個類上說明這個類是SpringBoot的主配置類,SpringBoot 就執行這個類的 main 方法來啟動 SpringBoot 應用;

進入 @SpringBootApplication 註解內部,可以看到它實際上是一個複合註解:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}
),@Filter(
    type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
複製程式碼

2、@SpringBootConfiguration

@SpringBootConfiguration:Spring Boot 的配置類;

作用:標註在某個類上,表示這是一個Spring Boot 的配置類;

進入 @SpringBootConfiguration 內部可以看到,它被 Spring 的 @Configuration 註解所註解:

@Configuration
public @interface SpringBootConfiguration {
複製程式碼

@Configuration:配置類上來標註這個註解;配置類 === 配置檔案;

進入 @Configuration 內部可以看到,它被 Spring 的 @Component 註解所註解;配置類其實也是容器中的一個元件。

@Component
public @interface Configuration {
複製程式碼

3、@EnableAutoConfiguration

@EnableAutoConfiguration:開啟自動配置功能

以前在 Spring 中我們需要配置的東西,現在 Spring Boot 幫我們自動配置;@EnableAutoConfiguration告訴SpringBoot 開啟自動配置功能,這樣自動配置才能生效;

進入 @EnableAutoConfiguration 內部可以看到下面的程式碼:

@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
複製程式碼

@AutoConfigurationPackage:自動配置包,將主配置類(@SpringBootApplication 標註的類)的所在包及下面所有子包裡面的所有元件掃描到 Spring 容器

進入 @Configuration 內部可以看到它被 @Import 註解所註解。Spring 的底層註解 @Import,給容器中匯入一個元件;匯入的元件由 Registrar.class 指定;

@Import({Registrar.class})
public @interface AutoConfigurationPackage {
複製程式碼

進入 Registrar 靜態內部類可以看到如下程式碼:

static class Registrar implements ImportBeanDefinitionRegistrar,DeterminableImports {
    Registrar() {
    }
	// 註冊一些 Bean 定義資訊,進行元件的匯入工作;metadata:註解的元資訊
    public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
        AutoConfigurationPackages.register(registry,(new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
    }

    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
    }
}
複製程式碼

我們在程式碼中打上斷點來看一下:

1-24

@Import({AutoConfigurationImportSelector.class}):給容器中匯入元件xxx

AutoConfigurationImportSelector:匯入哪些元件的選擇器;

我們進入 AutoConfigurationImportSelector 中可以看到如下程式碼:

// 將所有需要匯入的元件以全類名的方式返回;這些元件就會被新增到容器中
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    } else {
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
        List<String> configurations = this.getCandidateConfigurations(annotationMetadata,attributes);
        configurations = this.removeDuplicates(configurations);
        Set<String> exclusions = this.getExclusions(annotationMetadata,attributes);
        this.checkExcludedClasses(configurations,exclusions);
        configurations.removeAll(exclusions);
        configurations = this.filter(configurations,autoConfigurationMetadata);
        this.fireAutoConfigurationImportEvents(configurations,exclusions);
        return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations,exclusions);
    }
}
複製程式碼

將所有需要匯入的元件以全類名的方式返回;這些元件就會被新增到容器中。即AutoConfigurationImportSelector會給容器中匯入非常多的自動配置類(xxxAutoConfiguration);就是給容器中匯入這個場景需要的所有元件,並配置好這些元件。

我們在程式碼中打斷點可以看到如下的結果:

自動配置類

有了自動配置類,就免去了我們手動編寫配置注入功能元件等的工作。

進入上面程式碼中的 getCandidateConfigurations 方法,可以看到如下程式碼:

// 獲取候選配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(),this.getBeanClassLoader());
    Assert.notEmpty(configurations,"No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging,make sure that file is correct.");
    return configurations;
}
複製程式碼

上面程式碼中呼叫了 SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,ClassLoader)方法,我們在這個進入這個方法可以看下面的程式碼:

public static List<String> loadFactoryNames(Class<?> factoryType,@Nullable ClassLoader classLoader) {
    // 獲取工廠名
    String factoryTypeName = factoryType.getName();
    return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName,Collections.emptyList());
}

private static Map<String,List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String,String> result = (MultiValueMap)cache.get(classLoader);
    if (result != null) {
        return result;
    } else {
        try {
            // 用類載入器從類路徑 "META-INF/spring.factories" 中獲取資源
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
            LinkedMultiValueMap result = new LinkedMultiValueMap();

            while(urls.hasMoreElements()) {
                URL url = (URL)urls.nextElement();
                UrlResource resource = new UrlResource(url);
                // 從獲取的資源中拿出 properties 配置檔案,把獲取的資源當作 properties 配置檔案
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                Iterator var6 = properties.entrySet().iterator();

                while(var6.hasNext()) {
                    Entry<?,?> entry = (Entry)var6.next();
                    // 從 properties 檔案中拿到 factoryTypeName
                    String factoryTypeName = ((String)entry.getKey()).trim();
                    String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                    int var10 = var9.length;

                    for(int var11 = 0; var11 < var10; ++var11) {
                        String factoryImplementationName = var9[var11];
                        result.add(factoryTypeName,factoryImplementationName.trim());
                    }
                }
            }

            cache.put(classLoader,result);
            return result;
        } catch (IOException var13) {
            throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]",var13);
        }
    }
}
複製程式碼

所以根據上面的分析我的得知:Spring Boot 在啟動的時候從類路徑下的 META-INF/spring.factories 中獲取 EnableAutoConfiguration 指定的值,將這些值作為自動配置類匯入到容器中,自動配置類就生效,幫我們進行自動配置工作。以前我們在 Spring 中需要自己配置的東西,在 Spring Boot 中自動配置類都幫我們配了。

自動配置類

綜上,J2EE 的整體整合解決方案和自動配置都在 spring-boot-autoconfigure-2.2.1.RELEASE.jar (spring-boot-autoconfigure-x.x.x.RELEASE.jar )中:

自動配置解決方案

6、使用 Spring Initializer 快速建立 Spring Boot專案

1、IDEA:使用 Spring Initializer快速建立專案

所有的 IDE 都支援使用 Spring 的專案建立嚮導快速建立一個 Spring Boot 專案,下面我們來看一下如何在 idea 中快速建立一個 Spring Boot 專案。

1、選擇FileNewProject 或是在一個已經存在的工程中選中工程名然後 NewModule

1-28

2、填寫基本資訊 GroupIDArtifactID

1-29

3、選擇我們需要依賴的模組

1-30

4、點選 Finish,接下來嚮導會聯網建立 Spring Boot 專案

1-31

檢視預設生成的 Spring Boot 專案,我們可以看到:

  • 主程式已經生成好了,我們只需要寫我們自己的邏輯
  • resources 資料夾中目錄結構:
    • static:儲存所有的靜態資源: js、css、 images等;
    • templates:儲存所有的模板頁面(Spring Boot 預設 jar 包使用嵌入式的 Tomcat,預設不支援 JSP 頁面),可以使用模板引擎(freemarkerthymeleaf等);
    • application.properties:Spring Boot 應用的配置檔案;可以修改一些預設設定,比如埠等

我們在 com.demo.spring.controller 包下新見一個 HelloController 來測試一下我們新建的專案

1-32

HelloController 程式碼如下:

//這個類的所有方法返回的資料直接寫給瀏覽器,(如果是物件轉為json資料)
//@ResponseBody
//@Controller
//  上面兩行一般多用在 Spring 專案中
@RestController // 在 Spring Boot 和 Spring4.2.x 之後使用這個來簡化開發,返回 RESTAPI 規範的介面
public class HelloController {
    @RequestMapping("/hello")
    public String hello(){
        return "hello world quick!";
    }
}
複製程式碼

在瀏覽器中訪問 http://localhost:8080/hello 可以得到下面的返回效果:

1-33

2、通過瀏覽器訪問https://start.spring.io/快速建立專案

1-34

執行完上面的操作後,瀏覽器會自動下載一個建立好的專案,我們使用idea開啟即可。

以上,就是我們 Spring Boot 快速入門的全部內容,更多詳細內容請檢視原始碼瞭解。

原始碼:

SpringBoot-In-Action