1.什麼是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的應用程式
● 能夠直接使用java main方法啟動內嵌的Tomcat伺服器執行SpringBoot程式,不需要部署war包檔案
● 提供約定的starter POM來簡化Maven配置,讓Maven的配置變得簡單
● 自動化配置,根據專案的Maven依賴配置,Springboot自動配置Spring、Spring mvc等
● 提供了程式的健康檢查等功能
● 基本可以完全不使用XML配置檔案,採用註解配置
SpringBoot四大核心
● 自動配置
針對很多Spring應用程式和常見的應用功能,SpringBoot能自動提供相關配置
● 起步依賴
告訴SpringBoot需要什麼功能,它就能引入需要的依賴庫
● Actuator
讓你能夠深入執行中的SpringBoot應用程式,一探SpringBoot程式的內部資訊
● 命令列介面
這是SpringBoot的可選特性,主要針對Groovy語言使用;
Groovy是一種基於JVM(Java虛擬機器) 的敏捷開發語言,它結合了Python、Ruby和Smalltalk的許多強大的特性,Groovy 程式碼能夠與Java程式碼很好地結合,也能用於擴充套件現有程式碼,由於其執行在JVM上的特性,Groovy可以使用其他Java語言編寫的庫。
2.第一個spring boot程式
2.1.使用 IDEA 直接建立專案
1、建立一個新專案
2、選擇spring initalizr , 可以看到預設就是去官網的快速構建工具那裡實現
3、填寫專案資訊
4、選擇初始化的元件(初學勾選 Web 即可)
5、填寫專案路徑
6、等待專案構建成功
2.2.pom.xml 分析
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 父依賴 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>springbootOne</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springbootOne</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- web場景啟動器 -->
<!--web依賴:tomcat,dispatcherServlet,xml...-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-boot-starter:所有的springboot依賴都是使用這個開頭的-->
<!-- 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>
</project>
-
專案後設資料:建立時候輸入的Project Metadata部分,也就是Maven專案的基本元素,包括:groupId、artifactId、version、name、description等
-
parent:繼承
spring-boot-starter-parent
的依賴管理,控制版本與打包等內容 -
dependencies:專案具體依賴,這裡包含了
spring-boot-starter-web
用於實現HTTP介面(該依賴中包含了Spring MVC),官網對它的描述是:使用Spring MVC構建Web(包括RESTful)應用程式的入門者,使用Tomcat作為預設嵌入式容器。spring-boot-starter-test
用於編寫單元測試的依賴包。更多功能模組的使用將在後面逐步展開。 -
build:構建配置部分。預設使用了
spring-boot-maven-plugin
,配合spring-boot-starter-parent
就可以把Spring Boot應用打包成JAR來直接執行。
2.3.編寫一個http介面
1、在主程式的同級目錄下,新建一個controller包,一定要在同級目錄下,否則識別不到
![image-20211108111755801](https://gitee.com/wyl1924/cdn/raw/master/img/blog/image-20211108111755801.png
2.4.將專案打成jar包
點選 maven的 package,等待生成。
如果測試用例影響到打包,可以跳過
<!--
在工作中,很多情況下我們打包是不想執行測試用例的
可能是測試用例不完事,或是測試用例會影響資料庫資料
跳過測試用例執
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!--跳過專案執行測試用例-->
<skipTests>true</skipTests>
</configuration>
</plugin>
打成了jar包後,就可以在任何地方執行了
2.5.埠
有沒有看到我的埠被佔用了
2.6.生成專案模板
為方便我們初始化專案,Spring Boot給我們提供一個專案模板生成網站。
-
開啟瀏覽器,訪問:https://start.spring.io/
-
根據頁面提示,選擇構建工具,開發語言,專案資訊等。
-
點選 Generate the project,生成專案模板,生成之後會將壓縮包下載到本地。
3.執行原理探究
我們之前寫的HelloSpringBoot,到底是怎麼執行的呢,我們從pom.xml檔案探究起;
3.1.父依賴
pom.xml
- spring-boot-dependencies:核心依賴在父工程中!
- 我們在寫或者引入一些Springboot依賴的時候,不需要指定版本,就因為有這些版本倉庫
1、其中它主要是依賴一個父專案,主要是管理專案的資源過濾及外掛!
<!-- 父依賴 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.6</version>
<relativePath/>
</parent>
2、點進去,發現還有一個父依賴
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.5.6</version>
</parent>
3、這裡才是真正管理SpringBoot應用裡面所有依賴版本的地方,SpringBoot的版本控制中心;
4、以後我們匯入依賴預設是不需要寫版本;但是如果匯入的包沒有在依賴中管理著就需要手動配置版本了
3.2.啟動器 spring-boot-starter
-
依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency>
-
springboot-boot-starter-xxx,說白了就是Springboot的啟動場景
-
比如spring-boot-starter-web,他就會幫我們自動匯入web的所有依賴
-
springboot會將所有的功能場景,都變成一個個的啟動器
-
我們要使用什麼功能,就只需要找到對應的啟動器就好了
start
3.3.主程式
3.3.1.預設的主啟動類
package com.example.springbootone;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//@SpringBootApplication 來標註一個主程式類
//說明這是一個Spring Boot應用
@SpringBootApplication
public class SpringbootOneApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootOneApplication.class, args);
}
}
3.3.2註解(@SpringBootApplication)
-
作用:標註在某個類上說明這個類是SpringBoot的主配置
-
SpringBoot就應該執行這個類的main方法來啟動SpringBoot應用;
-
進入這個註解:可以看到上面還有很多其他註解!
@ComponentScan
-
這個註解在Spring中很重要 ,它對應XML配置中的元素。
-
作用:自動掃描並載入符合條件的元件或者bean , 將這個bean定義載入到IOC容器中
@SpringBootConfiguration
-
作用:SpringBoot的配置類 ,標註在某個類上 , 表示這是一個SpringBoot的配置類;
-
我們繼續進去這個註解檢視
-
這裡的 @Configuration,說明這是一個spring的配置類 ,配置類就是對應Spring的xml 配置檔案;
-
@Component 這就說明,啟動類本身也是Spring中的一個元件而已,負責啟動應用!
-
我們回到 SpringBootApplication 註解中繼續看。
@EnableAutoConfiguration
-
開啟自動配置功能
- 以前我們需要自己配置的東西,而現在SpringBoot可以自動幫我們配置 ;
- @EnableAutoConfiguration告訴SpringBoot開啟自動配置功能,這樣自動配置才能生效;
點進註解接續檢視:
-
@AutoConfigurationPackage :自動配置包
-
-
@import :Spring底層註解@import , 給容器中匯入一個元件
-
Registrar.class 作用:自動配置包註冊,將主啟動類的所在包及包下面所有子包裡面的所有元件掃描到Spring容器 ;
-
這個分析完了,退到上一步,繼續看
-
-
@Import({AutoConfigurationImportSelector.class}) :給容器匯入元件 ;
- AutoConfigurationImportSelector :自動配置匯入選擇器,那麼它會匯入哪些元件的選擇器呢?我們點選去這個類看原始碼:
// 獲取所有的配置
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
-
獲得候選的配置
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; } //和上面的類的方法loadFactoryNames裡面的第一個引數對應 //這裡的getSpringFactoriesLoaderFactoryClass()方法 //返回的就是我們最開始看的啟動自動匯入配置檔案的註解類;EnableAutoConfiguration protected Class<?> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class; }
-
這個方法
getCandidateConfigurations()
又呼叫了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()); }
-
我們繼續點選檢視 loadSpringFactories 方法
- 專案資源:
META-INF/spring.factories
- 系統資源:
META-INF/spring.factories
- 從這些資源中配置了所有的nextElement(自動配置),分裝成properties
//將所有的資源載入到配置類中(將下面的抽離出來分析,第15行) Properties properties = PropertiesLoaderUtils.loadProperties(resource);
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); } } }
- 專案資源:
-
發現一個多次出現的檔案:spring.factories
3.3.3.spring.factories
我們根據源頭開啟spring.factories , 看到了很多自動配置的檔案;這就是自動配置根源所在!
3.3.4.WebMvcAutoConfiguration
在上面的自動配置類隨便找一個開啟看看,比如 :WebMvcAutoConfiguration
@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 {
public static final String DEFAULT_PREFIX = "";
public static final String DEFAULT_SUFFIX = "";
private static final String[] SERVLET_LOCATIONS = { "/" };
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
可以看到這些一個個的都是JavaConfig配置類,而且都注入了一些Bean,可以找一些自己認識的類,看著熟悉一下!
所以,自動配置真正實現是從classpath中搜尋所有的META-INF/spring.factories配置檔案 ,並將其中對應的 org.springframework.boot.autoconfigure. 包下的配置項,通過反射例項化為對應標註了 @Configuration的JavaConfig形式的IOC容器配置類 , 然後將這些都彙總成為一個例項並載入到IOC容器中
3.3.5.結論:
-
SpringBoot在啟動的時候從類路徑下的
META-INF/spring.factories
中獲取EnableAutoConfiguration
指定的值 -
將這些值作為自動配置類匯入容器 , 自動配置類就生效 , 幫我們進行自動配置工作;
-
以前我們需要自動配置的東西,現在springboot幫我們做了
-
整合JavaEE,整體解決方案和自動配置的東西都在
springboot-autoconfigure
的jar包中; -
它會把所有需要匯入的元件,以類名的方式返回,這些元件就會被新增到容器中
-
它會給容器中匯入非常多的自動配置類 (xxxAutoConfiguration), 就是給容器中匯入這個場景需要的所有元件 , 並自動配置,@Configuration(javaConfig) ;
-
有了自動配置類 , 免去了我們手動編寫配置注入功能元件等的工作;
3.4啟動
@SpringBootApplication
public class Springboot01HellowordApplication {
public static void main(String[] args) {
//該方法返回一個ConfigurableApplicationContext物件
//引數一:應用入口的類; 引數二:命令列引數
SpringApplication.run(Springboot01HellowordApplication.class, args);
}
}
SpringApplication.run分析
- 分析該方法主要分兩部分
- 一是SpringApplication的例項化,
- 二是run方法的執行;
3.4.1.SpringApplication
這個類主要做了以下四件事情:
-
推斷應用的型別是普通的專案還是Web專案
-
查詢並載入所有可用初始化器 , 設定到initializers屬性中
-
找出所有的應用程式監聽器,設定到listeners屬性中
-
推斷並設定main方法的定義類,找到執行的主類
檢視構造器:
3.4.2.run方法流程分析
4.yaml語法學習
4.1.配置檔案
SpringBoot使用一個全域性的配置檔案 , 配置檔名稱是固定的
-
application.properties
-
- 語法結構 :key=value
-
application.yaml
-
- 語法結構 :key:空格 value
配置檔案的作用 :修改SpringBoot自動配置的預設值,因為SpringBoot在底層都給我們自動配置好了;
比如我們可以在配置檔案中修改Tomcat 預設啟動的埠號!測試一下!
server:
port: 8081
4.2.YAML
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
-
yaml基礎語法
說明:語法要求嚴格!
-
空格不能省略
-
以縮排來控制層級關係,只要是左邊對齊的一列資料都是同一個層級的。
-
屬性和值的大小寫都是十分敏感的。
字面量:普通的值 [ 數字,布林值,字串 ]
-
字面量直接寫在後面就可以 , 字串預設不用加上雙引號或者單引號;
k: v
注意:
-
“ ” 雙引號,不會轉義字串裡面的特殊字元 , 特殊字元會作為本身想表示的意思;
比如 :name: "wang\n jingmo" 輸出 :wang換行 jingmo
-
'' 單引號,會轉義特殊字元 , 特殊字元最終會變成和普通字元一樣輸出
比如 :name: ‘wang\n jingmo’ 輸出 :wang\n jingmo
-
物件、Map(鍵值對)
#物件、Map格式
k:
v1:
v2:
在下一行來寫物件的屬性和值得關係,注意縮排;比如:
student:
name: wangyanling
age: 3
行內寫法
student: {name: wangyanling,age: 3}
陣列( List、set )
用 - 值表示陣列中的一個元素,比如:
pets:
- cat
- dog
- pig
行內寫法
pets: [cat,dog,pig]
修改SpringBoot的預設埠號
配置檔案中新增,埠號的引數,就可以切換埠;
server:
port: 8082
4.3.注入配置檔案
yaml檔案強大的地方在於可以給實體類直接注入匹配值
4.3.1. yaml注入配置檔案
① 在springboot專案中的resources目錄下新建一個檔案 application.yml
② 編寫一個實體類 Dog
package com.wyl.springboot.pojo;
@Component //註冊bean到容器中
public class Dog {
private String name;
private Integer age;
//有參無參構造、get、set方法、toString()方法
}
③ 試著用@Value給bean注入屬性值
@Component //註冊bean
public class Dog {
@Value("旺財")
private String name;
@Value("1")
private Integer age;
}
④ 在SpringBoot的測試類下注入並輸出
@SpringBootTest
class DemoApplicationTests {
@Autowired //將狗狗自動注入進來
Dog dog;
@Test
public void contextLoads() {
System.out.println(dog); //列印看下狗狗物件
}
}
結果成功輸出,@Value注入成功
Dog{name='旺財', age=1}
⑤ 再編寫一個複雜點的實體類
@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()方法
}
⑥ 使用yaml配置的方式進行注入
寫的時候注意區別和優勢,首先編寫一個yaml配置
person:
name: wjm
age: 3
happy: false
birth: 2000/01/01
maps: {k1: v1,k2: v2}
lists:
- code
- girl
- music
dog:
name: 旺財
age: 1
⑦ 把物件的所有值都寫好後,注入到類中
/*
@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;
}
⑧ IDEA 提示,springboot配置註解處理器沒有找到
Not Found
The requested URL /spring-boot/docs/2.3.3.RELEASE/reference/html/configuration-metadata.html was not found on this server.
檢視文件(在網址中更改版本獲得,如回到2.1.9),找到一個依賴
<!-- 匯入配置檔案處理器,配置檔案進行繫結就會有提示,需要重啟 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
⑨ 確認以上配置都完成後,去測試類中測試
@SpringBootTest
class DemoApplicationTests {
@Autowired
Person person; //將person自動注入進來
@Test
public void contextLoads() {
System.out.println(person); //列印person資訊
}
}
結果:所有值全部注入成功
4.3.2. 載入指定配置檔案
@PropertySource :載入指定的配置檔案;
@configurationProperties:預設從全域性配置檔案中獲取值
- 在resources目錄下新建一個person.properties檔案
name=hello
- 在程式碼中指定載入person.properties檔案
@PropertySource(value = "classpath:person.properties")
@Component //註冊bean
public class Person {
@Value("${name}")
private String name;
......
}
- 再次輸出測試,指定配置檔案繫結成功
4.3.3.配置檔案佔位符
配置檔案還可以編寫佔位符生成隨機數
person:
name: wangjingmo${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
4.3.4.回顧properties配置
上面採用的yaml方法都是最簡單的方式,也是開發中最常用的、pringboot所推薦的
接下來看看其他的實現方式,原理都是相同的,寫還是那樣寫
配置檔案除了yml還有之前常用的properties
【注意】properties配置檔案在寫中文的時候會有亂碼 , 需要去IDEA中設定編碼格式為UTF-8:settings-->FileEncodings 中配置
測試步驟
- 新建一個實體類User
@Component //註冊bean
public class User {
private String name;
private int age;
private String sex;
}
- 編輯配置檔案 user.properties
user1.name=wyl
user1.age=18
user1.sex=男
- 在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;
}
- Springboot測試
SpringBootTest
class DemoApplicationTests {
@Autowired
User user;
@Test
public void contextLoads() {
System.out.println(user);
}
}
結果正常輸出
4.3.4.對比小結
@Value使用起來並不友好!我們需要為每個屬性單獨註解賦值比較麻煩
- @ConfigurationProperties只需要寫一次即可 , @Value則需要每個欄位都新增
- 鬆散繫結:這個什麼意思呢? 比如yml中寫的last-name,這個和lastName是一樣的,
-
後面跟著的字母預設是大寫的。這就是鬆散繫結 - JSR303資料校驗 ,可以在欄位是增加一層過濾器驗證 , 保證資料的合法性
- 複雜型別封裝,yml中可以封裝物件 , 使用value就不支援
結論:
- 配置yml和配置properties都可以獲取到值 , 強烈推薦 yml;
- 如果在某個業務中,只需要獲取配置檔案中的某個值,可以使用一下 @value;
- 如果專門編寫了一個JavaBean來和配置檔案進行一一對映,就直接使用@configurationProperties
5.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.1.多環境切換
profile是Spring對不同環境提供不同配置功能的支援,可以通過啟用不同的環境版本,實現快速切換環境
5.1.1.多配置檔案
在主配置檔案編寫的時候,檔名可以是application-{profile}.properties/yml
, 用來指定多個環境版本。例如:application-test.properties
代表測試環境配置 application-dev.properties
代表開發環境配置
但是Springboot並不會直接啟動這些配置檔案,它預設使用application.properties主配置檔案。但可以通過配置來選擇需要啟用的環境
#比如在配置檔案中指定使用dev環境,我們可以通過設定不同的埠號進行測試;
#我們啟動SpringBoot,就可以看到已經切換到dev下的配置了;
spring.profiles.active=dev
5.1.2.yml的多文件塊
和properties配置檔案中一樣,但使用yml去實現不需要建立多個配置檔案,更加方便
server:
port: 8081
#選擇要啟用那個環境塊
spring:
profiles:
active: prod
---
server:
port: 8083
spring:
profiles: dev #配置環境的名稱
---
server:
port: 8084
spring:
profiles: prod #配置環境的名稱
注意:如果yml和properties同時都配置了埠,並且沒有啟用其他環境 , 預設會使用properties配置檔案的
5.1.3.配置檔案載入位置
外部載入配置檔案的方式很多,一般選擇最常用的即可,在開發的資原始檔中進行配置
springboot 啟動會掃描以下位置的application.properties或者application.yml檔案作為Spring boot的預設配置檔案
優先順序1:專案路徑下的config資料夾配置檔案
優先順序2:專案路徑下配置檔案
優先順序3:資源路徑下的config資料夾配置檔案
優先順序4:資源路徑下配置檔案
優先順序由高到底,高優先順序的配置會覆蓋低優先順序的配置;
SpringBoot會從這四個位置全部載入主配置檔案;互補配置
4.4.4.運維小技巧
指定位置載入配置檔案
我們還可以通過spring.config.location
來改變預設的配置檔案位置
專案打包好以後,我們可以使用命令列引數的形式,啟動專案的時候來指定配置檔案的新位置;
這種情況,一般是後期運維做的多,相同配置,外部指定的配置檔案優先順序最高
java -jar spring-boot-config.jar --spring.config.location=F:/application.properties
6.自動配置原理
----聯絡---- spring.factories
SpringBoot官方文件中有大量的配置,我們無法全部記住,官網:https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/reference/html/appendix-application-properties.html#core-properties
6.1.分析自動配置原理
-
SpringBoot啟動的時候載入主配置類,開啟了自動配置功能 @EnableAutoConfiguration
-
@EnableAutoConfiguration 作用
-
利用EnableAutoConfigurationImportSelector給容器中匯入一些元件
-
可以檢視selectImports()方法的內容,他返回了一個autoConfigurationEnty,來自
this.getAutoConfigurationEntry(autoConfigurationMetadata,annotationMetadata);
這個方法我們繼續來跟蹤: -
這個方法有一個值:
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
叫做獲取候選的配置 ,我們點選繼續跟蹤SpringFactoriesLoader.loadFactoryNames()
- 掃描所有jar包類路徑下
META-INF/spring.factories
- 把掃描到的這些檔案的內容包裝成properties物件
- 從properties中獲取到EnableAutoConfiguration.class類(類名)對應的值,然後把他們新增在容器中
-
在類路徑下,
META-INF/spring.factories
裡面配置的所有EnableAutoConfiguration的值加入到容器中:
-
-
- 每一個這樣的 xxxAutoConfiguration類都是容器中的一個元件,都加入到容器中;用他們來做自動配置;
-
每一個自動配置類進行自動配置功能;
-
我們以HttpEncodingAutoConfiguration(Http編碼自動配置)為例解釋自動配置原理;
//表示這是一個配置類,和以前編寫的配置檔案一樣,也可以給容器中新增元件; @Configuration //啟動指定類的ConfigurationProperties功能; //進入這個HttpProperties檢視,將配置檔案中對應的值和HttpProperties繫結起來; //並把HttpProperties加入到ioc容器中 @EnableConfigurationProperties({HttpProperties.class}) //Spring底層@Conditional註解 //根據不同的條件判斷,如果滿足指定的條件,整個配置類裡面的配置就會生效; //這裡的意思就是判斷當前應用是否是web應用,如果是,當前配置類生效 @ConditionalOnWebApplication( type = Type.SERVLET ) //判斷當前專案有沒有這個類CharacterEncodingFilter;SpringMVC中進行亂碼解決的過濾器; @ConditionalOnClass({CharacterEncodingFilter.class}) //判斷配置檔案中是否存在某個配置:spring.http.encoding.enabled; //如果不存在,判斷也是成立的 //即使我們配置檔案中不配置pring.http.encoding.enabled=true,也是預設生效的; @ConditionalOnProperty( prefix = "spring.http.encoding", value = {"enabled"}, matchIfMissing = true ) public class HttpEncodingAutoConfiguration { //他已經和SpringBoot的配置檔案對映了 private final Encoding properties; //只有一個有參構造器的情況下,引數的值就會從容器中拿 public HttpEncodingAutoConfiguration(HttpProperties properties) { this.properties = properties.getEncoding(); } //給容器中新增一個元件,這個元件的某些值需要從properties中獲取 @Bean @ConditionalOnMissingBean //判斷容器沒有這個元件? public CharacterEncodingFilter characterEncodingFilter() { CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter(); filter.setEncoding(this.properties.getCharset().name()); filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST)); filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE)); return filter; } //。。。。。。。 }
一句話總結 :根據當前不同的條件判斷,決定這個配置類是否生效!
-
一但這個配置類生效;這個配置類就會給容器中新增各種元件;
-
這些元件的屬性是從對應的properties類中獲取的,這些類裡面的每一個屬性又是和配置檔案繫結的;
-
所有在配置檔案中能配置的屬性都是在xxxxProperties類中封裝著;
-
配置檔案能配置什麼就可以參照某個功能對應的這個屬性類
//從配置檔案中獲取指定的值和bean的屬性進行繫結 @ConfigurationProperties(prefix = "spring.http") public class HttpProperties { // ..... }
我們去配置檔案裡面試試字首,看提示!
這就是自動裝配的原理!
6.2.總結
-
SpringBoot啟動會載入大量的自動配置類
-
我們看我們需要的功能有沒有在SpringBoot預設寫好的自動配置類當中;
-
我們再來看這個自動配置類中到底配置了哪些元件;(只要我們要用的元件存在在其中,我們就不需要再手動配置了)
-
給容器中自動配置類新增元件的時候,會從properties類中獲取某些屬性。我們只需要在配置檔案中指定這些屬性的值即可;
xxxxAutoConfigurartion:自動配置類;給容器中新增元件
xxxxProperties:封裝配置檔案中相關屬性;
6.3.@Conditional
瞭解完自動裝配的原理後,我們來關注一個細節問題,自動配置類必須在一定的條件下才能生效;
@Conditional派生註解(Spring註解版原生的@Conditional作用)
作用:必須是@Conditional指定的條件成立,才給容器中新增元件,配置配裡面的所有內容才生效;
@Conditional擴充套件註解 | 作用(判斷是否滿足當前指定條件) |
---|---|
@ConditionalOnJava | 系統的java版本是否符合要求 |
@ConditionalOnJava | 容器中存在指定Bean ; |
@ConditionalOnMissingBean | 容器中不存在指定Bean ; |
@ConditionalOnExpression | 滿足SpEL表示式指定 |
@ConditionalOnClass | 系統中有指定的類 |
@ConditionalOnMissingClass | 系統中沒有指定的類 |
@ConditionalOnSingleCandidate | 容器中只有一個指定的Bean ,或者這個Bean是首選Bean |
@ConditionalOnProperty | 系統中指定的屬性是否有指定的值 |
@ConditionalOnResource | 類路徑下是否存在指定資原始檔 |
@ConditionalOnWebApplication | 當前是web環境 |
@ConditionalOnNotWebApplication | 當前不是web環境 |
@ConditionalOnJndi | JNDI存在指定項 |
那麼多的自動配置類,必須在一定的條件下才能生效;也就是說,我們載入了這麼多的配置類,但不是所有的都生效了。
6.4.自動配置類是否生效
我們可以在application.properties通過啟用 debug=true
屬性;
在控制檯列印自動配置報告,這樣我們就可以很方便的知道哪些自動配置類生效;
#開啟springboot的除錯類
debug=true
-
Positive matches:(自動配置類啟用的:正匹配)
-
Negative matches:(沒有啟動,沒有匹配成功的自動配置類:負匹配)
-
Unconditional classes: (沒有條件的類)
6.5.自定義Starter
我們分析完畢了原始碼以及自動裝配的過程,我們可以嘗試自定義一個啟動器來玩玩!
6.5.1.說明
啟動器模組是一個 空 jar 檔案,僅提供輔助性依賴管理,這些依賴可能用於自動裝配或者其他類庫;
命名歸約:
官方命名:
- 字首:spring-boot-starter-xxx
- 比如:spring-boot-starter-web....
自定義命名:
- xxx-spring-boot-starter
- 比如:mybatis-spring-boot-starter
6.5.2.編寫啟動器
-
在IDEA中新建一個空專案 spring-boot-starter-diy
-
新建一個普通Maven模組:kuang-spring-boot-starter
-
新建一個Springboot模組:kuang-spring-boot-starter-autoconfigure
-
點選apply即可,基本結構
-
在我們的 starter 中 匯入 autoconfigure 的依賴!
<!-- 啟動器 --> <dependencies> <!-- 引入自動配置模組 --> <dependency> <groupId>com.kuang</groupId> <artifactId>kuang-spring-boot-starter-autoconfigure</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies>
-
將 autoconfigure 專案下多餘的檔案都刪掉,Pom中只留下一個 starter,這是所有的啟動器基本配置!
-
我們編寫一個自己的服務
package wyl.ss; 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(); } }
-
編寫
HelloProperties
配置類package wyl.ss; 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; } }
-
編寫我們的自動配置類並注入bean,測試!
package wyl.ss; 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; } }
-
在resources編寫一個自己的
META-INF\spring.factories
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ wyl.ss.HelloServiceAutoConfiguration
-
編寫完成後,可以安裝到maven倉庫中!
6.5.3.新建專案測試我們自己寫的啟動器
-
新建一個SpringBoot 專案
-
匯入我們自己寫的啟動器
<dependency> <groupId>wyl.ss</groupId> <artifactId>ss-spring-boot-starter</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
-
編寫一個
HelloController
進行測試我們自己的寫的介面!package wyl.ss.controller; @RestController public class HelloController { @Autowired HelloService helloService; @RequestMapping("/hello") public String hello(){ return helloService.sayHello("zxc"); } }
-
編寫配置檔案
application.properties
ss.hello.prefix="ppp" ss.hello.suffix="sss"
-
啟動專案進行測試,結果成功 !
7.整合JDBC
7.1.SpringData簡介
對於資料訪問層,無論是 SQL(關係型資料庫) 還是 NOSQL(非關係型資料庫),Spring Boot 底層都是採用 Spring Data 的方式進行統一處理。
Spring Boot 底層都是採用 Spring Data 的方式進行統一處理各種資料庫,Spring Data 也是 Spring 中與 Spring Boot、Spring Cloud 等齊名的知名專案。
Sping Data 官網
資料庫相關的啟動器 :可以參考官方文件
7.2.建立專案
7.2.1.新建專案測試
引入相應的模組:Spring Web、SQL中的JDBC API、MySql Driver
專案建好之後,Spring Boot自動匯入了啟動器
7.2.2.編寫yaml配置檔案,連線資料庫
新建一個 application.yml
spring:
datasource:
username: root
password: 123456
#?serverTimezone=UTC解決時區的報錯
url: jdbc:mysql://localhost:3306/資料庫名稱?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
7.2.3.測試
配置完這一些東西后就可以直接去使用了,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
, 我們並沒有手動配置
HikariDataSource 號稱 Java WEB 當前速度最快的資料來源,相比於傳統的 C3P0 、DBCP、Tomcat jdbc 等連線池更加優秀
可以使用 spring.datasource.type 指定自定義的資料來源型別,值為使用的連線池實現的完全限定名
有了資料庫連線,就可以 CRUD 運算元據庫了。但需要先了解物件 JdbcTemplate
7.2.4.原始碼
開啟 DataSourceProperties 的原始碼,能配置的所有東西都在這
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
private ClassLoader classLoader;
private String name;
private boolean generateUniqueName = true;
private Class<? extends DataSource> type;
private String driverClassName;
private String url;
private String username;
private String password;
............
開啟 DataSourceAutoConfiguration 原始碼,資料來源的所有自動配置都在這裡
7.2.5.JDBCTemplate
- 有了資料來源(com.zaxxer.hikari.HikariDataSource),然後可以拿到資料庫連線(java.sql.Connection),有了連線,就可以使用原生的 JDBC 語句來運算元據庫
- 即使不使用第三方第資料庫操作框架,如 MyBatis等,Spring 本身也對原生的JDBC 做了輕量級的封裝,即JdbcTemplate。
- 資料庫操作的所有 CRUD 方法都在 JdbcTemplate 中。
- Spring Boot 不僅提供了預設的資料來源,同時預設已經配置好了 JdbcTemplate 放在了容器中,程式設計師只需自己注入即可使用
- JdbcTemplate 的自動配置是依賴 org.springframework.boot.autoconfigure.jdbc 包下的 JdbcTemplateConfiguration 類
JdbcTemplate主要提供以下幾類方法:
- execute方法:可以用於執行任何SQL語句,一般用於執行DDL語句;
- update方法及batchUpdate方法:update方法用於執行新增、修改、刪除等語句;batchUpdate方法用於執行批處理相關語句;
- query方法及queryForXXX方法:用於執行查詢相關語句;
- call方法:用於執行儲存過程、函式相關語句。
測試
新建一個 controller 目錄,在裡面編寫一個 JdbcController
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("/userList")
public List<Map<String,Object>> userList(){
String sql = "select * from user";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
return maps;
}
//新增一個使用者
@GetMapping("/add")
public String addUser(){
//插入語句,注意時間問題
String sql = "insert into mybatis.user(id, name,pwd)" +
" values (9,'Java程式設計思想','qqwweer987')";
jdbcTemplate.update(sql);
//查詢
return "addOk";
}
//修改使用者資訊
@GetMapping("/update/{id}")
public String updateUser(@PathVariable("id") int id){
//插入語句
String sql = "update mybatis.user set name=?,pwd=? where id="+id;
//資料封裝
Object[] objects = new Object[2];
objects[0] = "大威天龍";
objects[1] = "qwert123";
jdbcTemplate.update(sql,objects);
//查詢
return "update-Ok";
}
//刪除使用者
@GetMapping("/delete/{id}")
public String delUser(@PathVariable("id") int id){
//插入語句
String sql = "delete from mybatis.user where id=?";
jdbcTemplate.update(sql,id);
//查詢
return "delete-Ok";
}
}
8.整合 druid
8.1.druid簡介
-
Java程式很大一部分要運算元據庫,為了提高效能運算元據庫的時候,又不得不使用資料庫連線池。
-
Druid 是阿里巴巴開源平臺上一個資料庫連線池實現,結合了 C3P0、DBCP 等 DB 池的優點,同時加入了日誌監控。
-
Druid 可以很好的監控 DB 池連線和 SQL 的執行情況,天生就是針對監控而生的 DB 連線池。
-
Druid已經在阿里巴巴部署了超過600個應用,經過一年多生產環境大規模部署的嚴苛考驗。
-
Spring Boot 2.0 以上預設使用 Hikari 資料來源,可以說 Hikari 與 Driud 都是當前 Java Web 上最優秀的資料來源,我們來重點介紹 Spring Boot 如何整合 Druid 資料來源,如何實現資料庫監控。
-
Github地址:https://github.com/alibaba/druid/
8.2.配置
加入druid相關配置(.yml配置檔案)
spring:
#資料庫配置
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false
username: root
password: root
druid:
# 初始連線數
initial-size: 10
# 最大連線池數量
max-active: 100
# 最小連線池數量
min-idle: 10
# 配置獲取連線等待超時的時間
max-wait: 60000
# 開啟PSCache,並且指定每個連線上PSCache的大小
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
# 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一個連線在池中最小生存的時間,單位是毫秒
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: false
test-on-return: false
stat-view-servlet:
enabled: true
url-pattern: /druid/*
filter:
stat:
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: false
wall:
config:
multi-statement-allow: true
com.alibaba.druid.pool.DruidDataSource 基本配置引數如下:
配置 | 預設值 | 說明 |
---|---|---|
name | 配置這個屬性的意義在於,如果存在多個資料來源,監控的時候可以通過名字來區分開來。 如果沒有配置,將會生成一個名字,格式是:“DataSource-” + System.identityHashCode(this) | |
jdbcUrl | 連線資料庫的url,不同資料庫不一樣。例如: mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto | |
username | 連線資料庫的使用者名稱 | |
password | 連線資料庫的密碼。如果你不希望密碼直接寫在配置檔案中,可以使用ConfigFilter。詳細看這裡:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter | |
driverClassName | 根據url自動識別 | 這一項可配可不配,如果不配置druid會根據url自動識別dbType,然後選擇相應的driverClassName(建議配置下) |
initialSize | 0 | 初始化時建立物理連線的個數。初始化發生在顯示呼叫init方法,或者第一次getConnection時 |
maxActive | 8 | 最大連線池數量 |
maxIdle | 8 | 已經不再使用,配置了也沒效果 |
minIdle | 最小連線池數量 | |
maxWait | 獲取連線時最大等待時間,單位毫秒。配置了maxWait之後,預設啟用公平鎖,併發效率會有所下降,如果需要可以通過配置useUnfairLock屬性為true使用非公平鎖。 | |
poolPreparedStatements | false | 是否快取preparedStatement,也就是PSCache。PSCache對支援遊標的資料庫效能提升巨大,比如說oracle。在mysql下建議關閉。 |
maxOpenPreparedStatements | -1 | 要啟用PSCache,必須配置大於0,當大於0時,poolPreparedStatements自動觸發修改為true。在Druid中,不會存在Oracle下PSCache佔用記憶體過多的問題,可以把這個數值配置大一些,比如說100 |
validationQuery | 用來檢測連線是否有效的sql,要求是一個查詢語句。如果validationQuery為null,testOnBorrow、testOnReturn、testWhileIdle都不會其作用。 | |
validationQueryTimeout | 單位:秒,檢測連線是否有效的超時時間。底層呼叫jdbc Statement物件的void setQueryTimeout(int seconds)方法 |
|
testOnBorrow | true | 申請連線時執行validationQuery檢測連線是否有效,做了這個配置會降低效能 |
testOnReturn | false | 歸還連線時執行validationQuery檢測連線是否有效,做了這個配置會降低效能 |
testWhileIdle | false | 建議配置為true,不影響效能,並且保證安全性。申請連線的時候檢測,如果空閒時間大於timeBetweenEvictionRunsMillis,執行validationQuery檢測連線是否有效 |
timeBetweenEvictionRunsMillis | 1分鐘 ( 1.0.14 ) |
有兩個含義: 1) Destroy執行緒會檢測連線的間隔時間 2) testWhileIdle的判斷依據,詳細看testWhileIdle屬性的說明 |
numTestsPerEvictionRun | 不再使用,一個DruidDataSource只支援一個EvictionRun | |
minEvictableIdleTimeMillis | 30分鐘 ( 1.0.14 ) |
連線保持空閒而不被驅逐的最長時間 |
connectionInitSqls | 物理連線初始化的時候執行的sql | |
exceptionSorter | 根據dbType自動識別 | 當資料庫丟擲一些不可恢復的異常時,拋棄連線 |
filters | 屬性型別是字串,通過別名的方式配置擴充套件外掛,常用的外掛有: 監控統計用的filter:stat日誌用的filter:log4j防禦sql注入的filter:wall | |
proxyFilters | 型別是List<com.alibaba.druid.filter.Filter>,如果同時配置了filters和proxyFilters,是組合關係,並非替換關係 |
引入druid依賴
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.9</version>
</dependency>
專案啟動後可通過show processlist檢視mysql的全部執行緒是否和配置的initial-size一致。
8.3.druid監控測試
訪問ip:port/druid驗證即可,url中的/druid要和配置檔案中的url-pattern一致
stat-view-servlet:
enabled: true
url-pattern: /druid/*
效果如下:
9.整合mybatis
9.1.匯入 MyBatis 所需要的依賴
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
9.2.配置資料庫連線資訊
spring:
datasource:
username: root
password: admin
#?serverTimezone=UTC解決時區的報錯
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#Spring Boot 預設是不注入這些屬性值的,需要自己繫結
#druid 資料來源專有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置監控統計攔截的filters,stat:監控統計、log4j:日誌記錄、wall:防禦sql注入
#如果允許時報錯 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#則匯入 log4j 依賴即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
9.3.建立實體類
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
9.4.建立mapper
UserMapper.java
// 這個註解表示了這是一個 mybatis 的 mapper 類
@Mapper
@Repository
public interface UserMapper {
List<User> queryUserList();
User queryUserById(int id);
int addUser(User user);
int updateUser(User user);
int deleteUser(int id);
}
-
對應的Mapper對映檔案
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!--namespace=繫結一個對應的Dao/Mapper介面--> <mapper namespace="com.wyl.mapper.UserMapper"> <select id="queryUserList" resultType="User"> select * from mybatis.user; </select> <select id="queryUserById" resultType="User"> select * from mybatis.user where id = #{id}; </select> <insert id="addUser" parameterType="User"> insert into mybatis.user (id, name, pwd) values (#{id},#{name},#{pwd}); </insert> <update id="updateUser" parameterType="User"> update mybatis.user set name=#{name},pwd = #{pwd} where id = #{id}; </update> <delete id="deleteUser" parameterType="int"> delete from mybatis.user where id = #{id} </delete> </mapper>
-
maven配置資源過濾問題
<resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources>
-
編寫部門的 UserController 進行測試!
@RestController public class UserController { @Autowired private UserMapper userMapper; @GetMapping("/queryUserList") public List<User> queryUserList() { List<User> userList = userMapper.queryUserList(); for (User user : userList) { System.out.println(user); } return userList; } //新增一個使用者 @GetMapping("/addUser") public String addUser() { userMapper.addUser(new User(7,"wyl","123456")); return "ok"; } //修改一個使用者 @GetMapping("/updateUser") public String updateUser() { userMapper.updateUser(new User(7,"wjm","123456")); return "ok"; } @GetMapping("/deleteUser") public String deleteUser() { userMapper.deleteUser(7); return "ok"; } }
啟動專案訪問進行測試!
9.5.分頁
新增相關依賴
首先,我們需要在 pom.xml 檔案中新增分頁外掛依賴包。
pom.xml
<!-- pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.5</version>
</dependency>
新增相關配置
然後在 application.yml 配置檔案中新增分頁外掛有關的配置。
application.yml
# pagehelper
pagehelper:
helperDialect: mysql
reasonable: true
supportMethodsArguments: true
params: count=countSql
編寫分頁程式碼
首先,在 DAO 層新增一個分頁查詢方法。這個查詢方法跟查詢全部資料的方法除了名稱幾乎一樣。
SysUserMapper.java
import java.util.List;
import com.louis.springboot.demo.model.SysUser;
public interface SysUserMapper {
int deleteByPrimaryKey(Long id);
int insert(SysUser record);
int insertSelective(SysUser record);
SysUser selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(SysUser record);
int updateByPrimaryKey(SysUser record);
/**
* 查詢全部使用者
* @return
*/
List<SysUser> selectAll();
/**
* 分頁查詢使用者
* @return
*/
List<SysUser> selectPage();
}
然後在 SysUserMapper.xml 中加入selectPage的實現,當然你也可以直接用@Select註解將查詢語句直接寫在DAO程式碼,但我們這裡選擇寫在XML對映檔案,這是一個普通的查詢全部記錄的查詢語句,並不需要寫分頁SQL,分頁外掛會攔截查詢請求,並讀取前臺傳來的分頁查詢引數重新生成分頁查詢語句。
SysUserMapper.xml
<select id="selectPage" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from sys_user
</select>
服務層通過呼叫DAO層程式碼完成分頁查詢,這裡統一封裝分頁查詢的請求和結果類,從而避免因為替換ORM框架而導致服務層、控制層的分頁介面也需要變動的情況,替換ORM框架也不會影響服務層以上的分頁介面,起到了解耦的作用。
SysUserService.java
import java.util.List;
import com.louis.springboot.demo.model.SysUser;
import com.louis.springboot.demo.util.PageRequest;
import com.louis.springboot.demo.util.PageResult;
public interface SysUserService {
/**
* 根據使用者ID查詢使用者
* @param userId
* @return
*/
SysUser findByUserId(Long userId);
/**
* 查詢所有使用者
* @return
*/
List<SysUser> findAll();
/**
* 分頁查詢介面
* 這裡統一封裝了分頁請求和結果,避免直接引入具體框架的分頁物件, 如MyBatis或JPA的分頁物件
* 從而避免因為替換ORM框架而導致服務層、控制層的分頁介面也需要變動的情況,替換ORM框架也不會
* 影響服務層以上的分頁介面,起到了解耦的作用
* @param pageRequest 自定義,統一分頁查詢請求
* @return PageResult 自定義,統一分頁查詢結果
*/
PageResult findPage(PageRequest pageRequest);
}
服務實現類通過呼叫分頁外掛完成最終的分頁查詢,關鍵程式碼是 PageHelper.startPage(pageNum, pageSize),將前臺分頁查詢引數傳入並攔截MyBtis執行實現分頁效果。
SysUserServiceImpl.java
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.louis.springboot.demo.dao.SysUserMapper;
import com.louis.springboot.demo.model.SysUser;
import com.louis.springboot.demo.service.SysUserService;
import com.louis.springboot.demo.util.PageRequest;
import com.louis.springboot.demo.util.PageResult;
import com.louis.springboot.demo.util.PageUtils;
@Service
public class SysUserServiceImpl implements SysUserService {
@Autowired
private SysUserMapper sysUserMapper;
@Override
public SysUser findByUserId(Long userId) {
return sysUserMapper.selectByPrimaryKey(userId);
}
@Override
public List<SysUser> findAll() {
return sysUserMapper.selectAll();
}
@Override
public PageResult findPage(PageRequest pageRequest) {
return PageUtils.getPageResult(pageRequest, getPageInfo(pageRequest));
}
/**
* 呼叫分頁外掛完成分頁
* @param pageQuery
* @return
*/
private PageInfo<SysUser> getPageInfo(PageRequest pageRequest) {
int pageNum = pageRequest.getPageNum();
int pageSize = pageRequest.getPageSize();
PageHelper.startPage(pageNum, pageSize);
List<SysUser> sysMenus = sysUserMapper.selectPage();
return new PageInfo<SysUser>(sysMenus);
}
}
在控制器SysUserController中新增分頁查詢方法,並呼叫服務層的分頁查詢方法。
SysUserController.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.louis.springboot.demo.service.SysUserService;
import com.louis.springboot.demo.util.PageRequest;
@RestController
@RequestMapping("user")
public class SysUserController {
@Autowired
private SysUserService sysUserService;
@GetMapping(value="/findByUserId")
public Object findByUserId(@RequestParam Long userId) {
return sysUserService.findByUserId(userId);
}
@GetMapping(value="/findAll")
public Object findAll() {
return sysUserService.findAll();
}
@PostMapping(value="/findPage")
public Object findPage(@RequestBody PageRequest pageQuery) {
return sysUserService.findPage(pageQuery);
}
}
分頁查詢請求封裝類。
PageRequest.java
/**
* 分頁請求
*/
public class PageRequest {
/**
* 當前頁碼
*/
private int pageNum;
/**
* 每頁數量
*/
private int pageSize;
public int getPageNum() {
return pageNum;
}
public void setPageNum(int pageNum) {
this.pageNum = pageNum;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
}
分頁查詢結果封裝類。
PageResult.java
import java.util.List;
/**
* 分頁返回結果
*/
public class PageResult {
/**
* 當前頁碼
*/
private int pageNum;
/**
* 每頁數量
*/
private int pageSize;
/**
* 記錄總數
*/
private long totalSize;
/**
* 頁碼總數
*/
private int totalPages;
/**
* 資料模型
*/
private List<?> content;
public int getPageNum() {
return pageNum;
}
public void setPageNum(int pageNum) {
this.pageNum = pageNum;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public long getTotalSize() {
return totalSize;
}
public void setTotalSize(long totalSize) {
this.totalSize = totalSize;
}
public int getTotalPages() {
return totalPages;
}
public void setTotalPages(int totalPages) {
this.totalPages = totalPages;
}
public List<?> getContent() {
return content;
}
public void setContent(List<?> content) {
this.content = content;
}
}
分頁查詢相關工具類。
PageUtils.java
import com.github.pagehelper.PageInfo;
public class PageUtils {
/**
* 將分頁資訊封裝到統一的介面
* @param pageRequest
* @param page
* @return
*/
public static PageResult getPageResult(PageRequest pageRequest, PageInfo<?> pageInfo) {
PageResult pageResult = new PageResult();
pageResult.setPageNum(pageInfo.getPageNum());
pageResult.setPageSize(pageInfo.getPageSize());
pageResult.setTotalSize(pageInfo.getTotal());
pageResult.setTotalPages(pageInfo.getPages());
pageResult.setContent(pageInfo.getList());
return pageResult;
}
}
編譯測試執行
啟動應用,訪問:localhost:8088/swagger-ui.html,找到對應介面,模擬測試,結果如下。
引數:pageNum: 1, pageSize: 5
10.事務管理
SpringBoot 使用事務非常簡單,底層依然採用的是Spring本身提供的事務管理
• 在入口類中使用註解 @EnableTransactionManagement 開啟事務支援
• 在訪問資料庫的Service方法上新增註解 @Transactional 即可
案例思路
通過SpringBoot +MyBatis實現對資料庫學生表的更新操作,在service層的方法中構建異常,檢視事務是否生效;
專案名稱:springboot--transacation
10.1.實現步驟
10.1.1.StudentController
@Controller
public class SpringBootController {
@Autowired
private StudentService studentService;
@RequestMapping(value = "/springBoot/update")
public @ResponseBody Object update() {
Student student = new Student();
student.setId(1);
student.setName("Mark");
student.setAge(100);
int updateCount = studentService.update(student);
return updateCount;
}
}
10.1.2.StudentService介面
public interface StudentService {
/**
* 根據學生標識更新學生資訊
* @param student
* @return
*/
int update(Student student);
}
10.1.3.StudentServiceImpl
介面實現類中對更新學生方法進行實現,並構建一個異常,同時在該方法上加@Transactional註解
@Override
@Transactional //新增此註解說明該方法新增的事務管理
public int update(Student student) {
int updateCount = studentMapper.updateByPrimaryKeySelective(student);
System.out.println("更新結果:" + updateCount);
//在此構造一個除數為0的異常,測試事務是否起作用
int a = 10/0;
return updateCount;
}
10.1.4.Application
在類上加@EnableTransactionManagement開啟事務支援
@EnableTransactionManagement可選,但是@Service必須新增事務才生效
@SpringBootApplication
@EnableTransactionManagement //SpringBoot開啟事務的支援
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
10.1.5.啟動Application
1.檢視資料庫,並沒有修改資料,通過以上結果,說明事務起作用了。
2.註釋掉StudentServiceImpl上的@Transactional測試----資料庫發上來改變。
11.SpringMVC註解
SpringBoot下的SpringMVC和之前的SpringMVC使用是完全一樣的,主要有以下註解:
1.@Controller
Spring MVC的註解,處理http請求
2.@RestController
Spring4後新增註解,是@Controller註解功能的增強,是@Controller與@ResponseBody的組合註解;
如果一個Controller類新增了@RestController,那麼該Controller類下的所有方法都相當於新增了@ResponseBody註解;
用於返回字串或json資料。
案例:
• 建立MyUserController類,演示@RestController替代@Controller + @ResponseBody
@RestController
public class MyUserController {
@Autowired
private UserService userService;
@RequestMapping("/user/getUser")
public Object getUser(){
return userService.getUser(1);
}
}
3.@RequestMapping(常用)
支援Get請求,也支援Post請求
4.@GetMapping
RequestMapping和Get請求方法的組合只支援Get請求;Get請求主要用於查詢操作。
5.@PostMapping
RequestMapping和Post請求方法的組合只支援Post請求;Post請求主要使用者新增資料。
6.@PutMapping
RequestMapping和Put請求方法的組合只支援Put請求;Put通常用於修改資料。
7.@DeleteMapping
RequestMapping 和 Delete請求方法的組合只支援Delete請求;通常用於刪除資料。
12.RESTful實現
Spring boot開發RESTFul 主要是幾個註解實現:
@PathVariable:獲取url中的資料,該註解是實現RESTFul最主要的一個註解。
@PostMapping:接收和處理Post方式的請求
@DeleteMapping:接收delete方式的請求,可以使用GetMapping代替
@PutMapping:接收put方式的請求,可以用PostMapping代替
@GetMapping:接收get方式的請求
RESTful的優點
• 輕量,直接基於http,不再需要任何別的諸如訊息協議,get/post/put/delete為CRUD操作
• 面向資源,一目瞭然,具有自解釋性。
• 資料描述簡單,一般以xml,json做資料交換。
• 無狀態,在呼叫一個介面(訪問、操作資源)的時候,可以不用考慮上下文,不用考慮當前狀態,極大的降低了複雜度。
• 簡單、低耦合
12.1.案例
使用RESTful風格模擬實現對學生的增刪改查操作
該專案整合了MyBatis、spring、SpringMVC,通過模擬實現對學生的增刪改查操作。
1.建立RESTfulController,並編寫程式碼
@RestController
public class RESTfulController {
/**
* 新增學生
* 請求地址:http://localhost:8080/springboot-restful/springBoot/student/wyl/23
* 請求方式:POST
* @param name
* @param age
* @return
*/
@PostMapping(value = "/springBoot/student/{name}/{age}")
public Object addStudent(@PathVariable("name") String name,
@PathVariable("age") Integer age) {
Map retMap = new HashMap();
retMap.put("name",name);
retMap.put("age",age);
return retMap;
}
/**
* 刪除學生
* 請求地址:http://localhost:8080/springboot-restful/springBoot/student/1
* 請求方式:Delete
* @param id
* @return
*/
@DeleteMapping(value = "/springBoot/student/{id}")
public Object removeStudent(@PathVariable("id") Integer id) {
return "刪除的學生id為:" + id;
}
/**
* 修改學生資訊
* 請求地址:http://localhost:8080/springboot-restful/springBoot/student/2
* 請求方式:Put
* @param id
* @return
*/
@PutMapping(value = "/springBoot/student/{id}")
public Object modifyStudent(@PathVariable("id") Integer id) {
return "修改學生的id為" + id;
}
@GetMapping(value = "/springBoot/student/{id}")
public Object queryStudent(@PathVariable("id") Integer id) {
return "查詢學生的id為" + id;
}
}
12.2.RESTful原則
• 增post請求、刪delete請求、改put請求、查get請求
• 請求路徑不要出現動詞
例如:查詢訂單介面
/boot/order/1021/1(推薦)
/boot/queryOrder/1021/1(不推薦)
• 分頁、排序等操作,不需要使用斜槓傳引數
例如:訂單列表介面 /boot/orders?page=1&sort=desc
一般傳的引數不是資料庫表的欄位,可以不採用斜槓
13.靜態資源處理
13.1.對哪些目錄對映?
classpath:/META-INF/resources/
classpath:/resources/
classpath:/static/
classpath:/public/
/:當前專案的根路徑
就我們在上面五個目錄下放靜態資源(比如:a.png等),可以直接訪問(http://localhost:8080/a.png),類似於以前web專案的webapp下;放到其他目錄下無法被訪問。
優先順序:resources > static(預設) > public
13.2.原始碼分析
SpringBoot自動配置的WebMvcAutoConfiguration.java
類
-
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/
找對應的資源;
13.2.1.什麼是webjars 呢?
Webjars本質就是以jar包的方式引入我們的靜態資源 , 我們以前要匯入一個靜態資原始檔,直接匯入即可。
13.2.2.第一種靜態資源對映規則
使用SpringBoot需要使用Webjars,我們可以去搜尋一下:
要使用jQuery,我們只要要引入jQuery對應版本的pom依賴即可!
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.4.1</version>
</dependency>
匯入完畢,檢視webjars目錄結構,並訪問Jquery.js檔案!
訪問:只要是靜態資源,SpringBoot就會去對應的路徑尋找資源,我們這裡訪問:http://localhost:8080/webjars/jquery/3.4.1/jquery.js
13.2.3.第二種靜態資源對映規則
1、那我們專案中要是使用自己的靜態資源該怎麼匯入呢?我們看下一行程式碼;
2、我們去找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/"
};
3、ResourceProperties 可以設定和我們靜態資源有關的引數;這裡面指向了它會去尋找資源的資料夾,即上面陣列的內容。
4、所以得出結論,以下四個目錄存放的靜態資源可以被我們識別:
"classpath:/META-INF/resources/"
"classpath:/resources/"
"classpath:/static/"
"classpath:/public/"
5、我們可以在resources根目錄下新建對應的資料夾,都可以存放我們的靜態檔案;
6、比如我們訪問 http://localhost:8080/1.js , 他就會去這些資料夾中尋找對應的靜態資原始檔;
13.2.4.引用
不用寫static 路徑
<link rel="stylesheet" href="/layui/css/layui.css">
<link rel="stylesheet" href="/easyui/default/easyui.css">
<script src="/layui/jquery-1.10.2.min.js" type="text/javascript"></script>
<script src="/easyui/jquery.easyui.min.js" type="text/javascript"></script>
13.3.自定義靜態資源路徑
首先,自定義會覆蓋預設!所以沒十足把握的情況下,不建議覆蓋,但可以新增。
兩種方式
13.3.1.配置類程式碼實現
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("file:F:/AppFiles/");
}
}
13.3.2.配置檔案
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,\
classpath:/static/,classpath:/public/,file:c:/appfiles/
13.3.3.相對路徑配置
以上的情況均是絕對路徑,受限於環境,開發生產LinuxWin等。這種變化情況多,建議理清思路,再決定是否適用。
解決方案
String gitPath = path.getParentFile().getParentFile().getParent()
+ File.separator + "logistics" + File.separator + "uploads"
+ File.separator;
工具類
spring框架自帶的ResourceUtils
,或者結合第三方工具
13.3.4. 歡迎頁與圖示
14.國際化
國際化(Internationalization 簡稱 I18n,其中“I”和“n”分別為首末字元,18 則為中間的字元數)是指軟體開發時應該具備支援多種語言和地區的功能。換句話說就是,開發的軟體需要能同時應對不同國家和地區的使用者訪問,並根據使用者地區和語言習慣,提供相應的、符合用具閱讀習慣的頁面和資料,例如,為中國使用者提供漢語介面顯示,為美國使用者提供提供英語介面顯示。
在 Spring 專案中實現國際化,通常需要以下 3 步:
- 編寫國際化資源(配置)檔案;
- 使用 ResourceBundleMessageSource 管理國際化資原始檔;
- 在頁面獲取國際化內容。
14.1. 編寫國際化資原始檔
在 Spring Boot 的類路徑下建立國際化資原始檔,檔名格式為:基本名_語言程式碼_國家或地區程式碼,例如 login_en_US.properties、login_zh_CN.properties。
以 spring-boot-springmvc-demo1為例,在 src/main/resources 下建立一個 i18n 的目錄,並在該目錄中按照國際化資原始檔命名格式分別建立以下三個檔案,
- login.properties:無語言設定時生效
- login_en_US.properties :英語時生效
- login_zh_CN.properties:中文時生效
以上國際化資原始檔建立完成後,IDEA 會自動識別它們,並轉換成如下的模式:
開啟任意一個國際化資原始檔,並切換為 Resource Bundle 模式,然後點選“+”號,建立所需的國際化屬性,如下圖。
14.2.配置檔案生效探究
Spring Boot 已經對 ResourceBundleMessageSource 提供了預設的自動配置。
Spring Boot 通過 MessageSourceAutoConfiguration 對 ResourceBundleMessageSource 提供了預設配置,其部分原始碼如下。
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, search = SearchStrategy.CURRENT)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Conditional(org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration.ResourceBundleCondition.class)
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {
private static final Resource[] NO_RESOURCES = {};
// 將 MessageSourceProperties 以元件的形式新增到容器中
// MessageSourceProperties 下的每個屬性都與以 spring.messages 開頭的屬性對應
@Bean
@ConfigurationProperties(prefix = "spring.messages")
public MessageSourceProperties messageSourceProperties() {
return new MessageSourceProperties();
}
//Spring Boot 會從容器中獲取 MessageSourceProperties
// 讀取國際化資原始檔的 basename(基本名)、encoding(編碼)等資訊
// 並封裝到 ResourceBundleMessageSource 中
@Bean
public MessageSource messageSource(MessageSourceProperties properties) {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
//讀取國際化資原始檔的 basename (基本名),並封裝到 ResourceBundleMessageSource 中
if (StringUtils.hasText(properties.getBasename())) {
messageSource.setBasenames(StringUtils
.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
}
//讀取國際化資原始檔的 encoding (編碼),並封裝到 ResourceBundleMessageSource 中
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;
}
...
}
從以上原始碼可知:
Spring Boot 將 MessageSourceProperties 以元件的形式新增到容器中;
MessageSourceProperties 的屬性與配置檔案中以“spring.messages”開頭的配置進行了繫結;
Spring Boot 從容器中獲取 MessageSourceProperties 元件,並從中讀取國際化資原始檔的 basename(檔案基本名)、encoding(編碼)等資訊,將它們封裝到 ResourceBundleMessageSource 中;
Spring Boot 將 ResourceBundleMessageSource 以元件的形式新增到容器中,進而實現對國際化資原始檔的管理。
檢視 MessageSourceProperties 類,其程式碼如下。
public class MessageSourceProperties {
private String basename = "messages";
private Charset encoding;
@DurationUnit(ChronoUnit.SECONDS)
private Duration cacheDuration;
private boolean fallbackToSystemLocale;
private boolean alwaysUseMessageFormat;
private boolean useCodeAsDefaultMessage;
public MessageSourceProperties() {
this.encoding = StandardCharsets.UTF_8;
this.fallbackToSystemLocale = true;
this.alwaysUseMessageFormat = false;
this.useCodeAsDefaultMessage = false;
}
...
}
通過以上程式碼,我們可以得到以下 3 點資訊:
- MessageSourceProperties 為 basename、encoding 等屬性提供了預設值;
- basename 表示國際化資原始檔的基本名,其預設取值為“message”,即 Spring Boot 預設會獲取類路徑下的 message.properties 以及 message_XXX.properties 作為國際化資原始檔;
- 在 application.porperties/yml 等配置檔案中,使用配置引數“spring.messages.basename”即可重新指定國際化資原始檔的基本名。
通過以上原始碼分析可知,Spring Boot 已經對國際化資原始檔的管理提供了預設自動配置,我們這裡只需要在 Spring Boot 全域性配置檔案中,使用配置引數“spring.messages.basename”指定我們自定義的國際資原始檔的基本名即可,程式碼如下(當指定多個資原始檔時,用逗號分隔)。
spring.messages.basename=i18n.login
14.3. 獲取國際化內容
由於頁面使用的是 Tymeleaf 模板引擎,因此我們可以通過表示式 #{...} 獲取國際化內容。
以 spring-boot-adminex 為例,在 login.html 中獲取國際化內容,程式碼如下。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<meta name="description" content="">
<meta name="author" content="ThemeBucket">
<link rel="shortcut icon" href="#" type="image/png">
<title>Login</title>
<!--將js css 等靜態資源的引用修改為 絕對路徑-->
<link href="css/style.css" th:href="@{/css/style.css}" rel="stylesheet">
<link href="css/style-responsive.css" th:href="@{/css/style-responsive.css}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="js/html5shiv.js" th:src="@{/js/html5shiv.js}"></script>
<script src="js/respond.min.js" th:src="@{/js/respond.min.js}"></script>
<![endif]-->
</head>
<body class="login-body">
<div class="container">
<form class="form-signin" th:action="@{/user/login}" method="post">
<div class="form-signin-heading text-center">
<h1 class="sign-title" th:text="#{login.btn}">Sign In</h1>
<img src="/images/login-logo.png" th:src="@{/images/login-logo.png}" alt=""/>
</div>
<div class="login-wrap">
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
<input type="text" class="form-control" name="username" placeholder="User ID" autofocus
th:placeholder="#{login.username}"/>
<input type="password" class="form-control" name="password" placeholder="Password"
th:placeholder="#{login.password}"/>
<label class="checkbox">
<input type="checkbox" value="remember-me" th:text="#{login.remember}">
<span class="pull-right">
<a data-toggle="modal" href="#myModal" th:text="#{login.forgot}"> </a>
</span>
</label>
<button class="btn btn-lg btn-login btn-block" type="submit">
<i class="fa fa-check"></i>
</button>
<div class="registration">
<!--Thymeleaf 行內寫法-->
[[#{login.not-a-member}]]
<a class="" href="/registration.html" th:href="@{/registration.html}">
[[#{login.signup}]]
</a>
<!--thymeleaf 模板引擎的引數用()代替 ?-->
<br/>
<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>
</div>
</div>
<!-- Modal -->
<div aria-hidden="true" aria-labelledby="myModalLabel" role="dialog" tabindex="-1" id="myModal"
class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title">Forgot Password ?</h4>
</div>
<div class="modal-body">
<p>Enter your e-mail address below to reset your password.</p>
<input type="text" name="email" placeholder="Email" autocomplete="off"
class="form-control placeholder-no-fix">
</div>
<div class="modal-footer">
<button data-dismiss="modal" class="btn btn-default" type="button">Cancel</button>
<button class="btn btn-primary" type="button">Submit</button>
</div>
</div>
</div>
</div>
<!-- modal -->
</form>
</div>
<!-- Placed js at the end of the document so the pages load faster -->
<!-- Placed js at the end of the document so the pages load faster -->
<script src="js/jquery-1.10.2.min.js" th:src="@{/js/jquery-1.10.2.min.js}"></script>
<script src="js/bootstrap.min.js" th:src="@{/js/bootstrap.min.js}"></script>
<script src="js/modernizr.min.js" th:src="@{/js/modernizr.min.js}"></script>
</body>
</html>
14.4.區域資訊解析器自動配置
我們知道,Spring MVC 進行國際化時有 2 個十分重要的物件:
- Locale:區域資訊物件
- LocaleResolver:區域資訊解析器,容器中的元件,負責獲取區域資訊物件
我們可以通過以上兩個物件對區域資訊的切換,以達到切換語言的目的。
Spring Boot 在 WebMvcAutoConfiguration 中為區域資訊解析器(LocaleResolver)進行了自動配置,原始碼如下。
@Bean
@ConditionalOnMissingBean(name = DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME)
@SuppressWarnings("deprecation")
public LocaleResolver localeResolver() {
if (this.webProperties.getLocaleResolver() == WebProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.webProperties.getLocale());
}
if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
}
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
Locale locale = (this.webProperties.getLocale() != null) ? this.webProperties.getLocale()
: this.mvcProperties.getLocale();
localeResolver.setDefaultLocale(locale);
return localeResolver;
}
從以上原始碼可知:
- 該方法預設向容器中新增了一個區域資訊解析器(LocaleResolver)元件,它會根據請求頭中攜帶的“Accept-Language”引數,獲取相應區域資訊(Locale)物件。
- 該方法上使用了 @ConditionalOnMissingBean 註解,其引數 name 的取值為 localeResolver(與該方法注入到容器中的元件名稱一致),該註解的含義為:當容器中不存在名稱為 localResolver 元件時,該方法才會生效。換句話說,當我們手動向容器中新增一個名為“localeResolver”的元件時,Spring Boot 自動配置的區域資訊解析器會失效,而我們定義的區域資訊解析器則會生效。
手動切換語言
- 修改 login.html 切換語言連結,在請求中攜帶國際化區域資訊,程式碼如下。
<!--thymeleaf 模板引擎的引數用()代替 ?-->
<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>
- 建立一個 component 包,並在該包中建立一個區域資訊解析器 MyLocalResolver,程式碼如下。
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 MyLocalResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
//獲取請求中引數
String l = request.getParameter("l");
//獲取預設的區域資訊解析器
Locale locale = Locale.getDefault();
//根據請求中的引數重新構造區域資訊物件
if (StringUtils.hasText(l)) {
String[] s = l.split("_");
locale = new Locale(s[0], s[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
為了讓我們的區域化資訊能夠生效,我們需要再配置一下這個元件!在我們自己的MvcConofig
下新增bean
;
@Bean
public LocaleResolver localeResolver(){
return new MyLocalResolver();
}
15.swagger
15.1.新增依賴
<!-- swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
15.2.新增配置類
新增一個swagger 配置類,在工程下新建 config 包並新增一個 SwaggerConfig 配置類。
SwaggerConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi(){
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any()).build();
}
private ApiInfo apiInfo(){
return new ApiInfoBuilder()
.title("Kitty API Doc")
.description("This is a restful api document of Kitty.")
.version("1.0")
.build();
}
}
15.3.新增控制器
新增一個控制器,在工程下新建 controller包並新增一個 HelloController控制器。
HelloController.java
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
/* 類註解 */
@Api(value = "desc of class")
@RestController
public class HelloController {
/* 方法註解 */
@ApiOperation(value = "desc of method", notes = "")
@GetMapping(value="/hello")
public Object hello( /* 引數註解 */ @ApiParam(value = "desc of param" , required=true ) @RequestParam String name) {
return "Hello " + name + "!";
}
}
執行即可
15.4.常用註解說明
swagger 通過註解介面生成文件,包括介面名,請求方法,引數,返回資訊等。
@Api: 修飾整個類,用於controller類上
@ApiOperation: 描述一個介面,使用者controller方法上
@ApiParam: 單個引數描述
@ApiModel: 用來物件接收引數,即返回物件
@ApiModelProperty: 物件接收引數時,描述物件的欄位
@ApiResponse: Http響應其中的描述,在ApiResonse中
@ApiResponses: Http響應所有的描述,用在
@ApiIgnore: 忽略這個API
@ApiError: 發生錯誤的返回資訊
@ApiImplicitParam: 一個請求引數
@ApiImplicitParam: 多個請求引數
更多使用說明,參考 Swagger 使用手冊。
15.5.新增請求引數
在很多時候,我們需要在呼叫我們每一個介面的時候都攜帶上一些通用引數,比如採取token驗證邏輯的往往在介面請求時需要把token也一起傳入後臺,接下來,我們就來講解一下如何給Swagger新增固定的請求引數。
修改SwaggerConfig配置類,替換成如下內容,利用ParameterBuilder構成請求引數。
SwaggerConfig.java
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi(){
// 新增請求引數,我們這裡把token作為請求頭部引數傳入後端
ParameterBuilder parameterBuilder = new ParameterBuilder();
List<Parameter> parameters = new ArrayList<Parameter>();
parameterBuilder.name("token").description("令牌")
.modelRef(new ModelRef("string")).parameterType("header").required(false).build();
parameters.add(parameterBuilder.build());
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()
.apis(RequestHandlerSelectors.any()).paths(PathSelectors.any())
.build().globalOperationParameters(parameters);
// return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
// .select()
// .apis(RequestHandlerSelectors.any())
// .paths(PathSelectors.any()).build();
}
private ApiInfo apiInfo(){
return new ApiInfoBuilder()
.title("Swagger API Doc")
.description("This is a restful api document of Swagger.")
.version("1.0")
.build();
}
}
完成之後重新啟動應用,再次檢視hello介面,可以看到已經支援傳送token請求引數了。
15.6.配置API分組
如果沒有配置分組,預設是default。通過groupName()方法即可配置分組
@Bean
public Docket docket(Environment environment) {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
.groupName("group1") // 配置分組
....
}
如何配置多個分組?配置多個分組只需要配置多個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");
}
16.攔截器
在 Spring Boot 專案中,使用攔截器功能通常需要以下 3 步:
-
定義攔截器;
-
註冊攔截器;
-
指定攔截規則(如果是攔截所有,靜態資源也會被攔截)。
16.1.定義攔截器
在 Spring Boot 中定義攔截器十分的簡單,只需要建立一個攔截器類,並實現 HandlerInterceptor 介面即可。
HandlerInterceptor 介面中定義以下 3 個方法,如下表。
返回值型別 | 方法宣告 | 描述 |
---|---|---|
boolean | preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) | 該方法在控制器處理請求方法前執行,其返回值表示是否中斷後續操作,返回 true 表示繼續向下執行,返回 false 表示中斷後續操作。 |
void | postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) | 該方法在控制器處理請求方法呼叫之後、解析檢視之前執行,可以通過此方法對請求域中的模型和檢視做進一步修改。 |
void | afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) | 該方法在檢視渲染結束後執行,可以通過此方法實現資源清理、記錄日誌資訊等工作。 |
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
/**
* 目標方法執行前
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object loginUser = request.getSession().getAttribute("loginUser");
if (loginUser == null) {
//未登入,返回登陸頁
request.setAttribute("msg", "您沒有許可權進行此操作,請先登陸!");
request.getRequestDispatcher("/index.html").forward(request, response);
return false;
} else {
//放行
return true;
}
}
/**
* 目標方法執行後
*
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle執行{}", modelAndView);
}
/**
* 頁面渲染後
*
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("afterCompletion執行異常{}", ex);
}
}
16.2.註冊攔截器
建立一個實現了 WebMvcConfigurer 介面的配置類(使用了 @Configuration 註解的類),重寫 addInterceptors() 方法,並在該方法中呼叫 registry.addInterceptor() 方法將自定義的攔截器註冊到容器中。
在配置類 MyMvcConfig 中,新增以下方法註冊攔截器,程式碼如下。
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
......
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor());
}
}
16.3.指定攔截規則
在使用 registry.addInterceptor() 方法將攔截器註冊到容器中後,我們便可以繼續指定攔截器的攔截規則了,程式碼如下
@Slf4j
@Configuration
public class MyConfig implements WebMvcConfigurer {
......
@Override
public void addInterceptors(InterceptorRegistry registry) {
log.info("註冊攔截器");
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**") //攔截所有請求,包括靜態資原始檔
.excludePathPatterns("/", "/login", "/index.html", "/user/login", "/css/**", "/images/**", "/js/**", "/fonts/**"); //放行登入頁,登陸操作,靜態資源
}
}
在指定攔截器攔截規則時,呼叫了兩個方法,這兩個方法的說明如下:
- addPathPatterns:該方法用於指定攔截路徑,例如攔截路徑為“/**”,表示攔截所有請求,包括對靜態資源的請求。
- excludePathPatterns:該方法用於排除攔截路徑,即指定不需要被攔截器攔截的請求。
至此,攔截器的基本功能已經完成,接下來,我們先實現 spring-boot-adminex 的登陸功能,為驗證登陸攔截做準備。
17.異常處理
Spring Boot 提供了一套預設的異常處理機制,一旦程式中出現了異常,Spring Boot 會自動識別客戶端的型別(瀏覽器客戶端或機器客戶端),並根據客戶端的不同,以不同的形式展示異常資訊。
- 對於瀏覽器客戶端而言,Spring Boot 會響應一個“ whitelabel”錯誤檢視,以 HTML 格式呈現錯誤資訊
- 對於機器客戶端而言,Spring Boot 將生成 JSON 響應,來展示異常訊息。
{
"timestamp": "2021-07-12T07:05:29.885+00:00",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/m1ain.html"
}
Spring Boot 異常處理自動配置原理
Spring Boot 通過配置類 ErrorMvcAutoConfiguration 對異常處理提供了自動配置,該配置類向容器中注入了以下 4 個元件。
-
ErrorPageCustomizer:該元件會在在系統發生異常後,預設將請求轉發到“/error”上。
-
BasicErrorController:處理預設的“/error”請求。
-
DefaultErrorViewResolver:預設的錯誤檢視解析器,將異常資訊解析到相應的錯誤檢視上。
-
DefaultErrorAttributes:用於頁面上共享異常資訊。
下面,我們依次對這四個元件進行詳細的介紹。
ErrorPageCustomizer
ErrorMvcAutoConfiguration 向容器中注入了一個名為 ErrorPageCustomizer 的元件,它主要用於定製錯誤頁面的響應規則。
@Bean
public ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) {
return new ErrorPageCustomizer(this.serverProperties, dispatcherServletPath);
}
ErrorPageCustomizer 通過 registerErrorPages() 方法來註冊錯誤頁面的響應規則。當系統中發生異常後,ErrorPageCustomizer 元件會自動生效,並將請求轉發到 “/error”上,交給 BasicErrorController 進行處理,其部分程式碼如下。
@Override
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
//將請求轉發到 /errror(this.properties.getError().getPath())上
ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
// 註冊錯誤頁面
errorPageRegistry.addErrorPages(errorPage);
}
BasicErrorController
ErrorMvcAutoConfiguration 還向容器中注入了一個錯誤控制器元件 BasicErrorController,程式碼如下。
@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
ObjectProvider<ErrorViewResolver> errorViewResolvers) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
errorViewResolvers.orderedStream().collect(Collectors.toList()));
}
BasicErrorController 的定義如下。
//BasicErrorController 用於處理 “/error” 請求
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
......
/**
* 該方法用於處理瀏覽器客戶端的請求發生的異常
* 生成 html 頁面來展示異常資訊
* @param request
* @param response
* @return
*/
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
//獲取錯誤狀態碼
HttpStatus status = getStatus(request);
//getErrorAttributes 根據錯誤資訊來封裝一些 model 資料,用於頁面顯示
Map<String, Object> model = Collections
.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
//為響應物件設定錯誤狀態碼
response.setStatus(status.value());
//呼叫 resolveErrorView() 方法,使用錯誤檢視解析器生成 ModelAndView 物件(包含錯誤頁面地址和頁面內容)
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
/**
* 該方法用於處理機器客戶端的請求發生的錯誤
* 產生 JSON 格式的資料展示錯誤資訊
* @param request
* @return
*/
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity<>(status);
}
Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
return new ResponseEntity<>(body, status);
}
......
}
Spring Boot 通過 BasicErrorController 進行統一的錯誤處理(例如預設的“/error”請求)。Spring Boot 會自動識別發出請求的客戶端的型別(瀏覽器客戶端或機器客戶端),並根據客戶端型別,將請求分別交給 errorHtml() 和 error() 方法進行處理。
返回值型別 | 方法宣告 | 客戶端型別 | 錯誤資訊返型別 |
---|---|---|---|
ModelAndView | errorHtml(HttpServletRequest request, HttpServletResponse response) | 瀏覽器客戶端 | text/html(錯誤頁面) |
ResponseEntity<Map<String, Object>> | error(HttpServletRequest request) | 機器客戶端(例如安卓、IOS、Postman 等等) | JSON |
換句話說,當使用瀏覽器訪問出現異常時,會進入 BasicErrorController 控制器中的 errorHtml() 方法進行處理,當使用安卓、IOS、Postman 等機器客戶端訪問出現異常時,就進入error() 方法處理。
在 errorHtml() 方法中會呼叫父類(AbstractErrorController)的 resolveErrorView() 方法,程式碼如下。
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,
Map<String, Object> model) {
//獲取容器中的所有的錯誤檢視解析器來處理該異常資訊
for (ErrorViewResolver resolver : this.errorViewResolvers) {
//呼叫錯誤檢視解析器的 resolveErrorView 解析到錯誤檢視頁面
ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
if (modelAndView != null) {
return modelAndView;
}
}
return null;
}
從上述原始碼可以看出,在響應頁面的時候,會在父類的 resolveErrorView 方法中獲取容器中所有的 ErrorViewResolver 物件(錯誤檢視解析器,包括 DefaultErrorViewResolver 在內),一起來解析異常資訊。
DefaultErrorViewResolver
ErrorMvcAutoConfiguration 還向容器中注入了一個預設的錯誤檢視解析器元件 DefaultErrorViewResolver,程式碼如下。
@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean(ErrorViewResolver.class)
DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext, this.resources);
}
當發出請求的客戶端為瀏覽器時,Spring Boot 會獲取容器中所有的 ErrorViewResolver 物件(錯誤檢視解析器),並分別呼叫它們的 resolveErrorView() 方法對異常資訊進行解析,其中自然也包括 DefaultErrorViewResolver(預設錯誤資訊解析器)。
DefaultErrorViewResolver 的部分程式碼如下。
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
private static final Map<HttpStatus.Series, String> SERIES_VIEWS;
static {
Map<HttpStatus.Series, String> views = new EnumMap<>(HttpStatus.Series.class);
views.put(Series.CLIENT_ERROR, "4xx");
views.put(Series.SERVER_ERROR, "5xx");
SERIES_VIEWS = Collections.unmodifiableMap(views);
}
......
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
//嘗試以錯誤狀態碼作為錯誤頁面名進行解析
ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
//嘗試以 4xx 或 5xx 作為錯誤頁面頁面進行解析
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
//錯誤模板頁面,例如 error/404、error/4xx、error/500、error/5xx
String errorViewName = "error/" + viewName;
//當模板引擎可以解析這些模板頁面時,就用模板引擎解析
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
this.applicationContext);
if (provider != null) {
//在模板能夠解析到模板頁面的情況下,返回 errorViewName 指定的檢視
return new ModelAndView(errorViewName, model);
}
//若模板引擎不能解析,則去靜態資原始檔夾下查詢 errorViewName 對應的頁面
return resolveResource(errorViewName, model);
}
private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
//遍歷所有靜態資原始檔夾
for (String location : this.resources.getStaticLocations()) {
try {
Resource resource = this.applicationContext.getResource(location);
//靜態資原始檔夾下的錯誤頁面,例如error/404.html、error/4xx.html、error/500.html、error/5xx.html
resource = resource.createRelative(viewName + ".html");
//若靜態資原始檔夾下存在以上錯誤頁面,則直接返回
if (resource.exists()) {
return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model);
}
} catch (Exception ex) {
}
}
return null;
}
......
}
DefaultErrorViewResolver 解析異常資訊的步驟如下:
- 根據錯誤狀態碼(例如 404、500、400 等),生成一個錯誤檢視 error/status,例如 error/404、error/500、error/400。
- 嘗試使用模板引擎解析 error/status 檢視,即嘗試從 classpath 類路徑下的 templates 目錄下,查詢 error/status.html,例如 error/404.html、error/500.html、error/400.html。
- 若模板引擎能夠解析到 error/status 檢視,則將檢視和資料封裝成 ModelAndView 返回並結束整個解析流程,否則跳轉到第 4 步。
- 依次從各個靜態資原始檔夾中查詢 error/status.html,若在靜態資料夾中找到了該錯誤頁面,則返回並結束整個解析流程,否則跳轉到第 5 步。
- 將錯誤狀態碼(例如 404、500、400 等)轉換為 4xx 或 5xx,然後重複前 4 個步驟,若解析成功則返回並結束整個解析流程,否則跳轉第 6 步。
- 處理預設的 “/error ”請求,使用 Spring Boot 預設的錯誤頁面(Whitelabel Error Page)。
DefaultErrorAttributes
ErrorMvcAutoConfiguration 還向容器中注入了一個元件預設錯誤屬性處理工具 DefaultErrorAttributes,程式碼如下。
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
DefaultErrorAttributes 是 Spring Boot 的預設錯誤屬性處理工具,它可以從請求中獲取異常或錯誤資訊,並將其封裝為一個 Map 物件返回,其部分程式碼如下。
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
......
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
Map<String, Object> errorAttributes = getErrorAttributes(webRequest, options.isIncluded(Include.STACK_TRACE));
if (!options.isIncluded(Include.EXCEPTION)) {
errorAttributes.remove("exception");
}
if (!options.isIncluded(Include.STACK_TRACE)) {
errorAttributes.remove("trace");
}
if (!options.isIncluded(Include.MESSAGE) && errorAttributes.get("message") != null) {
errorAttributes.remove("message");
}
if (!options.isIncluded(Include.BINDING_ERRORS)) {
errorAttributes.remove("errors");
}
return errorAttributes;
}
private Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<>();
errorAttributes.put("timestamp", new Date());
addStatus(errorAttributes, webRequest);
addErrorDetails(errorAttributes, webRequest, includeStackTrace);
addPath(errorAttributes, webRequest);
return errorAttributes;
}
......
}
在 Spring Boot 預設的 Error 控制器(BasicErrorController)處理錯誤時,會呼叫 DefaultErrorAttributes 的 getErrorAttributes() 方法獲取錯誤或異常資訊,並封裝成 model 資料(Map 物件),返回到頁面或 JSON 資料中。該 model 資料主要包含以下屬性:
- timestamp:時間戳;
- status:錯誤狀態碼
- error:錯誤的提示
- exception:導致請求處理失敗的異常物件
- message:錯誤/異常訊息
- trace: 錯誤/異常棧資訊
- path:錯誤/異常丟擲時所請求的URL路徑
18.全域性異常處理
我們知道 Spring Boot 已經提供了一套預設的異常處理機制,但是 Spring Boot 提供的預設異常處理機制卻並不一定適合我們實際的業務場景,因此,我們通常會根據自身的需要對 Spring Boot 全域性異常進行統一定製,例如定製錯誤頁面,定製錯誤資料等。
定製錯誤頁面
我們可以通過以下 3 種方式定製 Spring Boot 錯誤頁面:
- 自定義 error.html
- 自定義動態錯誤頁面
- 自定義靜態錯誤頁面
自定義 error.html
我們可以直接在模板引擎資料夾(/resources/templates)下建立 error.html ,覆蓋 Spring Boot 預設的錯誤檢視頁面(Whitelabel Error Page)。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>自定義 error.html</title>
</head>
<body>
<h1>自定義 error.html</h1>
<p>status:<span th:text="${status}"></span></p>
<p>error:<span th:text="${error}"></span></p>
<p>timestamp:<span th:text="${timestamp}"></span></p>
<p>message:<span th:text="${message}"></span></p>
<p>path:<span th:text="${path}"></span></p>
</body>
</html>
如果 Sprng Boot 專案使用了模板引擎,當程式發生異常時,Spring Boot 的預設錯誤檢視解析器(DefaultErrorViewResolver)就會解析模板引擎資料夾(resources/templates/)下 error 目錄中的錯誤檢視頁面。
精確匹配
我們可以根據錯誤狀態碼(例如 404、500、400 等等)的不同,分別建立不同的動態錯誤頁面(例如 404.html、500.html、400.html 等等),並將它們存放在模板引擎資料夾下的 error 目錄中。當發生異常時,Spring Boot 會根據其錯誤狀態碼精確匹配到對應的錯誤頁面上。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h1>自定義動態錯誤頁面 404.html</h1>
<p>status:<span th:text="${status}"></span></p>
<p>error:<span th:text="${error}"></span></p>
<p>timestamp:<span th:text="${timestamp}"></span></p>
<p>message:<span th:text="${message}"></span></p>
<p>path:<span th:text="${path}"></span></p>
</body>
</html>
匹配
我們還可以使用 4xx.html 和 5xx.html 作為動態錯誤頁面的檔名,並將它們存放在模板引擎資料夾下的 error 目錄中,來模糊匹配對應型別的所有錯誤,例如 404、400 等錯誤狀態碼以“4”開頭的所有異常,都會解析到動態錯誤頁面 4xx.html 上。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h1>自定義動態錯誤頁面 4xx.html</h1>
<p>status:<span th:text="${status}"></span></p>
<p>error:<span th:text="${error}"></span></p>
<p>timestamp:<span th:text="${timestamp}"></span></p>
<p>message:<span th:text="${message}"></span></p>
<p>path:<span th:text="${path}"></span></p>
</body>
</html>
自定義靜態錯誤頁面
若 Sprng Boot 專案沒有使用模板引擎,當程式發生異常時,Spring Boot 的預設錯誤檢視解析器(DefaultErrorViewResolver)則會解析靜態資原始檔夾下 error 目錄中的靜態錯誤頁面。
精確匹配
我們可以根據錯誤狀態碼(例如 404、500、400 等等)的不同,分別建立不同的靜態錯誤頁面(例如 404.html、500.html、400.html 等等),並將它們存放在靜態資原始檔夾下的 error 目錄中。當發生異常時,Spring Boot 會根據錯誤狀態碼精確匹配到對應的錯誤頁面上。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h1>自定義靜態錯誤頁面 404.html</h1>
<p>status:<span th:text="${status}"></span></p>
<p>error:<span th:text="${error}"></span></p>
<p>timestamp:<span th:text="${timestamp}"></span></p>
<p>message:<span th:text="${message}"></span></p>
<p>path:<span th:text="${path}"></span></p>
</body>
</html>
模糊匹配
我們還可以使用 4xx.html 和 5xx.html 作為靜態錯誤頁面的檔名,並將它們存放在靜態資原始檔夾下的 error 目錄中,來模糊匹配對應型別的所有錯誤,例如 404、400 等錯誤狀態碼以“4”開頭的所有錯誤,都會解析到靜態錯誤頁面 4xx.html 上。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h1>自定義靜態錯誤頁面 4xx.html</h1>
<p>status:<span th:text="${status}"></span></p>
<p>error:<span th:text="${error}"></span></p>
<p>timestamp:<span th:text="${timestamp}"></span></p>
<p>message:<span th:text="${message}"></span></p>
<p>path:<span th:text="${path}"></span></p>
</body>
</html>
錯誤頁面優先順序
以上 5 種方式均可以定製 Spring Boot 錯誤頁面,且它們的優先順序順序為:自定義動態錯誤頁面(精確匹配)>自定義靜態錯誤頁面(精確匹配)>自定義動態錯誤頁面(模糊匹配)>自定義靜態錯誤頁面(模糊匹配)>自定義 error.html。
當遇到錯誤時,Spring Boot 會按照優先順序由高到低,依次查詢解析錯誤頁,一旦找到可用的錯誤頁面,則直接返回客戶端展示。
定製錯誤資料
我們知道,Spring Boot 提供了一套預設的異常處理機制,其主要流程如下:
- 發生異常時,將請求轉發到“/error”,交由 BasicErrorController(Spring Boot 預設的 Error 控制器) 進行處理;
- BasicErrorController 根據客戶端的不同,自動適配返回的響應形式,瀏覽器客戶端返回錯誤頁面,機器客戶端返回 JSON 資料。
- BasicErrorController 處理異常時,會呼叫 DefaultErrorAttributes(預設的錯誤屬性處理工具) 的 getErrorAttributes() 方法獲取錯誤資料。
我們還可以定製 Spring Boot 的錯誤資料,具體步驟如下。
- 自定義異常處理類,將請求轉發到 “/error”,交由 Spring Boot 底層(BasicErrorController)進行處理,自動適配瀏覽器客戶端和機器客戶端。
- 通過繼承 DefaultErrorAttributes 來定義一個錯誤屬性處理工具,並在原來的基礎上新增自定義的錯誤資料。
1. 自定義異常處理類
被 @ControllerAdvice 註解的類可以用來實現全域性異常處理,這是 Spring MVC 中提供的功能,在 Spring Boot 中可以直接使用。
建立一個名為 UserNotExistException 的異常類,程式碼如下
/**
* 自定義異常
*/
public class UserNotExistException extends RuntimeException {
public UserNotExistException() {
super("使用者不存在!");
}
}
在 IndexController 新增以下方法,觸發 UserNotExistException 異常,程式碼如下
@Controller
public class IndexController {
......
@GetMapping(value = {"/testException"})
public String testException(String user) {
if ("user".equals(user)) {
throw new UserNotExistException();
}
//跳轉到登入頁 login.html
return "login";
}
}
建立一個名為 MyExceptionHandler 異常處理類,程式碼如下
import com.wyl.Exception.UserNotExistException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(UserNotExistException.class)
public String handleException(Exception e, HttpServletRequest request) {
Map<String, Object> map = new HashMap<>();
//向 request 物件傳入錯誤狀態碼
request.setAttribute("javax.servlet.error.status_code",500);
//根據當前處理的異常,自定義的錯誤資料
map.put("code", "user.notexist");
map.put("message", e.getMessage());
//將自定的錯誤資料傳入 request 域中
request.setAttribute("ext",map);
return "forward:/error";
}
}
2. 自定義錯誤屬性處理工具
1)在 net.biancheng.www.componet 包內,建立一個錯誤屬性處理工具類 MyErrorAttributes(繼承 DefaultErrorAttributes ),通過該類我們便可以新增自定義的錯誤資料,程式碼如下。
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;
import java.util.Map;
//向容器中新增自定義的儲物屬性處理工具
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, options);
//新增自定義的錯誤資料
errorAttributes.put("company", "www.biancheng.net");
//獲取 MyExceptionHandler 傳入 request 域中的錯誤資料
Map ext = (Map) webRequest.getAttribute("ext", 0);
errorAttributes.put("ext", ext);
return errorAttributes;
}
}
在 templates/error 目錄下,建立動態錯誤頁面 5xx.html,程式碼如下。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>自定義 error.html</title>
</head>
<body>
<p>status:<span th:text="${status}"></span></p>
<p>error:<span th:text="${error}"></span></p>
<p>timestamp:<span th:text="${timestamp}"></span></p>
<p>message:<span th:text="${message}"></span></p>
<p>path:<span th:text="${path}"></span></p>
<!--取出定製的錯誤資訊-->
<h3>以下為定製錯誤資料:</h3>
<p>company:<span th:text="${company}"></span></p>
<p>code:<span th:text="${ext.code}"></span></p>
<p>path:<span th:text="${ext.message}"></span></p>
</body>
</html>