Springboot核心技術(shù)學(xué)習(xí)筆記一

  • 第 1 章 SpringBoot 入門
    • 1.1 Spring Boot 簡介
    • 1.2 微服務(wù)
    • 1.3 環(huán)境準(zhǔn)備
    • 1.4 SpringBoot HelloWorld
    • 1.5 探究 HelloWorld
        1. pom 文件
        1. 導(dǎo)入依賴
        1. Main 主程序類
        1. 啟動日志分析
    • 1.6 快速創(chuàng)建 SpringBoot 項(xiàng)目
  • 第 2 章 SpringBoot 配置
    • 2.1 配置文件
    • 2.2 YAML 語法
        1. 基本語法
        1. 值的寫法
        1. 配置屬性列表
    • 2.3 配置文件值的注入
        1. @ConfigurationProperties 配置文件屬性的綁定
        1. 松散綁定
        1. Properties 亂碼問題
        1. @ConfigurationProperties 與 @Value
        1. @PropertySource 與 @ImportResource
        1. @Configuration 和 @Bean
    • 2.3 配置文件占位符
        1. 屬性配置占位符
        1. 隨機(jī)數(shù)
    • 2.4 Profile 多環(huán)境配置文件切換
    • 2.5 配置文件加載位置
    • 2.6 配置文件加載順序(重點(diǎn))
    • 2.7 自動配置原理(重難點(diǎn))
    • 2.8 @Conditional 自動配置報(bào)告
  • 第 3 章 SpringBoot 日志
    • 3.1 常見日志框架
    • 3.2 SLF4j 使用
    • 3.3 SpringBoot 日志關(guān)系(了解)
    • 3.4 日志使用
        1. 常見日志配置屬性
        1. 指定日志框架配置文件
    • 3.5 切換日志框架
  • 第 4 章 SpringBoot 與 WEB 開發(fā)
    • 4.1 簡介

    • 4.2 SpringBoot 靜態(tài)資源映射規(guī)則

        1. Webjars
    • 4.3 模板引擎

        1. 引入Thymeleaf
        1. Thymeleaf 的使用
        1. Thymeleaf 語法
    • 4.4 SpringMVC 自動配置

    • 4.5 修改 SpringBoot 默認(rèn)配置

    • 4.6 Restful crud 項(xiàng)目

        1. 國際化
          1. 國際化原理
          1. 實(shí)現(xiàn)語言切換功能
        1. 登錄
        1. 顯示員工列表
        1. 跳轉(zhuǎn)到員工頁面
        1. 添加員工
        1. 重定向與轉(zhuǎn)發(fā)
        1. 日期格式化
        1. 跳轉(zhuǎn)到員工編輯頁面
        1. 編輯員工信息
        1. 員工刪除
    • 4.7 定制錯誤頁面

        1. SpringBoot 默認(rèn)錯誤處理機(jī)制
    • 4.8 配置嵌入式 Servlet 容器

        1. 修改 Servlet 容器的相關(guān)配置
      • 使用其他嵌入式 Servlet 容器
    • 4.9 嵌入式 Servlet 容器自動配置原理

    • 4.10 嵌入式 Servlet 容器啟動原理

    • 4.11 使用外置的 Servlet 容器

  • 第 5 章 SpringBoot 與 Docker
    • 5.1 Docker 簡介
    • 5.2 核心概念
    • 5.3 安裝Docker
      • 安裝linux虛擬機(jī)
      • 在linux虛擬機(jī)上安裝docker
    • 5.4 Docker常用命令&操作
        1. 鏡像操作
        1. 容器操作
        1. 安裝 MySQL 示例
  • 第 6 章 SpringBoot 與數(shù)據(jù)訪問
    • 6.1 數(shù)據(jù)源初始化與 JDBC
        1. 配置 MySQL
        1. 數(shù)據(jù)源自動配置原理
        1. 數(shù)據(jù)表自動初始化
        1. 使用 JdbcTemplate 查詢數(shù)據(jù)
        1. 數(shù)據(jù)庫自動初始化原理
    • 6.2 使用外部數(shù)據(jù)源
    • 6.3 自定義數(shù)據(jù)源原理
    • 6.4 配置 Druid 數(shù)據(jù)源
    • 6.5 整合 MyBatis
        1. 注解版
        1. Mybatis 常見配置
        1. xml 版
    • 6.6 整合 SpringData JPA
        1. Spring Data 簡介
        1. 整合 SpringData JPA
  • 第 7 章 SpringBoot 啟動配置原理
    • 7.1 啟動流程
        1. 創(chuàng)建SpringApplication對象
        1. 運(yùn)行run方法
    • 7.2 事件監(jiān)聽機(jī)制
  • 第 8 章 SpringBoot 自定義 starter
    • 8.1 starter 原理
    • 8.2 自定義 starter
    1. SpringBoot 與開發(fā)熱部署
  • 進(jìn)階學(xué)習(xí)
  • 待補(bǔ)充
  • 推薦閱讀
  • 參考文檔

你無法掌握所有的知識,抓大放小憔古,不要關(guān)注邊邊角角的知識,保證最重要的知識爛熟于胸即可 —— 羅翔

第 1 章 SpringBoot 入門

1.1 Spring Boot 簡介

簡化Spring應(yīng)用開發(fā)的一個框架;

整個Spring技術(shù)棧的一個大整合扬虚;

J2EE開發(fā)的一站式解決方案丙猬;

傳統(tǒng)的 Spring 開發(fā)需要經(jīng)歷以下步驟:

  1. 配置 pom.xml,引入 SSM 項(xiàng)目各種依賴
  2. 配置web.xml实蔽,設(shè)置監(jiān)聽器 ContextLoaderListener卷扮,當(dāng)項(xiàng)目啟動時自動啟動 Spring 容器
  3. 配置web.xml荡澎,設(shè)置 Spring 的配置文件applicationContext.xml
  4. 配置web.xml,設(shè)置 SpringMVC 前端控制器DispatcherServlet攔截所有請求
  5. 配置web.xml晤锹,設(shè)置字符集編碼過濾器 CharacterEncodingFilter摩幔,隱藏HTTP請求過濾器HiddenHttpMethodFilter
  6. 配置applicationContext.xml,加載 properties 配置文件鞭铆,設(shè)置 Spring 自動掃描的包 component-scan
  7. 配置applicationContext.xml或衡,設(shè)置數(shù)據(jù)源,數(shù)據(jù)庫連接信息和相關(guān)屬性
  8. 配置applicationContext.xml,和 Mybatis 整合封断,設(shè)置SqlSessionFactory Bean斯辰,設(shè)置 Mybatis 掃描的接口,會生成代理類加入容器
  9. 配置dispatcherServlet-servlet.xml坡疼,設(shè)置 SpringMVC 視圖解析器InternalResourceViewResolver
  10. 配置dispatcherServlet-servlet.xml椒涯,設(shè)置注解驅(qū)動,設(shè)置上傳文件的bean CommonsMultipartResolver
  11. 對數(shù)據(jù)源配置事務(wù)管理器DataSourceTransactionmanager回梧,開啟基于注解的事務(wù)
  12. 配置pom.xml,開發(fā)環(huán)境配置 Jetty Maven 插件祖搓,使用 jetty:run啟動項(xiàng)目
  13. 項(xiàng)目發(fā)布需要打 war 包狱意,配置服務(wù)器環(huán)境,包括 tomcat拯欧,MySQL等详囤。

[圖片上傳失敗...(image-927ff1-1606212213488)]

具體配置參考項(xiàng)目 spring-boot-01-ssm,更多相關(guān)參考 SSM整合教程镐作,
繁多的配置藏姐,低下的開發(fā)效率,復(fù)雜的部署流程该贾,第三方技術(shù)集成難度大羔杨,SpringBoot 是為了簡化 Spring 應(yīng)用開發(fā)而生,具有以下優(yōu)點(diǎn):

  1. 快速創(chuàng)建 Spring 項(xiàng)目杨蛋,快速與主流框架集成
  2. 去除了 Spring 中繁瑣的 XML 配置兜材,開箱即用,以前需要配置 web.xml逞力、applicationContext.xml等文件才可以使用
  3. 使用嵌入式 Tomcat 容器曙寡,無需打成 war 包,幫助快速開發(fā)和部署
  4. starters 自動依賴管理與版本控制寇荧,比如要使用 WEB 工程举庶,導(dǎo)入 WEB starters 即可,WEB 依賴的其他 jar 包揩抡,starters 會自動導(dǎo)入依賴并控制版本户侥,避免了版本不兼容和依賴繁瑣的問題
  5. 大量的自動配置,簡化開發(fā)捅膘。以前創(chuàng)建一個項(xiàng)目添祸,要懂得 SpringMVC Mybatis 如何配置
  6. 準(zhǔn)生產(chǎn)環(huán)境的運(yùn)行時應(yīng)用監(jiān)控
  7. 與云計(jì)算的天然集成

1.2 微服務(wù)

2014,martin fowler 在博客中提出了微服務(wù)概念

微服務(wù):架構(gòu)風(fēng)格(服務(wù)微化)

一個應(yīng)用應(yīng)該是一組小型服務(wù)寻仗;

可以通過HTTP的方式進(jìn)行互通刃泌;(RPC?)

單體應(yīng)用:ALL IN ONE,所有服務(wù)都在一個應(yīng)用中,不方便擴(kuò)展耙替,一處錯誤可能影響整個應(yīng)用使用

微服務(wù):每一個功能元素最終都是一個可獨(dú)立替換和獨(dú)立升級的軟件單元亚侠;

1.3 環(huán)境準(zhǔn)備

  • jdk1.8,Spring Boot 推薦jdk1.7及以上
  • Apache Maven 3.3.9
  • IntelliJIDEA2017
  • SpringBoot 1.5.9.RELEASE

1.4 SpringBoot HelloWorld

實(shí)現(xiàn)一個功能:瀏覽器發(fā)送 hello 請求俗扇,服務(wù)器接收請求并處理硝烂,返回 Hello SpringBoot 字符串。

傳統(tǒng) Spring 開發(fā): 進(jìn)行 Spring铜幽、SpringMVC 各種 xml 配置滞谢,然后進(jìn)行開發(fā),最后打成 war 包除抛,放入 Tomcat 后運(yùn)行狮杨。

SpringBoot 開發(fā):

  1. 創(chuàng)建一個 Maven 項(xiàng)目
  2. 引入 SpringBoot 依賴
<!--  繼承SpringBoot父項(xiàng)目  -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.9.RELEASE</version>
</parent>

<!-- 添加SpringBoot web啟動器依賴,不需要設(shè)置版本到忽,在父項(xiàng)目中已經(jīng)添加 -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
  1. 編寫一個主程序橄教,啟動 SpringBoot 應(yīng)用
/**
 * @SpringBootApplication 來標(biāo)注一個主程序類,說明這是一個 SpringBoot 應(yīng)用
 */
@SpringBootApplication
public class HelloWorldMainApplication {
    public static void main(String[] args) {

        // 啟動Spring應(yīng)用
        SpringApplication.run(HelloWorldMainApplication.class, args);
    }
}

  1. 開發(fā):直接編寫 Controller 與 Service喘漏,不需要配置 Spring 與 SpringMVC 了
@Controller
public class HelloContorller {

    @RequestMapping("/hello")     // 設(shè)置訪問的url
    @ResponseBody                 // 表示數(shù)據(jù)直接返回給瀏覽器护蝶,如果是對象則轉(zhuǎn)為json數(shù)據(jù)
    public String hello() {
        return "Hello SpringBoot...";
    }
}
  1. 測試:直接啟動主程序 Main 即可,不需要打包到 Tomcat 啟動

訪問 http://localhost:8080/hello 翩迈,顯示 Hello SpringBoot...

  1. 部署:

    1. 引入 Maven SpringBoot 插件
    <!-- 這個插件持灰,將應(yīng)用打包成一個可執(zhí)行的jar包,用于項(xiàng)目部署時使用帽馋,會包含項(xiàng)目所有依賴的Jar包搅方,包括Tomcat*.jar,Sprint-aop.jar等-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    
    1. maven package 打 jar 包绽族,在/target下生成spring-boot-01-helloworld-1.0-SNAPSHOT.jar姨涡,可以在 jar 包 \BOOT-INF\lib 目錄下看到項(xiàng)目所依賴的 jar,包括 log4j.jar吧慢,spring-aop.jar涛漂,spring-beans.jar,spring-webmvc.jar检诗,tomcat.jar 等匈仗;\BOOT-INF\classes 下為我們的源碼。
    2. java -jar spring-boot-01-helloworld-1.0-SNAPSHOT.jar 在生產(chǎn)環(huán)境 Cmd 命令行啟動該項(xiàng)目逢慌,可以正常訪問悠轩,不需要部署Tomcat環(huán)境。

注意: 如果不引入Maven SpringBoot插件攻泼,使用 Maven 打包雖然也能生成 jar火架,但是不包含項(xiàng)目依賴的jar包鉴象,測試環(huán)境部署會出現(xiàn)錯誤。

1.5 探究 HelloWorld

1. pom 文件

SpringBoot 項(xiàng)目都需要繼承一個父項(xiàng)目 spring-boot-starter-parent何鸡,雙擊可以進(jìn)入 spring-boot-starter-parent.pom 文件

<!-- SpringBoot項(xiàng)目的父項(xiàng)目纺弊,父項(xiàng)目一般用來做依賴管理 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.9.RELEASE</version>
</parent>

<!-- 上項(xiàng)目的父項(xiàng)目是spring-boot-dependencies -->
<!-- 用來管理 SpringBoot 項(xiàng)目中所有依賴的版本 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>1.5.9.RELEASE</version>
    <relativePath>../../spring-boot-dependencies</relativePath>
</parent>

spring-boot-dependencies.pom 中設(shè)置了各種依賴的版本 version,包括 aspect.version骡男,log4j.version淆游,commons-dbcp.version,servlet-api.version隔盛,所以 SpringBoot 項(xiàng)目依賴一般不需要聲明版本version犹菱,這就是前文中所說的 SpringBoot 自動依賴管理與版本控制功能。

2. 導(dǎo)入依賴

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

spring-boot-starter:SpringBoot場景啟動器吮炕;幫我們導(dǎo)入了 web 模塊運(yùn)行所依賴的組件已亥,包括 tomcat,jackson来屠,webmvc 等。如果沒有 starter 啟動器震鹉,我們就需要手動導(dǎo)入依賴的組件并設(shè)置版本俱笛,這個操作非常麻煩且容易出錯。

SpringBoot 將所有的功能場景都抽取成 starter 啟動器传趾,比如需要 aop 功能就導(dǎo)入 spring-boot-starter-aop 依賴迎膜,需要 redis 功能就導(dǎo)入 spring-boot-starter-data-redis 依賴,相關(guān)場景的所有依賴都會導(dǎo)入進(jìn)來浆兰。

3. Main 主程序類

/**
 * @SpringBootApplication 來標(biāo)注一個主程序類磕仅,說明這是一個 SpringBoot 應(yīng)用
 */
@SpringBootApplication
public class HelloWorldMainApplication {
    public static void main(String[] args) {

        // 啟動Spring應(yīng)用
        SpringApplication.run(HelloWorldMainApplication.class, args);
    }
}
  • @SpringBootApplication:標(biāo)注SpringBoot的的主配置類,SpringBoot 就應(yīng)該運(yùn)行這個類的 main 方法來啟動 SpringBoot 應(yīng)用

    下面是作用相同的使用注解創(chuàng)建容器的 Spring 代碼簸呈,@Configuration 的作用是設(shè)置配置類榕订,在創(chuàng)建容器時傳入被標(biāo)記的配置類Application,相當(dāng)于之前的new ClassPathXmlApplicationContext("spring-config.xml")蜕便,

    @Configuration  // 標(biāo)注該類為配置類, 與 spring-config.xml 作用類似
    @ComponentScan("com.imooc")
    public class Application {
    
        public static void main(String[] args) {
            // 1. 啟動容器, 解析配置類, 掃描注解標(biāo)記的 Bean 到容器
            ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Application.class);
    
            // 2. 從容器中獲取 Bean
            WelcomService welcomeService = (WelcomService) applicationContext.getBean("welcomeService");
            welcomeService.sayHello("spring...");
        }
    

    @SpringBootApplication 是一個組合注解劫恒,包括 @SpringBootConfiguration,而該注解底層是 Spring @Configuration轿腺,即 Spring 的配置類注解两嘴,類似于配置文件,@Configuration 底層是 @Component族壳,也是 Spring 的一個組件憔辫,會被 Spring 掃描加入到容器中。

    @SpringBootApplication 替代了 @Configuration + @ComponentScan仿荆,在 main 方法中傳入被標(biāo)記的類作為配置類贰您,該注解源碼如下:

    // SpringBoot配置類坏平,自動配置注解
    @SpringBootConfiguration
    @EnableAutoConfiguration
    // 設(shè)置為\掃描的包為標(biāo)記類所在的包
    @ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class}), @Filter(type = FilterType.CUSTOM, classes ={AutoConfigurationExcludeFilter.class})})
    public @interface SpringBootApplication {
    
  • @EnableAutoConfiguration:開啟自動配置功能;

以前我們需要配置的東西枉圃,SpringBoot幫我們自動配置功茴,該注解就是告訴 SpringBoot 開啟自動配置功能。是一個組合注解孽亲,源碼如下:

    @AutoConfigurationPackage
    @Import({EnableAutoConfigurationImportSelector.class})
    public @interface EnableAutoConfiguration {
  • @AutoConfigurationPackage:自動配置包坎穿,將主配置類(@SpringBootApplication標(biāo)注的類)所在包下及子包里的所有組件都掃描添加到 Spring 容器,這也是為什么我們配有配置掃描包 component-scan返劲,依然能夠注入所有 Component 組件的原因

    底層是 Spring @Import玲昧,作用是給容器中導(dǎo)入一個組件。類似于 @Componet

    為了驗(yàn)證 @AutoConfigurationPackage 是掃描主配置類所在包下及子包里的所有組件到 Spring 容器篮绿,所以我們在其他包下創(chuàng)建一個 Controller孵延,驗(yàn)證發(fā)現(xiàn)確實(shí)不會被掃描到。

上述注解@Import亲配,@AutoConfigurationPackage 的底層都是 Spring 的注解尘应,參考Spring注解驅(qū)動教程

  • EnableAutoConfigurationImportSelector:導(dǎo)入哪些組件的選擇器;

    Spring Boot在啟動的時候從類路徑下的META-INF/spring.factories中獲取EnableAutoConfiguration指定的值吼虎,將這些值作為自動配置類導(dǎo)入到容器中犬钢,自動配置類就生效,幫我們進(jìn)行自動配置工作思灰;

4. 啟動日志分析

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.9.RELEASE)      說明SpringBoot版本

: Starting HelloWorldMainApplication on DESKTOP-TDM8SAD with PID 15920    # 項(xiàng)目啟動玷犹,進(jìn)程ID
: No active profile set, falling back to default profiles: default        # 生效的配置文件profile
: Tomcat initialized with port(s): 8080 (http)                            # Tomcat的訪問端口
: Starting service [Tomcat]
: Starting Servlet Engine: Apache Tomcat/8.5.23
: Initializing Spring embedded WebApplicationContext
: Root WebApplicationContext: initialization completed in 1297 ms
: Mapping servlet: 'dispatcherServlet' to [/]                        # dispatcherServlet 攔截所有請求
: Mapping filter: 'characterEncodingFilter' to: [/*]                 # 字符集編碼過濾器
: Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
: Mapping filter: 'httpPutFormContentFilter' to: [/*]
: Mapping filter: 'requestContextFilter' to: [/*]
: Looking for @ControllerAdvice: org.springframework.boot.context.embedded.
: Mapped "{[/hello]}" onto public String com.atguigu.controller.HelloContorller.hello()    # 映射用戶編寫的Controller
: Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity                  # 映射/error頁面
: Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
: Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
: Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
: Registering beans for JMX exposure on startup
: Tomcat started on port(s): 8080 (http)                 # Tomcat啟動完成,端口為8080
: Started HelloWorldMainApplication in 2.047 seconds (JVM running for 2.35)  # HelloWorldMainApplication項(xiàng)目啟動完成洒疚,耗時2.0秒

1.6 快速創(chuàng)建 SpringBoot 項(xiàng)目

在 IDEA 中安裝 Spring Assistant 插件歹颓,然后新建項(xiàng)目:

  1. 選擇 Spring Assistant,選擇 Default 即從 Spring 官網(wǎng)(https://start.spring.io/ )聯(lián)網(wǎng)創(chuàng)建 SpringBoot項(xiàng)目油湖,
  2. 填寫項(xiàng)目名稱信息
  3. 選擇需要導(dǎo)入的依賴巍扛,如 web,MySQL 等乏德,還會自動引入 Maven SpringBoot 打包插件电湘。

這樣就很快捷的創(chuàng)建了一個 SpringBoot 項(xiàng)目,這種創(chuàng)建方式需要聯(lián)網(wǎng)鹅经,創(chuàng)建成功后查看主程序與 POM 文件寂呛,發(fā)現(xiàn)與手動創(chuàng)建完全一致。

默認(rèn)生成的Spring Boot項(xiàng)目瘾晃;

  • 主程序已經(jīng)生成好了贷痪,我們只需要寫自己的業(yè)務(wù)邏輯
  • resources文件夾中目錄結(jié)構(gòu) static:保存所有的靜態(tài)資源; js css images蹦误;
  • templates:保存所有的模板頁面劫拢;(Spring Boot默認(rèn)jar包使用嵌入式的Tomcat肉津,默認(rèn)不支持JSP頁 面);可以使用模板引擎(freemarker舱沧、thymeleaf)妹沙;
  • application.properties:Spring Boot應(yīng)用的配置文件;可以修改一些默認(rèn)設(shè)置熟吏,如Tomcat端口距糖;

補(bǔ)充: @ResponseBody 注解表示返回字符串,對象則轉(zhuǎn)為json數(shù)據(jù)

//@ResponseBody                 // 表示數(shù)據(jù)直接返回給瀏覽器牵寺,如果是對象則轉(zhuǎn)為json數(shù)據(jù)
//@Controller
@RestController         // 等價于@ResponseBody + @Controller悍引,可以通過@RestController源碼查看
public class HelloController {

    @RequestMapping("/hello")     // 設(shè)置訪問的url
    public String hello() {
        return "Hello SpringBoot quickly...";
    }

    // RESTAPI的方式,即把返回?cái)?shù)據(jù)直接發(fā)送給瀏覽器帽氓,而不是頁面跳轉(zhuǎn)
}

第 2 章 SpringBoot 配置

2.1 配置文件

SpringBoot 使用一個全局的配置文件趣斤,文件名稱默認(rèn)為

  • application.properties
  • application.yml

配置文件的作用:修改 SpringBoot 自動配置的默認(rèn)值,如修改訪問端口黎休,修改項(xiàng)目根路徑 Context-path

YAML 適合用來做配置文件浓领,替換我們以前使用的 xml 配置文件:

server:
  port: 8081

傳統(tǒng)的 XML 配置如下,不禁冗長势腮,還容易出現(xiàn)標(biāo)簽筆誤:

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

2.2 YAML 語法

1. 基本語法

key: value 表示一對鍵值對镊逝,大小寫敏感,注意冒號后必須有空格

以空格的縮進(jìn)來控制層級關(guān)系嫉鲸,左對齊的一列數(shù)據(jù)都是同一層級

2. 值的寫法

字面量:

普通的值(數(shù)字,字符串歹啼,布爾) k: v:字面直接來寫玄渗;

字符串默認(rèn)不用加上單引號或者雙引號;

"":雙引號狸眼;不會轉(zhuǎn)義字符串里面的特殊字符藤树;特殊字符會作為本身想表示的意思

name: "zhangsan \n lisi":輸出;zhangsan 換行 lisi 

'':單引號拓萌;原樣輸出岁钓,特殊字符最終只是一個普通的字符串?dāng)?shù)據(jù)

name: ‘zhangsan \n lisi’:輸出俱尼;zhangsan \n lisi

對象胞锰、Map:

friends:
    lastName: zhangsan 
    age: 20

行內(nèi)寫法:

friends: {lastName: zhangsan, age: 18}

數(shù)組(List池磁,Set):

-標(biāo)志數(shù)組中的一個元素

pets:
    - cat
    - dog
    - pig

行內(nèi)寫法

pets: [cat, dog, pig]

3. 配置屬性列表

配置文件中所有能夠配置的屬性可以查看官方文檔

2.3 配置文件值的注入

1. @ConfigurationProperties 配置文件屬性的綁定

配置文件使用 @ConfigurationProperties 注解與Java類的綁定汇恤。

配置文件:

    # application.yml 配置文件囚玫,定義了person對象的值
    person:
        lastName: 張三    # 使用last-name也可以寸谜,支持松散綁定
        age: 18
        boss: false
        birth: 2002/1/2
        maps: {k1: v1, k2: 12}
        list:
            - lisi
            - wangwu
        dog:
            name: doudou
            age: 3

JavaBean:

/**
 * 將配置文件中每一個屬性的值朽褪,映射到這個類中
 * @ConfigurationProperties 告訴SpringBoot將本類中所有屬性與配置文件中
 * 相關(guān)配置進(jìn)行綁定, prefix選擇配置文件中的屬性
 *
 * @Component 作用是將組件加入 Spring 容器窍霞,使用時 @Autowired 獲取
 */

@Component
@ConfigurationProperties(prefix = "person")
public class Person {
    private String lastName;
    private Integer age;
    private Boolean boss;
    private Date birth;

    private Map<String, Object> maps;
    private List<Object> lists;
    private Dog dog;

這是一個非常重要的注解罩旋,SpringBoot 自動配置原理也是使用該注解啊央,根據(jù)配置前綴(prefix="spring.dataSource")來讀取相關(guān)配置與配置類 DataSourceProperties 綁定眶诈。

使用@Value注解也可以實(shí)現(xiàn)屬性的映射

/**
 * 使用 @Value 注解實(shí)現(xiàn)配置屬性的注入
 */
@Component
public class Person2 {

    /**
     * <bean class="com.atguigu.springboot.Person">
     *      <property name="lastName" value="${person.last-name}"></property>
     * </bean>
     * 在xml中注入bean并配置屬性,Spring注解@Component替代了xml配置注入bean的方式
     * 注解@Value用于設(shè)置屬性值
     */

    // @Value不支持松散綁定瓜饥,${person.lastName}無法獲取到值逝撬,必須與配置文件key完全一致
    @Value("${person.last-name}")   // ${} 從配置文件獲取屬性值
    // @Email     // @Value 不支持JSR303校驗(yàn),該注解無作用
    private String lastName;

    @Value("#{10+8}")       // SpEL表達(dá)式
    private Integer age;

IDEA提示功能:

在 pom.xml 中導(dǎo)入配置文件處理器乓土,這樣在配置文件中寫配置就會有提示屬性功能

<!--  導(dǎo)入配置文件處理器宪潮,配置文件設(shè)置屬性時會有提示   -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>

2. 松散綁定

在上文例子中,屬性名為lastName帐我,配置文件中last-name: zhangsan坎炼,因?yàn)?@ConfigurationProperties 注解支持松散綁定,SpringBoot可以準(zhǔn)確識別并綁定拦键。而@value注解則不支持松散綁定谣光,屬性名必須與配置文件中的 key 完全一致。

對于以下三種寫法芬为,屬性lastName均可成功匹配綁定:

– person.lastName:
– person.last-name:
– person.last_name:
– PERSON_LAST_NAME:

3. Properties 亂碼問題

SprinBoot 除了 YAML 配置文件萄金,也可以使用 application.properties 作為配置文件,

    # server.port=8081
    # 配置person的值媚朦,中文會出現(xiàn)亂碼問題
    person.last-name=張三
    person.age=18
    person.birth=2001/2/3
    person.boss=false

    # 設(shè)置map和數(shù)組的屬性值
    person.maps.k1=v1
    person.maps.k2=v2
    person.lists=a,b,c

    # 設(shè)置對象的屬性值
    person.dog.name=doudou
    person.dog.age=2

YAML 中不會出現(xiàn)中文亂碼問題氧敢,IDEA中 Properties 文件默認(rèn)使用GBK編碼,而IDEA項(xiàng)目使用 UTF-8編碼询张,導(dǎo)致運(yùn)行時亂碼孙乖。

解決辦法: 在IDEA->設(shè)置-> file encoding,將編碼設(shè)置為 UTF-8份氧,勾選 native->ascii唯袄。
修改后,可以在 IDEA 右下角看到文件編碼變?yōu)?UTF-8蜗帜,勾選 native->ascii 的作用是為了方便在 IDEA中查看中文內(nèi)容恋拷。

[圖片上傳失敗...(image-b925ca-1606212213489)]

4. @ConfigurationProperties 與 @Value

  • @ConfigurationProperties 與 @Value 兩個注解均是從配置文件中獲取屬性值,并注入給示例相關(guān)屬性厅缺。
  • @ConfigurationProperties 一般用于注入屬性值到整個對象蔬顾,更為便捷,支持松散綁定湘捎,支持 JSR303 數(shù)據(jù)校驗(yàn)诀豁。
  • @ConfigurationProperties 只是綁定屬性,但并不會將標(biāo)注的類加入到 Spring 容器中窥妇,一般與@Component且叁,@EnableConfigurationProperties,@Bean 配合使用秩伞,將標(biāo)注的類加入到 Spring 容器
@ConfigurationProperties @Value
功能 批量注入配置文件中的屬性 需要一個個指定屬性值
松散綁定 支持 不支持
JSR303數(shù)據(jù)校驗(yàn) 支持 不支持 (2.x版本也支持逞带?)
SpEL表達(dá)式 不支持 支持

@ConfigurationProperties 組件加入容器的三種方式:

  1. @EnableConfigurationProperties
// 將配置文件中屬性與該類屬性綁定
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties {

}

@Configuration(proxyBeanMethods = false)
// 將制定的配置類DataSourceProperties 加入Spring容器欺矫,使其生效
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {
}
  1. @Bean
    // 配置制定的 Druid 數(shù)據(jù)源,將方法返回的對象加入Spring容器
    @Bean
    // 綁定配置文件中的屬性到指定類
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druid() {
        return new DruidDataSource();
    }
  1. @Component

@Value 注入 Map 等復(fù)雜類型參考文章展氓,教程中說 @Value 不支持復(fù)雜類型注入是錯誤的穆趴。

  • 專門編寫的JavaBean來和配置文件映射,推薦使用 @ConfigurationProperties
  • 業(yè)務(wù)邏輯某處使用配置文件中的屬性時遇汞,推薦使用 @Value

5. @PropertySource 與 @ImportResource

@PropertySource: 加載指定的配置文件未妹,默認(rèn)加載application.properties配置文件。

@Component
// 表示加載person.properties配置文件
@PropertySource({"classpath:person.properties"})     
@ConfigurationProperties(prefix = "person")
public class Person {    

@ImportResource: 導(dǎo)入 Spring 的配置文件空入,不推薦使用

下面的代碼導(dǎo)入了 Spring 的配置文件 beans.xml络它,配置了JavaBean HelloService,類似于 HelloService 類上添加 @Service 注解歪赢,使自動注入 Spring 容器化戳,可以被 @AutoWired 使用。

@SpringBootApplication
@ImportResource("classpath:beans.xml")
public class SpringBoot02ConfigApplication {
 <!--  beans.xml  使用xml方式注入bean  -->
    <bean id="helloService" class="com.atguigu.springboot.service.HelloService"></bean>

@Component 與 @Bean 類似埋凯,標(biāo)注這個類需要注入到 Spring 容器点楼,與xml中配置<bean>的作用一致。

6. @Configuration 和 @Bean

小知識: IDEA 中 Spring Beans 與 MVC 的可視化

顯示Spring容器中所有的Bean白对,方便查看Bean是否添加到了容器掠廓,對于Spring的內(nèi)置Bean,還可以直接跳轉(zhuǎn)到文檔查看bean的介紹
[圖片上傳失敗...(image-9f4842-1606212213489)]

顯示所有url映射甩恼,方便查看url與Controller的映射蟀瞧,還可以過濾各種類型的請求
[圖片上傳失敗...(image-549186-1606212213489)]

@Configuration:

  • @Configuration 底層是 @Component 注解,會被 Spring 掃描添加到容器中

  • 使用全注解的方式映射配置類與 Spring 配置条摸,類似于@ConfigurationProperties悦污,不過后者針對的是配置文件properties。

  • @Configuration用于定義配置類屈溉,可替換xml配置文件,被注解的類內(nèi)部包含有一個或多個被@Bean注解的方法抬探,這些方法將會被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext類進(jìn)行掃描子巾,并將這些 Bean 加入到 Spring 容器

@Bean:

  • @Bean 用于告訴方法,返回一個Bean對象小压,將其加入Spring容器线梗,產(chǎn)生這個Bean對象的方法Spring只會調(diào)用一次(單例)
  • @Bean注解把當(dāng)前方法的返回值作為bean對象存入spring容器中,其name屬性用于指定bean的id(若沒寫該屬性怠益,默認(rèn)值是當(dāng)前的方法名)
  • @Bean 是一個方法級別上的注解仪搔,主要用在@Configuration注解的類里,也可以用在@Component注解的類里蜻牢。

SpringBoot 推薦用全注解的方式向容器中添加組件烤咧,替代編寫 Spring XML配置文件

  1. 標(biāo)注 Spring 配置類 @Configuration
  2. 使用 @Bean 向容器中添加組件
/**
 * 以前在配置文件beans.xml中添加<bean></bean>偏陪,然后使用@ImportResource導(dǎo)入
 * @Configuration 標(biāo)注類為一個配置類,就是來替代之前的Spring xml配置文件
 */
@Configuration
public class MyAppConfig {

    /**
     * 將方法的返回值添加到容器中煮嫌,容器中這個組件Bean默認(rèn)的id就是方法名
     * @Bean 給容器中添加組件
     */
    @Bean
    public HelloService helloService2() {
        System.out.println("配置類@Bean給容器中添加組件...");
        return new HelloService();
    }
}

@Configuration 和 @Bean 的詳細(xì)講解參考Spring注解驅(qū)動教程

2.3 配置文件占位符

1. 屬性配置占位符

可以引用前面定義的屬性笛谦,使用冒號設(shè)置默認(rèn)值

app.name=WeChat
app.description=${app.name:MyApp} is a SpringBoot Application.

2. 隨機(jī)數(shù)

${randowm.uuid}、 ${random.int}昌阿、 ${random.long}
${random.int(10)}饥脑、 ${random.int[1024,65536]}

2.4 Profile 多環(huán)境配置文件切換

生產(chǎn)環(huán)境和開發(fā)環(huán)境一般使用的配置文件并不相同,SpringBoot提供了三種多環(huán)境配置文件切換功能

  1. 配置文件中指定
# properties設(shè)置啟動的配置文件為 application-dev.properties
spring.profiles.active=dev
# yml設(shè)置啟動的配置文件為 application-dev.yml
spring:
  profiles:
    active: dev

注意: 是 profiles 不是 profile

  1. 命令行參數(shù)指定
java -jar MyApp.jar --spring.profiles.active=dev

也可以在IDEA 運(yùn)行->編輯配置->程序參數(shù) 中添加--spring.profiles.active=dev

  1. JVM 參數(shù)指定
# 生產(chǎn)環(huán)境一般使用 jvm 參數(shù)激活配置文件:
C:\Users\mao> java -jar -Dspring.profiles.active=prod MyApp.jar

2.5 配置文件加載位置

SpringBoot會從以下 4 個位置加載全部配置文件懦冰,不相同的屬性進(jìn)行互補(bǔ)灶轰,優(yōu)先級1級的會覆蓋優(yōu)先級2級的相同的屬性配置,知曉即可刷钢,不常用

demo/       # 項(xiàng)目根路徑
    config/
          application.properties     # 優(yōu)先級1
    application.properties           # 優(yōu)先級2
    src/
        java/
        resource/   # classpath:下面的文件打包后會在 classes下  
                config/
                      application.properties    # 優(yōu)先級2
                application.properties          # 優(yōu)先級4(常用)

在項(xiàng)目打包完成后笋颤,需要修改配置時可以使用命令行參數(shù)spring.config.location指定配置文件,優(yōu)先級最高

java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --spring.config.location=G:/application.properties

2.6 配置文件加載順序(重點(diǎn))

優(yōu)先級由高到低闯捎,高優(yōu)先級會覆蓋低優(yōu)先級配置:

  1. 命令行參數(shù)
  2. Java系統(tǒng)屬性(System.getProperties())
  3. jar包外部的application-{profile}.properties/yml(帶spring.profile)配置文件
  4. jar包內(nèi)部的application-{profile}.properties/yml(帶spring.profile)配置文件
  5. jar包外部的application.properties/yml(不帶spring.profile)配置文件
  6. jar包內(nèi)部的application.properties/yml(不帶spring.profile)配置文件
  7. @Configuration注解類上的@PropertySource指定的配置文件(參考#2.3.5 中Person類指定的配置文件)
  8. 通過SpringApplication.setDefaultProperties指定的默認(rèn)屬性

命令行參數(shù)適用生產(chǎn)環(huán)境修改某個配置時使用椰弊,jar包外面的配置文件,適合生產(chǎn)環(huán)境批量修改多個配置時使用

2.7 自動配置原理(重難點(diǎn))

// 補(bǔ)充:自動配置原理其實(shí)并沒有搞懂瓤鼻,后面進(jìn)行補(bǔ)充

  1. SpringBoot 啟動時加載主程序類秉版,@SpringBootApplication 注解包含了 @EnableAutoConfiguration
  2. @EnableAutoConfiguration 表示開啟自動配置功能
  3. @EnableAutoConfiguration 作用是將 META-INF/spring.factories 里面配置的所有XXXAutoConfiguration的組件加入到了容器中
    • 源碼參考 AutoConfigurationImportSelector#selectImports,里面獲取了所有自動配置類茬祷,如 DataSourceAutoConfiguration清焕,WebMvcAutoConfiguration
  4. xxxAutoConfiguration類都是容器中的一個組件,都加入到容器中祭犯;用他們來做自動配置秸妥,如HttpEncodingAutoConfiguration 來做Http編碼自動配置
@Configuration //表示這是一個配置類,以前編寫的配置文件一樣沃粗,也可以給容器中添加組件 

//讓指定的 HttpEncodingProperties 配置類生效粥惧;將配置文件中對應(yīng)的值和HttpEncodingProperties綁定起來;并把 HttpEncodingProperties加入到ioc容器中 
@EnableConfigurationProperties(HttpEncodingProperties.class) 

//Spring底層@Conditional注解(Spring注解版)最盅,根據(jù)不同的條件突雪,如果 滿足指定的條件,整個配置類里面的配置就會生效涡贱; 判斷當(dāng)前應(yīng)用是否是web應(yīng)用咏删,如果是,當(dāng)前配置類生效 
@ConditionalOnWebApplication 

//判斷當(dāng)前項(xiàng)目有沒有這個類 CharacterEncodingFilter问词;SpringMVC中進(jìn)行亂碼解決的過濾器督函; 
@ConditionalOnClass(CharacterEncodingFilter.class) 
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true) //判斷配置文件中是否存在某個配置 spring.http.encoding.enabled;如果不存在,判斷也是成立的 //即使我們配置文件中不配置pring.http.encoding.enabled=true辰狡,也是默認(rèn)生效的锋叨; 
public class HttpEncodingAutoConfiguration { 
    //他已經(jīng)和SpringBoot的配置文件映射了 
    private final HttpEncodingProperties properties; 
  1. 所有在配置文件中能配置的屬性都是在 xxxxProperties 類中封裝著,配置文件能配置什么就可以參照某個功能對應(yīng)的這個屬性類搓译。如HttpEncodingProperties.class
    //從配置文件中獲取指定的值和bean的屬 性進(jìn)行綁定 
    @ConfigurationProperties(prefix = "spring.http.encoding") 
    public class HttpEncodingProperties { 
        public static final Charset DEFAULT_CHARSET = Charset.forName("UTF‐8");
    

·

擴(kuò)展:
向Spring容器添加組件的三種方式:

  1. @Componet悲柱,@Controller,@Service些己,@Repository豌鸡,@Configuration:局限于自己寫的組件
  2. @Bean:注解用于告訴方法,返回一個Bean對象段标,將其加入Spring容器涯冠。產(chǎn)生這個Bean對象的方法Spring只會調(diào)用一次(單例)。Bean導(dǎo)入第三方包的組件逼庞,是一個方法級別上的注解蛇更,主要用在@Configuration注解的類里,也可以用在@Component注解的類里赛糟。@Bean注解把當(dāng)前方法的返回值作為bean對象存入spring容器中派任,其name屬性用于指定bean的id(若沒寫該屬性,默認(rèn)值是當(dāng)前的方法名)
  3. @Import:導(dǎo)入組件

2.8 @Conditional 自動配置報(bào)告

// 補(bǔ)充:沒搞懂璧南,配合SPring注解教程學(xué)習(xí)

第 3 章 SpringBoot 日志

3.1 常見日志框架

小張掌逛;開發(fā)一個大型系統(tǒng);

  1. 剛開始使用System.out.println("")司倚;將關(guān)鍵數(shù)據(jù)打印在控制臺豆混;
  2. 為了記錄記錄系統(tǒng)的一些運(yùn)行時信息,開發(fā)了一個日志框架 zhanglogging-1.0.jar动知;
  3. 迭代更新日志框架皿伺,異步模式,按日期自動歸檔等 zhanglogging-2.0.jar
  4. 為了替換日志框架盒粮,重新修改之前相關(guān)的API鸵鸥,特別麻煩
  5. 參考 JDBC--數(shù)據(jù)庫驅(qū)動,門面設(shè)計(jì)模式丹皱,統(tǒng)一的接口層妒穴,不同數(shù)據(jù)庫的代碼都是一樣的,需要數(shù)據(jù)庫去實(shí)現(xiàn)驅(qū)動接口
  6. 實(shí)現(xiàn)一個統(tǒng)一的接口層:日志門面(日志的一個抽象層)logging-abstract.jar
  7. 給項(xiàng)目中導(dǎo)入具體的日志實(shí)現(xiàn)就行了种呐,日志框架升級宰翅、替換也不需要修改我們的代碼
日志門面(抽象層) 日志實(shí)現(xiàn)
SLF4j弃甥,jakarta-commons-Logging Logback爽室,Log4j,Log4j2

Spring 日志框架是 jakarta-commons-Logging,SpringBoot 默認(rèn)日志框架是 SLF4j + Logback (推薦使用)

3.2 SLF4j 使用

參考SLF4j 官網(wǎng)手冊阔墩,記錄日志只需要使用 SLF4j 的 API嘿架,不應(yīng)該直接調(diào)用日志框架 Logback 的實(shí)現(xiàn)類。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(HelloWorld.class);
        logger.info("Hello World");
    }
}
SLF4j 與日志框架配合使用

3.3 SpringBoot 日志關(guān)系(了解)

SpringBoot-Starter-Web 依賴 spring-boot-starter啸箫,后者又依賴 spring-boot-starter-logging耸彪,SpringBoot 使用它來做日志記錄。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-logging</artifactId>
    <version>2.3.1.RELEASE</version>
    <scope>compile</scope>
</dependency>

SpringBoot 依賴 Spring忘苛,而 Spring 使用的是 jakarta-commons-Logging 日志框架蝉娜,需要集成的Hibernate 使用 jboss-logging日志框架,SLF4j 把這些日志框架也都替換為了 SLF4j 框架扎唾。

[圖片上傳失敗...(image-ef1761-1606212213489)]

總結(jié):

  1. SpringBoot 底層也是使用 slf4j+logback 的方式進(jìn)行日志記錄
  2. SpringBoot 也把其他的日志都替換成了 slf4j 中間替換包
  3. slf4j 中間替換包本質(zhì)是將 jakarta-commons-Logging 等日志框架使用相同類名重寫了一次
  4. 如果要引入其他日志框架召川,需要將中間替換包移除

slf4j 中間替換包本質(zhì)是將 JCL(jakarta-commons-Logging) Log4j 等日志框架使用相同類名重寫了一次,下圖是slf4j的替換包胸遇,與被替換框架的包名荧呐、類名完全一致。

slf4j 中間替換包

3.4 日志使用

    // 日志記錄器纸镊,注意是slf4j 的 LoggerFactory
    Logger logger = LoggerFactory.getLogger(getClass());
    /**
     * 使用slf4j倍阐,
     */
    @Test
    void contextLoads() {
        // 日志級別由低到高
        logger.trace("這是trace日志...");
        logger.debug("這是debug日志...");
        // 默認(rèn)使用info級別日志輸出
        logger.info("這是info日志...");
        logger.warn("這是warn日志...");
        logger.error("這是error日志...");
    }

1. 常見日志配置屬性

在 SpringBoot 配置文件 application.properties 中修改日志配置

# 生產(chǎn)環(huán)境下設(shè)置項(xiàng)目所有日志級別為WARN
# logging.level.root=WARN

# 設(shè)置下面包 com.atguigu 的日志級別為trace
logging.level.com.atguigu=TRACE

logging.file.path=/mao/log

# 指定日志輸出文件,默認(rèn)在當(dāng)前項(xiàng)目根目錄逗威,與1.x的屬性不同
logging.file.name=springboot.log

# 設(shè)置在控制臺輸出日志的格式
logging.pattern.console=%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n

# 設(shè)置在日志文件輸出日志的格式
logging.pattern.file=

# 設(shè)置控制臺日志顏色
spring.output.ansi.enabled=ALWAYS

# 設(shè)置啟動時打印所有的請求路徑映射
logging.level.org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping=trace

測試環(huán)境可以使用命令行輸出 DEBUG 級別的日志記錄

$ java -jar myapp.jar --debug

2. 指定日志框架配置文件

在 SpringBoot 配置文件 application.properties 中修改日志配置峰搪,如果要修改的屬性過多,推薦加入日志框架自己的配置文件庵楷,會自動生效

Logging System 配置文件名稱
Logback logback-spring.xml罢艾,logback.xml
Log4J2 log4j2-spring.xml,log4j2.xml

logback-spring.xml 這種名稱可以使用 SpringBoot profile 高級功能尽纽,即在不同環(huán)境下使用不同的日志配置咐蚯。使用spring.profiles.active=dev屬性控制。

<springProfile name="dev">
    <!-- 可以指定某段配置在開發(fā)環(huán)境下生效 -->
    <!--  spring.profiles.active=dev 配置該屬性設(shè)置環(huán)境為開發(fā)環(huán)境 -->
</springProfile>

logback-spring.xml 的配置在項(xiàng)目 spring-boot-03-logging 下弄贿,有詳細(xì)注釋春锋,使用時可進(jìn)行參考。

3.5 切換日志框架

切換為 slf4j + log4j:

排除 SpringBoot 默認(rèn)依賴的日志框架差凹,移除 logback 依賴期奔,移除 slf4j-log4j 中間包,然后引入 log4j 依賴危尿。由于 log4j 框架性能一般呐萌,不推薦使用

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <artifactId>logback-classic</artifactId>
                <groupId>ch.qos.logback</groupId>
            </exclusion>
            <exclusion>
                <artifactId>log4j-over-slf4j</artifactId>
                <groupId>org.slf4j</groupId>
            </exclusion>
        </exclusions>
    </dependency>

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
    </dependency>

切換為 slf4j + log4j2:

SpringBoot 提供 log4j2 starter 啟動器,包含了 log4j2 及 slf4j-log4j2 中間包肺孤,移除之前自帶的 logging starter依賴,添加 log4j2 啟動器依賴。

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <artifactId>spring‐boot‐starter‐logging</artifactId>
                <groupId>org.springframework.boot</groupId>
            </exclusion>
        </exclusions>
    </dependency>

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

// 補(bǔ)充:SpringBoot 2.x 版本日志框架有了變化赠堵,沒有這么麻煩了小渊?

第 4 章 SpringBoot 與 WEB 開發(fā)

4.1 簡介

SpringBoot 開發(fā)三步走:

  1. 創(chuàng)建SpringBoot 應(yīng)用,選擇需要使用的模塊茫叭,如 web酬屉,Mybatis等
  2. SpringBoot 已經(jīng)默認(rèn)將這些場景配置好了,只需要配置文件中指定少量配置就可以運(yùn)行起來
  3. 編寫業(yè)務(wù)代碼

自動配置原理:

自動配置相關(guān)的類都在spring-boot-autoconfiguration-2.3.1.jar

  • DataSourceAutoConfiguration:數(shù)據(jù)源自動配置類揍愁,該類使用注解 @EnableConfigurationProperties 讓指定的數(shù)據(jù)源配置映射類 DataSourceProperties 生效
  • DataSourceProperties:數(shù)據(jù)源配置映射類呐萨,所有可以配置的屬性都在該類中,該類使用注解 @ConfigurationProperties 指定映射的配置屬性的前綴 prefix="spring.datasource"

小知識: 分析類的依賴

IDEA 中右鍵相關(guān)類莽囤,選擇分析->分析依賴->整個項(xiàng)目垛吗,可以看到當(dāng)前類的依賴。如下圖所示烁登,項(xiàng)目主程序依賴了String怯屉、SpringBootApplication 等類。在大型項(xiàng)目中不熟悉項(xiàng)目結(jié)構(gòu)可以使用這種方式查看饵沧。

[圖片上傳失敗...(image-12175f-1606212213489)]

4.2 SpringBoot 靜態(tài)資源映射規(guī)則

1. Webjars

webjars 是以jar包的方式引入靜態(tài)資源

對于日常的web開發(fā)而言锨络,像css、js狼牺、images羡儿、font等靜態(tài)資源文件管理是非常的混亂的、比如jQuery是钥、
Bootstrap掠归、Vue.js等,可能每個框架使用的版本都不一樣悄泥、一不注意就會出現(xiàn)版本沖突或者重復(fù)添加的問題虏冻。所以誕生了 WebJars 技術(shù)。

原本我們在進(jìn)行web開發(fā)時弹囚,一般上都是講靜態(tài)資源文件放置在webapp目錄下厨相,在SpringBoot里面,一般是將資源文件放置在src/main/resources/static目錄下鸥鹉。而在Servlet3中蛮穿,允許我們直接訪問WEB-INF/lib下的jar包中的/META-INF/resources目錄資源,即WEB-INF/lib/{*.jar}/META-INF/resources下的資源可以直接訪問毁渗。
WebJars 正是利用了此功能践磅,將所有前端的靜態(tài)文件打包成一個jar包.

對于用戶而言,和普通的jar引入是一樣的灸异,還能很好的對前端靜態(tài)資源進(jìn)行管理府适。WebJars是將這些通用的Web前端資源打包成Java的Jar包幻碱,然后借助Maven工具對其管理,保證這些Web資源版本唯一性细溅,依賴配置參考 https://www.webjars.org/

  1. 所有的/webjars/** 下的靜態(tài)資源,都去 classpath:/META-INF/resources/webjars/ 下尋找資源
    <!--  以webjars的方式引入 jQuery 依賴  -->
    <dependency>
        <groupId>org.webjars</groupId>
        <artifactId>jquery</artifactId>
        <version>3.3.1</version>
    </dependency>

引入 jQuery webjar 的依賴儡嘶,結(jié)構(gòu)如下圖所示喇聊,jquery.js 確實(shí)在 classpath:/META-INF/resources/webjars/ 目錄下:

jQuery webjar的結(jié)構(gòu)
  1. 訪問靜態(tài)資源jquery.js http://localhost:8080/webjars/jquery/3.3.1/jquery.js

靜態(tài)資源的配置類為 ResourceProperties,源碼如下:

@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {
    //可以設(shè)置和靜態(tài)資源有關(guān)的參數(shù)箭券,緩存時間等
WebMvcAutoConfiguration:

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        if (!this.resourceProperties.isAddMappings()) {
            logger.debug("Default resource handling disabled");
            return;
        }
        Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
        CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
        if (!registry.hasMappingForPattern("/webjars/**")) {
            customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
                    .addResourceLocations("classpath:/META-INF/resources/webjars/")
                    .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
        }

        String staticPathPattern = this.mvcProperties.getStaticPathPattern();
        
        // 未注冊的資源熟菲,都去靜態(tài)資源目錄尋找
        if (!registry.hasMappingForPattern(staticPathPattern)) {
            customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
                    // 添加靜態(tài)資源目錄
                    .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
                    .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
        }
    }
  1. 靜態(tài)資源路徑撩荣,classpath 指項(xiàng)目/src/main/resource,與java類的根路徑同級窜骄,使用java/resource區(qū)分資源,本身都是在classpath下摆屯,打包后剛好在classpath下即\BOOT-INF\classes

    • classpath:/META‐INF/resources/
    • classpath:/resources/
    • classpath:/static/
    • classpath:/public/
    • /:當(dāng)前項(xiàng)目的根路徑
  2. 歡迎頁映射邻遏,訪問 localhost:8080,會去靜態(tài)資源文件夾下尋找index頁面

WebMvcAutoConfiguration:
//配置歡迎頁映射 
    @Bean
    public WelcomePageHandlerMapping welcomePageHandlerMapping(ResourceProperties resourceProperties) {
        return new WelcomePageHandlerMapping(resourceProperties.getWelcomePage(), this.mvcProperties.getStaticPathPattern());
    }
  1. 網(wǎng)站icon映射虐骑,會去靜態(tài)資源文件夾下尋找 favicon.ico

小知識

IDEA 搜索 文件: 雙擊 Shift 快捷鍵准验,可以搜索項(xiàng)目和庫中的 類、符號(屬性方法)廷没、模板(xml等文件)

IDEA 搜索 文件內(nèi)容: Ctrl+Shift+F 快捷鍵糊饱,可以搜索項(xiàng)目中的內(nèi)容,支持限制文件類型(.xml颠黎,.java)另锋,還可以限制搜索 注釋、字符串等狭归。

4.3 模板引擎

模板引擎作用是將模板與動態(tài)數(shù)據(jù)結(jié)合生成html頁面

[圖片上傳失敗...(image-76c3ac-1606212213489)]

常見的模板引擎:JSP夭坪,Thymeleaf,F(xiàn)reemarker

SpringBoot 推薦使用 Thymeleaf过椎,語法簡單台舱,功能強(qiáng)大。

1. 引入Thymeleaf

Thymeleaf 讀作 [taim li:f]

    <!--  引入thymeleaf模板引擎  -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>

2. Thymeleaf 的使用

根據(jù) Thymeleaf 默認(rèn)配置可知竞惋,只要講HTML頁面放在classpath:/templates/下(就是src/main/resources/templates/),Thymeleaf 就能自動渲染灰嫉。

Thymeleaf只渲染 /templates 下的頁面HTML頁面 拆宛,不能放在static或resource下面。

@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {

    private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;

    public static final String DEFAULT_PREFIX = "classpath:/templates/";

    public static final String DEFAULT_SUFFIX = ".html";
  1. 在 src/main/resources/templates/ 下創(chuàng)建 success.html 文件
  2. 向 success.html 中導(dǎo)入 Thymeleaf 的名稱空間讼撒,代碼提示功能還需要使用IDEA-U版打開 Thymeleaf 插件
  3. 使用 Thymeleaf 語法編寫html文件浑厚,<div th:text="${key1}"></div>
    // 攜帶map數(shù)據(jù)股耽,跳轉(zhuǎn)到success.html頁面,
    @RequestMapping("/hello2")
    public String hello2(Map<String, String> map) {
        map.put("key1", "hello thymeleaf...");

        // 跳轉(zhuǎn)到 classpath:/templates/success.html
        return "success";
    }
success.html:
<!DOCTYPE html>
<!-- 引入Thymeleaf名稱空間钳幅,配合IDEA收費(fèi)版才有代碼提示功能 -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2>訪問success成功...</h2>

<!-- th:text設(shè)置div內(nèi)的文本內(nèi)容 -->
<!-- 如果不經(jīng)過模板引擎解析物蝙,則顯示"你好",否則顯示后端傳遞的hello變量-->
<div th:text="${key1}">你好</div>
</body>
</html>

小知識 :IDEA-U 激活

4. Thymeleaf 語法

標(biāo)簽:

  1. th:text 改變當(dāng)前元素內(nèi)的文本內(nèi)容

    th: 可以搭配任意的 html 屬性敢艰,來覆蓋原生屬性的值诬乞,如th:id th:class

    <!-- 后臺傳來的數(shù)據(jù)會覆蓋掉html中的標(biāo)簽數(shù)據(jù),id钠导,class等 -->
    <div id="div01" class="div02"
        th:id="${myId}"
        th:class="${myClass}"
        th:text="${hello}">你好</div>
    
  2. th:each 遍歷標(biāo)簽

  3. th:if 條件標(biāo)簽

  4. 更多th標(biāo)簽參考官方文檔
    [圖片上傳失敗...(image-da2260-1606212213489)]

表達(dá)式:

  • ${} 變量震嫉,后臺傳過來的變量
  • {} 國際化

  • @{} 項(xiàng)目根路徑,引入css牡属,js等
  • ~{} 頁面公共片段(導(dǎo)航欄)引入
  • [[${}]] 獲取變量行內(nèi)寫法票堵,等價于標(biāo)簽內(nèi)th:text="${}"
Simple expressions:
    Variable Expressions: ${...}   # 獲取變量值,對象屬性逮栅,數(shù)組元素悴势,內(nèi)置對象
    Selection Variable Expressions: *{...}   
    Message Expressions: #{...}    # 國際化內(nèi)容
    Link URL Expressions: @{...}   # 定義URL,表示項(xiàng)目根路徑措伐,引用webjar中的css資源
    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,…
Arithmetic operations:          # 數(shù)學(xué)運(yùn)算 
    Binary operators: +, -, *, /, %
    Minus sign (unary operator): -
Boolean operations:            # 布爾運(yùn)算
    Binary operators: and, or
    Boolean negation (unary operator): !, not
Comparisons and equality:      # 比較運(yùn)算
    Comparators: >, <, >=, <= (gt, lt, ge, le)
    Equality operators: ==, != (eq, ne)
Conditional operators:         # 條件運(yùn)算
    If-then: (if) ? (then)
    If-then-else: (if) ? (then) : (else)
    Default: (value) ?: (defaultvalue)
    Special tokens:

語法比較亂瞳浦,和Freemarker一樣,用的時候多查多模仿多總結(jié)即可废士,不要死記硬背叫潦。

https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#standard-expression-syntax

遍歷數(shù)組的兩種方式:

<!--  遍歷數(shù)組方式1,后臺傳遞過來的數(shù)組 users -->
<h4 th:each="user:${users}" th:text="${user}"> </h4>

<!--  遍歷數(shù)組方式2官硝,行內(nèi)寫法矗蕊,與上面等價 -->
<h4>
    <span th:each="user:${users}">[[${user}]]</span>
</h4>

引入webjars的靜態(tài)資源

<link th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet">

4.4 SpringMVC 自動配置

SpringBoot 自動配置好了 SpringMVC,包含以下默認(rèn)配置:

  • 配置了ViewResolver視圖解析器氢架,根據(jù)方法的返回值得到視圖對象傻咖,渲染頁面 ContentNegotiatingViewResolver BeanNameViewResolver

  • 靜態(tài)資源路徑、webjars

  • 靜態(tài)首頁訪問 index.html

  • 網(wǎng)站圖標(biāo)訪問 Favicon.ico

  • 自動注冊了 Converter岖研,GenericConverter卿操,F(xiàn)ormatter bean

    • Converter:轉(zhuǎn)換器,類型轉(zhuǎn)換
    • Formatter:格式化器孙援,時間格式轉(zhuǎn)換
  • HttpMessageConverters SpringMVC用來轉(zhuǎn)換http請求和響應(yīng)害淤,比如將user對象轉(zhuǎn)為json返回給瀏覽器

// 補(bǔ)充: p31 p32源碼這里不太懂建議復(fù)習(xí)SpringMVC,9小時

4.5 修改 SpringBoot 默認(rèn)配置

  1. SpringBoot在自動配置很多組件的時候拓售,先看容器中有沒有用戶自己配置的(@Bean窥摄、@Component)如 果有就用用戶配置的,如果沒有础淤,才自動配置崭放;如果有些組件可以有多個(ViewResolver)將用戶配置的和自己默 認(rèn)的組合起來哨苛;
  2. 在SpringBoot中會有非常多的xxxConfigurer幫助我們進(jìn)行擴(kuò)展配置
  3. 在SpringBoot中會有很多的xxxCustomizer幫助我們進(jìn)行定制配置

4.6 Restful crud 項(xiàng)目

1. 國際化

  1. 創(chuàng)建和編寫國際化配置文件
    [圖片上傳失敗...(image-ea5caa-1606212213489)]

  2. SpringBoot 自動配置好了 ResourceBundleMessageSource 管理國際化資源文件的組件,默認(rèn)國際化文件路徑為classpath:message.properties币砂,需要修改如下

# application.properties 設(shè)置國際化資源文件目錄,默認(rèn)為classpath:messages
spring.messages.basename=i18n.login

通過源碼可知建峭,國際化資源文件默認(rèn)路徑為classpath:message.properties

ResourceBundleMessageSource:
    private String basename = "messages";

    public String getBasename() {
        return this.basename;
    }
    @Bean
    public MessageSource messageSource(MessageSourceProperties properties) {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        if (StringUtils.hasText(properties.getBasename())) {
            // 設(shè)置國際化資源文件的基礎(chǔ)名為message.properties,放在類路徑下
            // 這里去除了國際化文件的語言國家后綴
            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;
    }
  1. 在頁面使用#{}獲取國際化內(nèi)容
    <label class="sr-only" th:text="#{login.username}">Username</label>
  1. 修改瀏覽器語言决摧,訪問頁面亿蒸,查看國際化效果

    修改瀏覽器語言為English(United States),此時的請求體中Accept-Language: en-US,zh-CN;
    頁面顯示語言為英文蜜徽,相關(guān)資源在 login.en_US.properties

    注意:如果將瀏覽器語言修改為English票摇,那么國際化將不生效拘鞋,此時的請求體中Accept-Language: en,zh-CN;,需要配置文件 login_en.properties才能生效矢门。

1. 國際化原理

國際化Locale(區(qū)域信息對象) LocaleResolver(獲取區(qū)域信息對象)

WebMvcAutoConfiguration:
    @Bean
    @ConditionalOnMissingBean   // 如果容器中不存在Id相同的bean盆色,才使用該bean,即用戶自定義bean可以替代
    @ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
    public LocaleResolver localeResolver() {
        // 優(yōu)先使用默認(rèn)的locale信息
        if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
            return new FixedLocaleResolver(this.mvcProperties.getLocale());
        }
        // 根據(jù)瀏覽器請求體中的AcceptHeader設(shè)置LocalResolver信息
        AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
        localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
        return localeResolver;
    }

2. 實(shí)現(xiàn)語言切換功能

  1. 修改前面頁面祟剔,添加語言切換按鈕隔躲,發(fā)送請求攜帶參數(shù)l='zh_CN'
        <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>
  1. 自定義區(qū)域解析器MyLocaleResolver,根據(jù)請求參數(shù)修改語言
/**
 * 實(shí)現(xiàn)網(wǎng)站語言切換功能
 * 自定義LocaleResolver實(shí)現(xiàn)LocaleResolver
 */
public class MyLocaleResolver implements LocaleResolver {


    // 解析區(qū)域信息物延,優(yōu)先返回 用戶選擇的語言>瀏覽器語言>服務(wù)器默認(rèn)語言
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        // 獲取切換語言請求中的參數(shù)
        String l = request.getParameter("l");
        if (!StringUtils.isEmpty(l)) {
            String language = l.split("_")[0];
            String country = l.split("_")[1];
            // 根據(jù)語言與國家信息en_US創(chuàng)建 locale
            Locale locale = new Locale(language, country);
            return locale;
        }
        // 獲取瀏覽器的區(qū)域信息 accept-language
        String acceptLanguage = request.getHeader("Accept-Language");
        if (!StringUtils.isEmpty(acceptLanguage)) {
            Locale requestLocale = request.getLocale();
             return requestLocale;
        }
        // 返回項(xiàng)目系統(tǒng)區(qū)域信息
        return Locale.getDefault();
    }

    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {

    }
}
  1. 將區(qū)域解析器MyLocaleResolver加入到容器宣旱,使其生效
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {

    // 向容器中加入我們自定義的bean MyLocaleResolver,bean的id為方法名叛薯,
    // 用于替換默認(rèn)的localeResolver浑吟,當(dāng)然也可以在@中設(shè)置id
    @Bean
    public LocaleResolver localeResolver(){
        return new MyLocaleResolver();
    }
}

2. 登錄

小知識: Thymeleaf 頁面修改實(shí)時生效,不用重啟項(xiàng)目

開發(fā)期間耗溜,模板引擎頁面修改以后组力,要實(shí)時生效

  1. 禁用模板引擎緩存
spring.thymeleaf.cache=false
  1. 頁面修改完成后 ctrl+f9,重新編譯

  1. 登錄表單

         <form class="form-signin" th:action="@{/user/login}" method="post">
             <input type="text" name="username" class="form-control"  th:placeholder="#{login.username}" required="" autofocus="">
             <input type="password" name="password" class="form-control" th:placeholder="#{login.password}" required="">
         </form> 
    
  2. 處理登錄請求抖拴,登錄成功后燎字,一定要重定向到主頁,如果是轉(zhuǎn)發(fā)阿宅,那么刷新頁面就會有表單重復(fù)提交問題

     @RequestMapping("/user/login")
     public String login(@RequestParam("username") String username,
                         @RequestParam String password,
                         Map<String, Object> map,
                         HttpSession session) {
         if ("admin".equals(username) && "admin".equals(password)) {
    
             // 在session中保存一個屬性候衍,用于訪問時識別是否登錄,攔截器中會使用
             session.setAttribute("loginUser", username);
    
             // 防止表單重復(fù)提交洒放,重定向到主頁
             return "redirect:/dashboard";
         }
    
         // 為啥能將返回map脱柱? SpringMvc入?yún)⒅械膍ap,方法返回時拉馋,會將Map數(shù)據(jù)添加到模型中
         map.put("msg", "用戶名或密碼錯誤");
         return "index";
     }
    
  3. 顯示登錄失敗信息

         <p th:if="${not #strings.isEmpty(msg)}" style="color: red" th:text="${msg}"></p>
    
  4. 攔截器榨为,http://localhost:8080/crud/dashboard惨好,需要登錄才能訪問,所以設(shè)置攔截器檢查用戶是否登錄

    • 繼承HandlerInterceptor實(shí)現(xiàn)登錄攔截器随闺,如果請求 session 中沒有 loginUser日川,則在請求域設(shè)置權(quán)限信息,再轉(zhuǎn)發(fā)到首頁矩乐。

    • Session對應(yīng)的類為javax.servlet.http.HttpSession類龄句。每個來訪者對應(yīng)一個Session對象,所有該客戶的狀態(tài)信息都保存在這個Session對象里散罕。Session對象是在客戶端第一次請求服務(wù)器的時候創(chuàng)建的分歇,保存在服務(wù)端。Servlet里通過request.getSession()方法欧漱,根據(jù)請求中攜帶的 JsessionID 在服務(wù)端查詢對應(yīng)的客戶Session职抡,然后獲得該客戶的所有 Session 屬性,如果能查到误甚,說明服務(wù)端保存了該會話缚甩,所以不需要登錄,如果查不到窑邦,則說明服務(wù)端不認(rèn)識該會話擅威,需要登錄。

    • 攔截器在 DispatchServlet.doDisatch 后生效冈钦,在具體請求的 Controller 前生效

    • 攔截器是通過 AOP 實(shí)現(xiàn)
      例如:

```java
public class LoginHandlerInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 根據(jù)request中攜帶的jsessionId郊丛,查詢對應(yīng)的Session,找到對應(yīng)屬性的值瞧筛,如果服務(wù)端保存了該會話宾袜,則不需要重新登錄
        Object user = request.getSession().getAttribute("loginUser");
        //判斷用戶身份在session中是否存在,其他屬性都可以驾窟,不一定要是loginUser庆猫,也可以在登錄時保存登錄時間到session
        if(user == null) {
            request.setAttribute("msg", "沒有權(quán)限,請先登錄");

            // 用戶未登錄绅络,則轉(zhuǎn)發(fā)到首頁月培,只有轉(zhuǎn)發(fā)才能攜帶請求信息
            request.getRequestDispatcher("/").forward(request, response);
            return false;
        }
        return true;
    }
```
- 注冊登錄攔截器,設(shè)置攔截的url

```java
@Configuration
public class MyMvcConfig extends  WebMvcConfigurer {

    // 添加url與視圖的映射
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        /*
        * 瀏覽器發(fā)送 /atguigu 請求來到 success
        * 因?yàn)閟uccess.html保存在/templates中恩急,只有模板引擎能夠訪問杉畜,所以需要映射url與頁面success
        * 為什么 index.html沒有在這里綁定url也能訪問? 因?yàn)樵?HelloController中綁定了
        */
        registry.addViewController("/atguigu").setViewName("success");

        // 映射url與視圖衷恭,前者是url此叠,后者是頁面,與LoginController.dashboard()作用一致
        registry.addViewController("/dashboard").setViewName("dashboard");
    }
    
    // 注冊攔截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // Springboot已經(jīng)做好了靜態(tài)資源映射随珠,所以我們不需要設(shè)置靜態(tài)資源
        registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**")
                .excludePathPatterns("/", "/index.html", "/user/login", "/asserts/**", "/webjars/**");
    }
    /**
    * 向容器中加入我們自定義的bean MyLocaleResolver灭袁,bean的id為方法名猬错,
    * 用于替換默認(rèn)的localeResolver,當(dāng)然也可以在@中設(shè)置id
    */
    @Bean
    public LocaleResolver localeResolver() {
        return new MyLocaleResolver();
    }
}
```

3. Restful CRUD

普通請求與 RestfulCRUD 的區(qū)別如下:

功能 普通CRUD(uri區(qū)分操作) RestfulCRUD
查詢 getEmp emp-GET
添加 addEmp?xxx emp-POST
修改 updateEmp?id=123&xxx=xx emp/{id}-PUT
刪除 deleteEmp?id=123 emp/{id}-DELETE

下面來實(shí)現(xiàn)員工的 RestfulCRUD:

功能 請求URI 請求方式
查詢所有員工 emps GET
查詢某個員工(訪問修改頁面) emp/{id} GET
訪問添加員工頁面 emp GET
添加員工 emp POST
修改員工 emp PUT
刪除員工 emp/{id} DELETE

4. 頁面公共元素抽取

  1. 抽取頁面公共元素茸歧,如導(dǎo)航欄等
1. 使用 ~{templatename::fragmentname}:模板名::片段名 
    抽取公共片段:
    <div th:fragment="copy"> 
        &copy; 2011 The Good Thymes Virtual Grocery 
    </div> 

    引入公共片段:footer是公共片段的文件名
    <div th:insert="~{footer :: copy}">
    </div> 
2. 使用 ~{templatename::#selector}:模板名::選擇器 
    <div id="copy1"> 
        &copy; 2011 The Good Thymes Virtual Grocery 
    </div> 
    引入公共片段
    <div th:insert="~{footer :: #copy1}">
    </div> 

3. 默認(rèn)效果: th:insert 是將公共片段放在下面的 div 標(biāo)簽中
    <div th:insert="~{footer :: #copy1}">

三種引入公共片段的th屬性:

  • th:insert:將公共片段整個插入到聲明引入的元素中
  • th:replace:將聲明引入的元素替換為公共片段 (推薦使用)
  • th:include:將被引入的片段的內(nèi)容包含進(jìn)這個標(biāo)簽中

詳細(xì)參考 Thymeleaf 8.2 Template Layout

5. 顯示員工列表

顯示員工列表:GET請求倦炒,url 為 /emps,查詢到的員工集合保存到model中返回前臺软瞎。

    @GetMapping("/emps")
    public String emps(Model model) {
        Collection<Employee> employees = employeeDao.getAll();
        // 添加數(shù)據(jù)到請求域中
        model.addAttribute("emps", employees);

        // 跳轉(zhuǎn)到Thymeleaf渲染的 classpath:/templates/emp/list.html
        return "/emp/list";
    }

使用 th:each 遍歷員工集合 emps逢唤,使用三元表達(dá)式顯示性別,使用內(nèi)置 date 對象格式化日期

    <tbody>
        <tr th:each="emp : ${emps}">
            <td th:text="${emp.id}"></td>
            <td th:text="${emp.lastName}"></td>
            <td th:text="${emp.gender}==1 ? '男' : '女' "></td>
            <td th:text="${emp.department.departmentName}"></td>
            <td th:text="${emp.email}"></td>

            <!-- 修改日期格式 -->
            <td th:text="${#dates.format(emp.birth, 'yyyy-MM-dd')}"></td>
            <td >
                <button class="btn btn-sm btn-primary">編輯</button>
                <button class="btn btn-sm btn-danger" >刪除</button>
            </td>
        </tr>

    </tbody>

6. 跳轉(zhuǎn)到員工頁面

員工列表顯示頁面涤浇,加入【添加員工】按鈕鳖藕,請求 /emp

    <a class="btn btn-sm btn-success" th:href="@{/emp}">添加員工</a>

返回添加員工頁面:響應(yīng) GET請求 /emp,添加員工頁面需要顯示部門名稱只锭,所以將部門集合返回著恩,跳轉(zhuǎn)到 /emp/add.html

    @GetMapping("/emp")
    public String toAddPage(Model model) {
        // 查詢部門,返回到添加頁面
        Collection<Department> departments = departmentDao.getDepartments();
        model.addAttribute("departments", departments);
        return "/emp/add";
    }

添加員工頁面:add.html纹烹,遍歷顯示部門名稱页滚,設(shè)置部門信息為id

<form th:action="@{/emp}" method="post">
        <div class="form-group">
            <label>LastName</label>
            <input name="lastName" type="text" class="form-control" placeholder="zhangsan" >
        </div>

        <div class="form-group">
            <label>department</label>
            <!-- 遍歷返回的部門集合召边,顯示部門名稱 -->
            <!--提交的是部門的id铺呵,發(fā)送的數(shù)據(jù)名稱name是 Employee.department.id,值value是dept.id 級聯(lián)屬性-->
            <select class="form-control" name="department.id">
                <option th:each="dept:${departments}"
                        th:text="${dept.departmentName}"
                        th:value="${dept.id}">
                </option>
            </select>
        </div>
    </form>

7. 添加員工

添加員工:POST請求/emp隧熙,保存員工信息后重定向請求員工列表/emps片挂,返回員工列表

  • SpringMVC自動將請求參數(shù)與入?yún)ο蟮膶傩赃M(jìn)行一一綁定,Employee屬性與請求參數(shù)名稱一致
  • 其中部門信息傳過來的是department.id=123贞盯,會自動與javabean Employee 的屬性 department 對象的 id 綁定起來音念。級聯(lián)屬性的綁定
  • 添加操作完成后,需要返回員工列表list.html頁面躏敢,但是不能返回闷愤,因?yàn)樘砑诱埱鬀]有查詢所有員工數(shù)據(jù)并返回,所以我們選擇重定向到顯示員工請求 /emps件余,該請求會查詢所有員工并返回到list.html頁面讥脐,參考第5小節(jié) 顯示員工列表
    // 添加員工
    // SpringMVC自動將請求參數(shù)與入?yún)ο蟮膶傩赃M(jìn)行一一綁定
    @PostMapping("/emp")
    public String addEmp(Employee employee) {
        System.out.println("保存的員工信息" + employee);
        employeeDao.save(employee);

        // 添加完成后不應(yīng)該返回/emp/list.html,因?yàn)檫@個請求不會攜帶list頁面需要的員工數(shù)據(jù)
        // 應(yīng)該轉(zhuǎn)發(fā)或重定向到/emps請求顯示全部員工數(shù)據(jù)
        // redirect:表示重定向到一個新地址
        // forward:表示轉(zhuǎn)發(fā)到一個新地址
        return "redirect:/emps";
    }

8. 重定向與轉(zhuǎn)發(fā)

重定向:發(fā)送一個新的請求啼器,并且不攜帶本次請求的數(shù)據(jù)

轉(zhuǎn)發(fā):

p40 重定向與轉(zhuǎn)發(fā)源碼講解

ThymeleafViewResolver:

    protected View createView(String viewName, Locale locale) throws Exception {
        if (!this.alwaysProcessRedirectAndForward && !this.canHandle(viewName, locale)) {
            vrlogger.trace("[THYMELEAF] View \"{}\" cannot be handled by ThymeleafViewResolver. Passing on to the next resolver in the chain.", viewName);
            return null;
        } else {
            String forwardUrl;
            // 視圖名稱以redirect:開頭旬渠,則進(jìn)行重定向
            if (viewName.startsWith("redirect:")) {
                vrlogger.trace("[THYMELEAF] View \"{}\" is a redirect, and will not be handled directly by ThymeleafViewResolver.", viewName);
                forwardUrl = viewName.substring("redirect:".length(), viewName.length());
                
                // 最終在renderMergedOutputModel方法 中調(diào)用servlet原生重定向response.sendRedirect(encodedURL);
                RedirectView view = new RedirectView(forwardUrl, this.isRedirectContextRelative(), this.isRedirectHttp10Compatible());
                return (View)this.getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, viewName);
            } else if (viewName.startsWith("forward:")) {
                // 視圖名稱以forward:,則進(jìn)行轉(zhuǎn)發(fā)
                vrlogger.trace("[THYMELEAF] View \"{}\" is a forward, and will not be handled directly by ThymeleafViewResolver.", viewName);
                forwardUrl = viewName.substring("forward:".length(), viewName.length());

                // 最終在renderMergedOutputModel方法 中調(diào)用servlet原生轉(zhuǎn)發(fā)requestDispatcher.forward
                return new InternalResourceView(forwardUrl);
            } else if (this.alwaysProcessRedirectAndForward && !this.canHandle(viewName, locale)) {
                vrlogger.trace("[THYMELEAF] View \"{}\" cannot be handled by ThymeleafViewResolver. Passing on to the next resolver in the chain.", viewName);
                return null;
            } else {
                vrlogger.trace("[THYMELEAF] View {} will be handled by ThymeleafViewResolver and a {} instance will be created for it", viewName, this.getViewClass().getSimpleName());
                return this.loadView(viewName, locale);
            }
        }
    }

9. 日期格式化

SpringBoot 默認(rèn)日期格式是yyyy/MM/dd端壳,如果需要修改可以在 application.properties 中配置

spring.mvc.format.date=yyyy-MM-dd

10. 跳轉(zhuǎn)到員工編輯頁面

GET請求 /emp/{id}告丢,將該員工信息返回到前臺,由于還要顯示所有部門信息损谦,所以查詢所有部門岖免,返回信息無論添加到Map岳颇,還是ModelAndView都是一樣的。

返回頁面為 /emp/add.html觅捆,這是將添加與編輯功能混合起來的頁面

    @GetMapping("/emp/{id}")
    public String toeditPage(@PathVariable("id")  Integer id, Map<String, Object> map) {
        Employee employee = employeeDao.get(id);
        map.put("employee", employee);

        // 查詢部門赦役,返回到編輯頁面
        Collection<Department> departments = departmentDao.getDepartments();
        map.put("departments", departments);

        return "/emp/add";
    }

11. 編輯員工信息

PUT請求,/emp栅炒,返回編輯員工頁面

  • 由于編輯員工與添加員工共用一個頁面add.html掂摔,需要在表單中加入 _method 屬性,當(dāng)編輯時生效
  • 注意發(fā)送的仍然為 POST 請求赢赊,只是會攜帶參數(shù) _method=PUT乙漓,但是 SpringBoot 中的 HiddenHttpMethodFilter 過濾器會對隱藏的請求方式進(jìn)行修改,將請求修改為 PUT 請求释移。
  • form 表單本身不支持 PUT DELETE 請求
  • 注意url中沒有 /{id}
<input type="hidden" name="_method" value="PUT" th:if="${employee != null}">

開啟SpringBoot 中的 HiddenHttpMethodFilter 過濾器叭披,這樣才能將 POST 請求轉(zhuǎn)為 PUT

# 將POST請求轉(zhuǎn)換為PUT請求,springboot2.x默認(rèn)關(guān)閉
spring.mvc.hiddenmethod.filter.enabled=true

保存員工信息玩讳,

    // 編輯員工信息
    @PutMapping("/emp")
    public String updateEmp(Employee employee) {
        System.out.println("保存的員工信息" + employee);

        // save 方法是employee沒有id涩蜘,則新增;有id則更新
        employeeDao.save(employee);

        return "redirect:/emps";
    }

隱藏的請求方式轉(zhuǎn)換過濾器 HiddenHttpMethodFilter 源碼如下

HiddenHttpMethodFilter:
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        HttpServletRequest requestToUse = request;

        // 查看是否為POST請求
        if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
            // 獲取請求中攜帶的_method屬性
            String paramValue = request.getParameter("_method");
            if (StringUtils.hasLength(paramValue)) {
                String method = paramValue.toUpperCase(Locale.ENGLISH);
                // 如果method為 PUT 或 DELETE熏纯,則創(chuàng)建新的請求同诫,設(shè)置請求方式為method
                if (ALLOWED_METHODS.contains(method)) {
                    requestToUse = new HttpMethodRequestWrapper(request, method);
                }
            }
        }

        filterChain.doFilter(requestToUse, response);
    }

12. 員工刪除

DELETE請求 /emp/{id},重定向到員工列表請求 /emps樟澜,返回員工列表頁面 /list.html

  • 發(fā)送的還是 POST請求误窖,SpringBoot 會轉(zhuǎn)換為 DELETE 請求

    // 刪除員工信息
    @DeleteMapping("/emp/{id}")
    public String deleteEmp(@PathVariable("id") Integer id) {

        employeeDao.delete(id);

        // 重定向到員工列表頁面,返回所有員工信息
        return "redirect:/emps";
    }

小知識: 不要從 pdf 復(fù)制代碼

查看下方兩個 form-control秩贰,上面是從pdf復(fù)制的代碼霹俺,下面是手動敲的代碼,看起來完全一致毒费,但是在代碼中替換會報(bào)錯丙唧。

查看二者的十六進(jìn)制編碼,發(fā)現(xiàn)上方-的編碼是 E28090觅玻,下方-編碼是 2D想际,對應(yīng) ASCII 碼表中的-

form‐control
form-control

00000000: 66 6F 72 6D E2 80 90 63 6F 6E 74 72 6F 6C 0D 0A    formb..control..
00000010: 66 6F 72 6D 2D 63 6F 6E 74 72 6F 6C                form-control

注:使用 VSCode 插件 Hexdump 查看的十六進(jìn)制編碼

4.7 定制錯誤頁面

1. SpringBoot 默認(rèn)錯誤處理機(jī)制

  1. 訪問一個不存在的url,會發(fā)生錯誤串塑,返回錯誤頁面沼琉。SpringBoot 默認(rèn)錯誤頁面如下

    SpringBoot錯誤頁面
  2. 如果使用其他客戶端(Postman),則返回一個json數(shù)據(jù)

    {
     "timestamp": "2020-07-07T09:20:38.492+00:00",
     "status": 404,
     "error": "Not Found",
     "message": "",
     "path": "/crud/dsahw"
     }
    

    原因:之所以二者返回結(jié)果不同桩匪,是因?yàn)闉g覽器請求錯誤頁面打瘪,請求頭中包含參數(shù)Accept: text/html,表示優(yōu)先接受 html 數(shù)據(jù),所以響應(yīng)返回html頁面闺骚。而postman 請求錯誤頁面請求頭參數(shù)為 Accept: */*彩扔,所以響應(yīng)返回json數(shù)據(jù)。

步驟:

  1. 當(dāng)系統(tǒng)發(fā)生 4xx 或 5xx 錯誤僻爽,ErrorPageCustomizer錯誤頁面響應(yīng)規(guī)則就會生效虫碉,就會轉(zhuǎn)發(fā)到 /error 請求
  2. /error 請求由 BasicErrorController 處理,返回 html 頁面或 json 數(shù)據(jù)
  3. 使用 DefaultErrorAttributes 獲取錯誤信息
  4. 將上一步獲取的錯誤信息添加到 ModelAndView中返回胸梆。如果返回 html 頁面敦捧,則 DefaultErrorViewResolver 根據(jù)狀態(tài)碼去 /error 目錄下查找對應(yīng)的 4xx.html 頁面并返回;

原理:

ErrorMvcAutoConfiguration碰镜,錯誤處理的自動配置兢卵,這個類給容器中添加了以下組件:

  1. ErrorPageCustomizer:錯誤頁面配置信息

    public class ErrorProperties {
    
        // 從配置文件讀取錯誤頁面路徑,默認(rèn)為/error
        // 類似與在web.xml中注冊的錯誤頁面
        @Value("${error.path:/error}")
        private String path = "/error";
    

    擴(kuò)展:在web.xml中配置錯誤響應(yīng)或異常對應(yīng)的頁面
    https://blog.csdn.net/qq_41642093/article/details/79100579

    
    
  2. BasicErrorController:處理/error請求

    @Controller
     @RequestMapping("${server.error.path:${error.path:/error}}")
     public class BasicErrorController extends AbstractErrorController {
    
     // 響應(yīng)html類型的數(shù)據(jù)绪颖,瀏覽器請求來這個方法處理
     @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
     public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
         HttpStatus status = getStatus(request);
    
         // 使用 DefaultErrorAttributes 獲取錯誤信息秽荤,并返回到頁面
         Map<String, Object> model = Collections
                 .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
         response.setStatus(status.value());
    
         // 哪個頁面作為錯誤頁面?ModelAndView包含頁面地址和頁面數(shù)據(jù)
         ModelAndView modelAndView = resolveErrorView(request, response, status, model);
         return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
     }
    
     // 響應(yīng)json類型的數(shù)據(jù)柠横,postman來這個方法處理
     @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);
     }
    
  3. DefaultErrorAttributes:獲取錯誤頁面屬性集合

     @Override 
     public Map<String, Object> getErrorAttributes(
         RequestAttributes requestAttributes, boolean includeStackTrace) { 
    
         Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>(); 
         // 返回錯誤頁面展示的信息窃款,包括時間,狀態(tài)碼牍氛,錯誤信息晨继,請求路徑等
         errorAttributes.put("timestamp", new Date()); 
         addStatus(errorAttributes, requestAttributes); 
         addErrorDetails(errorAttributes, requestAttributes, includeStackTrace); 
         addPath(errorAttributes, requestAttributes); 
         
         return errorAttributes; 
     }
    
  1. DefaultErrorViewResolver:根據(jù)錯誤碼去 /error 下查找對應(yīng)的 4xx.html 并返回
    @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())) {
            // 根據(jù)狀態(tài)碼去查找錯誤頁面
            modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
        }
        return modelAndView;
    }
    
    private ModelAndView resolve(String viewName, Map<String, Object> model) {
        // SpringBoot默認(rèn)去找錯誤頁面,路徑為 error/45xx.html
        String errorViewName = "error/" + viewName;
    
        
        TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
                this.applicationContext);
        if (provider != null) {
            // 如果模板引擎可用糜俗,則返回并渲染錯誤頁面
            return new ModelAndView(errorViewName, model);
        }
        // 模板引擎不可用踱稍,則去返回 static/45xx.html 靜態(tài)頁面
        return resolveResource(errorViewName, model);
    }
    

2. 定制錯誤響應(yīng)頁面

  1. 定制錯誤頁面
    • 創(chuàng)建錯誤頁面 /templates/error/404.xml曲饱,若發(fā)生 404 錯誤悠抹,模板引擎則渲染返回該頁面
    • 也可以創(chuàng)建 4xx.html 來匹配 4xx 錯誤,精確優(yōu)先
    • 頁面可以獲取的信息:
      • timestamp 時間戳扩淀,
      • status 錯誤狀態(tài)碼楔敌,
      • error 錯誤提示,
      • exception 異常對象驻谆,
      • messgae 異常信息
      • errors JSR303數(shù)據(jù)校驗(yàn)錯誤
    • 如果模板引擎找不到匹配的錯誤頁面卵凑,則去靜態(tài)資源文件夾下查找并返回對應(yīng)的靜態(tài)頁面
    • 如果以上都沒有,則使用SpringBoot默認(rèn)的錯誤頁面

在錯誤頁面顯示異常信息需要手動開啟:

# 返回異常信息到錯誤頁面
server.error.include-exception=true
server.error.include-message=always

在錯誤頁面顯示錯誤信息:

    <!-- 行內(nèi)寫法與普通寫法胜臊,前者更簡便 -->
    <h2>status: [[${status}]]</h2>
    <h2 th:text="'timestamp: ' + ${timestamp}"></h2>
    <h2>error: [[${error}]]</h2>
    <h2>exception: [[${exception}]]</h2>
    <h2>message: [[${message}]]</h2>
    <h2>errors: [[${errors}]]</h2>
  1. 定制錯誤json數(shù)據(jù)
  • 使用注解@ControllerAdvice 自定義異常處理器勺卢,發(fā)生異常后返回 json 數(shù)據(jù)

    // 自定義異常處理器,需要注解 @ControllerAdvice
    @ControllerAdvice
    public class MyExceptionHandler {
        private static final Logger logger = LoggerFactory.getLogger(MyExceptionHandler.class);
    
        // 只處理UserNotExistException異常象对,返回json數(shù)據(jù)黑忱,包含錯誤碼code,異常信息message
        @ResponseBody
        @ExceptionHandler(UserNotExistException.class)
        public Map<String, Object> handlerUserNotException(Exception e) {
            logger.error("用戶不存在異常:{}", e);
    
            Map<String, Object> map = new HashMap<>();
            map.put("code", "user.notexist");
            map.put("message", e.getMessage());
    
            return map;
        }
    }
    
  • 轉(zhuǎn)發(fā)到/error進(jìn)行自適應(yīng)響應(yīng)效果處理,瀏覽器返回頁面甫煞,postman返回json

        @ExceptionHandler(UserNotExistException.class)
        public String handleException(Exception e, HttpServletRequest request){
            Map<String,Object> map = new HashMap<>();
            //傳入我們自己的錯誤狀態(tài)碼  4xx 5xx菇曲,
            // 不傳入的話會訪問/error成功,狀態(tài)碼為200抚吠,就不返回錯誤頁面 5xx.html了
            /**
             * BasicErrorController中獲取狀態(tài)碼如下:
             * Integer statusCode = (Integer) request
             .getAttribute("javax.servlet.error.status_code");
            */
            request.setAttribute("javax.servlet.error.status_code",500);
            map.put("code","user.notexist");
            map.put("message","用戶出錯啦");
    
            request.setAttribute("ext",map);
            //轉(zhuǎn)發(fā)到/error
            return "forward:/error";
        }
    

4.8 配置嵌入式 Servlet 容器

SpringBoot 默認(rèn)使用的嵌入式 Servlet 容器為 Tomcat常潮,查看 pom 依賴,可以看到 web-starter 依賴 tomcat-starter楷力,使用的是 9.0 版本的 Tomcat喊式。

[圖片上傳失敗...(image-4ead43-1606212213489)]

1. 修改 Servlet 容器的相關(guān)配置

  • application.properties中配置 Tomcat,屬性參考 ServerProperties

    server.port=8090
    server.servlet.context-path=/crud
    
    server.tomcat.max-connections=8
    server.tomcat.uri-encoding=UTF-8
    

    下面是 ServerProperties 源碼萧朝,配置的 server 屬性都綁定到該類屬性

    @ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
    public class ServerProperties {
        // Tomcat端口垃帅,server.port
        private Integer port;
        // Servlet有屬性contextPath,表示項(xiàng)目路徑 server.servlet.context-path
        private final Servlet servlet = new Servlet();
        // Tomcat有屬性uriEncoding剪勿,表示uri編碼  server.tomcat.uri-encoding
        private final Tomcat tomcat = new Tomcat();
    
  • 自定義 WebServerFactoryCustomizer贸诚,配置 Tomcat 屬性,優(yōu)先級高于配置文件

    參考 SpringBoot 服務(wù)器配置文檔(與SpringBoot 1.x 配置方式不同)厕吉,自定義 WebServerFactoryCustomizer酱固,設(shè)置Tomcat 端口,uri編碼等規(guī)則头朱。

    @Configuration
    public class MyMvcConfig extends WebMvcConfigurerAdapter {
    
    // 配置 Tomcat运悲,將自定義配置加入容器
    @Bean
    public WebServerFactoryCustomizer webServerFactoryCustomizer() {
        return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() {
    
            // 定制嵌入式Servlet容器相關(guān)的規(guī)則,對其他容器也生效
            @Override
            public void customize(ConfigurableWebServerFactory factory) {
                factory.setPort(8090);
            }
        };
    }
    

2. 注冊 Servlet 三大組件

請求處理過程: 一個請求進(jìn)入Tomcat项钮,需要經(jīng)過 Filter -> Servlet -> Interceptor -> Controller 四個步驟班眯,詳細(xì)如下:

20200708043128

Servlet、Filter烁巫、Listener 三大組件之前是配置在 web.xml 中署隘,SpringBoot默認(rèn)以 jar 包方式啟動嵌入式 Tomcat 來運(yùn)行 SpringBoot 的 web應(yīng)用,沒有 web.xml,所以使用以下方式注冊 Servlet 三大組件:

  1. 注冊自定義 Servlet,
    • 使用 ServletRegistrationBean 注冊自定義 Servlet 組件到容器变勇,需要@Bean注解
    • 這個Servlet不會被前面的攔截器攔截卓缰,因?yàn)樽远x的 MyServlet 與 DispatchServlet 平級,攔截器是在 DispatchServlet 后,對應(yīng) Controller 處理前生效的,所以不會攔截自定義的 MyServlet
    • Spring的@Bean注解用于告訴方法,返回一個Bean對象脾还,將其加入Spring容器。產(chǎn)生這個Bean對象的方法Spring只會調(diào)用一次(單例)入愧。
// 自定義Servlet
public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 返回 hello serlvet 給頁面
        resp.getWriter().write(" hello serlvet...");
    }
}

// @Configuration用于定義配置類鄙漏,可替換xml配置文件赛蔫,被注解的類內(nèi)部包含有一個或多個被@Bean注解的方法
// 這些方法將會被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext類進(jìn)行掃描,并將這些 Bean 加入到 Spring 容器
// 注冊自定義 Servlet 組件到容器
@Configuration
public class MyServerConfig {

    // 注冊Servlet組件
    @Bean
    public ServletRegistrationBean myServlet() {
        // 創(chuàng)建注冊器泥张,參數(shù)為MyServlet與映射路徑/mySerlvet
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(new MyServlet(), "/mySerlvet");

        return registrationBean;
    }
}

實(shí)際案例: SpringBoot 不需要在 web.xml 中配置 SpringMVC 的前端控制器 DispatcherServlet 呵恢,其自動注冊 DispatcherServlet,注冊方式就是使用 ServletRegistrationBean 將 DispatcherServlet 添加到容器

    @Bean(name = {"dispatcherServletRegistration"})
    @ConditionalOnBean(value = {DispatcherServlet.class}, name = {"dispatcherServlet"})
    public ServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet) {
        // 創(chuàng)建注冊器媚创,參數(shù)為DispatcherServlet與映射路徑 /
        // 可以通過 server.servlet-path 修改dispatcherServlet 的映射路徑
        ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet, new String[]{this.serverProperties.getServletMapping()});
        registration.setName("dispatcherServlet");
        // 設(shè)置啟動順序?yàn)?-1
        registration.setLoadOnStartup(this.webMvcProperties.getServlet().getLoadOnStartup());
        if (this.multipartConfig != null) {
            registration.setMultipartConfig(this.m  ultipartConfig);
        }

        return registration;
    }
  1. 注冊自定義過濾器 Filter
    • 使用 FilterRegistrationBean 注冊自定義過濾器到容器渗钉,需要@Bean注解
    • 過濾器會在指定url請求到 servlet 之前生效
    • 過濾器由Servlet容器管理,而攔截器則可以通過IoC容器來管理
    • 常見過濾器:編碼過濾器钞钙,敏感詞過濾器鳄橘,壓縮資源過濾器
// 自定義過濾器
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 過濾處理
        System.out.println("MyFilter doFilter...");
                
        // 調(diào)用過濾器鏈中的下一個過濾器
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {}
}

@Configuration
public class MyServerConfig {
    // 注冊自定義過濾器到容器
    @Bean
    public FilterRegistrationBean myFilter() {
        FilterRegistrationBean<MyFilter> registrationBean = new FilterRegistrationBean<>();
        
        registrationBean.setFilter(new MyFilter());
        // 設(shè)置過濾的url
        registrationBean.setUrlPatterns(Arrays.asList("/myServlet"));
        return registrationBean;
    }

除了注冊過濾器的方式,還可以使用注解注冊過濾器 https://www.cnblogs.com/paddix/p/8365558.html

常見的過濾器實(shí)現(xiàn)參考芒炼,包括編碼過濾器瘫怜,敏感詞過濾器,壓縮資源過濾器 https://mp.weixin.qq.com/s/psRMhj4IlcjyVPE0a64vBA

  1. 注冊自定義監(jiān)聽器
    • 使用 ServletListenerRegistrationBean 注冊自定義監(jiān)聽器到容器本刽,需要@Bean注解
    • 常見監(jiān)聽器:網(wǎng)站訪問人數(shù)統(tǒng)計(jì)
public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("contextInitialized...web應(yīng)用啟動");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("contextInitialized...web應(yīng)用銷毀");
    }
}

    // 注冊Listener到容器
    @Bean
    public ServletListenerRegistrationBean myListener() {
        ServletListenerRegistrationBean<MyListener> registrationBean = new ServletListenerRegistrationBean<>(new MyListener());

        return registrationBean;
    }

過濾器與監(jiān)聽器都可以參考 https://github.com/ZhongFuCheng3y/3y

使用其他嵌入式 Servlet 容器

SpringBoot 支持 3 種嵌入式 Servlet 容器:

  • Tomcat 默認(rèn)
  • Jetty 長鏈接友好
  • Undertow 非阻塞式鲸湃,并發(fā)性能好

切換為 Jetty: 修改 pom.xml 配置文件,移除 tomcat-starter子寓,引入 jetty-starter

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

<!-- 前面排除默認(rèn)依賴的tomcat暗挑,現(xiàn)在引入jetty-starter -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

重新啟動項(xiàng)目,Jetty started on port(s) 8080 (http/1.1) with context path '/crud'

切換為 Undertow:

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

<!-- 前面排除默認(rèn)依賴的tomcat斜友,現(xiàn)在引入undertow-starter -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

小技巧: IDEA 排除 pom.xml 中的依賴包

右擊 pom.xml炸裆,選擇 Diagram,找到要排除的包鲜屏,右擊選擇 Exclude

4.9 嵌入式 Servlet 容器自動配置原理

// 補(bǔ)充:p49烹看,SpringBoot 2.x 這塊變動較大

4.10 嵌入式 Servlet 容器啟動原理

// 補(bǔ)充:p50,SpringBoot 2.x 這塊變動較大洛史。這兩個章節(jié)是面試重點(diǎn)惯殊,但是并沒有搞懂

4.11 使用外置的 Servlet 容器

  • 嵌入式Servlet容器:應(yīng)用打成可執(zhí)行的jar

    • 優(yōu)點(diǎn):簡單、便攜虹菲;
    • 缺點(diǎn):默認(rèn)不支持JSP靠胜、優(yōu)化定制比較復(fù)雜
  • 外置的Servlet容器:外面安裝Tomcat掉瞳,將應(yīng)用打?yàn)閣ar包并部署毕源;

// 補(bǔ)充:流程與原理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市陕习,隨后出現(xiàn)的幾起案子霎褐,更是在濱河造成了極大的恐慌,老刑警劉巖该镣,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冻璃,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)省艳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門娘纷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人跋炕,你說我怎么就攤上這事赖晶。” “怎么了辐烂?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵遏插,是天一觀的道長。 經(jīng)常有香客問我纠修,道長胳嘲,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任扣草,我火速辦了婚禮了牛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘辰妙。我一直安慰自己白魂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布上岗。 她就那樣靜靜地躺著福荸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪肴掷。 梳的紋絲不亂的頭發(fā)上敬锐,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機(jī)與錄音呆瞻,去河邊找鬼台夺。 笑死,一個胖子當(dāng)著我的面吹牛痴脾,可吹牛的內(nèi)容都是我干的颤介。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼赞赖,長吁一口氣:“原來是場噩夢啊……” “哼滚朵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起前域,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤辕近,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后匿垄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體移宅,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡归粉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了漏峰。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片糠悼。...
    茶點(diǎn)故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖浅乔,靈堂內(nèi)的尸體忽然破棺而出绢掰,到底是詐尸還是另有隱情,我是刑警寧澤童擎,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布滴劲,位于F島的核電站,受9級特大地震影響顾复,放射性物質(zhì)發(fā)生泄漏班挖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一芯砸、第九天 我趴在偏房一處隱蔽的房頂上張望萧芙。 院中可真熱鬧,春花似錦假丧、人聲如沸双揪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽渔期。三九已至,卻和暖如春渴邦,著一層夾襖步出監(jiān)牢的瞬間疯趟,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工谋梭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留信峻,地道東北人。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓瓮床,卻偏偏與公主長得像盹舞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子隘庄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評論 2 355