SpringBoot學習筆記整理詳細

文章目錄


#回顧什麼是Spring

Spring是一個開源框架,2003 年興起的一個輕量級的Java 開發框架,作者:Rod Johnson 。

Spring是爲了解決企業級應用開發的複雜性而創建的,簡化開發。

1. Spring是如何簡化Java開發的

爲了降低Java開發的複雜性,Spring採用了以下4種關鍵策略:

1、基於POJO的輕量級和最小侵入性編程,所有東西都是bean;

2、通過IOC依賴注入(DI)和麪向接口實現松耦合

3、基於切面(AOP)和慣例進行聲明式編程

4、通過切面和模版減少樣式代碼,RedisTemplate,xxxTemplate;

2. 什麼是SpringBoot

學過javaweb的同學就知道,開發一個web應用,從最初開始接觸Servlet結合Tomcat, 跑出一個Hello Wolrld程序,是要經歷特別多的步驟;後來就用了框架Struts,再後來是SpringMVC,到了現在的SpringBoot,過一兩年又會有其他web框架出現;你們有經歷過框架不斷的演進,然後自己開發項目所有的技術也在不斷的變化、改造嗎?建議都可以去經歷一遍;

言歸正傳,什麼是SpringBoot呢,就是一個javaweb的開發框架,和SpringMVC類似,對比其他javaweb框架的好處,官方說是簡化開發,約定大於配置, you can “just run”,能迅速的開發web應用,幾行代碼開發一個http接口。

所有的技術框架的發展似乎都遵循了一條主線規律:從一個複雜應用場景 衍生 一種規範框架,人們只需要進行各種配置而不需要自己去實現它,這時候強大的配置功能成了優點;發展到一定程度之後,人們根據實際生產應用情況,選取其中實用功能和設計精華,重構出一些輕量級的框架;之後爲了提高開發效率,嫌棄原先的各類配置過於麻煩,於是開始提倡“約定大於配置”,進而衍生出一些一站式的解決方案。

是的這就是Java企業級應用->J2EE->spring->springboot的過程。

隨着 Spring 不斷的發展,涉及的領域越來越多,項目整合開發需要配合各種各樣的文件,慢慢變得不那麼易用簡單,違背了最初的理念,甚至人稱配置地獄。Spring Boot 正是在這樣的一個背景下被抽象出來的開發框架,目的爲了讓大家更容易的使用 Spring 、更容易的集成各種常用的中間件、開源軟件;

Spring Boot 基於 Spring 開發,Spirng Boot 本身並不提供 Spring 框架的核心特性以及擴展功能,只是用於快速、敏捷地開發新一代基於 Spring 框架的應用程序。也就是說,它並不是用來替代 Spring 的解決方案,而是和 Spring 框架緊密結合用於提升 Spring 開發者體驗的工具。Spring Boot 以約定大於配置的核心思想,默認幫我們進行了很多設置,多數 Spring Boot 應用只需要很少的 Spring 配置。同時它集成了大量常用的第三方庫配置(例如 Redis、MongoDB、Jpa、RabbitMQ、Quartz 等等),Spring Boot 應用中這些第三方庫幾乎可以零配置的開箱即用。

簡單來說就是SpringBoot其實不是什麼新的框架,它默認配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot整合了所有的框架 。Spring Boot 出生名門,從一開始就站在一個比較高的起點,又經過這幾年的發展,生態足夠完善,Spring Boot 已經當之無愧成爲 Java 領域最熱門的技術。

Spring Boot的主要優點:

  • 爲所有Spring開發者更快的入門
  • 開箱即用,提供各種默認配置來簡化項目配置
  • 內嵌式容器簡化Web項目
  • 沒有冗餘代碼生成和XML配置的要求

3. Hello,World

3.1 準備工作

我們將學習如何快速的創建一個Spring Boot應用,並且實現一個簡單的Http請求處理。通過這個例子對Spring Boot有一個初步的瞭解,並體驗其結構簡單、開發快速的特性。

我的環境準備:

  • jdk1.8
  • Maven-3.6.1
  • SpringBoot 2.x 最新版

開發工具:

  • IDEA

3.2 創建基礎項目說明

Spring官方提供了非常方便的工具讓我們快速構建應用

Spring Initializr:https://start.spring.io/

**項目創建方式一:**使用Spring Initializr 的 Web頁面創建項目

1、打開 https://start.spring.io/

在這裏插入圖片描述

2、填寫項目信息

添加Dependencies依賴,選擇web項目的依賴

3、點擊”Generate Project“按鈕生成項目;下載此項目

4、解壓項目包,並用IDEA以Maven項目導入,一路下一步即可,直到項目導入完畢。

5、如果是第一次使用,可能速度會比較慢,包比較多、需要耐心等待一切就緒。

項目創建方式二: 使用 IDEA 直接創建項目

1、創建一個新項目

在這裏插入圖片描述

2、選擇spring initalizr , 可以看到默認就是去官網的快速構建工具那裏實現

這裏可能會出現加載出錯的問題:

問題:用Spring Initializr創建springBoot工程時,錯誤提示:Spring Initializr Error
解決方法:file— settings— 搜索 HTTP Proxy:

  1. 勾選Auto-detect proxy settings
  2. 點擊Check connection 彈出新的窗口,輸入https://start.spring.io
  3. 點擊ok,彈出successful 即連接成功(如果還是報錯,將第二步的網址換成:http://start.spring.io)

在這裏插入圖片描述

3、填寫項目信息

在這裏插入圖片描述

4、選擇初始化的組件(初學勾選 Web 即可)

在這裏插入圖片描述

5、填寫項目路徑

在這裏插入圖片描述

6、等待項目構建成功

項目結構分析:

通過上面步驟完成了基礎項目的創建。就會自動生成以下文件。

1、程序的主啓動類

在這裏插入圖片描述

2、一個 application.properties 配置文件

在這裏插入圖片描述

3、一個 測試類

在這裏插入圖片描述

4、一個 pom.xml

<?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 https://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.3.0.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.zhang</groupId>
	<artifactId>helloword</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>helloword</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

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

        
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>

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

3.3 pom.xml 分析

打開pom.xml,看看Spring Boot項目的依賴:

<!-- 父依賴 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.5.RELEASE</version>
    <relativePath/>
</parent>

<dependencies>
    <!-- web場景啓動器 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- springboot單元測試 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <!-- 剔除依賴 -->
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

<build>
    <plugins>
        <!-- 打包插件 -->
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

3.4 編寫一個http接口

1、在主程序的同級目錄下,新建一個controller包,一定要在同級目錄下,否則識別不到

2、在包中新建一個HelloController類

@RestController
public class HelloController {

    @RequestMapping("/hello")
    public String hello() {
        return "Hello World";
    }
    
}

3、編寫完畢後,從主程序啓動項目,瀏覽器發起請求,看頁面返回;控制檯輸出了 Tomcat 訪問的端口號!

img

簡單幾步,就完成了一個web接口的開發,SpringBoot就是這麼簡單。所以我們常用它來建立我們的微服務項目!

3.5 將項目打成jar包

點擊 maven的 package

img

如果遇到以上錯誤,可以配置打包時 跳過項目運行測試用例

<!--
    在工作中,很多情況下我們打包是不想執行測試用例的
    可能是測試用例不完事,或是測試用例會影響數據庫數據
    跳過測試用例執
    -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <!--跳過項目運行測試用例-->
        <skipTests>true</skipTests>
    </configuration>
</plugin>

如果打包成功,則會在target目錄下生成一個 jar 包

img

打成了jar包後,就可以在任何地方運行了!OK

3.6 彩蛋

如何更改啓動時顯示的字符拼成的字母,SpringBoot呢?也就是 banner 圖案;

只需一步:到項目下的 resources 目錄下新建一個banner.txt 即可。

在這裏插入圖片描述

圖案可以到:https://www.bootschool.net/ascii 這個網站生成,然後拷貝到文件中即可!

在這裏插入圖片描述

SpringBoot這麼簡單的東西背後一定有故事,我們之後去進行一波源碼分析!

4. 運行原理探究

我們之前寫的HelloSpringBoot,到底是怎麼運行的呢,Maven項目,我們一般從pom.xml文件探究起;

4.1 pom.xml

4.1.1 父依賴

其中它主要是依賴一個父項目,主要是管理項目的資源過濾及插件!

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.5.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

點進去,發現還有一個父依賴

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.2.5.RELEASE</version>
    <relativePath>../../spring-boot-dependencies</relativePath>
</parent>

這裏纔是真正管理SpringBoot應用裏面所有依賴版本的地方,SpringBoot的版本控制中心;

以後我們導入依賴默認是不需要寫版本;但是如果導入的包沒有在依賴中管理着就需要手動配置版本了;

4.1.2 啓動器 spring-boot-starter

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

springboot-boot-starter-xxx:就是spring-boot的場景啓動器

4.1.3 spring-boot-starter-web:

幫我們導入了web模塊正常運行所依賴的組件;

SpringBoot將所有的功能場景都抽取出來,做成一個個的starter (啓動器),只需要在項目中引入這些starter即可,所有相關的依賴都會導入進來 , 我們要用什麼功能就導入什麼樣的場景啓動器即可 ;我們未來也可以自己自定義 starter;

4.2 主啓動類

分析完了 pom.xml 來看看這個啓動類

4.2.1 默認的主啓動類

//@SpringBootApplication 來標註一個主程序類
//說明這是一個Spring Boot應用
@SpringBootApplication
public class SpringbootApplication {

   public static void main(String[] args) {
     //以爲是啓動了一個方法,沒想到啓動了一個服務
      SpringApplication.run(SpringbootApplication.class, args);
   }

}

但是**一個簡單的啓動類並不簡單!**我們來分析一下這些註解都幹了什麼

4.2.2 @SpringBootApplication

作用:標註在某個類上說明這個類是SpringBoot的主配置類 , SpringBoot就應該運行這個類的main方法來啓動SpringBoot應用;

進入這個註解:可以看到上面還有很多其他註解!

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    // ......
}

4.2.3 @ComponentScan

這個註解在Spring中很重要 ,它對應XML配置中的元素。

作用:自動掃描並加載符合條件的組件或者bean , 將這個bean定義加載到IOC容器中

4.2.4 @SpringBootConfiguration

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

我們繼續進去這個註解查看

// 點進去得到下面的 @Component
@Configuration
public @interface SpringBootConfiguration {}

@Component
public @interface Configuration {}

這裏的 @Configuration,說明這是一個配置類 ,配置類就是對應Spring的xml 配置文件;

裏面的 @Component 這就說明,啓動類本身也是Spring中的一個組件而已,負責啓動應用!

我們回到 SpringBootApplication 註解中繼續看。

4.2.5 @EnableAutoConfiguration

@EnableAutoConfiguration :開啓自動配置功能

以前我們需要自己配置的東西,而現在SpringBoot可以自動幫我們配置 ;@EnableAutoConfiguration告訴SpringBoot開啓自動配置功能,這樣自動配置才能生效;

點進註解接續查看:

@AutoConfigurationPackage :自動配置包

@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}

@import :Spring底層註解@import , 給容器中導入一個組件

Registrar.class 作用:將主啓動類的所在包及包下面所有子包裏面的所有組件掃描到Spring容器 ;

這個分析完了,退到上一步,繼續看

@Import({AutoConfigurationImportSelector.class}) :給容器導入組件 ;

AutoConfigurationImportSelector :自動配置導入選擇器,那麼它會導入哪些組件的選擇器呢?我們點擊去這個類看源碼:

1、這個類中有一個這樣的方法

// 獲得候選的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    //這裏的getSpringFactoriesLoaderFactoryClass()方法
    //返回的就是我們最開始看的啓動自動導入配置文件的註解類;EnableAutoConfiguration
    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;
}

2、這個方法又調用了 SpringFactoriesLoader 類的靜態方法!我們進入SpringFactoriesLoader類loadFactoryNames() 方法

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    //這裏它又調用了 loadSpringFactories 方法
    return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

3、我們繼續點擊查看 loadSpringFactories 方法

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    //獲得classLoader , 我們返回可以看到這裏得到的就是EnableAutoConfiguration標註的類本身
    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();

            //將讀取到的資源遍歷,封裝成爲一個Properties
            while(urls.hasMoreElements()) {
                URL url = (URL)urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                Iterator var6 = properties.entrySet().iterator();

                while(var6.hasNext()) {
                    Entry<?, ?> entry = (Entry)var6.next();
                    String factoryClassName = ((String)entry.getKey()).trim();
                    String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                    int var10 = var9.length;

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

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

4、發現一個多次出現的文件:spring.factories,全局搜索它

4.2.6 spring.factories

我們根據源頭打開spring.factories , 看到了很多自動配置的文件;這就是自動配置根源所在!

img

WebMvcAutoConfiguration

我們在上面的自動配置類隨便找一個打開看看,比如 :WebMvcAutoConfiguration

img

可以看到這些一個個的都是JavaConfig配置類,而且都注入了一些Bean,可以找一些自己認識的類,看着熟悉一下!

所以,自動配置真正實現是從classpath中搜尋所有的META-INF/spring.factories配置文件 ,並將其中對應的 org.springframework.boot.autoconfigure. 包下的配置項,通過反射實例化爲對應標註了 @Configuration的JavaConfig形式的IOC容器配置類 , 然後將這些都彙總成爲一個實例並加載到IOC容器中。

結論:

  1. SpringBoot在啓動的時候從類路徑下的META-INF/spring.factories中獲取EnableAutoConfiguration指定的值
  2. 將這些值作爲自動配置類導入容器 , 自動配置類就生效 , 幫我們進行自動配置工作;
  3. 整個J2EE的整體解決方案和自動配置都在springboot-autoconfigure的jar包中;
  4. 它會給容器中導入非常多的自動配置類 (xxxAutoConfiguration), 就是給容器中導入這個場景需要的所有組件 , 並配置好這些組件 ;
  5. 有了自動配置類 , 免去了我們手動編寫配置注入功能組件等的工作;

現在大家應該大概的瞭解了下,SpringBoot的運行原理,後面我們還會深化一次!

4.3 SpringApplication

4.3.1 不簡單的方法

我最初以爲就是運行了一個main方法,沒想到卻開啓了一個服務;

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

SpringApplication.run分析

分析該方法主要分兩部分,一部分是SpringApplication的實例化,二是run方法的執行;

4.3.2 SpringApplication

這個類主要做了以下四件事情:

1、推斷應用的類型是普通的項目還是Web項目

2、查找並加載所有可用初始化器 , 設置到initializers屬性中

3、找出所有的應用程序監聽器,設置到listeners屬性中

4、推斷並設置main方法的定義類,找到運行的主類

查看構造器:

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
    // ......
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.setInitializers(this.getSpringFactoriesInstances();
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = this.deduceMainApplicationClass();
}

4.3.3 run方法流程分析

img

跟着源碼和這幅圖就可以一探究竟了!

5. yaml語法學習

導入註解配置依賴到pom.xml中:

        <!--yaml-@ConfigurationProperties-配置依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

如果想要yaml輸入自動提示功能:

如下圖:
在這裏插入圖片描述

需要下載兩個插件:

yaml插件

在這裏插入圖片描述
Spring Assistant插件

在這裏插入圖片描述

5.1 配置文件

SpringBoot使用一個全局的配置文件 , 配置文件名稱是固定的

  • application.properties

    • 語法結構 :key=value
  • application.yml

    • 語法結構 :key:空格 value

配置文件的作用 : 修改SpringBoot自動配置的默認值,因爲SpringBoot在底層都給我們自動配置好了;

比如我們可以在配置文件中修改Tomcat 默認啓動的端口號!測試一下!

server.port=8081

5.2 yaml概述

YAML是 “YAML Ain’t a Markup Language” (YAML不是一種標記語言)的遞歸縮寫。在開發的這種語言時,YAML 的意思其實是:“Yet Another Markup Language”(仍是一種標記語言)

這種語言以數據爲中心,而不是以標記語言爲重點!

以前的配置文件,大多數都是使用xml來配置;比如一個簡單的端口配置,我們來對比下yaml和xml

傳統xml配置:

<server>
    <port>8081<port>
</server>

yaml配置:

server:
  prot: 8080

5.3 yaml基礎語法

說明:語法要求嚴格!

1、空格不能省略

2、以縮進來控制層級關係,只要是左邊對齊的一列數據都是同一個層級的。

3、屬性和值的大小寫都是十分敏感的。

字面量:普通的值 [ 數字,布爾值,字符串 ]

字面量直接寫在後面就可以 , 字符串默認不用加上雙引號或者單引號;

k: v

注意:

  • “ ” 雙引號,不會轉義字符串裏面的特殊字符 , 特殊字符會作爲本身想表示的意思;

    比如 :name: “kuang \n shen” 輸出 :kuang 換行 shen

  • ‘’ 單引號,會轉義特殊字符 , 特殊字符最終會變成和普通字符一樣輸出

    比如 :name: ‘kuang \n shen’ 輸出 :kuang \n shen

對象、Map(鍵值對)

#對象、Map格式
k: 
    v1:
    v2:

在下一行來寫對象的屬性和值得關係,注意縮進;比如:

student:
    name: qinjiang
    age: 3

行內寫法

student: {name: qinjiang,age: 3}

數組( List、set )

用 - 值表示數組中的一個元素,比如:

pets:
 - cat
 - dog
 - pig

行內寫法

pets: [cat,dog,pig]

修改SpringBoot的默認端口號

配置文件中添加,端口號的參數,就可以切換端口;

server:
  port: 8082

5.3.1 注入配置文件

yaml文件更強大的地方在於,他可以給我們的實體類直接注入匹配值!

5.4 yaml注入配置文件

1、在springboot項目中的resources目錄下新建一個文件 application.yml

2、編寫一個實體類 Dog;

package com.kuang.springboot.pojo;

@Component  //註冊bean到容器中
public class Dog {
    private String name;
    private Integer age;
    
    //有參無參構造、get、set方法、toString()方法  
}

3、思考,我們原來是如何給bean注入屬性值的!@Value,給狗狗類測試一下:

@Component //註冊bean
public class Dog {
    @Value("阿黃")
    private String name;
    @Value("18")
    private Integer age;
}

4、在SpringBoot的測試類下注入狗狗輸出一下;

@SpringBootTest
class DemoApplicationTests {

    @Autowired //將狗狗自動注入進來
    Dog dog;

    @Test
    public void contextLoads() {
        System.out.println(dog); //打印看下狗狗對象
    }

}

結果成功輸出,@Value注入成功,這是我們原來的辦法對吧。

img

5、我們在編寫一個複雜一點的實體類:Person 類

@Component //註冊bean到容器中
public class Person {
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;
    
    //有參無參構造、get、set方法、toString()方法  
}

6、我們來使用yaml配置的方式進行注入,大家寫的時候注意區別和優勢,我們編寫一個yaml配置!

person:
  name: qinjiang
  age: 3
  happy: false
  birth: 2000/01/01
  maps: {k1: v1,k2: v2}
  lists:
   - code
   - girl
   - music
  dog:
    name: 旺財
    age: 1

7、我們剛纔已經把person這個對象的所有值都寫好了,我們現在來注入到我們的類中!

/*
@ConfigurationProperties作用:
將配置文件中配置的每一個屬性的值,映射到這個組件中;
告訴SpringBoot將本類中的所有屬性和配置文件中相關的配置進行綁定
參數 prefix = “person” : 將配置文件中的person下面的所有屬性一一對應
*/
@Component //註冊bean
@ConfigurationProperties(prefix = "person")
public class Person {
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;
}

8、IDEA 提示,springboot配置註解處理器沒有找到,讓我們看文檔,我們可以查看文檔,找到一個依賴!

img

img

<!-- 導入配置文件處理器,配置文件進行綁定就會有提示,需要重啓 -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-configuration-processor</artifactId>
  <optional>true</optional>
</dependency>

9、確認以上配置都OK之後,我們去測試類中測試一下:

@SpringBootTest
class DemoApplicationTests {

    @Autowired
    Person person; //將person自動注入進來

    @Test
    public void contextLoads() {
        System.out.println(person); //打印person信息
    }

}

yaml配置注入到實體類完全OK!

課堂測試:

1、將配置文件的key 值 和 屬性的值設置爲不一樣,則結果輸出爲null,注入失敗

2、在配置一個person2,然後將 @ConfigurationProperties(prefix = “person2”) 指向我們的person2;

5.4.1 加載指定的配置文件

**@PropertySource :**加載指定的配置文件;

@configurationProperties:默認從全局配置文件中獲取值;

1、我們去在resources目錄下新建一個person.properties文件

name=kuangshen

2、然後在我們的代碼中指定加載person.properties文件

@PropertySource(value = "classpath:person.properties")
@Component //註冊bean
public class Person {

    @Value("${name}")
    private String name;

    ......  
}

3、再次輸出測試一下:指定配置文件綁定成功!

img

5.4.2 配置文件佔位符

配置文件還可以編寫佔位符生成隨機數

person:
    name: qinjiang${random.uuid} # 隨機uuid
    age: ${random.int}  # 隨機int
    happy: false
    birth: 2000/01/01
    maps: {k1: v1,k2: v2}
    lists:
      - code
      - girl
      - music
    dog:
      name: ${person.hello:other}_旺財
      age: 1

5.4.3 回顧properties配置

我們上面採用的yaml方法都是最簡單的方式,開發中最常用的;也是springboot所推薦的!那我們來嘮嘮其他的實現方式,道理都是相同的;寫還是那樣寫;配置文件除了yml還有我們之前常用的properties , 我們沒有講,我們來嘮嘮!

【注意】properties配置文件在寫中文的時候,會有亂碼 , 我們需要去IDEA中設置編碼格式爲UTF-8;

settings–>FileEncodings 中配置;

img

測試步驟:

1、新建一個實體類User

@Component //註冊bean
public class User {
    private String name;
    private int age;
    private String sex;
}

2、編輯配置文件 user.properties

user1.name=kuangshen
user1.age=18
user1.sex=男

3、我們在User類上使用@Value來進行注入!

@Component //註冊bean
@PropertySource(value = "classpath:user.properties")
public class User {
    //直接使用@value
    @Value("${user.name}") //從配置文件中取值
    private String name;
    @Value("#{9*2}")  // #{SPEL} Spring表達式
    private int age;
    @Value("男")  // 字面量
    private String sex;
}

4、Springboot測試

@SpringBootTest
class DemoApplicationTests {

    @Autowired
    User user;

    @Test
    public void contextLoads() {
        System.out.println(user);
    }

}

結果正常輸出:

img

5.5 對比小結

@Value這個使用起來並不友好!我們需要爲每個屬性單獨註解賦值,比較麻煩;我們來看個功能對比圖

img

1、@ConfigurationProperties只需要寫一次即可 , @Value則需要每個字段都添加

2、鬆散綁定:這個什麼意思呢? 比如我的yml中寫的last-name,這個和lastName是一樣的, - 後面跟着的字母默認是大寫的。這就是鬆散綁定。可以測試一下

3、JSR303數據校驗 , 這個就是我們可以在字段是增加一層過濾器驗證 , 可以保證數據的合法性

4、複雜類型封裝,yml中可以封裝對象 , 使用value就不支持

結論:

配置yml和配置properties都可以獲取到值 , 強烈推薦 yml;

如果我們在某個業務中,只需要獲取配置文件中的某個值,可以使用一下 @value;

如果說,我們專門編寫了一個JavaBean來和配置文件進行一一映射,就直接@configurationProperties,不要猶豫!

6. JSR303數據校驗

第一步:導入pom.xml的數據校驗依賴,不然會找不到@Email

<!--數據校驗依賴-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

6.1 先看看如何使用

Springboot中可以用@validated來校驗數據,如果數據異常則會統一拋出異常,方便異常中心統一處理。我們這裏來寫個註解讓我們的name只能支持Email格式;

@Component //註冊bean
@ConfigurationProperties(prefix = "person")
@Validated  //數據校驗
public class Person {

    @Email(message="郵箱格式錯誤") //name必須是郵箱格式
    private String name;
}

運行結果 :default message [不是一個合法的電子郵件地址];

img

使用數據校驗,可以保證數據的正確性;

6.2 常見參數

@NotNull(message="名字不能爲空")
private String userName;
@Max(value=120,message="年齡最大不能查過120")
private int age;
@Email(message="郵箱格式錯誤")
private String email;

空檢查
@Null       驗證對象是否爲null
@NotNull    驗證對象是否不爲null, 無法查檢長度爲0的字符串
@NotBlank   檢查約束字符串是不是Null還有被Trim的長度是否大於0,只對字符串,且會去掉前後空格.
@NotEmpty   檢查約束元素是否爲NULL或者是EMPTY.
    
Booelan檢查
@AssertTrue     驗證 Boolean 對象是否爲 true  
@AssertFalse    驗證 Boolean 對象是否爲 false  
    
長度檢查
@Size(min=, max=) 驗證對象(Array,Collection,Map,String)長度是否在給定的範圍之內  
@Length(min=, max=) string is between min and max included.

日期檢查
@Past       驗證 Date 和 Calendar 對象是否在當前時間之前  
@Future     驗證 Date 和 Calendar 對象是否在當前時間之後  
@Pattern    驗證 String 對象是否符合正則表達式的規則

.......等等
除此以外,我們還可以自定義一些數據校驗規則

6.3 多環境切換

profile是Spring對不同環境提供不同配置功能的支持,可以通過激活不同的環境版本,實現快速切換環境;

6.3.1 配置文件

我們在主配置文件編寫的時候,文件名可以是 application-{profile}.properties/yml , 用來指定多個環境版本;

例如:

application-test.properties 代表測試環境配置

application-dev.properties 代表開發環境配置

但是Springboot並不會直接啓動這些配置文件,它默認使用application.properties主配置文件

我們需要通過一個配置來選擇需要激活的環境:

#比如在配置文件中指定使用dev環境,我們可以通過設置不同的端口號進行測試;
#我們啓動SpringBoot,就可以看到已經切換到dev下的配置了;
spring.profiles.active=dev

6.3.2 yaml的多文檔塊

和properties配置文件中一樣,但是使用yml去實現不需要創建多個配置文件,更加方便了 !

server:
  port: 8081
#選擇要激活那個環境塊
spring:
  profiles:
    active: prod

---
server:
  port: 8083
spring:
  profiles: dev #配置環境的名稱


---

server:
  port: 8084
spring:
  profiles: prod  #配置環境的名稱

注意:如果yml和properties同時都配置了端口,並且沒有激活其他環境 , 默認會使用properties配置文件的!

6.3.3 配置文件加載位置

外部加載配置文件的方式十分多,我們選擇最常用的即可,在開發的資源文件中進行配置!

springboot 啓動會掃描以下位置的application.properties或者application.yml文件作爲Spring boot的默認配置文件:

優先級1:項目路徑下的config文件夾配置文件
優先級2:項目路徑下配置文件
優先級3:資源路徑下的config文件夾配置文件
優先級4:資源路徑下配置文件

優先級1:

在這裏插入圖片描述

優先級2:

在這裏插入圖片描述

優先級3:

在這裏插入圖片描述

優先級4:

在這裏插入圖片描述

優先級由高到底,高優先級的配置會覆蓋低優先級的配置;

SpringBoot會從這四個位置全部加載主配置文件;互補配置;

我們在最低級的配置文件中設置一個項目訪問路徑的配置來測試互補問題;

配置項目的訪問路徑
server.servlet.context-path=/kuang

6.3.4 拓展,運維小技巧

指定位置加載配置文件

我們還可以通過spring.config.location來改變默認的配置文件位置

項目打包好以後,我們可以使用命令行參數的形式,啓動項目的時候來指定配置文件的新位置;這種情況,一般是後期運維做的多,相同配置,外部指定的配置文件優先級最高

java -jar spring-boot-config.jar --spring.config.location=F:/application.properties

7. 自動配置原理

配置文件到底能寫什麼?怎麼寫?SpringBoot官方文檔中有大量的配置,我們無法全部記住

img

7.1 分析自動配置原理

我們以**HttpEncodingAutoConfiguration(Http編碼自動配置)**爲例解釋自動配置原理;

//表示這是一個配置類,和以前編寫的配置文件一樣,也可以給容器中添加組件;
@Configuration 

//啓動指定類的ConfigurationProperties功能;
  //進入這個HttpProperties查看,將配置文件中對應的值和HttpProperties綁定起來;
  //並把HttpProperties加入到ioc容器中
@EnableConfigurationProperties({HttpProperties.class}) 

//Spring底層@Conditional註解
  //根據不同的條件判斷,如果滿足指定的條件,整個配置類裏面的配置就會生效;
  //這裏的意思就是判斷當前應用是否是web應用,如果是,當前配置類生效
@ConditionalOnWebApplication(
    type = Type.SERVLET
)

//判斷當前項目有沒有這個類CharacterEncodingFilter;SpringMVC中進行亂碼解決的過濾器;
@ConditionalOnClass({CharacterEncodingFilter.class})

//判斷配置文件中是否存在某個配置:spring.http.encoding.enabled;
  //如果不存在,判斷也是成立的
  //即使我們配置文件中不配置pring.http.encoding.enabled=true,也是默認生效的;
@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true
)

public class HttpEncodingAutoConfiguration {
    //他已經和SpringBoot的配置文件映射了
    private final Encoding properties;
    //只有一個有參構造器的情況下,參數的值就會從容器中拿
    public HttpEncodingAutoConfiguration(HttpProperties properties) {
        this.properties = properties.getEncoding();
    }
    
    //給容器中添加一個組件,這個組件的某些值需要從properties中獲取
    @Bean
    @ConditionalOnMissingBean //判斷容器沒有這個組件?
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
        return filter;
    }
    //。。。。。。。
}

一句話總結 :根據當前不同的條件判斷,決定這個配置類是否生效!

  • 一但這個配置類生效;這個配置類就會給容器中添加各種組件;
  • 這些組件的屬性是從對應的properties類中獲取的,這些類裏面的每一個屬性又是和配置文件綁定的;
  • 所有在配置文件中能配置的屬性都是在xxxxProperties類中封裝着;
  • 配置文件能配置什麼就可以參照某個功能對應的這個屬性類
//從配置文件中獲取指定的值和bean的屬性進行綁定
@ConfigurationProperties(prefix = "spring.http") 
public class HttpProperties {
    // .....
}

我們去配置文件裏面試試前綴,看提示!

img

這就是自動裝配的原理!

7.2 精髓

1、SpringBoot啓動會加載大量的自動配置類

2、我們看我們需要的功能有沒有在SpringBoot默認寫好的自動配置類當中;

3、我們再來看這個自動配置類中到底配置了哪些組件;(只要我們要用的組件存在在其中,我們就不需要再手動配置了)

4、給容器中自動配置類添加組件的時候,會從properties類中獲取某些屬性。我們只需要在配置文件中指定這些屬性的值即可;

**xxxxAutoConfigurartion:自動配置類;**給容器中添加組件

xxxxProperties:封裝配置文件中相關屬性;

7.3 瞭解:@Conditional

瞭解完自動裝配的原理後,我們來關注一個細節問題,自動配置類必須在一定的條件下才能生效;

@Conditional派生註解(Spring註解版原生的@Conditional作用)

作用:必須是@Conditional指定的條件成立,纔給容器中添加組件,配置配裏面的所有內容才生效;

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-tY96SA5p-1591702307215)(17.png)]

那麼多的自動配置類,必須在一定的條件下才能生效;也就是說,我們加載了這麼多的配置類,但不是所有的都生效了。

我們怎麼知道哪些自動配置類生效?

我們可以通過啓用 debug=true屬性;來讓控制檯打印自動配置報告,這樣我們就可以很方便的知道哪些自動配置類生效;

#開啓springboot的調試類
debug=true

Positive matches:(自動配置類啓用的:正匹配)

Negative matches:(沒有啓動,沒有匹配成功的自動配置類:負匹配)

Unconditional classes: (沒有條件的類)

8. SpringBoot Web開發

靜態資源如何導入?

8.1 springboot靜態資源的導入源碼分析

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
        logger.debug("Default resource handling disabled");
        return;
    }
    Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
    CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
    if (!registry.hasMappingForPattern("/webjars/**")) {
        customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
                                             .addResourceLocations("classpath:/META-INF/resources/webjars/")
                                             .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
    }
    String staticPathPattern = this.mvcProperties.getStaticPathPattern();
    if (!registry.hasMappingForPattern(staticPathPattern)) {
        customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
                                             .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
                                             .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
    }
}
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
                                                              "classpath:/resources/", "classpath:/static/", "classpath:/public/" };

其中三個路徑就對應了下圖的包:

在這裏插入圖片描述

還有一個路徑對應的是依賴下的路徑位置,需要下載對應的jar包才能訪問。

這三個文件夾的優先級:

在發生三個同名的資源衝突時,生效的優先級如下

resources>static>public

訪問方式:http://localhost:8080/

8.2 總結

1.在springboot,我們可以使用以下方式處理靜態資源
webjars 1ocalhost:8080/webjars/
public, static, /**, resources localhost: 8080/
2.優先級: resources>staticI(默認) >public

    // webjars 配置
    if (!registry.hasMappingForPattern("/webjars/**")) {
        customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
                                             .addResourceLocations("classpath:/META-INF/resources/webjars/")
                                             .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
    }
    // 靜態資源配置
    String staticPathPattern = this.mvcProperties.getStaticPathPattern();
    if (!registry.hasMappingForPattern(staticPathPattern)) {
        customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
                                             .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
                                             .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
    }
}

讀一下源代碼:比如所有的 /webjars/** , 都需要去 classpath:/META-INF/resources/webjars/ 找對應的資源;

8.3 什麼是webjars 呢?

Webjars本質就是以jar包的方式引入我們的靜態資源 , 我們以前要導入一個靜態資源文件,直接導入即可。

使用SpringBoot需要使用Webjars,我們可以去搜索一下:

網站:https://www.webjars.org

要使用jQuery,我們只要要引入jQuery對應版本的pom依賴即可!

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.4.1</version>
</dependency>

導入完畢,查看webjars目錄結構,並訪問Jquery.js文件!

img

訪問:只要是靜態資源,SpringBoot就會去對應的路徑尋找資源,我們這裏訪問:http://localhost:8080/webjars/jquery/3.4.1/jquery.js

img

8.4 第二種靜態資源映射規則

那我們項目中要是使用自己的靜態資源該怎麼導入呢?我們看下一行代碼;

我們去找staticPathPattern發現第二種映射規則 :/** , 訪問當前的項目任意資源,它會去找 resourceProperties 這個類,我們可以點進去看一下分析:

// 進入方法
public String[] getStaticLocations() {
    return this.staticLocations;
}
// 找到對應的值
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
// 找到路徑
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { 
    "classpath:/META-INF/resources/",
  "classpath:/resources/", 
    "classpath:/static/", 
    "classpath:/public/" 
};

ResourceProperties 可以設置和我們靜態資源有關的參數;這裏面指向了它會去尋找資源的文件夾,即上面數組的內容。

所以得出結論,以下四個目錄存放的靜態資源可以被我們識別:

"classpath:/META-INF/resources/"
"classpath:/resources/"
"classpath:/static/"
"classpath:/public/"

我們可以在resources根目錄下新建對應的文件夾,都可以存放我們的靜態文件;

比如我們訪問 http://localhost:8080/1.js , 他就會去這些文件夾中尋找對應的靜態資源文件;

8.5 自定義靜態資源路徑

我們也可以自己通過配置文件來指定一下,哪些文件夾是需要我們放靜態資源文件的,在application.properties中配置;

spring.resources.static-locations=classpath:/coding/,classpath:/kuang/

一旦自己定義了靜態文件夾的路徑,原來的自動配置就都會失效了!

8.6 首頁處理

靜態資源文件夾說完後,我們繼續向下看源碼!可以看到一個歡迎頁的映射,就是我們的首頁!

@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
                                                           FormattingConversionService mvcConversionService,
                                                           ResourceUrlProvider mvcResourceUrlProvider) {
    WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
        new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(), // getWelcomePage 獲得歡迎頁
        this.mvcProperties.getStaticPathPattern());
    welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
    return welcomePageHandlerMapping;
}

點進去繼續看

private Optional<Resource> getWelcomePage() {
    String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
    // ::是java8 中新引入的運算符
    // Class::function的時候function是屬於Class的,應該是靜態方法。
    // this::function的funtion是屬於這個對象的。
    // 簡而言之,就是一種語法糖而已,是一種簡寫
    return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
}
// 歡迎頁就是一個location下的的 index.html 而已
private Resource getIndexHtml(String location) {
    return this.resourceLoader.getResource(location + "index.html");
}

歡迎頁,靜態資源文件夾下的所有 index.html 頁面;被 /** 映射。

比如我訪問 http://localhost:8080/ ,就會找靜態資源文件夾下的 index.html

新建一個 index.html ,在我們上面的3個目錄中任意一個;然後訪問測試 http://localhost:8080/ 看結果!

關於網站圖標說明

img

與其他靜態資源一樣,Spring Boot在配置的靜態內容位置中查找 favicon.ico。如果存在這樣的文件,它將自動用作應用程序的favicon。

1、關閉SpringBoot默認圖標

#關閉默認圖標
spring.mvc.favicon.enabled=false

2、自己放一個圖標在靜態資源目錄下,我放在 public 目錄下

3、清除瀏覽器緩存!刷新網頁,發現圖標已經變成自己的了!

img

注意:

2.3.0.RELEASE版本以後可以不用設置關閉圖標配置,直接將ico圖片放在public文件中就可以顯示

9. Thymeleaf

9.1 模板引擎

前端交給我們的頁面,是html頁面。如果是我們以前開發,我們需要把他們轉成jsp頁面,jsp好處就是當我們查出一些數據轉發到JSP頁面以後,我們可以用jsp輕鬆實現數據的顯示,及交互等。

jsp支持非常強大的功能,包括能寫Java代碼,但是呢,我們現在的這種情況,SpringBoot這個項目首先是以jar的方式,不是war,像第二,我們用的還是嵌入式的Tomcat,所以呢,他現在默認是不支持jsp的

那不支持jsp,如果我們直接用純靜態頁面的方式,那給我們開發會帶來非常大的麻煩,那怎麼辦呢?

SpringBoot推薦你可以來使用模板引擎:

模板引擎,我們其實大家聽到很多,其實jsp就是一個模板引擎,還有用的比較多的freemarker,包括SpringBoot給我們推薦的Thymeleaf,模板引擎有非常多,但再多的模板引擎,他們的思想都是一樣的,什麼樣一個思想呢我們來看一下這張圖:

img

模板引擎的作用就是我們來寫一個頁面模板,比如有些值呢,是動態的,我們寫一些表達式。而這些值,從哪來呢,就是我們在後臺封裝一些數據。然後把這個模板和這個數據交給我們模板引擎,模板引擎按照我們這個數據幫你把這表達式解析、填充到我們指定的位置,然後把這個數據最終生成一個我們想要的內容給我們寫出去,這就是我們這個模板引擎,不管是jsp還是其他模板引擎,都是這個思想。只不過呢,就是說不同模板引擎之間,他們可能這個語法有點不一樣。其他的我就不介紹了,我主要來介紹一下SpringBoot給我們推薦的Thymeleaf模板引擎,這模板引擎呢,是一個高級語言的模板引擎,他的這個語法更簡單。而且呢,功能更強大。

我們呢,就來看一下這個模板引擎,那既然要看這個模板引擎。首先,我們來看SpringBoot裏邊怎麼用。

9.2 引入Thymeleaf

怎麼引入呢,對於springboot來說,什麼事情不都是一個start的事情嘛,我們去在項目中引入一下。給大家三個網址:

Thymeleaf 官網:https://www.thymeleaf.org/

Thymeleaf 在Github 的主頁:https://github.com/thymeleaf/thymeleaf

Spring官方文檔:找到我們對應的版本

https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter

找到對應的pom依賴:可以適當點進源碼看下本來的包!

<!--thymeleaf-->
<dependency>
   <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

Maven會自動下載jar包,我們可以去看下下載的東西;

img

9.3 Thymeleaf分析

前面呢,我們已經引入了Thymeleaf,那這個要怎麼使用呢?

我們首先得按照SpringBoot的自動配置原理看一下我們這個Thymeleaf的自動配置規則,在按照那個規則,我們進行使用。

我們去找一下Thymeleaf的自動配置類:ThymeleafProperties

@ConfigurationProperties(
    prefix = "spring.thymeleaf"
)
public class ThymeleafProperties {
    private static final Charset DEFAULT_ENCODING;
    public static final String DEFAULT_PREFIX = "classpath:/templates/";
    public static final String DEFAULT_SUFFIX = ".html";
    private boolean checkTemplate = true;
    private boolean checkTemplateLocation = true;
    private String prefix = "classpath:/templates/";
    private String suffix = ".html";
    private String mode = "HTML";
    private Charset encoding;
}

我們可以在其中看到默認的前綴和後綴!

我們只需要把我們的html頁面放在類路徑下的templates下,thymeleaf就可以幫我們自動渲染了。

使用thymeleaf什麼都不需要配置,只需要將他放在指定的文件夾下即可!

9.4 測試

1、編寫一個TestController

@Controller
public class TestController {
    
    @RequestMapping("/t1")
    public String test1(){
        //classpath:/templates/test.html
        return "test";
    }
    
}

2、編寫一個測試頁面 test.html 放在 templates 目錄下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>測試頁面</h1>

</body>
</html>

3、啓動項目請求測試

9.5 Thymeleaf 語法學習

要學習語法,還是參考官網文檔最爲準確,我們找到對應的版本看一下;

Thymeleaf 官網:https://www.thymeleaf.org/ , 簡單看一下官網!我們去下載Thymeleaf的官方文檔!

我們做個最簡單的練習 :我們需要查出一些數據,在頁面中展示

1、修改測試請求,增加數據傳輸;

@RequestMapping("/t1")
public String test1(Model model){
    //存入數據
    model.addAttribute("msg","Hello,Thymeleaf");
    //classpath:/templates/test.html
    return "test";
}

2、我們要使用thymeleaf,需要在html文件中導入命名空間的約束,方便提示。

我們可以去官方文檔的#3中看一下命名空間拿來過來:

 xmlns:th="http://www.thymeleaf.org"

3、我們去編寫下前端頁面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>狂神說</title>
</head>
<body>
<h1>測試頁面</h1>

<!--th:text就是將div中的內容設置爲它指定的值,和之前學習的Vue一樣-->
<div th:text="${msg}"></div>
</body>
</html>

4、啓動測試!

img

OK,入門搞定,我們來認真研習一下Thymeleaf的使用語法!

1、我們可以使用任意的 th:attr 來替換Html中原生屬性的值!

img

2、我們能寫哪些表達式呢?

Simple expressions:(表達式語法)
Variable Expressions: ${...}:獲取變量值;OGNL;
    1)、獲取對象的屬性、調用方法
    2)、使用內置的基本對象:#18
         #ctx : the context object.
         #vars: the context variables.
         #locale : the context locale.
         #request : (only in Web Contexts) the HttpServletRequest object.
         #response : (only in Web Contexts) the HttpServletResponse object.
         #session : (only in Web Contexts) the HttpSession object.
         #servletContext : (only in Web Contexts) the ServletContext object.

    3)、內置的一些工具對象:
      #execInfo : information about the template being processed.
      #uris : methods for escaping parts of URLs/URIs
      #conversions : methods for executing the configured conversion service (if any).
      #dates : methods for java.util.Date objects: formatting, component extraction, etc.
      #calendars : analogous to #dates , but for java.util.Calendar objects.
      #numbers : methods for formatting numeric objects.
      #strings : methods for String objects: contains, startsWith, prepending/appending, etc.
      #objects : methods for objects in general.
      #bools : methods for boolean evaluation.
      #arrays : methods for arrays.
      #lists : methods for lists.
      #sets : methods for sets.
      #maps : methods for maps.
      #aggregates : methods for creating aggregates on arrays or collections.
==================================================================================

  Selection Variable Expressions: *{...}:選擇表達式:和${}在功能上是一樣;
  Message Expressions: #{...}:獲取國際化內容
  Link URL Expressions: @{...}:定義URL;
  Fragment Expressions: ~{...}:片段引用表達式

Literals(字面量)
      Text literals: 'one text' , 'Another one!' ,…
      Number literals: 0 , 34 , 3.0 , 12.3 ,…
      Boolean literals: true , false
      Null literal: null
      Literal tokens: one , sometext , main ,…
      
Text operations:(文本操作)
    String concatenation: +
    Literal substitutions: |The name is ${name}|
    
Arithmetic operations:(數學運算)
    Binary operators: + , - , * , / , %
    Minus sign (unary operator): -
    
Boolean operations:(布爾運算)
    Binary operators: and , or
    Boolean negation (unary operator): ! , not
    
Comparisons and equality:(比較運算)
    Comparators: > , < , >= , <= ( gt , lt , ge , le )
    Equality operators: == , != ( eq , ne )
    
Conditional operators:條件運算(三元運算符)
    If-then: (if) ? (then)
    If-then-else: (if) ? (then) : (else)
    Default: (value) ?: (defaultvalue)
    
Special tokens:
    No-Operation: _

9.6 練習測試

1、 我們編寫一個Controller,放一些數據

@RequestMapping("/t2")
public String test2(Map<String,Object> map){
    //存入數據
    map.put("msg","<h1>Hello</h1>");
    map.put("users", Arrays.asList("qinjiang","kuangshen"));
    //classpath:/templates/test.html
    return "test";
}

2、測試頁面取出數據

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>狂神說</title>
</head>
<body>
<h1>測試頁面</h1>

<div th:text="${msg}"></div>
<!--不轉義-->
<div th:utext="${msg}"></div>

<!--遍歷數據-->
<!--th:each每次遍歷都會生成當前這個標籤:官網#9-->
<h4 th:each="user :${users}" th:text="${user}"></h4>

<h4>
    <!--行內寫法:官網#12-->
    <span th:each="user:${users}">[[${user}]]</span>
</h4>

</body>
</html>

3、啓動項目測試!

我們看完語法,很多樣式,我們即使現在學習了,也會忘記,所以我們在學習過程中,需要使用什麼,根據官方文檔來查詢,纔是最重要的,要熟練使用官方文檔!

10. MVC自動配置原理

10.1 官網閱讀

在進行項目編寫前,我們還需要知道一個東西,就是SpringBoot對我們的SpringMVC還做了哪些配置,包括如何擴展,如何定製。

只有把這些都搞清楚了,我們在之後使用纔會更加得心應手。途徑一:源碼分析,途徑二:官方文檔!

地址 :https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#boot-features-spring-mvc-auto-configuration

Spring MVC Auto-configuration
// Spring Boot爲Spring MVC提供了自動配置,它可以很好地與大多數應用程序一起工作。
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
// 自動配置在Spring默認設置的基礎上添加了以下功能:
The auto-configuration adds the following features on top of Spring’s defaults:
// 包含視圖解析器
Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
// 支持靜態資源文件夾的路徑,以及webjars
Support for serving static resources, including support for WebJars 
// 自動註冊了Converter:
// 轉換器,這就是我們網頁提交數據到後臺自動封裝成爲對象的東西,比如把"1"字符串自動轉換爲int類型
// Formatter:【格式化器,比如頁面給我們了一個2019-8-10,它會給我們自動格式化爲Date對象】
Automatic registration of Converter, GenericConverter, and Formatter beans.
// HttpMessageConverters
// SpringMVC用來轉換Http請求和響應的的,比如我們要把一個User對象轉換爲JSON字符串,可以去看官網文檔解釋;
Support for HttpMessageConverters (covered later in this document).
// 定義錯誤代碼生成規則的
Automatic registration of MessageCodesResolver (covered later in this document).
// 首頁定製
Static index.html support.
// 圖標定製
Custom Favicon support (covered later in this document).
// 初始化數據綁定器:幫我們把請求數據綁定到JavaBean中!
Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).

/*
如果您希望保留Spring Boot MVC功能,並且希望添加其他MVC配置(攔截器、格式化程序、視圖控制器和其他功能),則可以添加自己
的@configuration類,類型爲webmvcconfiguer,但不添加@EnableWebMvc。如果希望提供
RequestMappingHandlerMapping、RequestMappingHandlerAdapter或ExceptionHandlerExceptionResolver的自定義
實例,則可以聲明WebMVCregistrationAdapter實例來提供此類組件。
*/
If you want to keep Spring Boot MVC features and you want to add additional MVC configuration 
(interceptors, formatters, view controllers, and other features), you can add your own 
@Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide 
custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or 
ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.

// 如果您想完全控制Spring MVC,可以添加自己的@Configuration,並用@EnableWebMvc進行註釋。
If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.

我們來仔細對照,看一下它怎麼實現的,它告訴我們SpringBoot已經幫我們自動配置好了SpringMVC,然後自動配置了哪些東西呢?

10.2 ContentNegotiatingViewResolver 內容協商視圖解析器

自動配置了ViewResolver,就是我們之前學習的SpringMVC的視圖解析器;

即根據方法的返回值取得視圖對象(View),然後由視圖對象決定如何渲染(轉發,重定向)。

我們去看看這裏的源碼:我們找到 WebMvcAutoConfiguration , 然後搜索ContentNegotiatingViewResolver。找到如下方法!

@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
    ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
    resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
    // ContentNegotiatingViewResolver使用所有其他視圖解析器來定位視圖,因此它應該具有較高的優先級
    resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return resolver;
}

我們可以點進這類看看!找到對應的解析視圖的代碼;

@Nullable // 註解說明:@Nullable 即參數可爲null
public View resolveViewName(String viewName, Locale locale) throws Exception {
    RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
    Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
    List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
    if (requestedMediaTypes != null) {
        // 獲取候選的視圖對象
        List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
        // 選擇一個最適合的視圖對象,然後把這個對象返回
        View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
        if (bestView != null) {
            return bestView;
        }
    }
    // .....
}

我們繼續點進去看,他是怎麼獲得候選的視圖的呢?

getCandidateViews中看到他是把所有的視圖解析器拿來,進行while循環,挨個解析!

Iterator var5 = this.viewResolvers.iterator();

所以得出結論:ContentNegotiatingViewResolver 這個視圖解析器就是用來組合所有的視圖解析器的

我們再去研究下他的組合邏輯,看到有個屬性viewResolvers,看看它是在哪裏進行賦值的!

protected void initServletContext(ServletContext servletContext) {
    // 這裏它是從beanFactory工具中獲取容器中的所有視圖解析器
    // ViewRescolver.class 把所有的視圖解析器來組合的
    Collection<ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.obtainApplicationContext(), ViewResolver.class).values();
    ViewResolver viewResolver;
    if (this.viewResolvers == null) {
        this.viewResolvers = new ArrayList(matchingBeans.size());
    }
    // ...............
}

既然它是在容器中去找視圖解析器,我們是否可以猜想,我們就可以去實現一個視圖解析器了呢?

我們可以自己給容器中去添加一個視圖解析器;這個類就會幫我們自動的將它組合進來;我們去實現一下

1、我們在我們的主程序中去寫一個視圖解析器來試試;

@Bean //放到bean中
public ViewResolver myViewResolver(){
    return new MyViewResolver();
}

//我們寫一個靜態內部類,視圖解析器就需要實現ViewResolver接口
private static class MyViewResolver implements ViewResolver{
    @Override
    public View resolveViewName(String s, Locale locale) throws Exception {
        return null;
    }
}

2、怎麼看我們自己寫的視圖解析器有沒有起作用呢?

我們給 DispatcherServlet 中的 doDispatch方法 加個斷點進行調試一下,因爲所有的請求都會走到這個方法中

img

3、我們啓動我們的項目,然後隨便訪問一個頁面,看一下Debug信息;

找到this

img

找到視圖解析器,我們看到我們自己定義的就在這裏了;

img

10.3 轉換器和格式化器

找到格式化轉換器:

@Bean
@Override
public FormattingConversionService mvcConversionService() {
    // 拿到配置文件中的格式化規則
    WebConversionService conversionService = 
        new WebConversionService(this.mvcProperties.getDateFormat());
    addFormatters(conversionService);
    return conversionService;
}

點擊去:

public String getDateFormat() {
    return this.dateFormat;
}

/**
* Date format to use. For instance, `dd/MM/yyyy`. 默認的
 */
private String dateFormat;

可以看到在我們的Properties文件中,我們可以進行自動配置它!

如果配置了自己的格式化方式,就會註冊到Bean中生效,我們可以在配置文件中配置日期格式化的規則:

img

其餘的就不一一舉例了,大家可以下去多研究探討即可!

10.4 修改SpringBoot的默認配置

這麼多的自動配置,原理都是一樣的,通過這個WebMVC的自動配置原理分析,我們要學會一種學習方式,通過源碼探究,得出結論;這個結論一定是屬於自己的,而且一通百通。

SpringBoot的底層,大量用到了這些設計細節思想,所以,沒事需要多閱讀源碼!得出結論;

SpringBoot在自動配置很多組件的時候,先看容器中有沒有用戶自己配置的(如果用戶自己配置@bean),如果有就用用戶配置的,如果沒有就用自動配置的;

如果有些組件可以存在多個,比如我們的視圖解析器,就將用戶配置的和自己默認的組合起來!

擴展使用SpringMVC 官方文檔如下:

If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.

我們要做的就是編寫一個@Configuration註解類,並且類型要爲WebMvcConfigurer,還不能標註@EnableWebMvc註解;我們去自己寫一個;我們新建一個包叫config,寫一個類MyMvcConfig;

//應爲類型要求爲WebMvcConfigurer,所以我們實現其接口
//可以使用自定義類擴展MVC的功能
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // 瀏覽器發送/test , 就會跳轉到test頁面;
        registry.addViewController("/test").setViewName("test");
    }
}

我們去瀏覽器訪問一下:

img

確實也跳轉過來了!所以說,我們要擴展SpringMVC,官方就推薦我們這麼去使用,既保SpringBoot留所有的自動配置,也能用我們擴展的配置!

我們可以去分析一下原理:

1、WebMvcAutoConfiguration 是 SpringMVC的自動配置類,裏面有一個類WebMvcAutoConfigurationAdapter

2、這個類上有一個註解,在做其他自動配置時會導入:@Import(EnableWebMvcConfiguration.class)

3、我們點進EnableWebMvcConfiguration這個類看一下,它繼承了一個父類:DelegatingWebMvcConfiguration

這個父類中有這樣一段代碼:

public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
    
  // 從容器中獲取所有的webmvcConfigurer
    @Autowired(required = false)
    public void setConfigurers(List<WebMvcConfigurer> configurers) {
        if (!CollectionUtils.isEmpty(configurers)) {
            this.configurers.addWebMvcConfigurers(configurers);
        }
    }
}

4、我們可以在這個類中去尋找一個我們剛纔設置的viewController當做參考,發現它調用了一個

protected void addViewControllers(ViewControllerRegistry registry) {
    this.configurers.addViewControllers(registry);
}

5、我們點進去看一下

public void addViewControllers(ViewControllerRegistry registry) {
    Iterator var2 = this.delegates.iterator();

    while(var2.hasNext()) {
        // 將所有的WebMvcConfigurer相關配置來一起調用!包括我們自己配置的和Spring給我們配置的
        WebMvcConfigurer delegate = (WebMvcConfigurer)var2.next();
        delegate.addViewControllers(registry);
    }

}

所以得出結論:所有的WebMvcConfiguration都會被作用,不止Spring自己的配置類,我們自己的配置類當然也會被調用;

10.5 全面接管SpringMVC

官方文檔:

If you want to take complete control of Spring MVC
you can add your own @Configuration annotated with @EnableWebMvc.

全面接管即:SpringBoot對SpringMVC的自動配置不需要了,所有都是我們自己去配置!

只需在我們的配置類中要加一個@EnableWebMvc。

我們看下如果我們全面接管了SpringMVC了,我們之前SpringBoot給我們配置的靜態資源映射一定會無效,我們可以去測試一下;

不加註解之前,訪問首頁:

img

給配置類加上註解:@EnableWebMvc

img

我們發現所有的SpringMVC自動配置都失效了!迴歸到了最初的樣子;

當然,我們開發中,不推薦使用全面接管SpringMVC

思考問題?爲什麼加了一個註解,自動配置就失效了!我們看下源碼:

1、這裏發現它是導入了一個類,我們可以繼續進去看

@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}

2、它繼承了一個父類 WebMvcConfigurationSupport

public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
  // ......
}

3、我們來回顧一下Webmvc自動配置類

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
// 這個註解的意思就是:容器中沒有這個組件的時候,這個自動配置類才生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
    ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
    
}

總結一句話:@EnableWebMvc將WebMvcConfigurationSupport組件導入進來了;

而導入的WebMvcConfigurationSupport只是SpringMVC最基本的功能!

在SpringBoot中會有非常多的擴展配置,只要看見了這個,我們就應該多留心注意~

11. 自定義Starter

我們分析完畢了源碼以及自動裝配的過程,我們可以嘗試自定義一個啓動器來玩玩!

11.1 說明

啓動器模塊是一個 空 jar 文件,僅提供輔助性依賴管理,這些依賴可能用於自動裝配或者其他類庫;

命名歸約:

官方命名:

  • 前綴:spring-boot-starter-xxx
  • 比如:spring-boot-starter-web…

自定義命名:

  • xxx-spring-boot-starter
  • 比如:mybatis-spring-boot-starter

11.2 編寫啓動器

1、在IDEA中新建一個空項目 spring-boot-starter-diy

2、新建一個普通Maven模塊:kuang-spring-boot-starter

img

3、新建一個Springboot模塊:kuang-spring-boot-starter-autoconfigure

img

4、點擊apply即可,基本結構

img

5、在我們的 starter 中 導入 autoconfigure 的依賴!

<!-- 啓動器 -->
<dependencies>
    <!--  引入自動配置模塊 -->
    <dependency>
        <groupId>com.kuang</groupId>
        <artifactId>kuang-spring-boot-starter-autoconfigure</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
</dependencies>

6、將 autoconfigure 項目下多餘的文件都刪掉,Pom中只留下一個 starter,這是所有的啓動器基本配置!

img

7、我們編寫一個自己的服務

package com.kuang;

public class HelloService {

    HelloProperties helloProperties;

    public HelloProperties getHelloProperties() {
        return helloProperties;
    }

    public void setHelloProperties(HelloProperties helloProperties) {
        this.helloProperties = helloProperties;
    }

    public String sayHello(String name){
        return helloProperties.getPrefix() + name + helloProperties.getSuffix();
    }

}

8、編寫HelloProperties 配置類

package com.kuang;

import org.springframework.boot.context.properties.ConfigurationProperties;

// 前綴 kuang.hello
@ConfigurationProperties(prefix = "kuang.hello")
public class HelloProperties {

    private String prefix;
    private String suffix;

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public String getSuffix() {
        return suffix;
    }

    public void setSuffix(String suffix) {
        this.suffix = suffix;
    }
}

9、編寫我們的自動配置類並注入bean,測試!

package com.kuang;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnWebApplication //web應用生效
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {

    @Autowired
    HelloProperties helloProperties;

    @Bean
    public HelloService helloService(){
        HelloService service = new HelloService();
        service.setHelloProperties(helloProperties);
        return service;
    }

}

10、在resources編寫一個自己的 META-INF\spring.factories

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.kuang.HelloServiceAutoConfiguration

11、編寫完成後,可以安裝到maven倉庫中!

img

11.3 新建項目測試我們自己寫的啓動器

1、新建一個SpringBoot 項目

2、導入我們自己寫的啓動器

<dependency>
    <groupId>com.kuang</groupId>
    <artifactId>kuang-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

3、編寫一個 HelloController 進行測試我們自己的寫的接口!

package com.kuang.controller;

@RestController
public class HelloController {

    @Autowired
    HelloService helloService;

    @RequestMapping("/hello")
    public String hello(){
        return helloService.sayHello("zxc");
    }

}

4、編寫配置文件 application.properties

kuang.hello.prefix="ppp"
kuang.hello.suffix="sss"

5、啓動項目進行測試,結果成功 !

img

12. 整合JDBC

12.1 SpringData簡介

對於數據訪問層,無論是 SQL(關係型數據庫) 還是 NOSQL(非關係型數據庫),Spring Boot 底層都是採用 Spring Data 的方式進行統一處理。

Spring Boot 底層都是採用 Spring Data 的方式進行統一處理各種數據庫,Spring Data 也是 Spring 中與 Spring Boot、Spring Cloud 等齊名的知名項目。

Sping Data 官網:https://spring.io/projects/spring-data

數據庫相關的啓動器 :可以參考官方文檔:

https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter

12.2 創建測試項目測試數據源

1、我去新建一個項目測試:springboot-data-jdbc ; 引入相應的模塊!基礎模塊

img

2、項目建好之後,發現自動幫我們導入瞭如下的啓動器:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

3、編寫yaml配置文件連接數據庫;

spring:
  datasource:
    username: root
    password: 123456
    #?serverTimezone=UTC解決時區的報錯
    url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver

4、配置完這一些東西后,我們就可以直接去使用了,因爲SpringBoot已經默認幫我們進行了自動配置;去測試類測試一下

@SpringBootTest
class SpringbootDataJdbcApplicationTests {

    //DI注入數據源
    @Autowired
    DataSource dataSource;

    @Test
    public void contextLoads() throws SQLException {
        //看一下默認數據源
        System.out.println(dataSource.getClass());
        //獲得連接
        Connection connection =   dataSource.getConnection();
        System.out.println(connection);
        //關閉連接
        connection.close();
    }
}

結果:我們可以看到他默認給我們配置的數據源爲 : class com.zaxxer.hikari.HikariDataSource , 我們並沒有手動配置

我們來全局搜索一下,找到數據源的所有自動配置都在 :DataSourceAutoConfiguration文件:

@Import(
    {Hikari.class, Tomcat.class, Dbcp2.class, Generic.class, DataSourceJmxConfiguration.class}
)
protected static class PooledDataSourceConfiguration {
    protected PooledDataSourceConfiguration() {
    }
}

這裏導入的類都在 DataSourceConfiguration 配置類下,可以看出 Spring Boot 2.2.5 默認使用HikariDataSource 數據源,而以前版本,如 Spring Boot 1.5 默認使用 org.apache.tomcat.jdbc.pool.DataSource 作爲數據源;

HikariDataSource 號稱 Java WEB 當前速度最快的數據源,相比於傳統的 C3P0 、DBCP、Tomcat jdbc 等連接池更加優秀;

可以使用 spring.datasource.type 指定自定義的數據源類型,值爲 要使用的連接池實現的完全限定名。

關於數據源我們並不做介紹,有了數據庫連接,顯然就可以 CRUD 操作數據庫了。但是我們需要先了解一個對象 JdbcTemplate

12.3 JDBCTemplate

1、有了數據源(com.zaxxer.hikari.HikariDataSource),然後可以拿到數據庫連接(java.sql.Connection),有了連接,就可以使用原生的 JDBC 語句來操作數據庫;

2、即使不使用第三方第數據庫操作框架,如 MyBatis等,Spring 本身也對原生的JDBC 做了輕量級的封裝,即JdbcTemplate。

3、數據庫操作的所有 CRUD 方法都在 JdbcTemplate 中。

4、Spring Boot 不僅提供了默認的數據源,同時默認已經配置好了 JdbcTemplate 放在了容器中,程序員只需自己注入即可使用

5、JdbcTemplate 的自動配置是依賴 org.springframework.boot.autoconfigure.jdbc 包下的 JdbcTemplateConfiguration 類

JdbcTemplate主要提供以下幾類方法:

  • execute方法:可以用於執行任何SQL語句,一般用於執行DDL語句;
  • update方法及batchUpdate方法:update方法用於執行新增、修改、刪除等語句;batchUpdate方法用於執行批處理相關語句;
  • query方法及queryForXXX方法:用於執行查詢相關語句;
  • call方法:用於執行存儲過程、函數相關語句。

12.4 測試

編寫一個Controller,注入 jdbcTemplate,編寫測試方法進行訪問測試;

package com.kuang.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/jdbc")
public class JdbcController {

    /**
     * Spring Boot 默認提供了數據源,默認提供了 org.springframework.jdbc.core.JdbcTemplate
     * JdbcTemplate 中會自己注入數據源,用於簡化 JDBC操作
     * 還能避免一些常見的錯誤,使用起來也不用再自己來關閉數據庫連接
     */
    @Autowired
    JdbcTemplate jdbcTemplate;

    //查詢employee表中所有數據
    //List 中的1個 Map 對應數據庫的 1行數據
    //Map 中的 key 對應數據庫的字段名,value 對應數據庫的字段值
    @GetMapping("/list")
    public List<Map<String, Object>> userList(){
        String sql = "select * from employee";
        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
        return maps;
    }
    
    //新增一個用戶
    @GetMapping("/add")
    public String addUser(){
        //插入語句,注意時間問題
        String sql = "insert into employee(last_name, email,gender,department,birth)" +
                " values ('狂神說','[email protected]',1,101,'"+ new Date().toLocaleString() +"')";
        jdbcTemplate.update(sql);
        //查詢
        return "addOk";
    }

    //修改用戶信息
    @GetMapping("/update/{id}")
    public String updateUser(@PathVariable("id") int id){
        //插入語句
        String sql = "update employee set last_name=?,email=? where id="+id;
        //數據
        Object[] objects = new Object[2];
        objects[0] = "秦疆";
        objects[1] = "[email protected]";
        jdbcTemplate.update(sql,objects);
        //查詢
        return "updateOk";
    }

    //刪除用戶
    @GetMapping("/delete/{id}")
    public String delUser(@PathVariable("id") int id){
        //插入語句
        String sql = "delete from employee where id=?";
        jdbcTemplate.update(sql,id);
        //查詢
        return "deleteOk";
    }
    
}

測試請求,結果正常;

到此,CURD的基本操作,使用 JDBC 就搞定了。

13. 集成Druid

13.1 Druid簡介

Java程序很大一部分要操作數據庫,爲了提高性能操作數據庫的時候,又不得不使用數據庫連接池。

Druid 是阿里巴巴開源平臺上一個數據庫連接池實現,結合了 C3P0、DBCP 等 DB 池的優點,同時加入了日誌監控。

Druid 可以很好的監控 DB 池連接和 SQL 的執行情況,天生就是針對監控而生的 DB 連接池。

Druid已經在阿里巴巴部署了超過600個應用,經過一年多生產環境大規模部署的嚴苛考驗。

Spring Boot 2.0 以上默認使用 Hikari 數據源,可以說 Hikari 與 Driud 都是當前 Java Web 上最優秀的數據源,我們來重點介紹 Spring Boot 如何集成 Druid 數據源,如何實現數據庫監控。

Github地址:https://github.com/alibaba/druid/

com.alibaba.druid.pool.DruidDataSource 基本配置參數如下:

img

img

img

13.2 配置數據源

1、添加上 Druid 數據源依賴。

<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.21</version>
</dependency>

2、切換數據源;之前已經說過 Spring Boot 2.0 以上默認使用 com.zaxxer.hikari.HikariDataSource 數據源,但可以 通過 spring.datasource.type 指定數據源。

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource # 自定義數據源

3、數據源切換之後,在測試類中注入 DataSource,然後獲取到它,輸出一看便知是否成功切換;

img

4、切換成功!既然切換成功,就可以設置數據源連接初始化大小、最大連接數、等待時間、最小連接數 等設置項;可以查看源碼

spring:
  datasource:
    username: root
    password: 123456
    #?serverTimezone=UTC解決時區的報錯
    url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

    #Spring Boot 默認是不注入這些屬性值的,需要自己綁定
    #druid 數據源專有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

    #配置監控統計攔截的filters,stat:監控統計、log4j:日誌記錄、wall:防禦sql注入
    #如果允許時報錯  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #則導入 log4j 依賴即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

5、導入Log4j 的依賴

<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

6、現在需要程序員自己爲 DruidDataSource 綁定全局配置文件中的參數,再添加到容器中,而不再使用 Spring Boot 的自動生成了;我們需要 自己添加 DruidDataSource 組件到容器中,並綁定屬性;

package com.kuang.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class DruidConfig {

    /*
       將自定義的 Druid數據源添加到容器中,不再讓 Spring Boot 自動創建
       綁定全局配置文件中的 druid 數據源屬性到 com.alibaba.druid.pool.DruidDataSource從而讓它們生效
       @ConfigurationProperties(prefix = "spring.datasource"):作用就是將 全局配置文件中
       前綴爲 spring.datasource的屬性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名參數中
     */
    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druidDataSource() {
        return new DruidDataSource();
    }

}

7、去測試類中測試一下;看是否成功!

@SpringBootTest
class SpringbootDataJdbcApplicationTests {

    //DI注入數據源
    @Autowired
    DataSource dataSource;

    @Test
    public void contextLoads() throws SQLException {
        //看一下默認數據源
        System.out.println(dataSource.getClass());
        //獲得連接
        Connection connection =   dataSource.getConnection();
        System.out.println(connection);

        DruidDataSource druidDataSource = (DruidDataSource) dataSource;
        System.out.println("druidDataSource 數據源最大連接數:" + druidDataSource.getMaxActive());
        System.out.println("druidDataSource 數據源初始化連接數:" + druidDataSource.getInitialSize());

        //關閉連接
        connection.close();
    }
}

輸出結果 :可見配置參數已經生效!

img

13.3 配置Druid數據源監控

Druid 數據源具有監控的功能,並提供了一個 web 界面方便用戶查看,類似安裝 路由器 時,人家也提供了一個默認的 web 頁面。

所以第一步需要設置 Druid 的後臺管理頁面,比如 登錄賬號、密碼 等;配置後臺管理;

//配置 Druid 監控管理後臺的Servlet;
//內置 Servlet 容器時沒有web.xml文件,所以使用 Spring Boot 的註冊 Servlet 方式
@Bean
public ServletRegistrationBean statViewServlet() {
    ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");

    // 這些參數可以在 com.alibaba.druid.support.http.StatViewServlet 
    // 的父類 com.alibaba.druid.support.http.ResourceServlet 中找到
    Map<String, String> initParams = new HashMap<>();
    initParams.put("loginUsername", "admin"); //後臺管理界面的登錄賬號
    initParams.put("loginPassword", "123456"); //後臺管理界面的登錄密碼

    //後臺允許誰可以訪問
    //initParams.put("allow", "localhost"):表示只有本機可以訪問
    //initParams.put("allow", ""):爲空或者爲null時,表示允許所有訪問
    initParams.put("allow", "");
    //deny:Druid 後臺拒絕誰訪問
    //initParams.put("kuangshen", "192.168.1.20");表示禁止此ip訪問

    //設置初始化參數
    bean.setInitParameters(initParams);
    return bean;
}

配置完畢後,我們可以選擇訪問 :http://localhost:8080/druid/login.html

img

進入之後

img

配置 Druid web 監控 filter 過濾器

//配置 Druid 監控 之  web 監控的 filter
//WebStatFilter:用於配置Web和Druid數據源之間的管理關聯監控統計
@Bean
public FilterRegistrationBean webStatFilter() {
    FilterRegistrationBean bean = new FilterRegistrationBean();
    bean.setFilter(new WebStatFilter());

    //exclusions:設置哪些請求進行過濾排除掉,從而不進行統計
    Map<String, String> initParams = new HashMap<>();
    initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*");
    bean.setInitParameters(initParams);

    //"/*" 表示過濾所有請求
    bean.setUrlPatterns(Arrays.asList("/*"));
    return bean;
}

平時在工作中,按需求進行配置即可,主要用作監控!

14. 整合MyBatis

官方文檔:http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/

Maven倉庫地址:https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter/2.1.1

img

14.1 整合測試

1、導入 MyBatis 所需要的依賴

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.1</version>
</dependency>

2、配置數據庫連接信息(不變)

spring:
  datasource:
    username: root
    password: 123456
    #?serverTimezone=UTC解決時區的報錯
    url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

    #Spring Boot 默認是不注入這些屬性值的,需要自己綁定
    #druid 數據源專有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

    #配置監控統計攔截的filters,stat:監控統計、log4j:日誌記錄、wall:防禦sql注入
    #如果允許時報錯  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #則導入 log4j 依賴即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

3、測試數據庫是否連接成功!

4、創建實體類,導入 Lombok!

Department.java

package com.kuang.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Department {

    private Integer id;
    private String departmentName;

}

5、創建mapper目錄以及對應的 Mapper 接口

DepartmentMapper.java

//@Mapper : 表示本類是一個 MyBatis 的 Mapper
@Mapper
@Repository
public interface DepartmentMapper {

    // 獲取所有部門信息
    List<Department> getDepartments();

    // 通過id獲得部門
    Department getDepartment(Integer id);

}

6、對應的Mapper映射文件

DepartmentMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.kuang.mapper.DepartmentMapper">

    <select id="getDepartments" resultType="Department">
       select * from department;
    </select>

    <select id="getDepartment" resultType="Department" parameterType="int">
       select * from department where id = #{id};
    </select>

</mapper>

7、maven配置資源過濾問題

<resources>
    <resource>
        <directory>src/main/java</directory>
        <includes>
            <include>**/*.xml</include>
        </includes>
        <filtering>true</filtering>
    </resource>
</resources>

8、編寫部門的 DepartmentController 進行測試!

@RestController
public class DepartmentController {
    
    @Autowired
    DepartmentMapper departmentMapper;
    
    // 查詢全部部門
    @GetMapping("/getDepartments")
    public List<Department> getDepartments(){
        return departmentMapper.getDepartments();
    }

    // 查詢全部部門
    @GetMapping("/getDepartment/{id}")
    public Department getDepartment(@PathVariable("id") Integer id){
        return departmentMapper.getDepartment(id);
    }
    
}

啓動項目訪問進行測試!

14.2 我們增加一個員工類再測試下,爲之後做準備

1、新建一個pojo類 Employee ;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee {

    private Integer id;
    private String lastName;
    private String email;
    //1 male, 0 female
    private Integer gender;
    private Integer department;
    private Date birth;

    private Department eDepartment; // 冗餘設計

}

2、新建一個 EmployeeMapper 接口

//@Mapper : 表示本類是一個 MyBatis 的 Mapper
@Mapper
@Repository
public interface EmployeeMapper {

    // 獲取所有員工信息
    List<Employee> getEmployees();

    // 新增一個員工
    int save(Employee employee);

    // 通過id獲得員工信息
    Employee get(Integer id);

    // 通過id刪除員工
    int delete(Integer id);

}

3、編寫 EmployeeMapper.xml 配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.kuang.mapper.EmployeeMapper">

    <resultMap id="EmployeeMap" type="Employee">
        <id property="id" column="eid"/>
        <result property="lastName" column="last_name"/>
        <result property="email" column="email"/>
        <result property="gender" column="gender"/>
        <result property="birth" column="birth"/>
        <association property="eDepartment"  javaType="Department">
            <id property="id" column="did"/>
            <result property="departmentName" column="dname"/>
        </association>
    </resultMap>

    <select id="getEmployees" resultMap="EmployeeMap">
        select e.id as eid,last_name,email,gender,birth,d.id as did,d.department_name as dname
        from department d,employee e
        where d.id = e.department
    </select>

    <insert id="save" parameterType="Employee">
        insert into employee (last_name,email,gender,department,birth)
        values (#{lastName},#{email},#{gender},#{department},#{birth});
    </insert>

    <select id="get" resultType="Employee">
        select * from employee where id = #{id}
    </select>

    <delete id="delete" parameterType="int">
        delete from employee where id = #{id}
    </delete>

</mapper>

4、編寫EmployeeController類進行測試

@RestController
public class EmployeeController {

    @Autowired
    EmployeeMapper employeeMapper;

    // 獲取所有員工信息
    @GetMapping("/getEmployees")
    public List<Employee> getEmployees(){
        return employeeMapper.getEmployees();
    }

    @GetMapping("/save")
    public int save(){
        Employee employee = new Employee();
        employee.setLastName("kuangshen");
        employee.setEmail("[email protected]");
        employee.setGender(1);
        employee.setDepartment(101);
        employee.setBirth(new Date());
        return employeeMapper.save(employee);
    }

    // 通過id獲得員工信息
    @GetMapping("/get/{id}")
    public Employee get(@PathVariable("id") Integer id){
        return employeeMapper.get(id);
    }

    // 通過id刪除員工
    @GetMapping("/delete/{id}")
    public int delete(@PathVariable("id") Integer id){
        return employeeMapper.delete(id);
    }

}

測試結果完成,搞定收工!

15. 頁面國際化

頁面國際化是什麼?

有的時候,我們的網站會去涉及中英文甚至多語言的切換,這時候我們就需要學習國際化了!

15.1 準備工作

先在IDEA中統一設置properties的編碼問題!

img

編寫國際化配置文件,抽取頁面需要顯示的國際化頁面消息。我們可以去登錄頁面查看一下,哪些內容我們需要編寫國際化的配置!

15.2 配置文件編寫

1、我們在resources資源文件下新建一個i18n目錄,存放國際化配置文件

2、建立一個login.properties文件,還有一個login_zh_CN.properties;發現IDEA自動識別了我們要做國際化操作;文件夾變了!

img

3、我們可以在這上面去新建一個文件;

img

彈出如下頁面:我們再添加一個英文的;

img

這樣就快捷多了!

img

4、接下來,我們就來編寫配置,我們可以看到idea下面有另外一個視圖;

img

這個視圖我們點擊 + 號就可以直接添加屬性了;我們新建一個login.tip,可以看到邊上有三個文件框可以輸入

img

我們添加一下首頁的內容!

img

然後依次添加其他頁面內容即可!

img

然後去查看我們的配置文件;

login.properties :默認

login.btn=登錄
login.password=密碼
login.remember=記住我
login.tip=請登錄
login.username=用戶名

英文:

login.btn=Sign in
login.password=Password
login.remember=Remember me
login.tip=Please sign in
login.username=Username

中文:

login.btn=登錄
login.password=密碼
login.remember=記住我
login.tip=請登錄
login.username=用戶名

OK,配置文件步驟搞定!

15.3 配置文件生效探究

我們去看一下SpringBoot對國際化的自動配置!這裏又涉及到一個類:MessageSourceAutoConfiguration

裏面有一個方法,這裏發現SpringBoot已經自動配置好了管理我們國際化資源文件的組件 ResourceBundleMessageSource;

// 獲取 properties 傳遞過來的值進行判斷
@Bean
public MessageSource messageSource(MessageSourceProperties properties) {
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    if (StringUtils.hasText(properties.getBasename())) {
        // 設置國際化文件的基礎名(去掉語言國家代碼的)
        messageSource.setBasenames(
            StringUtils.commaDelimitedListToStringArray(
                                       StringUtils.trimAllWhitespace(properties.getBasename())));
    }
    if (properties.getEncoding() != null) {
        messageSource.setDefaultEncoding(properties.getEncoding().name());
    }
    messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
    Duration cacheDuration = properties.getCacheDuration();
    if (cacheDuration != null) {
        messageSource.setCacheMillis(cacheDuration.toMillis());
    }
    messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
    messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
    return messageSource;
}

我們真實 的情況是放在了i18n目錄下,所以我們要去配置這個messages的路徑;

spring.messages.basename=i18n.login

15.4 配置頁面國際化值

去頁面獲取國際化的值,查看Thymeleaf的文檔,找到message取值操作爲:#{…}。我們去頁面測試下:

IDEA還有提示,非常智能的!

img

我們可以去啓動項目,訪問一下,發現已經自動識別爲中文的了!

在這裏插入圖片描述

但是我們想要更好!可以根據按鈕自動切換中文英文!

15.5 配置國際化解析

在Spring中有一個國際化的Locale (區域信息對象);裏面有一個叫做LocaleResolver (獲取區域信息對象)的解析器!

我們去我們webmvc自動配置文件,尋找一下!看到SpringBoot默認配置:


@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
    // 容器中沒有就自己配,有的話就用用戶配置的
    if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
        return new FixedLocaleResolver(this.mvcProperties.getLocale());
    }
    // 接收頭國際化分解
    AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
    localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
    return localeResolver;
}

AcceptHeaderLocaleResolver 這個類中有一個方法

public Locale resolveLocale(HttpServletRequest request) {
    Locale defaultLocale = this.getDefaultLocale();
    // 默認的就是根據請求頭帶來的區域信息獲取Locale進行國際化
    if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
        return defaultLocale;
    } else {
        Locale requestLocale = request.getLocale();
        List<Locale> supportedLocales = this.getSupportedLocales();
        if (!supportedLocales.isEmpty() && !supportedLocales.contains(requestLocale)) {
            Locale supportedLocale = this.findSupportedLocale(request, supportedLocales);
            if (supportedLocale != null) {
                return supportedLocale;
            } else {
                return defaultLocale != null ? defaultLocale : requestLocale;
            }
        } else {
            return requestLocale;
        }
    }
}

那假如我們現在想點擊鏈接讓我們的國際化資源生效,就需要讓我們自己的Locale生效!

我們去自己寫一個自己的LocaleResolver,可以在鏈接上攜帶區域信息!

修改一下前端頁面的跳轉連接:

<!-- 這裏傳入參數不需要使用 ?使用 (key=value)-->
<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>

我們去寫一個處理的組件類!


package com.kuang.component;

import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

//可以在鏈接上攜帶區域信息
public class MyLocaleResolver implements LocaleResolver {

    //解析請求
    @Override
    public Locale resolveLocale(HttpServletRequest request) {

        String language = request.getParameter("l");
        Locale locale = Locale.getDefault(); // 如果沒有獲取到就使用系統默認的
        //如果請求鏈接不爲空
        if (!StringUtils.isEmpty(language)){
            //分割請求參數
            String[] split = language.split("_");
            //國家,地區
            locale = new Locale(split[0],split[1]);
        }
        return locale;
    }

    @Override
    public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {

    }
}

爲了讓我們的區域化信息能夠生效,我們需要再配置一下這個組件!在我們自己的MvcConofig下添加bean;

@Bean
public LocaleResolver localeResolver(){
    return new MyLocaleResolver();
}

我們重啓項目,來訪問一下,發現點擊按鈕可以實現成功切換!搞定收工!

16. 集成SpringSecurity

SpringSecurity是什麼?

16.1 安全簡介

在 Web 開發中,安全一直是非常重要的一個方面。安全雖然屬於應用的非功能性需求,但是應該在應用開發的初期就考慮進來。如果在應用開發的後期才考慮安全的問題,就可能陷入一個兩難的境地:一方面,應用存在嚴重的安全漏洞,無法滿足用戶的要求,並可能造成用戶的隱私數據被攻擊者竊取;另一方面,應用的基本架構已經確定,要修復安全漏洞,可能需要對系統的架構做出比較重大的調整,因而需要更多的開發時間,影響應用的發佈進程。因此,從應用開發的第一天就應該把安全相關的因素考慮進來,並在整個應用的開發過程中。

市面上存在比較有名的:Shiro,Spring Security !

這裏需要闡述一下的是,每一個框架的出現都是爲了解決某一問題而產生了,那麼Spring Security框架的出現是爲了解決什麼問題呢?

首先我們看下它的官網介紹:

Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.

Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements

Spring Security是一個功能強大且高度可定製的身份驗證和訪問控制框架。它實際上是保護基於spring的應用程序的標準。

Spring Security是一個框架,側重於爲Java應用程序提供身份驗證和授權。與所有Spring項目一樣,Spring安全性的真正強大之處在於它可以輕鬆地擴展以滿足定製需求

從官網的介紹中可以知道這是一個權限框架。想我們之前做項目是沒有使用框架是怎麼控制權限的?對於權限 一般會細分爲功能權限,訪問權限,和菜單權限。代碼會寫的非常的繁瑣,冗餘。

怎麼解決之前寫權限代碼繁瑣,冗餘的問題,一些主流框架就應運而生而Spring Scecurity就是其中的一種。

Spring 是一個非常流行和成功的 Java 應用開發框架。Spring Security 基於 Spring 框架,提供了一套 Web 應用安全性的完整解決方案。一般來說,Web 應用的安全性包括用戶認證(Authentication)和用戶授權(Authorization)兩個部分。用戶認證指的是驗證某個用戶是否爲系統中的合法主體,也就是說用戶能否訪問該系統。用戶認證一般要求用戶提供用戶名和密碼。系統通過校驗用戶名和密碼來完成認證過程。用戶授權指的是驗證某個用戶是否有權限執行某個操作。在一個系統中,不同用戶所具有的權限是不同的。比如對一個文件來說,有的用戶只能進行讀取,而有的用戶可以進行修改。一般來說,系統會爲不同的用戶分配不同的角色,而每個角色則對應一系列的權限。

對於上面提到的兩種應用情景,Spring Security 框架都有很好的支持。在用戶認證方面,Spring Security 框架支持主流的認證方式,包括 HTTP 基本認證、HTTP 表單驗證、HTTP 摘要認證、OpenID 和 LDAP 等。在用戶授權方面,Spring Security 提供了基於角色的訪問控制和訪問控制列表(Access Control List,ACL),可以對應用中的領域對象進行細粒度的控制。

16.2 實戰測試

16.2.1 實驗環境搭建

1、新建一個初始的springboot項目web模塊,thymeleaf模塊

2、導入靜態資源

welcome.html
|views
|level1
1.html
2.html
3.html
|level2
1.html
2.html
3.html
|level3
1.html
2.html
3.html
Login.html

3、controller跳轉!

package com.kuang.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class RouterController {

   @RequestMapping({"/","/index"})
   public String index(){
       return "index";
  }

   @RequestMapping("/toLogin")
   public String toLogin(){
       return "views/login";
  }

   @RequestMapping("/level1/{id}")
   public String level1(@PathVariable("id") int id){
       return "views/level1/"+id;
  }

   @RequestMapping("/level2/{id}")
   public String level2(@PathVariable("id") int id){
       return "views/level2/"+id;
  }

   @RequestMapping("/level3/{id}")
   public String level3(@PathVariable("id") int id){
       return "views/level3/"+id;
  }

}

4、測試實驗環境是否OK!

16.2.2 認識SpringSecurity

Spring Security 是針對Spring項目的安全框架,也是Spring Boot底層安全模塊默認的技術選型,他可以實現強大的Web安全控制,對於安全控制,我們僅需要引入 spring-boot-starter-security 模塊,進行少量的配置,即可實現強大的安全管理!

記住幾個類:

  • WebSecurityConfigurerAdapter:自定義Security策略
  • AuthenticationManagerBuilder:自定義認證策略
  • @EnableWebSecurity:開啓WebSecurity模式

Spring Security的兩個主要目標是 “認證” 和 “授權”(訪問控制)。

“認證”(Authentication)

身份驗證是關於驗證您的憑據,如用戶名/用戶ID和密碼,以驗證您的身份。

身份驗證通常通過用戶名和密碼完成,有時與身份驗證因素結合使用。

“授權” (Authorization)

授權發生在系統成功驗證您的身份後,最終會授予您訪問資源(如信息,文件,數據庫,資金,位置,幾乎任何內容)的完全權限。

這個概念是通用的,而不是隻在Spring Security 中存在。

16.2.3 認證和授權

目前,我們的測試環境,是誰都可以訪問的,我們使用 Spring Security 增加上認證和授權的功能

1、引入 Spring Security 模塊

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

2、編寫 Spring Security 配置類

參考官網:https://spring.io/projects/spring-security

查看我們自己項目中的版本,找到對應的幫助文檔:

https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5 #servlet-applications 8.16.4

在這裏插入圖片描述

3、編寫基礎配置類

package com.kuang.config;

import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity // 開啓WebSecurity模式
public class SecurityConfig extends WebSecurityConfigurerAdapter {

   @Override
   protected void configure(HttpSecurity http) throws Exception {
       
  }
}

4、定製請求的授權規則

@Override
protected void configure(HttpSecurity http) throws Exception {
   // 定製請求的授權規則
   // 首頁所有人可以訪問
   http.authorizeRequests().antMatchers("/").permitAll()
  .antMatchers("/level1/**").hasRole("vip1")
  .antMatchers("/level2/**").hasRole("vip2")
  .antMatchers("/level3/**").hasRole("vip3");
}

5、測試一下:發現除了首頁都進不去了!因爲我們目前沒有登錄的角色,因爲請求需要登錄的角色擁有對應的權限纔可以!

6、在configure()方法中加入以下配置,開啓自動配置的登錄功能!

// 開啓自動配置的登錄功能
// /login 請求來到登錄頁
// /login?error 重定向到這裏表示登錄失敗
http.formLogin();

7、測試一下:發現,沒有權限的時候,會跳轉到登錄的頁面!

在這裏插入圖片描述

8、查看剛纔登錄頁的註釋信息;

我們可以定義認證規則,重寫configure(AuthenticationManagerBuilder auth)方法

//定義認證規則
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
   
   //在內存中定義,也可以在jdbc中去拿....
   auth.inMemoryAuthentication()
          .withUser("kuangshen").password("123456").roles("vip2","vip3")
          .and()
          .withUser("root").password("123456").roles("vip1","vip2","vip3")
          .and()
          .withUser("guest").password("123456").roles("vip1","vip2");
}

9、測試,我們可以使用這些賬號登錄進行測試!發現會報錯!

There is no PasswordEncoder mapped for the id “null”

在這裏插入圖片描述

10、原因,我們要將前端傳過來的密碼進行某種方式加密,否則就無法登錄,修改代碼

//定義認證規則
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
   //在內存中定義,也可以在jdbc中去拿....
   //Spring security 5.0中新增了多種加密方式,也改變了密碼的格式。
   //要想我們的項目還能夠正常登陸,需要修改一下configure中的代碼。我們要將前端傳過來的密碼進行某種方式加密
   //spring security 官方推薦的是使用bcrypt加密方式。
   
   auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
          .withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
          .and()
          .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
          .and()
          .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2");
}

11、測試,發現,登錄成功,並且每個角色只能訪問自己認證下的規則!搞定

16.2.4 權限控制和註銷

1、開啓自動配置的註銷的功能

//定製請求的授權規則
@Override
protected void configure(HttpSecurity http) throws Exception {
   //....
   //開啓自動配置的註銷的功能
      // /logout 註銷請求
   http.logout();
}

2、我們在前端,增加一個註銷的按鈕,index.html 導航欄中

<a class="item" th:href="@{/logout}">
   <i class="address card icon"></i> 註銷
</a>

3、我們可以去測試一下,登錄成功後點擊註銷,發現註銷完畢會跳轉到登錄頁面!

4、但是,我們想讓他註銷成功後,依舊可以跳轉到首頁,該怎麼處理呢?

// .logoutSuccessUrl("/"); 註銷成功來到首頁
http.logout().logoutSuccessUrl("/");

5、測試,註銷完畢後,發現跳轉到首頁OK

6、我們現在又來一個需求:用戶沒有登錄的時候,導航欄上只顯示登錄按鈕,用戶登錄之後,導航欄可以顯示登錄的用戶信息及註銷按鈕!還有就是,比如kuangshen這個用戶,它只有 vip2,vip3功能,那麼登錄則只顯示這兩個功能,而vip1的功能菜單不顯示!這個就是真實的網站情況了!該如何做呢?

我們需要結合thymeleaf中的一些功能

sec:authorize=“isAuthenticated()”:是否認證登錄!來顯示不同的頁面

Maven依賴:

<!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity4 -->
<dependency>
   <groupId>org.thymeleaf.extras</groupId>
   <artifactId>thymeleaf-extras-springsecurity5</artifactId>
   <version>3.0.4.RELEASE</version>
</dependency>

7、修改我們的 前端頁面

  1. 導入命名空間

  2. xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
    
  3. 修改導航欄,增加認證判斷

  4. <!--登錄註銷-->
    <div class="right menu">
    
       <!--如果未登錄-->
       <div sec:authorize="!isAuthenticated()">
           <a class="item" th:href="@{/login}">
               <i class="address card icon"></i> 登錄
           </a>
       </div>
    
       <!--如果已登錄-->
       <div sec:authorize="isAuthenticated()">
           <a class="item">
               <i class="address card icon"></i>
              用戶名:<span sec:authentication="principal.username"></span>
              角色:<span sec:authentication="principal.authorities"></span>
           </a>
       </div>
    
       <div sec:authorize="isAuthenticated()">
           <a class="item" th:href="@{/logout}">
               <i class="address card icon"></i> 註銷
           </a>
       </div>
    </div>
    

8、重啓測試,我們可以登錄試試看,登錄成功後確實,顯示了我們想要的頁面;

9、如果註銷404了,就是因爲它默認防止csrf跨站請求僞造,因爲會產生安全問題,我們可以將請求改爲post表單提交,或者在spring security中關閉csrf功能;我們試試:在 配置中增加 http.csrf().disable();

http.csrf().disable();//關閉csrf功能:跨站請求僞造,默認只能通過post方式提交logout請求
http.logout().logoutSuccessUrl("/");

10、我們繼續將下面的角色功能塊認證完成!

<!-- sec:authorize="hasRole('vip1')" -->
<div class="column" sec:authorize="hasRole('vip1')">
   <div class="ui raised segment">
       <div class="ui">
           <div class="content">
               <h5 class="content">Level 1</h5>
               <hr>
               <div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
               <div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
               <div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
           </div>
       </div>
   </div>
</div>

<div class="column" sec:authorize="hasRole('vip2')">
   <div class="ui raised segment">
       <div class="ui">
           <div class="content">
               <h5 class="content">Level 2</h5>
               <hr>
               <div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
               <div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
               <div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
           </div>
       </div>
   </div>
</div>

<div class="column" sec:authorize="hasRole('vip3')">
   <div class="ui raised segment">
       <div class="ui">
           <div class="content">
               <h5 class="content">Level 3</h5>
               <hr>
               <div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
               <div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
               <div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
           </div>
       </div>
   </div>
</div>

11、測試一下!

12、權限控制和註銷搞定!

16.2.5 記住我

現在的情況,我們只要登錄之後,關閉瀏覽器,再登錄,就會讓我們重新登錄,但是很多網站的情況,就是有一個記住密碼的功能,這個該如何實現呢?很簡單

1、開啓記住我功能

//定製請求的授權規則
@Override
protected void configure(HttpSecurity http) throws Exception {
   //記住我 此處是cookie
   http.rememberMe();
}

2、我們再次啓動項目測試一下,發現登錄頁多了一個記住我功能,我們登錄之後關閉 瀏覽器,然後重新打開瀏覽器訪問,發現用戶依舊存在!

思考:如何實現的呢?其實非常簡單

我們可以查看瀏覽器的cookie

在這裏插入圖片描述

3、我們點擊註銷的時候,可以發現,spring security 幫我們自動刪除了這個 cookie

在這裏插入圖片描述

4、結論:登錄成功後,將cookie發送給瀏覽器保存,以後登錄帶上這個cookie,只要通過檢查就可以免登錄了。如果點擊註銷,則會刪除這個cookie,具體的原理我們在JavaWeb階段都講過了,這裏就不在多說了!

16.2.6 定製登錄頁

現在這個登錄頁面都是spring security 默認的,怎麼樣可以使用我們自己寫的Login界面呢?

1、在剛纔的登錄頁配置後面指定 loginpage

http.formLogin().loginPage("/toLogin");

2、然後前端也需要指向我們自己定義的 login請求

<a class="item" th:href="@{/toLogin}">
   <i class="address card icon"></i> 登錄
</a>

3、我們登錄,需要將這些信息發送到哪裏,我們也需要配置,login.html 配置提交請求及方式,方式必須爲post:

在 loginPage()源碼中的註釋上有寫明:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-PaEiSVDb-1591702307319)(…/…/AppData/Roaming/Typora/typora-user-images/image-20200528151220095.png)]

<form th:action="@{/login}" method="post">
   <div class="field">
       <label>Username</label>
       <div class="ui left icon input">
           <input type="text" placeholder="Username" name="username">
           <i class="user icon"></i>
       </div>
   </div>
   <div class="field">
       <label>Password</label>
       <div class="ui left icon input">
           <input type="password" name="password">
           <i class="lock icon"></i>
       </div>
   </div>
   <input type="submit" class="ui blue submit button"/>
</form>

4、這個請求提交上來,我們還需要驗證處理,怎麼做呢?我們可以查看formLogin()方法的源碼!我們配置接收登錄的用戶名和密碼的參數!

http.formLogin()
  .usernameParameter("username")
  .passwordParameter("password")
  .loginPage("/toLogin")
  .loginProcessingUrl("/login"); // 登陸表單提交請求

5、在登錄頁增加記住我的多選框

<input type="checkbox" name="remember"> 記住我

6、後端驗證處理!

//定製記住我的參數!
http.rememberMe().rememberMeParameter("remember");

7、測試,OK

16.3 完整配置代碼

package com.kuang.config;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

   //定製請求的授權規則
   @Override
   protected void configure(HttpSecurity http) throws Exception {

       http.authorizeRequests().antMatchers("/").permitAll()
      .antMatchers("/level1/**").hasRole("vip1")
      .antMatchers("/level2/**").hasRole("vip2")
      .antMatchers("/level3/**").hasRole("vip3");


       //開啓自動配置的登錄功能:如果沒有權限,就會跳轉到登錄頁面!
           // /login 請求來到登錄頁
           // /login?error 重定向到這裏表示登錄失敗
       http.formLogin()
          .usernameParameter("username")
          .passwordParameter("password")
          .loginPage("/toLogin")
          .loginProcessingUrl("/login"); // 登陸表單提交請求

       //開啓自動配置的註銷的功能
           // /logout 註銷請求
           // .logoutSuccessUrl("/"); 註銷成功來到首頁

       http.csrf().disable();//關閉csrf功能:跨站請求僞造,默認只能通過post方式提交logout請求
       http.logout().logoutSuccessUrl("/");

       //記住我
       http.rememberMe().rememberMeParameter("remember");
  }

   //定義認證規則
   @Override
   protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       //在內存中定義,也可以在jdbc中去拿....
       //Spring security 5.0中新增了多種加密方式,也改變了密碼的格式。
       //要想我們的項目還能夠正常登陸,需要修改一下configure中的代碼。我們要將前端傳過來的密碼進行某種方式加密
       //spring security 官方推薦的是使用bcrypt加密方式。

       auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
              .withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
              .and()
              .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
              .and()
              .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2");
  }
}

17. 集成Swagger終極版

項目集成Swagger

img

學習目標:

  • 瞭解Swagger的概念及作用
  • 掌握在項目中集成Swagger自動生成API文檔

17.1 Swagger簡介

前後端分離

  • 前端 -> 前端控制層、視圖層
  • 後端 -> 後端控制層、服務層、數據訪問層
  • 前後端通過API進行交互
  • 前後端相對獨立且松耦合

產生的問題

  • 前後端集成,前端或者後端無法做到“及時協商,儘早解決”,最終導致問題集中爆發

解決方案

  • 首先定義schema [ 計劃的提綱 ],並實時跟蹤最新的API,降低集成風險

Swagger

  • 號稱世界上最流行的API框架
  • Restful Api 文檔在線自動生成器 => API 文檔 與API 定義同步更新
  • 直接運行,在線測試API
  • 支持多種語言 (如:Java,PHP等)
  • 官網:https://swagger.io/

17.2 SpringBoot集成Swagger

SpringBoot集成Swagger => springfox,兩個jar包

  • Springfox-swagger2
  • swagger-springmvc

使用Swagger

要求:jdk 1.8 + 否則swagger2無法運行

步驟:

1、新建一個SpringBoot-web項目

2、添加Maven依賴

<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
   <groupId>io.springfox</groupId>
   <artifactId>springfox-swagger2</artifactId>
   <version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
   <groupId>io.springfox</groupId>
   <artifactId>springfox-swagger-ui</artifactId>
   <version>2.9.2</version>
</dependency>

3、編寫HelloController,測試確保運行成功!

4、要使用Swagger,我們需要編寫一個配置類-SwaggerConfig來配置 Swagger

@Configuration //配置類
@EnableSwagger2// 開啓Swagger2的自動配置
public class SwaggerConfig {  
}

5、訪問測試 :http://localhost:8080/swagger-ui.html ,可以看到swagger的界面;

img

17.3 配置Swagger

1、Swagger實例Bean是Docket,所以通過配置Docket實例來配置Swaggger。

@Bean //配置docket以配置Swagger具體參數
public Docket docket() {
   return new Docket(DocumentationType.SWAGGER_2);
}

2、可以通過apiInfo()屬性配置文檔信息

//配置文檔信息
private ApiInfo apiInfo() {
   Contact contact = new Contact("聯繫人名字", "http://xxx.xxx.com/聯繫人訪問鏈接", "聯繫人郵箱");
   return new ApiInfo(
           "Swagger學習", // 標題
           "學習演示如何配置Swagger", // 描述
           "v1.0", // 版本
           "http://terms.service.url/組織鏈接", // 組織鏈接
           contact, // 聯繫人信息
           "Apach 2.0 許可", // 許可
           "許可鏈接", // 許可連接
           new ArrayList<>()// 擴展
  );
}

3、Docket 實例關聯上 apiInfo()

@Bean
public Docket docket() {
   return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
}

4、重啓項目,訪問測試 http://localhost:8080/swagger-ui.html 看下效果;

17.4 配置掃描接口

1、構建Docket時通過select()方法配置怎麼掃描接口。

@Bean
public Docket docket() {
   return new Docket(DocumentationType.SWAGGER_2)
      .apiInfo(apiInfo())
      .select()// 通過.select()方法,去配置掃描接口,RequestHandlerSelectors配置如何掃描接口
      .apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller"))
      .build();
}

2、重啓項目測試,由於我們配置根據包的路徑掃描接口,所以我們只能看到一個類

3、除了通過包路徑配置掃描接口外,還可以通過配置其他方式掃描接口,這裏註釋一下所有的配置方式:

any() // 掃描所有,項目中的所有接口都會被掃描到
none() // 不掃描接口
// 通過方法上的註解掃描,如withMethodAnnotation(GetMapping.class)只掃描get請求
withMethodAnnotation(final Class<? extends Annotation> annotation)
// 通過類上的註解掃描,如.withClassAnnotation(Controller.class)只掃描有controller註解的類中的接口
withClassAnnotation(final Class<? extends Annotation> annotation)
basePackage(final String basePackage) // 根據包路徑掃描接口

4、除此之外,我們還可以配置接口掃描過濾:

@Bean
public Docket docket() {
   return new Docket(DocumentationType.SWAGGER_2)
      .apiInfo(apiInfo())
      .select()// 通過.select()方法,去配置掃描接口,RequestHandlerSelectors配置如何掃描接口
      .apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller"))
       // 配置如何通過path過濾,即這裏只掃描請求以/kuang開頭的接口
      .paths(PathSelectors.ant("/kuang/**"))
      .build();
}

5、這裏的可選值還有

any() // 任何請求都掃描
none() // 任何請求都不掃描
regex(final String pathRegex) // 通過正則表達式控制
ant(final String antPattern) // 通過ant()控制

img

17.5 配置Swagger開關

1、通過enable()方法配置是否啓用swagger,如果是false,swagger將不能在瀏覽器中訪問了

@Bean
public Docket docket() {
   return new Docket(DocumentationType.SWAGGER_2)
      .apiInfo(apiInfo())
      .enable(false) //配置是否啓用Swagger,如果是false,在瀏覽器將無法訪問
      .select()// 通過.select()方法,去配置掃描接口,RequestHandlerSelectors配置如何掃描接口
      .apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller"))
       // 配置如何通過path過濾,即這裏只掃描請求以/kuang開頭的接口
      .paths(PathSelectors.ant("/kuang/**"))
      .build();
}

2、如何動態配置當項目處於test、dev環境時顯示swagger,處於prod時不顯示?

@Bean
public Docket docket(Environment environment) {
   // 設置要顯示swagger的環境
   Profiles of = Profiles.of("dev", "test");
   // 判斷當前是否處於該環境
   // 通過 enable() 接收此參數判斷是否要顯示
   boolean b = environment.acceptsProfiles(of);
   
   return new Docket(DocumentationType.SWAGGER_2)
      .apiInfo(apiInfo())
      .enable(b) //配置是否啓用Swagger,如果是false,在瀏覽器將無法訪問
      .select()// 通過.select()方法,去配置掃描接口,RequestHandlerSelectors配置如何掃描接口
      .apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller"))
       // 配置如何通過path過濾,即這裏只掃描請求以/kuang開頭的接口
      .paths(PathSelectors.ant("/kuang/**"))
      .build();
}

3、可以在項目中增加一個dev的配置文件查看效果!

17.6 配置API分組

1、如果沒有配置分組,默認是default。通過groupName()方法即可配置分組:

@Bean
public Docket docket(Environment environment) {
   return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
      .groupName("hello") // 配置分組
       // 省略配置....
}

2、重啓項目查看分組

3、如何配置多個分組?配置多個分組只需要配置多個docket即可:

@Bean
public Docket docket1(){
   return new Docket(DocumentationType.SWAGGER_2).groupName("group1");
}
@Bean
public Docket docket2(){
   return new Docket(DocumentationType.SWAGGER_2).groupName("group2");
}
@Bean
public Docket docket3(){
   return new Docket(DocumentationType.SWAGGER_2).groupName("group3");
}

4、重啓項目查看即可

17.7 實體配置

1、新建一個實體類

@ApiModel("用戶實體")
public class User {
   @ApiModelProperty("用戶名")
   public String username;
   @ApiModelProperty("密碼")
   public String password;
}

2、只要這個實體在請求接口的返回值上(即使是泛型),都能映射到實體項中:

@RequestMapping("/getUser")
public User getUser(){
   return new User();
}

3、重啓查看測試

注:並不是因爲@ApiModel這個註解讓實體顯示在這裏了,而是隻要出現在接口方法的返回值上的實體都會顯示在這裏,而@ApiModel和@ApiModelProperty這兩個註解只是爲實體添加註釋的。

@ApiModel爲類添加註釋

@ApiModelProperty爲類屬性添加註釋

17.8 常用註解

Swagger的所有註解定義在io.swagger.annotations包下

下面列一些經常用到的,未列舉出來的可以另行查閱說明:

Swagger註解 簡單說明
@Api(tags = “xxx模塊說明”) 作用在模塊類上
@ApiOperation(“xxx接口說明”) 作用在接口方法上
@ApiModel(“xxxPOJO說明”) 作用在模型類上:如VO、BO
@ApiModelProperty(value = “xxx屬性說明”,hidden = true) 作用在類方法和屬性上,hidden設置爲true可以隱藏該屬性
@ApiParam(“xxx參數說明”) 作用在參數、方法和字段上,類似@ApiModelProperty

我們也可以給請求的接口配置一些註釋

@ApiOperation("狂神的接口")
@PostMapping("/kuang")
@ResponseBody
public String kuang(@ApiParam("這個名字會被返回")String username){
   return username;
}

這樣的話,可以給一些比較難理解的屬性或者接口,增加一些配置信息,讓人更容易閱讀!

相較於傳統的Postman或Curl方式測試接口,使用swagger簡直就是傻瓜式操作,不需要額外說明文檔(寫得好本身就是文檔)而且更不容易出錯,只需要錄入數據然後點擊Execute,如果再配合自動化框架,可以說基本就不需要人爲操作了。

Swagger是個優秀的工具,現在國內已經有很多的中小型互聯網公司都在使用它,相較於傳統的要先出Word接口文檔再測試的方式,顯然這樣也更符合現在的快速迭代開發行情。當然了,提醒下大家在正式環境要記得關閉Swagger,一來出於安全考慮二來也可以節省運行時內存。

拓展:17.9 其他皮膚

我們可以導入不同的包實現不同的皮膚定義:

1、默認的 訪問 http://localhost:8080/swagger-ui.html

<dependency>
   <groupId>io.springfox</groupId>
   <artifactId>springfox-swagger-ui</artifactId>
   <version>2.9.2</version>
</dependency>

2、bootstrap-ui 訪問 http://localhost:8080/doc.html

<!-- 引入swagger-bootstrap-ui包 /doc.html-->
<dependency>
   <groupId>com.github.xiaoymin</groupId>
   <artifactId>swagger-bootstrap-ui</artifactId>
   <version>1.9.1</version>
</dependency>

img

3、Layui-ui 訪問 http://localhost:8080/docs.html

<!-- 引入swagger-ui-layer包 /docs.html-->
<dependency>
   <groupId>com.github.caspar-chen</groupId>
   <artifactId>swagger-ui-layer</artifactId>
   <version>1.1.3</version>
</dependency>

img

4、mg-ui 訪問 http://localhost:8080/document.html

<!-- 引入swagger-ui-layer包 /document.html-->
<dependency>
   <groupId>com.zyplayer</groupId>
   <artifactId>swagger-mg-ui</artifactId>
   <version>1.0.6</version>
</dependency>

img

18. 異步、定時、郵件任務

前言

在我們的工作中,常常會用到異步處理任務,比如我們在網站上發送郵件,後臺會去發送郵件,此時前臺會造成響應不動,直到郵件發送完畢,響應纔會成功,所以我們一般會採用多線程的方式去處理這些任務。還有一些定時任務,比如需要在每天凌晨的時候,分析一次前一天的日誌信息。還有就是郵件的發送,微信的前身也是郵件服務呢?這些東西都是怎麼實現的呢?其實SpringBoot都給我們提供了對應的支持,我們上手使用十分的簡單,只需要開啓一些註解支持,配置一些配置文件即可!那我們來看看吧~

最後編輯於2020.3.26 作者:狂神說

18.1 異步任務

1、創建一個service包

2、創建一個類AsyncService

異步處理還是非常常用的,比如我們在網站上發送郵件,後臺會去發送郵件,此時前臺會造成響應不動,直到郵件發送完畢,響應纔會成功,所以我們一般會採用多線程的方式去處理這些任務。

編寫方法,假裝正在處理數據,使用線程設置一些延時,模擬同步等待的情況;

@Service
public class AsyncService {

   public void hello(){
       try {
           Thread.sleep(3000);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       System.out.println("業務進行中....");
  }
}

3、編寫controller包

4、編寫AsyncController類

我們去寫一個Controller測試一下

@RestController
public class AsyncController {

   @Autowired
   AsyncService asyncService;

   @GetMapping("/hello")
   public String hello(){
       asyncService.hello();
       return "success";
  }

}

5、訪問http://localhost:8080/hello進行測試,3秒後出現success,這是同步等待的情況。

問題:我們如果想讓用戶直接得到消息,就在後臺使用多線程的方式進行處理即可,但是每次都需要自己手動去編寫多線程的實現的話,太麻煩了,我們只需要用一個簡單的辦法,在我們的方法上加一個簡單的註解即可,如下:

6、給hello方法添加@Async註解;

//告訴Spring這是一個異步方法
@Async
public void hello(){
   try {
       Thread.sleep(3000);
  } catch (InterruptedException e) {
       e.printStackTrace();
  }
   System.out.println("業務進行中....");
}

SpringBoot就會自己開一個線程池,進行調用!但是要讓這個註解生效,我們還需要在主程序上添加一個註解@EnableAsync ,開啓異步註解功能;

@EnableAsync //開啓異步註解功能
@SpringBootApplication
public class SpringbootTaskApplication {

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

}

7、重啓測試,網頁瞬間響應,後臺代碼依舊執行!

18.2 定時任務

項目開發中經常需要執行一些定時任務,比如需要在每天凌晨的時候,分析一次前一天的日誌信息,Spring爲我們提供了異步執行任務調度的方式,提供了兩個接口。

  • TaskExecutor接口
  • TaskScheduler接口

兩個註解:

  • @EnableScheduling
  • @Scheduled

cron表達式:

img

img

測試步驟:

1、創建一個ScheduledService

我們裏面存在一個hello方法,他需要定時執行,怎麼處理呢?

@Service
public class ScheduledService {
   
   //秒   分   時     日   月   周幾
   //0 * * * * MON-FRI
   //注意cron表達式的用法;
   @Scheduled(cron = "0 * * * * 0-7")
   public void hello(){
       System.out.println("hello.....");
  }
}

2、這裏寫完定時任務之後,我們需要在主程序上增加@EnableScheduling 開啓定時任務功能

@EnableAsync //開啓異步註解功能
@EnableScheduling //開啓基於註解的定時任務
@SpringBootApplication
public class SpringbootTaskApplication {

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

}

3、我們來詳細瞭解下cron表達式;

http://www.bejson.com/othertools/cron/

4、常用的表達式

(1)0/2 * * * * ?   表示每2秒 執行任務
(1)0 0/2 * * * ?   表示每2分鐘 執行任務
(1)0 0 2 1 * ?   表示在每月的1日的凌晨2點調整任務
(2)0 15 10 ? * MON-FRI   表示週一到週五每天上午10:15執行作業
(3)0 15 10 ? 6L 2002-2006   表示2002-2006年的每個月的最後一個星期五上午10:15執行作
(4)0 0 10,14,16 * * ?   每天上午10點,下午2點,4點
(5)0 0/30 9-17 * * ?   朝九晚五工作時間內每半小時
(6)0 0 12 ? * WED   表示每個星期三中午12點
(7)0 0 12 * * ?   每天中午12點觸發
(8)0 15 10 ? * *   每天上午10:15觸發
(9)0 15 10 * * ?     每天上午10:15觸發
(10)0 15 10 * * ?   每天上午10:15觸發
(11)0 15 10 * * ? 2005   2005年的每天上午10:15觸發
(12)0 * 14 * * ?     在每天下午2點到下午2:59期間的每1分鐘觸發
(13)0 0/5 14 * * ?   在每天下午2點到下午2:55期間的每5分鐘觸發
(14)0 0/5 14,18 * * ?     在每天下午2點到2:55期間和下午6點到6:55期間的每5分鐘觸發
(15)0 0-5 14 * * ?   在每天下午2點到下午2:05期間的每1分鐘觸發
(16)0 10,44 14 ? 3 WED   每年三月的星期三的下午2:10和2:44觸發
(17)0 15 10 ? * MON-FRI   週一至週五的上午10:15觸發
(18)0 15 10 15 * ?   每月15日上午10:15觸發
(19)0 15 10 L * ?   每月最後一日的上午10:15觸發
(20)0 15 10 ? * 6L   每月的最後一個星期五上午10:15觸發
(21)0 15 10 ? * 6L 2002-2005   2002年至2005年的每月的最後一個星期五上午10:15觸發
(22)0 15 10 ? * 6#3   每月的第三個星期五上午10:15觸發

18.3 郵件任務

郵件發送,在我們的日常開發中,也非常的多,Springboot也幫我們做了支持

  • 郵件發送需要引入spring-boot-start-mail
  • SpringBoot 自動配置MailSenderAutoConfiguration
  • 定義MailProperties內容,配置在application.yml中
  • 自動裝配JavaMailSender
  • 測試郵件發送

測試:

1、引入pom依賴

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

看它引入的依賴,可以看到 jakarta.mail

<dependency>
   <groupId>com.sun.mail</groupId>
   <artifactId>jakarta.mail</artifactId>
   <version>1.6.4</version>
   <scope>compile</scope>
</dependency>

2、查看自動配置類:MailSenderAutoConfiguration

在這裏插入圖片描述

這個類中存在bean,JavaMailSenderImpl

img

然後我們去看下配置文件

@ConfigurationProperties(
   prefix = "spring.mail"
)
public class MailProperties {
   private static final Charset DEFAULT_CHARSET;
   private String host;
   private Integer port;
   private String username;
   private String password;
   private String protocol = "smtp";
   private Charset defaultEncoding;
   private Map<String, String> properties;
   private String jndiName;
}

3、配置文件:

[email protected]
spring.mail.password=你的qq授權碼
spring.mail.host=smtp.qq.com
# qq需要配置ssl
spring.mail.properties.mail.smtp.ssl.enable=true

獲取授權碼:在QQ郵箱中的設置->賬戶->開啓pop3和smtp服務

4、Spring單元測試

@Autowired
JavaMailSenderImpl mailSender;

@Test
public void contextLoads() {
   //郵件設置1:一個簡單的郵件
   SimpleMailMessage message = new SimpleMailMessage();
   message.setSubject("通知-明天來狂神這聽課");
   message.setText("今晚7:30開會");

   message.setTo("[email protected]");
   message.setFrom("[email protected]");
   mailSender.send(message);
}

@Test
public void contextLoads2() throws MessagingException {
   //郵件設置2:一個複雜的郵件
   MimeMessage mimeMessage = mailSender.createMimeMessage();
   MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);

   helper.setSubject("通知-明天來狂神這聽課");
   helper.setText("<b style='color:red'>今天 7:30來開會</b>",true);

   //發送附件
   helper.addAttachment("1.jpg",new File(""));
   helper.addAttachment("2.jpg",new File(""));

   helper.setTo("[email protected]");
   helper.setFrom("[email protected]");

   mailSender.send(mimeMessage);
}

查看郵箱,郵件接收成功!

程的方式進行處理即可,但是每次都需要自己手動去編寫多線程的實現的話,太麻煩了,我們只需要用一個簡單的辦法,在我們的方法上加一個簡單的註解即可,如下:

6、給hello方法添加@Async註解;

//告訴Spring這是一個異步方法
@Async
public void hello(){
   try {
       Thread.sleep(3000);
  } catch (InterruptedException e) {
       e.printStackTrace();
  }
   System.out.println("業務進行中....");
}

SpringBoot就會自己開一個線程池,進行調用!但是要讓這個註解生效,我們還需要在主程序上添加一個註解@EnableAsync ,開啓異步註解功能;

@EnableAsync //開啓異步註解功能
@SpringBootApplication
public class SpringbootTaskApplication {

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

}

7、重啓測試,網頁瞬間響應,後臺代碼依舊執行!

18.4 定時任務

項目開發中經常需要執行一些定時任務,比如需要在每天凌晨的時候,分析一次前一天的日誌信息,Spring爲我們提供了異步執行任務調度的方式,提供了兩個接口。

  • TaskExecutor接口
  • TaskScheduler接口

兩個註解:

  • @EnableScheduling
  • @Scheduled

cron表達式:

[外鏈圖片轉存中…(img-GAaFNmRa-1591702307330)]

[外鏈圖片轉存中…(img-JoNZZ4cv-1591702307331)]

測試步驟:

1、創建一個ScheduledService

我們裏面存在一個hello方法,他需要定時執行,怎麼處理呢?

@Service
public class ScheduledService {
   
   //秒   分   時     日   月   周幾
   //0 * * * * MON-FRI
   //注意cron表達式的用法;
   @Scheduled(cron = "0 * * * * 0-7")
   public void hello(){
       System.out.println("hello.....");
  }
}

2、這裏寫完定時任務之後,我們需要在主程序上增加@EnableScheduling 開啓定時任務功能

@EnableAsync //開啓異步註解功能
@EnableScheduling //開啓基於註解的定時任務
@SpringBootApplication
public class SpringbootTaskApplication {

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

}

3、我們來詳細瞭解下cron表達式;

http://www.bejson.com/othertools/cron/

4、常用的表達式

(1)0/2 * * * * ?   表示每2秒 執行任務
(1)0 0/2 * * * ?   表示每2分鐘 執行任務
(1)0 0 2 1 * ?   表示在每月的1日的凌晨2點調整任務
(2)0 15 10 ? * MON-FRI   表示週一到週五每天上午10:15執行作業
(3)0 15 10 ? 6L 2002-2006   表示2002-2006年的每個月的最後一個星期五上午10:15執行作
(4)0 0 10,14,16 * * ?   每天上午10點,下午2點,4點
(5)0 0/30 9-17 * * ?   朝九晚五工作時間內每半小時
(6)0 0 12 ? * WED   表示每個星期三中午12點
(7)0 0 12 * * ?   每天中午12點觸發
(8)0 15 10 ? * *   每天上午10:15觸發
(9)0 15 10 * * ?     每天上午10:15觸發
(10)0 15 10 * * ?   每天上午10:15觸發
(11)0 15 10 * * ? 2005   2005年的每天上午10:15觸發
(12)0 * 14 * * ?     在每天下午2點到下午2:59期間的每1分鐘觸發
(13)0 0/5 14 * * ?   在每天下午2點到下午2:55期間的每5分鐘觸發
(14)0 0/5 14,18 * * ?     在每天下午2點到2:55期間和下午6點到6:55期間的每5分鐘觸發
(15)0 0-5 14 * * ?   在每天下午2點到下午2:05期間的每1分鐘觸發
(16)0 10,44 14 ? 3 WED   每年三月的星期三的下午2:10和2:44觸發
(17)0 15 10 ? * MON-FRI   週一至週五的上午10:15觸發
(18)0 15 10 15 * ?   每月15日上午10:15觸發
(19)0 15 10 L * ?   每月最後一日的上午10:15觸發
(20)0 15 10 ? * 6L   每月的最後一個星期五上午10:15觸發
(21)0 15 10 ? * 6L 2002-2005   2002年至2005年的每月的最後一個星期五上午10:15觸發
(22)0 15 10 ? * 6#3   每月的第三個星期五上午10:15觸發

18.5 郵件任務

郵件發送,在我們的日常開發中,也非常的多,Springboot也幫我們做了支持

  • 郵件發送需要引入spring-boot-start-mail
  • SpringBoot 自動配置MailSenderAutoConfiguration
  • 定義MailProperties內容,配置在application.yml中
  • 自動裝配JavaMailSender
  • 測試郵件發送

測試:

1、引入pom依賴

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

看它引入的依賴,可以看到 jakarta.mail

<dependency>
   <groupId>com.sun.mail</groupId>
   <artifactId>jakarta.mail</artifactId>
   <version>1.6.4</version>
   <scope>compile</scope>
</dependency>

2、查看自動配置類:MailSenderAutoConfiguration

[外鏈圖片轉存中…(img-DogVcFZj-1591702307331)]

這個類中存在bean,JavaMailSenderImpl

[外鏈圖片轉存中…(img-sRHdcWwt-1591702307332)]

然後我們去看下配置文件

@ConfigurationProperties(
   prefix = "spring.mail"
)
public class MailProperties {
   private static final Charset DEFAULT_CHARSET;
   private String host;
   private Integer port;
   private String username;
   private String password;
   private String protocol = "smtp";
   private Charset defaultEncoding;
   private Map<String, String> properties;
   private String jndiName;
}

3、配置文件:

[email protected]
spring.mail.password=你的qq授權碼
spring.mail.host=smtp.qq.com
# qq需要配置ssl
spring.mail.properties.mail.smtp.ssl.enable=true

獲取授權碼:在QQ郵箱中的設置->賬戶->開啓pop3和smtp服務

[外鏈圖片轉存中…(img-mE5lQZ4o-1591702307333)]

4、Spring單元測試

@Autowired
JavaMailSenderImpl mailSender;

@Test
public void contextLoads() {
   //郵件設置1:一個簡單的郵件
   SimpleMailMessage message = new SimpleMailMessage();
   message.setSubject("通知-明天來狂神這聽課");
   message.setText("今晚7:30開會");

   message.setTo("[email protected]");
   message.setFrom("[email protected]");
   mailSender.send(message);
}

@Test
public void contextLoads2() throws MessagingException {
   //郵件設置2:一個複雜的郵件
   MimeMessage mimeMessage = mailSender.createMimeMessage();
   MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);

   helper.setSubject("通知-明天來狂神這聽課");
   helper.setText("<b style='color:red'>今天 7:30來開會</b>",true);

   //發送附件
   helper.addAttachment("1.jpg",new File(""));
   helper.addAttachment("2.jpg",new File(""));

   helper.setTo("[email protected]");
   helper.setFrom("[email protected]");

   mailSender.send(mimeMessage);
}

查看郵箱,郵件接收成功!

我們只需要使用Thymeleaf進行前後端結合即可開發自己網站郵件收發功能了!

博主小結

| 博主整理的springboot筆記是學習B站狂神說教程後所記錄的筆記,對學習過程進行回顧整理的。附上視頻教程:
https://www.bilibili.com/video/BV1aE41167Tu.
如果整理過程中博主有理解不對的地方,歡迎各位小夥伴留言指正,博主一定虛心接受並改正!
如果覺得博主的學習筆記對你有所幫助,可以給博主點一個贊👍

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章