SpringBoot學習筆記
- 2021 年 1 月 31 日
- 筆記
- JAVA, spring, springboot
本筆記整理自 B站UP主 狂神說://www.bilibili.com/video/BV1PE411i7CV
相關程式碼://gitee.com/antia11/MyStudy/tree/master/Java/SpringBoot-Study
1、SpringBoot簡介
1.1、回顧什麼是Spring
Spring是一個開源框架,2003 年興起的一個輕量級的Java 開發框架,作者:Rod Johnson 。
Spring是為了解決企業級應用開發的複雜性而創建的,簡化開發。
1.2、Spring是如何簡化Java開發的
為了降低Java開發的複雜性,Spring採用了以下4種關鍵策略:
1、基於POJO的輕量級和最小侵入性編程,所有東西都是bean;
2、通過IOC,依賴注入(DI)和面向介面實現松耦合;
3、基於切面(AOP)和慣例進行聲明式編程;
4、通過切面和模版減少樣式程式碼,RedisTemplate,xxxTemplate;
1.3、什麼是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配置的要求
真的很爽,我們快速去體驗開發個介面的感覺吧!
2、Hello,World
2.1、準備工作
我們將學習如何快速的創建一個Spring Boot應用,並且實現一個簡單的Http請求處理。通過這個例子對Spring Boot有一個初步的了解,並體驗其結構簡單、開發快速的特性。
我的環境準備:
- java version “1.8.0_181”
- Maven-3.6.1
- SpringBoot 2.x 最新版
開發工具:
- IDEA
2.2、創建基礎項目說明
Spring官方提供了非常方便的工具讓我們快速構建應用
Spring Initializr://start.spring.io/
項目創建方式一:使用Spring Initializr 的 Web頁面創建項目
1、打開 //start.spring.io/
2、填寫項目資訊
3、點擊」Generate Project「按鈕生成項目;下載此項目
4、解壓項目包,並用IDEA以Maven項目導入,一路下一步即可,直到項目導入完畢。
5、如果是第一次使用,可能速度會比較慢,包比較多、需要耐心等待一切就緒。
項目創建方式二:使用 IDEA 直接創建項目
1、創建一個新項目
2、選擇spring initalizr , 可以看到默認就是去官網的快速構建工具那裡實現
3、填寫項目資訊
4、選擇初始化的組件(初學勾選 Web 即可)
5、填寫項目路徑
6、等待項目構建成功
項目結構分析:
通過上面步驟完成了基礎項目的創建。就會自動生成以下文件。
1、程式的主啟動類
2、一個 application.properties 配置文件
3、一個 測試類
4、一個 pom.xml
2.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>
2.4、編寫一個http介面
1、在主程式的同級目錄下,新建一個controller包,一定要在同級目錄下,否則識別不到
2、在包中新建一個HelloController類
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello() {
return "Hello World";
}
}
3、編寫完畢後,從主程式啟動項目,瀏覽器發起請求,看頁面返回;控制台輸出了 Tomcat 訪問的埠號!
簡單幾步,就完成了一個web介面的開發,SpringBoot就是這麼簡單。所以我們常用它來建立我們的微服務項目!
2.5、將項目打成jar包,點擊 maven的 package
如果遇到以上錯誤,可以配置打包時 跳過項目運行測試用例
<!--
在工作中,很多情況下我們打包是不想執行測試用例的
可能是測試用例不完事,或是測試用例會影響資料庫數據
跳過測試用例執
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!--跳過項目運行測試用例-->
<skipTests>true</skipTests>
</configuration>
</plugin>
如果打包成功,則會在target目錄下生成一個 jar 包
打成了jar包後,就可以在任何地方運行了!OK
彩蛋
如何更改啟動時顯示的字元拼成的字母,SpringBoot呢?也就是 banner 圖案;
只需一步:到項目下的 resources 目錄下新建一個banner.txt 即可。
圖案可以到://www.bootschool.net/ascii 這個網站生成,然後拷貝到文件中即可!
3、運行原理初探
我們之前寫的HelloSpringBoot,到底是怎麼運行的呢,Maven項目,我們一般從pom.xml文件探究起;
3.1、pom.xml
父依賴
其中它主要是依賴一個父項目,主要是管理項目的資源過濾及插件!
<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的版本控制中心;
以後我們導入依賴默認是不需要寫版本;但是如果導入的包沒有在依賴中管理著就需要手動配置版本了;
3.2、啟動器 spring-boot-starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
springboot-boot-starter-xxx:就是spring-boot的場景啟動器
spring-boot-starter-web:幫我們導入了web模組正常運行所依賴的組件;
SpringBoot將所有的功能場景都抽取出來,做成一個個的starter (啟動器),只需要在項目中引入這些starter即可,所有相關的依賴都會導入進來 , 我們要用什麼功能就導入什麼樣的場景啟動器即可 ;我們未來也可以自己自定義 starter;
3.3、主啟動類
分析完了 pom.xml 來看看這個啟動類
默認的主啟動類
//@SpringBootApplication 來標註一個主程式類
//說明這是一個Spring Boot應用
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
//以為是啟動了一個方法,沒想到啟動了一個服務
SpringApplication.run(SpringbootApplication.class, args);
}
}
但是一個簡單的啟動類並不簡單!我們來分析一下這些註解都幹了什麼
@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 {
// ......
}
@ComponentScan
這個註解在Spring中很重要 ,它對應XML配置中的元素。
作用:自動掃描並載入符合條件的組件或者bean , 將這個bean定義載入到IOC容器中
@SpringBootConfiguration
作用:SpringBoot的配置類 ,標註在某個類上 , 表示這是一個SpringBoot的配置類;
我們繼續進去這個註解查看
// 點進去得到下面的 @Component
@Configuration
public @interface SpringBootConfiguration {}
@Component
public @interface Configuration {}
這裡的 @Configuration,說明這是一個配置類 ,配置類就是對應Spring的xml 配置文件;
裡面的 @Component 這就說明,啟動類本身也是Spring中的一個組件而已,負責啟動應用!
我們回到 SpringBootApplication 註解中繼續看。
@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,全局搜索它
spring.factories
我們根據源頭打開spring.factories , 看到了很多自動配置的文件;這就是自動配置根源所在!
WebMvcAutoConfiguration
我們在上面的自動配置類隨便找一個打開看看,比如 :WebMvcAutoConfiguration
可以看到這些一個個的都是JavaConfig配置類,而且都注入了一些Bean,可以找一些自己認識的類,看著熟悉一下!
所以,自動配置真正實現是從classpath中搜尋所有的META-INF/spring.factories配置文件 ,並將其中對應的 org.springframework.boot.autoconfigure. 包下的配置項,通過反射實例化為對應標註了 @Configuration的JavaConfig形式的IOC容器配置類 , 然後將這些都匯總成為一個實例並載入到IOC容器中。
結論:
- SpringBoot在啟動的時候從類路徑下的META-INF/spring.factories中獲取EnableAutoConfiguration指定的值
- 將這些值作為自動配置類導入容器 , 自動配置類就生效 , 幫我們進行自動配置工作;
- 整個J2EE的整體解決方案和自動配置都在springboot-autoconfigure的jar包中;
- 它會給容器中導入非常多的自動配置類 (xxxAutoConfiguration), 就是給容器中導入這個場景需要的所有組件 , 並配置好這些組件 ;
- 有了自動配置類 , 免去了我們手動編寫配置注入功能組件等的工作;
現在大家應該大概的了解了下,SpringBoot的運行原理,後面我們還會深化一次!
3.4、SpringApplication
不簡單的方法
我最初以為就是運行了一個main方法,沒想到卻開啟了一個服務;
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}
SpringApplication.run分析
分析該方法主要分兩部分,一部分是SpringApplication的實例化,二是run方法的執行;
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();
}
run方法流程分析
4、YAML 配置注入
4.1、yaml語法學習
配置文件
SpringBoot使用一個全局的配置文件 , 配置文件名稱是固定的
-
application.properties
-
- 語法結構 :key=value
-
application.yml
-
- 語法結構 :key:空格 value
配置文件的作用 :修改SpringBoot自動配置的默認值,因為SpringBoot在底層都給我們自動配置好了;
比如我們可以在配置文件中修改Tomcat 默認啟動的埠號!測試一下!
server.port=8081
4.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
4.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
4.4、注入配置文件
yaml文件更強大的地方在於,他可以給我們的實體類直接注入匹配值!
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注入成功,這是我們原來的辦法對吧。
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配置註解處理器沒有找到,讓我們看文檔,我們可以查看文檔,找到一個依賴!
<!-- 導入配置文件處理器,配置文件進行綁定就會有提示,需要重啟 -->
<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;
載入指定的配置文件
@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、再次輸出測試一下:指定配置文件綁定成功!
配置文件佔位符
配置文件還可以編寫佔位符生成隨機數
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
回顧properties配置
我們上面採用的yaml方法都是最簡單的方式,開發中最常用的;也是springboot所推薦的!那我們來嘮嘮其他的實現方式,道理都是相同的;寫還是那樣寫;配置文件除了yml還有我們之前常用的properties , 我們沒有講,我們來嘮嘮!
【注意】properties配置文件在寫中文的時候,會有亂碼 , 我們需要去IDEA中設置編碼格式為UTF-8;
settings–>FileEncodings 中配置;
測試步驟:
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);
}
}
結果正常輸出:
對比小結
@Value這個使用起來並不友好!我們需要為每個屬性單獨註解賦值,比較麻煩;我們來看個功能對比圖
1、@ConfigurationProperties只需要寫一次即可 , @Value則需要每個欄位都添加
2、鬆散綁定:這個什麼意思呢? 比如我的yml中寫的last-name,這個和lastName是一樣的, – 後面跟著的字母默認是大寫的。這就是鬆散綁定。可以測試一下
3、JSR303數據校驗 , 這個就是我們可以在欄位是增加一層過濾器驗證 , 可以保證數據的合法性
4、複雜類型封裝,yml中可以封裝對象 , 使用value就不支援
結論:
配置yml和配置properties都可以獲取到值 , 強烈推薦 yml;
如果我們在某個業務中,只需要獲取配置文件中的某個值,可以使用一下 @value;
如果說,我們專門編寫了一個JavaBean來和配置文件進行一一映射,就直接@configurationProperties,不要猶豫!
5、JSR303數據校驗及多環境切換
5.1、JSR303數據校驗
先看看如何使用
Springboot中可以用@validated來校驗數據,如果數據異常則會統一拋出異常,方便異常中心統一處理。我們這裡來寫個註解讓我們的name只能支援Email格式;
@Component //註冊bean
@ConfigurationProperties(prefix = "person")
@Validated //數據校驗
public class Person {
@Email(message="郵箱格式錯誤") //name必須是郵箱格式
private String name;
}
運行結果 :default message [不是一個合法的電子郵件地址];
使用數據校驗,可以保證數據的正確性;
常見參數
@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 對象是否符合正則表達式的規則
.......等等
除此以外,我們還可以自定義一些數據校驗規則
5.2、多環境切換
profile是Spring對不同環境提供不同配置功能的支援,可以通過激活不同的環境版本,實現快速切換環境;
多配置文件
我們在主配置文件編寫的時候,文件名可以是 application-{profile}.properties/yml , 用來指定多個環境版本;
例如:
application-test.properties 代表測試環境配置
application-dev.properties 代表開發環境配置
但是Springboot並不會直接啟動這些配置文件,它默認使用application.properties主配置文件;
我們需要通過一個配置來選擇需要激活的環境:
#比如在配置文件中指定使用dev環境,我們可以通過設置不同的埠號進行測試;
#我們啟動SpringBoot,就可以看到已經切換到dev下的配置了;
spring.profiles.active=dev
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配置文件的!
配置文件載入位置
外部載入配置文件的方式十分多,我們選擇最常用的即可,在開發的資源文件中進行配置!
官方外部配置文件說明參考文檔
springboot 啟動會掃描以下位置的application.properties或者application.yml文件作為Spring boot的默認配置文件:
優先順序由高到底,高優先順序的配置會覆蓋低優先順序的配置;
SpringBoot會從這四個位置全部載入主配置文件;互補配置;
我們在最低級的配置文件中設置一個項目訪問路徑的配置來測試互補問題;
拓展,運維小技巧
指定位置載入配置文件
我們還可以通過spring.config.location來改變默認的配置文件位置fd
項目打包好以後,我們可以使用命令行參數的形式,啟動項目的時候來指定配置文件的新位置;這種情況,一般是後期運維做的多,相同配置,外部指定的配置文件優先順序最高
java -jar spring-boot-config.jar --spring.config.location=F:/application.properties
6、自動配置原理
配置文件到底能寫什麼?怎麼寫?
SpringBoot官方文檔中有大量的配置,我們無法全部記住
6.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;
//如果不存在,判斷也是成立的
//即使我們配置文件中不配置spring.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 {
// .....
}
我們去配置文件裡面試試前綴,看提示!
這就是自動裝配的原理!
精髓
1、SpringBoot啟動會載入大量的自動配置類
2、我們看我們需要的功能有沒有在SpringBoot默認寫好的自動配置類當中;
3、我們再來看這個自動配置類中到底配置了哪些組件;(只要我們要用的組件存在在其中,我們就不需要再手動配置了)
4、給容器中自動配置類添加組件的時候,會從properties類中獲取某些屬性。我們只需要在配置文件中指定這些屬性的值即可;
xxxxAutoConfigurartion:自動配置類;給容器中添加組件
xxxxProperties:封裝配置文件中相關屬性;
了解:@Conditional
了解完自動裝配的原理後,我們來關注一個細節問題,自動配置類必須在一定的條件下才能生效;
@Conditional派生註解(Spring註解版原生的@Conditional作用)
作用:必須是@Conditional指定的條件成立,才給容器中添加組件,配置配裡面的所有內容才生效;
那麼多的自動配置類,必須在一定的條件下才能生效;也就是說,我們載入了這麼多的配置類,但不是所有的都生效了。
我們怎麼知道哪些自動配置類生效?
我們可以通過啟用 debug=true屬性;來讓控制台列印自動配置報告,這樣我們就可以很方便的知道哪些自動配置類生效;
#開啟springboot的調試類
debug=true
Positive matches:(自動配置類啟用的:正匹配)
Negative matches:(沒有啟動,沒有匹配成功的自動配置類:負匹配)
Unconditional classes: (沒有條件的類)
7、自定義Starter
說明
啟動器模組是一個 空 jar 文件,僅提供輔助性依賴管理,這些依賴可能用於自動裝配或者其他類庫;
命名歸約:
官方命名:
- 前綴:spring-boot-starter-xxx
- 比如:spring-boot-starter-web….
自定義命名:
- xxx-spring-boot-starter
- 比如:mybatis-spring-boot-starter
7.1、編寫啟動器
1、在IDEA中新建一個空項目 spring-boot-starter-diy
2、新建一個普通Maven模組:kuang-spring-boot-starter
3、新建一個Springboot模組:kuang-spring-boot-starter-autoconfigure
4、點擊apply即可,基本結構
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,這是所有的啟動器基本配置!
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倉庫中!
7.2、新建項目測試我們自己寫的啟動器
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、啟動項目進行測試,結果成功 !
8、整合JDBC
8.1、SpringData簡介
對於數據訪問層,無論是 SQL(關係型資料庫) 還是 NOSQL(非關係型資料庫),Spring Boot 底層都是採用 Spring Data 的方式進行統一處理。
Spring Boot 底層都是採用 Spring Data 的方式進行統一處理各種資料庫,Spring Data 也是 Spring 中與 Spring Boot、Spring Cloud 等齊名的知名項目。
Sping Data 官網://spring.io/projects/spring-data
資料庫相關的啟動器 :可以參考官方文檔:
//docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter
8.2、整合JDBC
創建測試項目測試數據源
1、我去新建一個項目測試:springboot-data-jdbc ; 引入相應的模組!基礎模組
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
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方法:用於執行存儲過程、函數相關語句。
測試
編寫一個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 就搞定了。
9、整合Druid
9.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地址://github.com/alibaba/druid/
com.alibaba.druid.pool.DruidDataSource 基本配置參數如下:
9.2、配置數據源
1、添加上 Druid 數據源依賴。
<!-- //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,然後獲取到它,輸出一看便知是否成功切換;
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 地址://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
5、導入Log4j 的依賴
<!-- //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();
}
}
輸出結果 :可見配置參數已經生效!
9.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;
}
配置完畢後,我們可以選擇訪問 ://localhost:8080/druid/login.html
進入之後
配置 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;
}
10、整合MyBatis
官方文檔://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/
Maven倉庫地址://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter/2.1.1
10.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 地址://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"
"//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);
}
}
啟動項目訪問進行測試!
我們增加一個員工類再測試下,為之後做準備
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"
"//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);
}
}
11、Web開發靜態資源處理
11.1、Web開發探究
簡介
使用SpringBoot的步驟:
1、創建一個SpringBoot應用,選擇我們需要的模組,SpringBoot就會默認將我們的需要的模組自動配置好
2、手動在配置文件中配置部分配置項目就可以運行起來了
3、專註編寫業務程式碼,不需要考慮以前那樣一大堆的配置了。
要熟悉掌握開發,之前學習的自動配置的原理一定要搞明白!
比如SpringBoot到底幫我們配置了什麼?我們能不能修改?我們能修改哪些配置?我們能不能擴展?
- 向容器中自動配置組件 :*** Autoconfiguration
- 自動配置類,封裝配置文件的內容:***Properties
11.2、靜態資源處理
首先,我們搭建一個普通的SpringBoot項目,回顧一下HelloWorld程式!
寫請求非常簡單,那我們要引入我們前端資源,我們項目中有許多的靜態資源,比如css,js等文件,這個SpringBoot怎麼處理呢?
如果我們是一個web應用,我們的main下會有一個webapp,我們以前都是將所有的頁面導在這裡面的,對吧!但是我們現在的pom呢,打包方式是為jar的方式,那麼這種方式SpringBoot能不能來給我們寫頁面呢?當然是可以的,但是SpringBoot對於靜態資源放置的位置,是有規定的!
我們先來聊聊這個靜態資源映射規則:
SpringBoot中,SpringMVC的web配置都在 WebMvcAutoConfiguration 這個配置類裡面;
我們可以去看看 WebMvcAutoConfigurationAdapter 中有很多配置方法;
有一個方法:addResourceHandlers 添加資源處理
@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();
// 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/ 找對應的資源;
什麼是webjars 呢?
Webjars本質就是以jar包的方式引入我們的靜態資源 , 我們以前要導入一個靜態資源文件,直接導入即可。
使用SpringBoot需要使用Webjars,我們可以去搜索一下:
要使用jQuery,我們只要要引入jQuery對應版本的pom依賴即可!
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.4.1</version>
</dependency>
導入完畢,查看webjars目錄結構,並訪問Jquery.js文件!
訪問:只要是靜態資源,SpringBoot就會去對應的路徑尋找資源,我們這裡訪問://localhost:8080/webjars/jquery/3.4.1/jquery.js
第二種靜態資源映射規則
那我們項目中要是使用自己的靜態資源該怎麼導入呢?我們看下一行程式碼;
我們去找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根目錄下新建對應的文件夾,都可以存放我們的靜態文件;
比如我們訪問 //localhost:8080/1.js , 他就會去這些文件夾中尋找對應的靜態資源文件;
自定義靜態資源路徑
我們也可以自己通過配置文件來指定一下,哪些文件夾是需要我們放靜態資源文件的,在application.properties中配置;
spring.resources.static-locations=classpath:/coding/,classpath:/kuang/
一旦自己定義了靜態文件夾的路徑,原來的自動配置就都會失效了!
11.3、首頁處理
靜態資源文件夾說完後,我們繼續向下看源碼!可以看到一個歡迎頁的映射,就是我們的首頁!
@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 頁面;被 /** 映射。
比如我訪問 //localhost:8080/ ,就會找靜態資源文件夾下的 index.html
新建一個 index.html ,在我們上面的3個目錄中任意一個;然後訪問測試 //localhost:8080/ 看結果!<
關於網站圖標說明:
與其他靜態資源一樣,Spring Boot在配置的靜態內容位置中查找 favicon.ico。如果存在這樣的文件,它將自動用作應用程式的favicon。
1、關閉SpringBoot默認圖標
#關閉默認圖標
spring.mvc.favicon.enabled=false
2、自己放一個圖標在靜態資源目錄下,我放在 public 目錄下
3、清除瀏覽器快取!刷新網頁,發現圖標已經變成自己的了!
12、Thymeleaf模板引擎
前端交給我們的頁面,是html頁面。如果是我們以前開發,我們需要把他們轉成jsp頁面,jsp好處就是當我們查出一些數據轉發到JSP頁面以後,我們可以用jsp輕鬆實現數據的顯示,及交互等。
jsp支援非常強大的功能,包括能寫Java程式碼,但是呢,我們現在的這種情況,SpringBoot這個項目首先是以jar的方式,不是war,像第二,我們用的還是嵌入式的Tomcat,所以呢,他現在默認是不支援jsp的。
那不支援jsp,如果我們直接用純靜態頁面的方式,那給我們開發會帶來非常大的麻煩,那怎麼辦呢?
SpringBoot推薦你可以來使用模板引擎:
模板引擎,我們其實大家聽到很多,其實jsp就是一個模板引擎,還有用的比較多的freemarker,包括SpringBoot給我們推薦的Thymeleaf,模板引擎有非常多,但再多的模板引擎,他們的思想都是一樣的,什麼樣一個思想呢我們來看一下這張圖:
模板引擎的作用就是我們來寫一個頁面模板,比如有些值呢,是動態的,我們寫一些表達式。而這些值,從哪來呢,就是我們在後台封裝一些數據。然後把這個模板和這個數據交給我們模板引擎,模板引擎按照我們這個數據幫你把這表達式解析、填充到我們指定的位置,然後把這個數據最終生成一個我們想要的內容給我們寫出去,這就是我們這個模板引擎,不管是jsp還是其他模板引擎,都是這個思想。只不過呢,就是說不同模板引擎之間,他們可能這個語法有點不一樣。其他的我就不介紹了,我主要來介紹一下SpringBoot給我們推薦的Thymeleaf模板引擎,這模板引擎呢,是一個高級語言的模板引擎,他的這個語法更簡單。而且呢,功能更強大。
我們呢,就來看一下這個模板引擎,那既然要看這個模板引擎。首先,我們來看SpringBoot裡邊怎麼用。
引入Thymeleaf
怎麼引入呢,對於springboot來說,什麼事情不都是一個start的事情嘛,我們去在項目中引入一下。給大家三個網址:
Thymeleaf 官網://www.thymeleaf.org/
Thymeleaf 在Github 的主頁://github.com/thymeleaf/thymeleaf
Spring官方文檔:找到我們對應的版本
//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包,我們可以去看下下載的東西;
12.1、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什麼都不需要配置,只需要將他放在指定的文件夾下即可!
測試
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>
<title>Title</title>
</head>
<body>
<h1>測試頁面</h1>
</body>
</html>
3、啟動項目請求測試
12.2、Thymeleaf 語法學習
要學習語法,還是參考官網文檔最為準確,我們找到對應的版本看一下;
Thymeleaf 官網://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="//www.thymeleaf.org"
3、我們去編寫下前端頁面
<!DOCTYPE html>
<html lang="en" xmlns:th="//www.thymeleaf.org">
<head>
<title>狂神說</title>
</head>
<body>
<h1>測試頁面</h1>
<!--th:text就是將div中的內容設置為它指定的值,和之前學習的Vue一樣-->
<div th:text="${msg}"></div>
</body>
</html>
4、啟動測試!
OK,入門搞定,我們來認真研習一下Thymeleaf的使用語法!
1、我們可以使用任意的 th:attr 來替換Html中原生屬性的值!
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: _
練習測試:
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="//www.thymeleaf.org">
<head>
<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、啟動項目測試!
13、MVC自動配置原理
官網閱讀
在進行項目編寫前,我們還需要知道一個東西,就是SpringBoot對我們的SpringMVC還做了哪些配置,包括如何擴展,如何訂製。
只有把這些都搞清楚了,我們在之後使用才會更加得心應手。途徑一:源碼分析,途徑二:官方文檔!
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,然後自動配置了哪些東西呢?
13.1、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方法 加個斷點進行調試一下,因為所有的請求都會走到這個方法中
3、我們啟動我們的項目,然後隨便訪問一個頁面,看一下Debug資訊;
找到this
找到視圖解析器,我們看到我們自己定義的就在這裡了;
所以說,我們如果想要使用自己訂製化的東西,我們只需要給容器中添加這個組件就好了!剩下的事情SpringBoot就會幫我們做了!
轉換器和格式化器
找到格式化轉換器:
@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中生效,我們可以在配置文件中配置日期格式化的規則:
其餘的就不一一舉例了,大家可以下去多研究探討即可!
13.2、修改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");
}
}
我們去瀏覽器訪問一下:
確實也跳轉過來了!所以說,我們要擴展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自己的配置類,我們自己的配置類當然也會被調用;
13.3、全面接管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給我們配置的靜態資源映射一定會無效,我們可以去測試一下;
不加註解之前,訪問首頁:
給配置類加上註解:@EnableWebMvc
我們發現所有的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中會有非常多的擴展配置,只要看見了這個,我們就應該多留心注意~
14、頁面國際化
14.1、準備工作
先在IDEA中統一設置properties的編碼問題!
編寫國際化配置文件,抽取頁面需要顯示的國際化頁面消息。我們可以去登錄頁面查看一下,哪些內容我們需要編寫國際化的配置!
14.2、配置文件編寫
1、我們在resources資源文件下新建一個i18n目錄,存放國際化配置文件
2、建立一個login.properties文件,還有一個login_zh_CN.properties;發現IDEA自動識別了我們要做國際化操作;文件夾變了!
3、我們可以在這上面去新建一個文件;
彈出如下頁面:我們再添加一個英文的;
這樣就快捷多了!
4、接下來,我們就來編寫配置,我們可以看到idea下面有另外一個視圖;
這個視圖我們點擊 + 號就可以直接添加屬性了;我們新建一個login.tip,可以看到邊上有三個文件框可以輸入
我們添加一下首頁的內容!
然後依次添加其他頁面內容即可!
然後去查看我們的配置文件;
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,配置文件步驟搞定!
14.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
14.4、配置頁面國際化值
去頁面獲取國際化的值,查看Thymeleaf的文檔,找到message取值操作為:#{…}。我們去頁面測試下:
IDEA還有提示,非常智慧的!
我們可以去啟動項目,訪問一下,發現已經自動識別為中文的了!
但是我們想要更好!可以根據按鈕自動切換中文英文!
14.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();
}
15、集成Swagger
學習目標:
- 了解Swagger的概念及作用
- 掌握在項目中集成Swagger自動生成API文檔
15.1、Swagger簡介
前後端分離
- 前端 -> 前端控制層、視圖層
- 後端 -> 後端控制層、服務層、數據訪問層
- 前後端通過API進行交互
- 前後端相對獨立且松耦合
產生的問題
- 前後端集成,前端或者後端無法做到「及時協商,儘早解決」,最終導致問題集中爆發
解決方案
- 首先定義schema [ 計劃的提綱 ],並實時跟蹤最新的API,降低集成風險
Swagger
- 號稱世界上最流行的API框架
- Restful Api 文檔在線自動生成器 => API 文檔 與API 定義同步更新
- 直接運行,在線測試API
- 支援多種語言 (如:Java,PHP等)
- 官網://swagger.io/
15.2、SpringBoot集成Swagger
SpringBoot集成Swagger => springfox,兩個jar包
- Springfox-swagger2
- swagger-springmvc
使用Swagger
要求:jdk 1.8 + 否則swagger2無法運行
步驟:
1、新建一個SpringBoot-web項目
2、添加Maven依賴
<!-- //mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!-- //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、訪問測試 ://localhost:8080/swagger-ui.html ,可以看到swagger的介面;
m
15.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("聯繫人名字", "//xxx.xxx.com/聯繫人訪問鏈接", "聯繫人郵箱");
return new ApiInfo(
"Swagger學習", // 標題
"學習演示如何配置Swagger", // 描述
"v1.0", // 版本
"//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、重啟項目,訪問測試 //localhost:8080/swagger-ui.html 看下效果;
15.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()控制
15.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的配置文件查看效果!
15.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、重啟項目查看即可
15.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為類屬性添加註釋
15.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,一來出於安全考慮二來也可以節省運行時記憶體。
16、非同步、定時、郵件任務
16.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、訪問//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、重啟測試,網頁瞬間響應,後台程式碼依舊執行!
16.2、定時任務
項目開發中經常需要執行一些定時任務,比如需要在每天凌晨的時候,分析一次前一天的日誌資訊,Spring為我們提供了非同步執行任務調度的方式,提供了兩個介面。
- TaskExecutor介面
- TaskScheduler介面
兩個註解:
- @EnableScheduling
- @Scheduled
cron表達式:
一、結構
corn從左到右(用空格隔開):秒 分 小時 月份中的日期 月份 星期中的日期 年份
二、各欄位的含義
欄位 | 允許值 | 允許的特殊字元 |
---|---|---|
秒(Seconds) | 0~59的整數 | , – * / 四個字元 |
分(Minutes) | 0~59的整數 | , – * / 四個字元 |
小時(Hours) | 0~23的整數 | , – * / 四個字元 |
日期(DayofMonth) | 1~31的整數(但是你需要考慮你月的天數) | ,- * ? / L W C 八個字元 |
月份(Month) | 1~12的整數或者 JAN-DEC | , – * / 四個字元 |
星期(DayofWeek) | 1~7的整數或者 SUN-SAT (1=SUN) | , – * ? / L C # 八個字元 |
年(可選,留空)(Year) | 1970~2099 | , – * / 四個字元 |
每一個域都使用數字,但還可以出現如下特殊字元,它們的含義是:
(1):表示匹配該域的任意值。假如在Minutes域使用, 即表示每分鐘都會觸發事件。
(2)?:只能用在DayofMonth和DayofWeek兩個域。它也匹配域的任意值,但實際不會。因為DayofMonth和DayofWeek會相互影響。例如想在每月的20日觸發調度,不管20日到底是星期幾,則只能使用如下寫法: 13 13 15 20 * ?, 其中最後一位只能用?,而不能使用,如果使用表示不管星期幾都會觸發,實際上並不是這樣。
(3)-:表示範圍。例如在Minutes域使用5-20,表示從5分到20分鐘每分鐘觸發一次
(4)/:表示起始時間開始觸發,然後每隔固定時間觸發一次。例如在Minutes域使用5/20,則意味著5分鐘觸發一次,而25,45等分別觸發一次.
(5),:表示列出枚舉值。例如:在Minutes域使用5,20,則意味著在5和20分每分鐘觸發一次。
(6)L:表示最後,只能出現在DayofWeek和DayofMonth域。如果在DayofWeek域使用5L,意味著在最後的一個星期四觸發。
(7)W:表示有效工作日(周一到周五),只能出現在DayofMonth域,系統將在離指定日期的最近的有效工作日觸發事件。例如:在 DayofMonth使用5W,如果5日是星期六,則將在最近的工作日:星期五,即4日觸發。如果5日是星期天,則在6日(周一)觸發;如果5日在星期一到星期五中的一天,則就在5日觸發。另外一點,W的最近尋找不會跨過月份 。
(8)LW:這兩個字元可以連用,表示在某個月最後一個工作日,即最後一個星期五。
(9)#:用於確定每個月第幾個星期幾,只能出現在DayofMonth域。例如在4#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觸發
測試步驟:
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);
}
}
16.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
然後我們去看下配置文件
@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);
}
查看郵箱,郵件接收成功!
我們只需要使用Thymeleaf進行前後端結合即可開發自己網站郵件收發功能了!
17、富文本編輯器
思考:我們平時在部落格園,或者CSDN等平台進行寫作的時候,有同學思考過他們的編輯器是怎麼實現的嗎?
在部落格園後台的選項設置中,可以看到一個文本編輯器的選項:
其實這個就是富文本編輯器,市面上有許多非常成熟的富文本編輯器,比如:
-
Editor.md——功能非常豐富的編輯器,左端編輯,右端預覽,非常方便,完全免費
-
wangEditor——基於javascript和css開發的 Web富文本編輯器, 輕量、簡潔、介面美觀、易用、開源免費。
-
TinyMCE——TinyMCE是一個輕量級的基於瀏覽器的所見即所得編輯器,由JavaScript寫成。它對IE6+和Firefox1.5+都有著非常良好的支援。功能齊全,介面美觀,就是文檔是英文的,對開發人員英文水平有一定要求。
-
百度ueditor——UEditor是由百度web前端研發部開發所見即所得富文本web編輯器,具有輕量,功能齊全,可訂製,注重用戶體驗等特點,開源基於MIT協議,允許自由使用和修改程式碼,缺點是已經沒有更新了
-
kindeditor——介面經典。
-
Textbox——Textbox是一款極簡但功能強大的在線文本編輯器,支援桌面設備和移動設備。主要功能包含內置的影像處理和存儲、文件拖放、拼寫檢查和自動更正。此外,該工具還實現了螢幕閱讀器等輔助技術,並符合WAI-ARIA可訪問性標準。
-
CKEditor——國外的,介面美觀。
-
quill——功能強大,還可以編輯公式等
-
simditor——介面美觀,功能較全。
-
summernote——UI好看,精美
-
jodit——功能齊全
-
froala Editor——介面非常好看,功能非常強大,非常好用(非免費)
Editor.md
我這裡使用的就是Editor.md,作為一個資深碼農,Mardown必然是我們程式猿最喜歡的格式,看下面,就愛上了!
我們可以在官網下載它://pandao.github.io/editor.md/ , 得到它的壓縮包!
解壓以後,在examples目錄下面,可以看到他的很多案例使用!學習,其實就是看人家怎麼寫的,然後進行模仿就好了!
我們可以將整個解壓的文件倒入我們的項目,將一些無用的測試和案例刪掉即可!
17.1、基礎工程搭建
資料庫設計
article:文章表
id | int | 文章的唯一ID |
---|---|---|
author | varchar | 作者 |
title | varchar | 標題 |
content | longtext | 文章的內容 |
建表SQL:
CREATE TABLE `article` (
`id` int(10) NOT NULL AUTO_INCREMENT COMMENT 'int文章的唯一ID',
`author` varchar(50) NOT NULL COMMENT '作者',
`title` varchar(100) NOT NULL COMMENT '標題',
`content` longtext NOT NULL COMMENT '文章的內容',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
基礎項目搭建
1、建一個SpringBoot項目配置
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
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
2、實體類:
//文章類
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Article implements Serializable {
private int id; //文章的唯一ID
private String author; //作者名
private String title; //標題
private String content; //文章的內容
}
3、mapper介面:
@Mapper
@Repository
public interface ArticleMapper {
//查詢所有的文章
List<Article> queryArticles();
//新增一個文章
int addArticle(Article article);
//根據文章id查詢文章
Article getArticleById(int id);
//根據文章id刪除文章
int deleteArticleById(int id);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"//mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kuang.mapper.ArticleMapper">
<select id="queryArticles" resultType="Article">
select * from article
</select>
<select id="getArticleById" resultType="Article">
select * from article where id = #{id}
</select>
<insert id="addArticle" parameterType="Article">
insert into article (author,title,content) values (#{author},#{title},#{content});
</insert>
<delete id="deleteArticleById" parameterType="int">
delete from article where id = #{id}
</delete>
</mapper>
既然已經提供了 myBatis 的映射配置文件,自然要告訴 spring boot 這些文件的位置
mybatis:
mapper-locations: classpath:com/kuang/mapper/*.xml
type-aliases-package: com.kuang.pojo
編寫一個Controller測試下,是否ok;
17.2、文章編輯整合(重點)
1、導入 editor.md 資源 ,刪除多餘文件
2、編輯文章頁面 editor.html、需要引入 jQuery;
<!DOCTYPE html>
<html class="x-admin-sm" lang="zh" xmlns:th="//www.thymeleaf.org">
<head>
<title>秦疆'Blog</title>
<!--Editor.md-->
<link rel="stylesheet" th:href="@{/editormd/css/editormd.css}"/>
<link rel="shortcut icon" href="//pandao.github.io/editor.md/favicon.ico" type="image/x-icon" />
</head>
<body>
<div class="layui-fluid">
<div class="layui-row layui-col-space15">
<div class="layui-col-md12">
<!--部落格表單-->
<form name="mdEditorForm">
<div>
標題:<input type="text" name="title">
</div>
<div>
作者:<input type="text" name="author">
</div>
<div id="article-content">
<textarea name="content" id="content" style="display:none;"> </textarea>
</div>
</form>
</div>
</div>
</div>
</body>
<!--editormd-->
<script th:src="@{/editormd/lib/jquery.min.js}"></script>
<script th:src="@{/editormd/editormd.js}"></script>
<script type="text/javascript">
var testEditor;
//window.onload = function(){ }
$(function() {
testEditor = editormd("article-content", {
width : "95%",
height : 400,
syncScrolling : "single",
path : "../editormd/lib/",
saveHTMLToTextarea : true, // 保存 HTML 到 Textarea
emoji: true,
theme: "dark",//工具欄主題
previewTheme: "dark",//預覽主題
editorTheme: "pastel-on-dark",//編輯主題
tex : true, // 開啟科學公式TeX語言支援,默認關閉
flowChart : true, // 開啟流程圖支援,默認關閉
sequenceDiagram : true, // 開啟時序/序列圖支援,默認關閉,
//圖片上傳
imageUpload : true,
imageFormats : ["jpg", "jpeg", "gif", "png", "bmp", "webp"],
imageUploadURL : "/article/file/upload",
onload : function() {
console.log('onload', this);
},
/*指定需要顯示的功能按鈕*/
toolbarIcons : function() {
return ["undo","redo","|",
"bold","del","italic","quote","ucwords","uppercase","lowercase","|",
"h1","h2","h3","h4","h5","h6","|",
"list-ul","list-ol","hr","|",
"link","reference-link","image","code","preformatted-text",
"code-block","table","datetime","emoji","html-entities","pagebreak","|",
"goto-line","watch","preview","fullscreen","clear","search","|",
"help","info","releaseIcon", "index"]
},
/*自定義功能按鈕,下面我自定義了2個,一個是發布,一個是返回首頁*/
toolbarIconTexts : {
releaseIcon : "<span bgcolor=\"gray\">發布</span>",
index : "<span bgcolor=\"red\">返回首頁</span>",
},
/*給自定義按鈕指定回調函數*/
toolbarHandlers:{
releaseIcon : function(cm, icon, cursor, selection) {
//表單提交
mdEditorForm.method = "post";
mdEditorForm.action = "/article/addArticle";//提交至伺服器的路徑
mdEditorForm.submit();
},
index : function(){
window.location.href = '/';
},
}
});
});
</script>
</html>
3、編寫Controller,進行跳轉,以及保存文章
@Controller
@RequestMapping("/article")
public class ArticleController {
@GetMapping("/toEditor")
public String toEditor(){
return "editor";
}
@PostMapping("/addArticle")
public String addArticle(Article article){
articleMapper.addArticle(article);
return "editor";
}
}
圖片上傳問題
1、前端js中添加配置
//圖片上傳
imageUpload : true,
imageFormats : ["jpg", "jpeg", "gif", "png", "bmp", "webp"],
imageUploadURL : "/article/file/upload", // //這個是上傳圖片時的訪問地址
2、後端請求,接收保存這個圖片, 需要導入 FastJson 的依賴!
//部落格圖片上傳問題
@RequestMapping("/file/upload")
@ResponseBody
public JSONObject fileUpload(@RequestParam(value = "editormd-image-file", required = true) MultipartFile file, HttpServletRequest request) throws IOException {
//上傳路徑保存設置
//獲得SpringBoot當前項目的路徑:System.getProperty("user.dir")
String path = System.getProperty("user.dir")+"/upload/";
//按照月份進行分類:
Calendar instance = Calendar.getInstance();
String month = (instance.get(Calendar.MONTH) + 1)+"月";
path = path+month;
File realPath = new File(path);
if (!realPath.exists()){
realPath.mkdir();
}
//上傳文件地址
System.out.println("上傳文件保存地址:"+realPath);
//解決文件名字問題:我們使用uuid;
String filename = "ks-"+UUID.randomUUID().toString().replaceAll("-", "");
//通過CommonsMultipartFile的方法直接寫文件(注意這個時候)
file.transferTo(new File(realPath +"/"+ filename));
//給editormd進行回調
JSONObject res = new JSONObject();
res.put("url","/upload/"+month+"/"+ filename);
res.put("success", 1);
res.put("message", "upload success!");
return res;
}
3、解決文件回顯顯示的問題,設置虛擬目錄映射!在我們自己拓展的MvcConfig中進行配置即可!
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
// 文件保存在真實目錄/upload/下,
// 訪問的時候使用虛路徑/upload,比如文件名為1.png,就直接/upload/1.png就ok了。
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/upload/**")
.addResourceLocations("file:"+System.getProperty("user.dir")+"/upload/");
}
}
表情包問題
自己手動下載,emoji 表情包,放到圖片路徑下:
修改editormd.js文件
// Emoji graphics files url path
editormd.emoji = {
path : "../editormd/plugins/emoji-dialog/emoji/",
ext : ".png"
};
17.3、文章展示
1、Controller 中增加方法
@GetMapping("/{id}")
public String show(@PathVariable("id") int id,Model model){
Article article = articleMapper.getArticleById(id);
model.addAttribute("article",article);
return "article";
}
2、編寫頁面 article.html
<!DOCTYPE html>
<html lang="en" xmlns:th="//www.thymeleaf.org">
<head>
<title th:text="${article.title}"></title>
</head>
<body>
<div>
<!--文章頭部資訊:標題,作者,最後更新日期,導航-->
<h2 style="margin: auto 0" th:text="${article.title}"></h2>
作者:<span style="float: left" th:text="${article.author}"></span>
<!--文章主體內容-->
<div id="doc-content">
<textarea style="display:none;" placeholder="markdown" th:text="${article.content}"></textarea>
</div>
</div>
<link rel="stylesheet" th:href="@{/editormd/css/editormd.preview.css}" />
<script th:src="@{/editormd/lib/jquery.min.js}"></script>
<script th:src="@{/editormd/lib/marked.min.js}"></script>
<script th:src="@{/editormd/lib/prettify.min.js}"></script>
<script th:src="@{/editormd/lib/raphael.min.js}"></script>
<script th:src="@{/editormd/lib/underscore.min.js}"></script>
<script th:src="@{/editormd/lib/sequence-diagram.min.js}"></script>
<script th:src="@{/editormd/lib/flowchart.min.js}"></script>
<script th:src="@{/editormd/lib/jquery.flowchart.min.js}"></script>
<script th:src="@{/editormd/editormd.js}"></script>
<script type="text/javascript">
var testEditor;
$(function () {
testEditor = editormd.markdownToHTML("doc-content", {//注意:這裡是上面DIV的id
htmlDecode: "style,script,iframe",
emoji: true,
taskList: true,
tocm: true,
tex: true, // 默認不解析
flowChart: true, // 默認不解析
sequenceDiagram: true, // 默認不解析
codeFold: true
});});
</script>
</body>
</html>
18、Dubbo和Zookeeper集成
18.1、分散式理論
什麼是分散式系統?
在《分散式系統原理與范型》一書中有如下定義:「分散式系統是若干獨立電腦的集合,這些電腦對於用戶來說就像單個相關係統」;
分散式系統是由一組通過網路進行通訊、為了完成共同的任務而協調工作的電腦節點組成的系統。分散式系統的出現是為了用廉價的、普通的機器完成單個電腦無法完成的計算、存儲任務。其目的是利用更多的機器,處理更多的數據。
分散式系統(distributed system)是建立在網路之上的軟體系統。
首先需要明確的是,只有當單個節點的處理能力無法滿足日益增長的計算、存儲任務的時候,且硬體的提升(加記憶體、加磁碟、使用更好的CPU)高昂到得不償失的時候,應用程式也不能進一步優化的時候,我們才需要考慮分散式系統。因為,分散式系統要解決的問題本身就是和單機系統一樣的,而由於分散式系統多節點、通過網路通訊的拓撲結構,會引入很多單機系統沒有的問題,為了解決這些問題又會引入更多的機制、協議,帶來更多的問題。。。
Dubbo文檔
隨著互聯網的發展,網站應用的規模不斷擴大,常規的垂直應用架構已無法應對,分散式服務架構以及流動計算架構勢在必行,急需一個治理系統確保架構有條不紊的演進。
在Dubbo的官網文檔有這樣一張圖
單一應用架構
當網站流量很小時,只需一個應用,將所有功能都部署在一起,以減少部署節點和成本。此時,用於簡化增刪改查工作量的數據訪問框架(ORM)是關鍵。
適用於小型網站,小型管理系統,將所有功能都部署到一個功能里,簡單易用。
缺點:
1、性能擴展比較難
2、協同開發問題
3、不利於升級維護
垂直應用架構
當訪問量逐漸增大,單一應用增加機器帶來的加速度越來越小,將應用拆成互不相干的幾個應用,以提升效率。此時,用於加速前端頁面開發的Web框架(MVC)是關鍵。
通過切分業務來實現各個模組獨立部署,降低了維護和部署的難度,團隊各司其職更易管理,性能擴展也更方便,更有針對性。
缺點:公用模組無法重複利用,開發性的浪費
分散式服務架構
當垂直應用越來越多,應用之間交互不可避免,將核心業務抽取出來,作為獨立的服務,逐漸形成穩定的服務中心,使前端應用能更快速的響應多變的市場需求。此時,用於提高業務復用及整合的分散式服務框架(RPC)是關鍵。
流動計算架構
當服務越來越多,容量的評估,小服務資源的浪費等問題逐漸顯現,此時需增加一個調度中心基於訪問壓力實時管理集群容量,提高集群利用率。此時,用於提高機器利用率的資源調度和治理中心(SOA)[ Service Oriented Architecture]是關鍵。
18.2、什麼是RPC
RPC【Remote Procedure Call】是指遠程過程調用,是一種進程間通訊方式,他是一種技術的思想,而不是規範。它允許程式調用另一個地址空間(通常是共享網路的另一台機器上)的過程或函數,而不用程式設計師顯式編碼這個遠程調用的細節。即程式設計師無論是調用本地的還是遠程的函數,本質上編寫的調用程式碼基本相同。
也就是說兩台伺服器A,B,一個應用部署在A伺服器上,想要調用B伺服器上應用提供的函數/方法,由於不在一個記憶體空間,不能直接調用,需要通過網路來表達調用的語義和傳達調用的數據。為什麼要用RPC呢?就是無法在一個進程內,甚至一個電腦內通過本地調用的方式完成的需求,比如不同的系統間的通訊,甚至不同的組織間的通訊,由於計算能力需要橫向擴展,需要在多台機器組成的集群上部署應用。RPC就是要像調用本地的函數一樣去調遠程函數;
推薦閱讀文章://www.jianshu.com/p/2accc2840a1b
RPC基本原理
步驟解析:
RPC兩個核心模組:通訊,序列化。
18.3、測試環境搭建
Dubbo
Apache Dubbo |ˈdʌbəʊ| 是一款高性能、輕量級的開源Java RPC框架,它提供了三大核心能力:面向介面的遠程方法調用,智慧容錯和負載均衡,以及服務自動註冊和發現。
dubbo官網 //dubbo.apache.org/zh-cn/index.html
1.了解Dubbo的特性
2.查看官方文檔
dubbo基本概念
服務提供者(Provider):暴露服務的服務提供方,服務提供者在啟動時,向註冊中心註冊自己提供的服務。
服務消費者(Consumer):調用遠程服務的服務消費方,服務消費者在啟動時,向註冊中心訂閱自己所需的服務,服務消費者,從提供者地址列表中,基於軟負載均衡演算法,選一台提供者進行調用,如果調用失敗,再選另一台調用。
註冊中心(Registry):註冊中心返回服務提供者地址列表給消費者,如果有變更,註冊中心將基於長連接推送變更數據給消費者
監控中心(Monitor):服務消費者和提供者,在記憶體中累計調用次數和調用時間,定時每分鐘發送一次統計數據到監控中心
調用關係說明
l 服務容器負責啟動,載入,運行服務提供者。
l 服務提供者在啟動時,向註冊中心註冊自己提供的服務。
l 服務消費者在啟動時,向註冊中心訂閱自己所需的服務。
l 註冊中心返回服務提供者地址列表給消費者,如果有變更,註冊中心將基於長連接推送變更數據給消費者。
l 服務消費者,從提供者地址列表中,基於軟負載均衡演算法,選一台提供者進行調用,如果調用失敗,再選另一台調用。
l 服務消費者和提供者,在記憶體中累計調用次數和調用時間,定時每分鐘發送一次統計數據到監控中心。
Dubbo環境搭建
點進dubbo官方文檔,推薦我們使用Zookeeper 註冊中心
什麼是zookeeper呢?可以查看官方文檔
Window下安裝zookeeper
1、下載zookeeper :地址, 我們下載3.4.14 , 最新版!解壓zookeeper
2、運行/bin/zkServer.cmd ,初次運行會報錯,沒有zoo.cfg配置文件;
可能遇到問題:閃退 !
解決方案:編輯zkServer.cmd文件末尾添加pause 。這樣運行出錯就不會退出,會提示錯誤資訊,方便找到原因。
3、修改zoo.cfg配置文件
將conf文件夾下面的zoo_sample.cfg複製一份改名為zoo.cfg即可。
注意幾個重要位置:
dataDir=./ 臨時數據存儲的目錄(可寫相對路徑)
clientPort=2181 zookeeper的埠號
修改完成後再次啟動zookeeper
4、使用zkCli.cmd測試
ls /:列出zookeeper根下保存的所有節點
[zk: 127.0.0.1:2181(CONNECTED) 4] ls /
[zookeeper]
create –e /kuangshen 123:創建一個kuangshen節點,值為123
get /kuangshen:獲取/kuangshen節點的值
我們再來查看一下節點
window下安裝dubbo-admin
dubbo本身並不是一個服務軟體。它其實就是一個jar包,能夠幫你的java程式連接到zookeeper,並利用zookeeper消費、提供服務。
但是為了讓用戶更好的管理監控眾多的dubbo服務,官方提供了一個可視化的監控程式dubbo-admin,不過這個監控即使不裝也不影響使用。
我們這裡來安裝一下:
1、下載dubbo-admin
地址 ://github.com/apache/dubbo-admin/tree/master
2、解壓進入目錄
修改 dubbo-admin\src\main\resources \application.properties 指定zookeeper地址
server.port=7001
spring.velocity.cache=false
spring.velocity.charset=UTF-8
spring.velocity.layout-url=/templates/default.vm
spring.messages.fallback-to-system-locale=false
spring.messages.basename=i18n/message
spring.root.password=root
spring.guest.password=guest
dubbo.registry.address=zookeeper://127.0.0.1:2181
3、在項目目錄下打包dubbo-admin
mvn clean package -Dmaven.test.skip=true
第一次打包的過程有點慢,需要耐心等待!直到成功!
4、執行 dubbo-admin\target 下的dubbo-admin-0.0.1-SNAPSHOT.jar
java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
【注意:zookeeper的服務一定要打開!】
執行完畢,我們去訪問一下 //localhost:7001/ , 這時候我們需要輸入登錄賬戶和密碼,我們都是默認的root-root;
登錄成功後,查看介面
安裝完成!
18.4、SpringBoot + Dubbo + zookeeper
框架搭建
1. 啟動zookeeper !
2. IDEA創建一個空項目;
3.創建一個模組,實現服務提供者:provider-server , 選擇web依賴即可
4.項目創建完畢,我們寫一個服務,比如賣票的服務;
編寫介面
package com.kuang.provider.service;
public interface TicketService {
public String getTicket();
}
編寫實現類
package com.kuang.provider.service;
public class TicketServiceImpl implements TicketService {
@Override
public String getTicket() {
return "《狂神說Java》";
}
}
5.創建一個模組,實現服務消費者:consumer-server , 選擇web依賴即可
6.項目創建完畢,我們寫一個服務,比如用戶的服務;
編寫service
package com.kuang.consumer.service;
public class UserService {
//我們需要去拿去註冊中心的服務
}
需求:現在我們的用戶想使用買票的服務,這要怎麼弄呢 ?
服務提供者
1、將服務提供者註冊到註冊中心,我們需要整合Dubbo和zookeeper,所以需要導包
我們從dubbo官網進入github,看下方的幫助文檔,找到dubbo-springboot,找到依賴包
<!-- Dubbo Spring Boot Starter -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.3</version>
</dependency>
zookeeper的包我們去maven倉庫下載,zkclient;
<!-- //mvnrepository.com/artifact/com.github.sgroschupf/zkclient -->
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
【新版的坑】zookeeper及其依賴包,解決日誌衝突,還需要剔除日誌依賴;
<!-- 引入zookeeper -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
<!--排除這個slf4j-log4j12-->
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
2、在springboot配置文件中配置dubbo相關屬性!
#當前應用名字
dubbo.application.name=provider-server
#註冊中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
#掃描指定包下服務
dubbo.scan.base-packages=com.kuang.provider.service
3、在service的實現類中配置服務註解,發布服務!注意導包問題
import org.apache.dubbo.config.annotation.Service;
import org.springframework.stereotype.Component;
@Service //將服務發布出去
@Component //放在容器中
public class TicketServiceImpl implements TicketService {
@Override
public String getTicket() {
return "《狂神說Java》";
}
}
邏輯理解 :應用啟動起來,dubbo就會掃描指定的包下帶有@component註解的服務,將它發布在指定的註冊中心中!
服務消費者
1、導入依賴,和之前的依賴一樣;
<!--dubbo-->
<!-- Dubbo Spring Boot Starter -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.3</version>
</dependency>
<!--zookeeper-->
<!-- //mvnrepository.com/artifact/com.github.sgroschupf/zkclient -->
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
<!-- 引入zookeeper -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
<!--排除這個slf4j-log4j12-->
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
2、配置參數
#當前應用名字
dubbo.application.name=consumer-server
#註冊中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
3. 本來正常步驟是需要將服務提供者的介面打包,然後用pom文件導入,我們這裡使用簡單的方式,直接將服務的介面拿過來,路徑必須保證正確,即和服務提供者相同;
4. 完善消費者的服務類
package com.kuang.consumer.service;
import com.kuang.provider.service.TicketService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;
@Service //注入到容器中
public class UserService {
@Reference //遠程引用指定的服務,他會按照全類名進行匹配,看誰給註冊中心註冊了這個全類名
TicketService ticketService;
public void bugTicket(){
String ticket = ticketService.getTicket();
System.out.println("在註冊中心買到"+ticket);
}
}
5. 測試類編寫
@RunWith(SpringRunner.class)
@SpringBootTest
public class ConsumerServerApplicationTests {
@Autowired
UserService userService;
@Test
public void contextLoads() {
userService.bugTicket();
}
}
啟動測試
1. 開啟zookeeper
2. 打開dubbo-admin實現監控【可以不用做】
3. 開啟服務者
4. 消費者消費測試,結果:
監控中心 :
ok , 這就是SpingBoot + dubbo + zookeeper實現分散式開發的應用,其實就是一個服務拆分的思想;
19、集成SpringSecurity
安全簡介
在 Web 開發中,安全一直是非常重要的一個方面。安全雖然屬於應用的非功能性需求,但是應該在應用開發的初期就考慮進來。如果在應用開發的後期才考慮安全的問題,就可能陷入一個兩難的境地:一方面,應用存在嚴重的安全漏洞,無法滿足用戶的要求,並可能造成用戶的隱私數據被攻擊者竊取;另一方面,應用的基本架構已經確定,要修復安全漏洞,可能需要對系統的架構做出比較重大的調整,因而需要更多的開發時間,影響應用的發布進程。因此,從應用開發的第一天就應該把安全相關的因素考慮進來,並在整個應用的開發過程中。
市面上存在比較有名的:Shiro,Spring Security !
這裡需要闡述一下的是,每一個框架的出現都是為了解決某一問題而產生了,那麼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),可以對應用中的領域對象進行細粒度的控制。
19.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!
19.2、認識SpringSecurity
Spring Security 是針對Spring項目的安全框架,也是Spring Boot底層安全模組默認的技術選型,他可以實現強大的Web安全控制,對於安全控制,我們僅需要引入 spring-boot-starter-security 模組,進行少量的配置,即可實現強大的安全管理!
記住幾個類:
- WebSecurityConfigurerAdapter:自定義Security策略
- AuthenticationManagerBuilder:自定義認證策略
- @EnableWebSecurity:開啟WebSecurity模式
Spring Security的兩個主要目標是 「認證」 和 「授權」(訪問控制)。
「認證」(Authentication)
身份驗證是關於驗證您的憑據,如用戶名/用戶ID和密碼,以驗證您的身份。
身份驗證通常通過用戶名和密碼完成,有時與身份驗證因素結合使用。
「授權」 (Authorization)
授權發生在系統成功驗證您的身份後,最終會授予您訪問資源(如資訊,文件,資料庫,資金,位置,幾乎任何內容)的完全許可權。
這個概念是通用的,而不是只在Spring Security 中存在。
19.3、認證和授權
目前,我們的測試環境,是誰都可以訪問的,我們使用 Spring Security 增加上認證和授權的功能
1、引入 Spring Security 模組
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2、編寫 Spring Security 配置類
參考官網://spring.io/projects/spring-security
查看我們自己項目中的版本,找到對應的幫助文檔:
//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、測試,發現,登錄成功,並且每個角色只能訪問自己認證下的規則!搞定
19.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依賴:
<!-- //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、修改我們的 前端頁面
-
導入命名空間
-
xmlns:sec="//www.thymeleaf.org/thymeleaf-extras-springsecurity5"
修改導航欄,增加認證判斷
<!--登錄註銷-->
<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、許可權控制和註銷搞定!
19.5、記住我
現在的情況,我們只要登錄之後,關閉瀏覽器,再登錄,就會讓我們重新登錄,但是很多網站的情況,就是有一個記住密碼的功能,這個該如何實現呢?很簡單
1、開啟記住我功能
//訂製請求的授權規則
@Override
protected void configure(HttpSecurity http) throws Exception {
//。。。。。。。。。。。
//記住我
http.rememberMe();
}
2、我們再次啟動項目測試一下,發現登錄頁多了一個記住我功能,我們登錄之後關閉 瀏覽器,然後重新打開瀏覽器訪問,發現用戶依舊存在!
思考:如何實現的呢?其實非常簡單
我們可以查看瀏覽器的cookie
3、我們點擊註銷的時候,可以發現,spring security 幫我們自動刪除了這個 cookie
4、結論:登錄成功後,將cookie發送給瀏覽器保存,以後登錄帶上這個cookie,只要通過檢查就可以免登錄了。如果點擊註銷,則會刪除這個cookie,具體的原理我們在JavaWeb階段都講過了,這裡就不在多說了!
19.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()源碼中的注釋上有寫明:
<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
19.7、完整配置程式碼
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");
}
}