天天看點

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

SpringBoot2 核心

  • 一、配置檔案
    • 1、檔案類型
      • 1.1、properties
      • 1.2、yaml
        • 1、簡介:
        • 2、基本文法:
        • 3、資料類型:
        • 4、示例:
    • 2、配置提示
  • 二、Web開發
    • 1、SpringMVC自動配置概覽
    • 2、簡單功能分析
      • 2.1、靜态資源通路
        • 1、靜态資源目錄:
        • 2、靜态資源通路字首:
        • 3、webjar
      • 2.2、歡迎頁支援
      • 2.3、自定義 Favicon
      • 2.4、靜态資源配置原理
        • 1、配置類隻有一個有參構造器
        • 2、資源處理的預設規則
        • 3、歡迎頁的處理規則
        • 4、favicon
    • 3、請求參數處理
      • 3.1、請求映射
        • 1、rest使用與原理
        • 2、請求映射原理
      • 3.2、普通參數與基本注解
        • 1、注解:
        • 2、Servlet API:
        • 3、複雜參數:
        • 4、自定義對象參數:
      • 3.3、POJO封裝過程
      • 3.4、參數處理原理
        • 1、HandlerAdapter
        • 2、執行目标方法
        • 3、參數解析器-HandlerMethodArgumentResolver
        • 4、傳回值處理器
        • 5、如何确定目标方法每一個參數的值
          • 5.1、挨個判斷所有參數解析器那個支援解析這個參數
          • 5.2、解析這個參數的值
          • 5.3、自定義類型參數 封裝POJO
        • 6、目标方法執行完成
        • 7、處理派發結果
    • 4、資料響應與内容協商
      • 4.1、響應JSON
        • 1、[email protected]
          • 傳回值解析器
          • 傳回值解析器原理
        • 2、SpringMVC到底支援哪些傳回值
        • 3、HTTPMessageConverter原理
          • 1、MessageConverter規範
          • 2、預設的MessageConverter
      • 4.2、内容協商
        • 1、引入xml依賴
        • 2、postman分别測試傳回json和xml
        • 3、開啟浏覽器參數方式内容協商功能
        • 4、内容協商原理
        • 5、自定義 MessageConverter
    • 5、視圖解析與模闆引擎
      • 5.1、視圖解析
        • 1、視圖解析原理流程
      • 5.2、模闆引擎-Thymeleaf
        • 1、thymeleaf簡介
        • 2、基本文法
          • 1、表達式:
          • 2、字面量:
          • 3、文本操作:
          • 4、數學運算:
          • 5、布爾運算:
          • 6、比較運算:
          • 7、條件運算:
          • 8、特殊操作:
        • 3、設定屬性值-th:attr
        • 4、疊代
        • 5、條件運算
        • 6、屬性優先級
      • 5.3、thymeleaf使用
        • 1、引入Starter
        • 2、自動配置好了thymeleaf
        • 3、頁面開發
      • 5.4、建構背景管理系統
        • 1、項目建立
        • 2、靜态資源處理
        • 3、路徑建構
        • 4、模闆抽取
        • 5、頁面跳轉
        • 6、資料渲染
    • 6、攔截器
      • 6.1、HandlerInterceptor 接口
      • 6.2、配置攔截器
      • 6.3、攔截器原理
    • 7、檔案上傳
      • 7.1、頁面表單
      • 7.2、檔案上傳代碼
      • 7.3、自動配置原理
    • 8、異常處理
      • 8.1、錯誤處理
        • 1、預設規則
        • 2、定制錯誤處理邏輯
        • 3、異常處理自動配置原理
        • 4、異常處理步驟流程
    • 9、Web原生元件注入(Servlet、Filter、Listener)
      • 9.1、使用Servlet API
      • 9.2、使用RegistrationBean
    • 10、嵌入式Servlet容器
      • 10.1、切換嵌入式Servlet容器
      • 10.2、定制Servlet容器
    • 11、定制化原理
      • 11.1、定制化的常見方式
      • 11.2、原理分析套路
  • 三、資料通路
    • 1、SQL
      • 1.1、資料源的自動配置-HikariDataSource
        • 1、導入JDBC場景
        • 2、分析自動配置
          • 2.1、自動配置的類
        • 3、修改配置項
        • 4、測試
      • 1.2、使用Druid資料源
        • 1、druid官方github位址
        • 2、自定義方式
          • 2.1、建立資料源
          • 2.2、StatViewServlet
          • 2.3、StatFilter
        • 3、使用官方starter方式
          • 3.1、引入druid-starter
          • 3.2、分析自動配置
          • 3.3、配置示例
      • 1.3、整合MyBatis操作
        • 1、配置模式
        • 2、注解模式
        • 3、混合模式
      • 1.4、整合 MyBatis-Plus 完成CRUD
        • 1、什麼是MyBatis-Plus
        • 2、整合MyBatis-Plus
        • 3、CRUD功能
    • 2、NoSQL
      • 2.1、Redis自動配置
      • 2.2、RedisTemplate與Lettuce
      • 2.3、切換至jedis
  • 四、單元測試
    • 1、JUnit5 的變化
    • 2、JUnit5常用注解
    • 3、斷言(assertions)
      • 3.1、簡單斷言
      • 3.2、數組斷言
      • 3.3、組合斷言
      • 3.4、異常斷言
      • 3.5、逾時斷言
      • 3.6、快速失敗
    • 4、前置條件(assumptions)
    • 5、嵌套測試
    • 6、參數化測試
    • 7、遷移指南
  • 五、名額監控
    • 1、SpringBoot Actuator
      • 1.1、簡介
      • 1.2、1.x與2.x的不同
      • 1.3、如何使用
      • 1.4、可視化
    • 2、Actuator Endpoint
      • 2.1、最常使用的端點
      • 2.2、Health Endpoint
      • 2.3、Metrics Endpoint
      • 2.4、管理Endpoints
        • 1、開啟與禁用Endpoints
        • 2、暴露Endpoints
    • 3、定制 Endpoint
      • 3.1、定制 Health 資訊
      • 3.2、定制info資訊
        • 1、編寫配置檔案
        • 2、編寫InfoContributor
      • 3.3、定制Metrics資訊
        • 1、SpringBoot支援自動适配的Metrics
        • 2、增加定制Metrics
      • 3.4、定制Endpoint
  • 六、原了解析
    • 1、Profile功能
      • 1.1、application-profile功能
      • 1.2、@Profile條件裝配功能
      • 1.3、profile分組
    • 2、外部化配置
      • 2.1、外部配置源
      • 2.2、配置檔案查找位置
      • 2.3、配置檔案加載順序:
      • 2.3、指定環境優先,外部優先,後面的可以覆寫前面的同名配置項
    • 3、自定義starter
      • 3.1、starter啟動原理
      • 3.2、自定義starter
    • 4、SpringBoot原理
      • 4.1、SpringBoot啟動過程
      • 4.2、Application Events and Listeners
      • 4.3、ApplicationRunner 與 CommandLineRunner

連接配接視訊

文檔位址

一、配置檔案

1、檔案類型

1.1、properties

同以前的properties用法

1.2、yaml

1、簡介:

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

非常适合用來做以資料為中心的配置檔案

2、基本文法:

  • key: value;kv之間有空格
  • 大小寫敏感
  • 使用縮進表示層級關系
  • 縮進不允許使用tab,隻允許空格
  • 縮進的空格數不重要,隻要相同層級的元素左對齊即可
  • '#'表示注釋
  • 字元串無需加引号,如果要加,’'與""表示字元串内容 會被 轉義/不轉義

3、資料類型:

  • 字面量:單個的、不可再分的值。date、boolean、string、number、null
    k: v
  • 對象:鍵值對的集合。map、hash、set、object

    行内寫法: k: {k1:v1,k2:v2,k3:v3}

    #或

    k:

        k1: v1

        k2: v2

        k3: v3

  • 數組:一組按次序排列的值。array、list、queue

    行内寫法: k: [v1,v2,v3]

    #或者

    k:

      - v1

      - v2

      - v3

4、示例:

對象

@Data
@ToString
@ConfigurationProperties(prefix = "person")
@Component
public class Person {
    private String name;
    private Boolean boss;
    private Date birth;
    private Integer age;
    private Pet pet;
    private String[] strs;
    private List<String> list;
    private Map<String, Object> map;
    private Set<Double> set;
    private Map<String, List<Pet>> pets;
}
           
@Data
public class Pet {
    private String name;
    private Double weight;
}
           

application.yml配置檔案:yaml表示以上對象:

person:
  name: "zhansan \n lala"
# 單引号會将 \n 作為字元串輸出  雙引号會将 \n 作為換行輸出
# 雙引号不會轉義  雙引号會轉義
  boss: true
  birth: 2019/12/9
  age: 30
#  strs: [遊戲,跑步]
  strs:
    - 遊戲
    - 跑步
    - 22
  list: [小貓,小狗]
#  map:
#    math: 80
#    english: 90
  map: {math: 80,english: 90}
  set:
    - 99.9
    - 99.2
  pet:
    name: 小貓
    weight: 8.8
  pets:
    sick:
      - {name: 小狗,weight: 10.9}
      - name: 小貓
        weight: 5.5
      - name: 小蟲
        weight: 1.5
    health:
      - {name: 小花,weight: 6.9}
      - {name: 小池,weight: 16.9}
           

controller層:

@Slf4j
@RestController
public class HelloController {

    @Autowired
    private Person person;

    @RequestMapping("/person")
    public Person person(){
        log.info("person={}",person);
        return person;
    }
}
           

啟動通路:

localhost:8080/person

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

2、配置提示

檢視手冊:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

自定義的類和配置檔案綁定一般沒有提示。

引入依賴

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <proc>none</proc>
                </configuration>
            </plugin>
        </plugins>
    </build>
           
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

二、Web開發

1、SpringMVC自動配置概覽

參照官網文檔

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

Spring Boot provides auto-configuration for Spring MVC that works well with most applications.(大多場景我們都無需自定義配置)

The auto-configuration adds the following features on top of Spring’s defaults:

  • Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans…
    • 内容協商視圖解析器和BeanName視圖解析器
  • Support for serving static resources, including support for WebJars (covered later in this document )
    • 靜态資源(包括webjars)
  • Automatic registration of Converter, GenericConverter, and Formatter beans.
    • 自動注冊 Converter,GenericConverter,Formatter
  • Support for HttpMessageConverters (covered later in this document )
    • 支援 HttpMessageConverters (後來我們配合内容協商了解原理)
  • Automatic registration of MessageCodesResolver (covered later in this document )
    • 自動注冊 MessageCodesResolver (國際化用)
  • Static index.html support.
    • 靜态index.html 頁支援
  • Custom Favicon support (covered later in this document )
    • 自定義 Favicon
  • Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document )
    • 自動使用 ConfigurableWebBindingInitializer,(DataBinder負責将請求資料綁定到JavaBean上)

If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own

@Configuration

class of type WebMvcConfigurer but without

@EnableWebMvc

.

不用@EnableWebMvc注解。使用 @Configuration + WebMvcConfigurer 自定義規則

如果您想保留這些Spring-Boot MVC定制并進行更多MVC定制(攔截器、格式化程式、視圖控制器和其他特性),您可以添加自己的WebMvcConfigurer類型的@Configuration類,但不添加@EnableWebMvc。

If you want to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, and still keep the Spring Boot MVC customizations, you can declare a bean of type WebMvcRegistrations and use it to provide custom instances of those components.

聲明 WebMvcRegistrations 改變預設底層元件

如果要提供RequestMappingHandlerMapping、RequestMappingHandlerAdapter或ExceptionHandlerExceptionResolver的自定義執行個體,并且仍然保留Spring Boot MVC自定義設定,可以聲明WebMvcRegistrations類型的bean,并使用它來提供這些元件的自定義執行個體。

If you want to take complete control of Spring MVC, you can add your own

@Configuration

annotated with

@EnableWebMvc

, or alternatively add your own

@Configuration-annotated

DelegatingWebMvcConfiguration as described in the Javadoc of

@EnableWebMvc

.

使用 @[email protected]+DelegatingWebMvcConfiguration 全面接管SpringMVC

如果您想完全控制springmvc,您可以添加自己的@Configuration,并用@EnableWebMvc注釋,或者也可以添加自己的@Configuration annotated delegatingwebmvc配置,如@EnableWebMvc的Javadoc中所述。

Note:

Spring MVC uses a different ConversionService to the one used to convert values from your application.properties or application.yaml file. It means that Period, Duration and DataSize converters are not available and that @DurationUnit and @DataSizeUnit annotations will be ignored.

If you want to customize the ConversionService used by Spring MVC, you can provide a WebMvcConfigurer bean with an addFormatters method. From this method you can register any converter that you like, or you can delegate to the static methods available on ApplicationConversionService.

SpringMVC使用不同的轉換服務來轉換application.properties或application.yaml檔案中的值。這意味着Period、Duration和DataSize轉換器不可用,@DurationUnit和@DataSizeUnit注釋将被忽略。

如果您想定制springmvc使用的ConversionService,可以提供一個帶有addFormatters方法的webmvcconfigurerbean。通過此方法,您可以注冊任何您喜歡的轉換器,也可以委托給ApplicationConversionService上可用的靜态方法。

2、簡單功能分析

建立boot-01-web-01 springboot項目

引用

spring-boot-starter-web

2.1、靜态資源通路

文檔

1、靜态資源目錄:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

隻要靜态資源放在類路徑下: called /static (or /public or /resources or /META-INF/resources

通路 : 目前項目根路徑/ + 靜态資源名

測試:在resources目錄下放入四張圖檔

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

啟動:通路

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

原理: 靜态映射 / 。

請求進來,先去找 Controller 看能不能處理。不能處理的所有請求又都交給靜态資源處理器。靜态資源也找不到則響應404頁面

測試:

@RestController
public class HelloController {

    @RequestMapping("/45.jpg")
    public String hello(){
        return "aaa";
    }
}
           

通路:

http://localhost:8080/45.jpg,這時候是響應 aaa,不是一張圖檔

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

通路不存在的靜态資源位址:404

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

改變預設的靜态資源路徑

spring:
  resources:
    static-locations: [classpath:/haha/]
           

添加haha目錄

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

重新開機測試:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

2、靜态資源通路字首:

application.yml檔案添加

spring:
  mvc:
    static-path-pattern: /res/**
           

重新開機,測試;

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

目前項目 + static-path-pattern + 靜态資源名 = 靜态資源檔案夾下找

3、webjar

自動映射 /webjars/**

https://www.webjars.org/

引入jquery依賴

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

重新開機:

通路位址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js 後面位址要按照依賴裡面的包路徑

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

2.2、歡迎頁支援

  • 靜态資源路徑下 index.html
    • 可以配置靜态資源路徑
    • 但是不可以配置靜态資源的通路字首。否則導緻 index.html不能被預設通路
spring:
#  mvc:
#    static-path-pattern: /res/**   這個會導緻welcome page功能失效
  resources:
    static-locations: [classpath:/haha/]
           

測試:建立html檔案:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

啟動。通路:http://localhost:8080/

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
  • controller能處理/index

2.3、自定義 Favicon

favicon.ico 放在靜态資源目錄下即可。(小圖示)

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

重新開機,通路:http://localhost:8080/

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

2.4、靜态資源配置原理

  • SpringBoot啟動預設加載 xxxAutoConfiguration 類(自動配置類)
  • SpringMVC功能的自動配置類

    WebMvcAutoConfiguration

    ,生效
package org.springframework.boot.autoconfigure.web.servlet;

....

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {...}
           
  • 給容器中配了什麼。

    WebMvcAutoConfigurationAdapter

package org.springframework.boot.autoconfigure.web.servlet;

....

public class WebMvcAutoConfiguration {
	....
	
	@Configuration(
        proxyBeanMethods = false
    )
    @Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
    @EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class, WebProperties.class})
    @Order(0)
    public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {...}

	....
}
           
  • 配置檔案(

    @EnableConfigurationProperties

    )的相關屬性和xxx進行了綁定。

    WebMvcProperties

    ==

    spring.mvc

    ResourceProperties

    ==

    spring.resources

    WebProperties

    ==

    spring.web

    SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
    SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
    SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

1、配置類隻有一個有參構造器

有參構造器所有參數的值都會從容器中确定

ResourceProperties resourceProperties

;擷取和

spring.resources

綁定的所有的值的對象

WebProperties webProperties

;擷取和

spring.web

綁定的所有的值的對象

WebMvcProperties mvcProperties

; 擷取和

spring.mvc

綁定的所有的值的對象

ListableBeanFactory beanFactory Spring

beanFactory

HttpMessageConverters

找到所有的

HttpMessageConverters

ResourceHandlerRegistrationCustomizer

找到 資源處理器的自定義器

DispatcherServletPath

處理路徑

ServletRegistrationBean

給應用注冊Servlet、Filter…

package org.springframework.boot.autoconfigure.web.servlet;

....

public class WebMvcAutoConfiguration {
	....
	
	@Configuration(
        proxyBeanMethods = false
    )
    @Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
    @EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class, WebProperties.class})
    @Order(0)
    public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {
	...
	// 有參構造
	public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, 
				WebProperties webProperties, 
				WebMvcProperties mvcProperties, 
				ListableBeanFactory beanFactory, 
				ObjectProvider<HttpMessageConverters> messageConvertersProvider, 
				ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, 
				ObjectProvider<DispatcherServletPath> dispatcherServletPath, 
				ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
            this.resourceProperties = (Resources)(resourceProperties.hasBeenCustomized() ? resourceProperties : webProperties.getResources());
            this.mvcProperties = mvcProperties;
            this.beanFactory = beanFactory;
            this.messageConvertersProvider = messageConvertersProvider;
            this.resourceHandlerRegistrationCustomizer = (WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer)resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
            this.dispatcherServletPath = dispatcherServletPath;
            this.servletRegistrations = servletRegistrations;
            this.mvcProperties.checkConfiguration();
        }

	}

	....
}
           

2、資源處理的預設規則

package org.springframework.boot.autoconfigure.web.servlet;

....

public class WebMvcAutoConfiguration {
	....
	
    public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {
	...
	// 有參構造
	public WebMvcAutoConfigurationAdapter(...}

	public void addResourceHandlers(ResourceHandlerRegistry registry) {
            if (!this.resourceProperties.isAddMappings()) {
                logger.debug("Default resource handling disabled");
            } else {
                this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
                this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
                    registration.addResourceLocations(this.resourceProperties.getStaticLocations());
                    if (this.servletContext != null) {
                        ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
                        registration.addResourceLocations(new Resource[]{resource});
                    }

                });
            }
        }
         
	}

	....
}
           

禁用所有靜态資源規則

spring.resources.add-mappings: false

spring:
  resources:
    add-mappings: false   # 禁用所有靜态資源規則
           
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

重新開機,測試:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

this.resourceProperties.getStaticLocations()

預設讀取靜态檔案路徑

package org.springframework.boot.autoconfigure.web;
...
@ConfigurationProperties("spring.web")
public class WebProperties {
    ....

    public static class Resources {
        private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};
        private String[] staticLocations;
        ....
        }
           

3、歡迎頁的處理規則

WelcomePageHandlerMapping welcomePageHandlerMapping()

HandlerMapping

:處理器映射。儲存了每一個Handler能處理哪些請求。

package org.springframework.boot.autoconfigure.web.servlet;

....

public class WebMvcAutoConfiguration {
	....
	
    @EnableConfigurationProperties({WebProperties.class})
    public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
    
		@Bean
        public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, 
        		FormattingConversionService mvcConversionService, 
        		ResourceUrlProvider mvcResourceUrlProvider) {
            WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), 
            		applicationContext, this.getWelcomePage(), 
            		this.mvcProperties.getStaticPathPattern());
            welcomePageHandlerMapping.setInterceptors(this.getInterceptors(mvcConversionService, mvcResourceUrlProvider));
            welcomePageHandlerMapping.setCorsConfigurations(this.getCorsConfigurations());
            return welcomePageHandlerMapping;
        }
	
	}
	....
}
           

WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping()

底層寫死進入歡迎頁(welcome page)的位址

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
package org.springframework.boot.autoconfigure.web.servlet;

.....

final class WelcomePageHandlerMapping extends AbstractUrlHandlerMapping {
    ....

    WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
    	 ApplicationContext applicationContext,
    	 Resource welcomePage, String staticPathPattern) {
        if (welcomePage != null && "/**".equals(staticPathPattern)) {
        	//要用歡迎頁功能,必須是/**
            logger.info("Adding welcome page: " + welcomePage);
            this.setRootViewName("forward:index.html");
        } else if (this.welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
        	// 調用Controller  /index
            logger.info("Adding welcome page template: index");
            this.setRootViewName("index");
        }

    }
    ....
}
           

4、favicon

浏覽器會發送 /favicon.ico 請求擷取到圖示,整個session期間不再擷取

3、請求參數處理

3.1、請求映射

1、rest使用與原理

  • @xxxMapping

  • Rest風格支援(使用HTTP請求方式動詞來表示對資源的操作)
    • 以前:/getUser 擷取使用者 /deleteUser 删除使用者 /editUser 修改使用者 /saveUser 儲存使用者
    • 現在: /user GET-擷取使用者 DELETE-删除使用者 PUT-修改使用者 POST-儲存使用者
    • 核心Filter;HiddenHttpMethodFilter
      • 用法: 表單method=post,隐藏域 _method=put
      • SpringBoot中手動開啟
    • 擴充:如何把 _method 這個名字換成我們自己喜歡的。
@RestController
public class HelloController {

    @RequestMapping("/45.jpg")
    public String hello(){
        return "aaa";
    }

    @RequestMapping(value = "/user",method = RequestMethod.GET)
    public String getUser(){
        return "GET-張三";
    }

    @RequestMapping(value = "/user",method = RequestMethod.POST)
    public String saveUser(){
        return "POST-張三";
    }


    @RequestMapping(value = "/user",method = RequestMethod.PUT)
    public String putUser(){
        return "PUT-張三";
    }

    @RequestMapping(value = "/user",method = RequestMethod.DELETE)
    public String deleteUser(){
        return "DELETE-張三";
    }
}
           

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
測試REST風格:
<form action="/user" method="get">
    <input value="REST-GET 送出" type="submit">
</form>
<form action="/user" method="post">
    <input value="REST-POST 送出" type="submit">
</form>
<form action="/user" method="delete">
    <input value="REST-DELETE 送出" type="submit">
</form>
<form action="/user" method="put">
    <input value="REST-PUT 送出" type="submit">
</form>
</body>
</html>
           

啟動測試:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

這裡delete和put請求是get請求方法

源碼底層做了限制,是false,

@ConditionalOnProperty( prefix = "spring.mvc.hiddenmethod.filter", name = {"enabled"}

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
package org.springframework.boot.autoconfigure.web.servlet;

....

...
public class WebMvcAutoConfiguration {
  	...

    @Bean
    @ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
    @ConditionalOnProperty(
        prefix = "spring.mvc.hiddenmethod.filter",
        name = {"enabled"}
    )
    public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new OrderedHiddenHttpMethodFilter();
    }
           

Rest原理(表單送出要使用REST的時候)

  • 表單送出會帶上_method=PUT
  • 請求過來被

    HiddenHttpMethodFilter

    攔截
    • 請求是否正常,并且是POST
      • 擷取到 _method 的值。
      • 相容以下請求;PUT.DELETE.PATCH
      • 原生request(post),包裝模式requesWrapper 重寫了 getMethod方法,傳回的是傳入的值。
      • 過濾器鍊放行的時候用wrapper。以後的方法調用getMethod是調用requesWrapper的。
package org.springframework.web.filter;
....

public class HiddenHttpMethodFilter extends OncePerRequestFilter {
    private static final List<String> ALLOWED_METHODS;
    public static final String DEFAULT_METHOD_PARAM = "_method";
    private String methodParam = "_method";
	....
	static {
        ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
    }
    
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        HttpServletRequest requestToUse = request;
        if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
            String paramValue = request.getParameter(this.methodParam);
            if (StringUtils.hasLength(paramValue)) {
                String method = paramValue.toUpperCase(Locale.ENGLISH);
                if (ALLOWED_METHODS.contains(method)) {
                    requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
                }
            }
        }

        filterChain.doFilter((ServletRequest)requestToUse, response);
    }
    
    private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
        private final String method;

        public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
            super(request);
            this.method = method;
        }

        public String getMethod() {
            return this.method;
        }
    }


           

application.yml添加配置:開啟頁面表單的Rest功能

#開啟頁面表單的Rest功能
spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true
           

index.html添加,

<input name="_method" type="hidden" value="DELETE">和<input name="_method" type="hidden" value="PUT">

,修改

method="post"

送出

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

重新開機測試:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

Rest使用用戶端工具,

  • 如PostMan 直接發送 Put、delete 等方式請求,無需Filter。

怎麼改變預設的_method

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

添加配置類:設定成

methodFilter.setMethodParam("_m");

package com.zzp.boot.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;

@Configuration(proxyBeanMethods = false)
public class WebConfig {

    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
        methodFilter.setMethodParam("_m");
        return methodFilter;
    }

}
           

修改html:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

啟動,測試:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

2、請求映射原理

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

SpringMVC功能分析都從

org.springframework.web.servlet.DispatcherServlet-》doDispatch()

package org.springframework.web.servlet;
...
public class DispatcherServlet extends FrameworkServlet {
	...
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            try {
                ModelAndView mv = null;
                Object dispatchException = null;

                try {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    // 找到目前請求使用哪個Handler(Controller的方法)處理
                    mappedHandler = this.getHandler(processedRequest);
                    //HandlerMapping:處理器映射。/xxx->>xxxx
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }

                   ....
    }

}
           
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

RequestMappingHandlerMapping

:儲存了所有

@RequestMapping

handler

的映射規則

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

所有的請求映射都在

HandlerMapping

中。

  • SpringBoot自動配置歡迎頁的

    WelcomePageHandlerMapping

    。通路 '/ '能通路到index.html;
  • SpringBoot自動配置了預設 的

    RequestMappingHandlerMapping

  • 請求進來,挨個嘗試所有的HandlerMapping看是否有請求資訊。
    • 如果有就找到這個請求對應的handler
    • 如果沒有就是下一個 HandlerMapping
  • 我們需要一些自定義的映射處理,我們也可以自己給容器中放

    HandlerMapping

    。自定義

    HandlerMapping

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}
           

3.2、普通參數與基本注解

1、注解:

@PathVariable

@RequestHeader

@ModelAttribute

@RequestParam

@MatrixVariable

@CookieValue

@RequestBody

@PathVariable

注解擷取路徑變量

@RestController
public class ParameterTestController {

    // car/2/owner/zhangsan
    @GetMapping("/car/{id}/owner/{username}")
    public Map<String,Object> getCar(@PathVariable("id") Integer id,
                                     @PathVariable("username") String name,
                                     @PathVariable Map<String,String> pv){
        Map<String,Object> map = new HashMap<>();

        map.put("id",id);
        map.put("name",name);
        map.put("pv",pv);
        return map;
    }
}
           

啟動,請求:http://localhost:8080/car/2/owner/zzp

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

@RequestHeader

注解擷取請求頭資訊

@GetMapping("/getHeader")
    public Map<String,Object> getHeader(@RequestHeader("User-Agent") String userAgent,
                                     @RequestHeader Map<String,String> header){
        Map<String,Object> map = new HashMap<>();
        map.put("userAgent",userAgent);
        map.put("header",header);
        return map;
    }
           

測試:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

@RequestParam

注解擷取請求參數資訊

@GetMapping("/getParams")
    public Map<String,Object> getParam(@RequestParam("age") Integer age,
                                       @RequestParam("inters") List<String> inters,
                                       @RequestHeader Map<String,String> params){
        Map<String,Object> map = new HashMap<>();
        map.put("age",age);
        map.put("inters",inters);
        map.put("params",params);
        return map;
    }

           

測試:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

@CookieValue

注解擷取cookie值

@GetMapping("/getCookie")
    public Map<String,Object> getCookie(@CookieValue("_ga") String _ga,
                                        @CookieValue("_ga") Cookie cookie){
        Map<String,Object> map = new HashMap<>();
        map.put("_ga",_ga);
        map.put("cookie",cookie);
        return map;
    }
           

@RequestBody

注解擷取請求體資訊

@PostMapping("/getBody")
    public Map getBody(@RequestBody String content){
        Map<String,Object> map = new HashMap<>();
        map.put("content",content);
        return map;
    }
           

測試:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

@RequestAttribute

注解擷取request域屬性

@Controller
public class RequestController {

    @GetMapping("/goto")
    public String getAttribute(HttpServletRequest request){
        request.setAttribute("msg","成功轉發到 /success");
        request.setAttribute("code",200);
        // 轉發到 /success 請求
        return "forward:/success";
    }
    @ResponseBody
    @GetMapping("/success")
    public Map success(@RequestAttribute("msg") String msg,
                       @RequestAttribute("code") Integer code,
                       HttpServletRequest request){
        Object obj1 =  request.getAttribute("msg");
        Map<String,Object> map = new HashMap<>();
        map.put("reqMethod_msg",obj1);
        map.put("msg",msg);
        map.put("code",code);

        return map;
    }
}
           

測試:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

@MatrixVariable

注解擷取矩陣變量

/car/{path}?xx=xxx&aa=ccc queryString 查詢字元串。 @RequestParam
/car/sell;low=34;brand=buy,audi,yd  ; 矩陣變量
頁面開發,cookie被禁用了,session裡面的内容怎麼使用;
session.set(a, b)  ---> jsessionid  --> cookie  ----> 每次發請求攜帶
url重寫,/acb;jsessionid=xxx 把cookie的值使用矩陣變量的方式進行傳遞
           
//1、文法: /cars/sell;low=34;brand=byd,audi,yd
    //2、SpringBoot預設是禁用了矩陣變量的功能
    //      手動開啟:原理。對于路徑的處理。UrlPathHelper進行解析。
    //              removeSemicolonContent(移除分号内容)支援矩陣變量的
    //3、矩陣變量必須有url路徑變量才能被解析
    @GetMapping("/cars/{path}")
    public Map carsSell(@MatrixVariable("low") Integer low,
                        @MatrixVariable("brand") List<String> brand,
                        @PathVariable("path") String path){
        Map<String,Object> map = new HashMap<>();
        map.put("low",low);
        map.put("brand",brand);
        map.put("path",path);
        return map;
    }
           

添加配置類:

@Configuration(proxyBeanMethods = false)
public class WebConfig /**implements WebMvcConfigurer */ {

    //方式二
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return  new WebMvcConfigurer() {
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                // 設定不移除; 後面的内容。矩陣變量功能就可以生效
                urlPathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }
        };
    }


    
}
           

測試:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

多個相同矩陣變量參數,指定路徑變量

pathVar =xxx

// /boss/1;age=20/2;age=10
    @GetMapping("/boss/{bossId}/{empId}")
    public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,
                    @MatrixVariable(value = "age",pathVar = "empId") Integer empAge){
        Map<String,Object> map = new HashMap<>();

        map.put("bossAge",bossAge);
        map.put("empAge",empAge);
        return map;

    }
           

測試:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

2、Servlet API:

WebRequest

ServletRequest

MultipartRequest

HttpSession

javax.servlet.http.PushBuilder

Principal

InputStream

Reader

HttpMethod

Locale

TimeZone

ZoneId

ServletRequestMethodArgumentResolver

以上的部分參數

package org.springframework.web.servlet.mvc.method.annotation;
...
public class ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver {
	....
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
        Class<?> paramType = parameter.getParameterType();
        return WebRequest.class.isAssignableFrom(paramType) ||
                ServletRequest.class.isAssignableFrom(paramType) ||
                MultipartRequest.class.isAssignableFrom(paramType) ||
                HttpSession.class.isAssignableFrom(paramType) ||
                pushBuilder != null && pushBuilder.isAssignableFrom(paramType) ||
                Principal.class.isAssignableFrom(paramType) && !parameter.hasParameterAnnotations() || 
                InputStream.class.isAssignableFrom(paramType) || 
                Reader.class.isAssignableFrom(paramType) || 
                HttpMethod.class == paramType || 
                Locale.class == paramType || 
                TimeZone.class == paramType || 
                ZoneId.class == paramType;
    }

}
           

3、複雜參數:

Map、Model(map、model裡面的資料會被放在

request

的請求域

request.setAttribute

)、

Errors/BindingResult

、RedirectAttributes( 重定向攜帶資料)、ServletResponse(response)、

SessionStatus

UriComponentsBuilder

ServletUriComponentsBuilder

代碼示範:

@Controller
public class RequestController {

 
    @ResponseBody
    @GetMapping("/success")
    public Map success(@RequestAttribute(value = "msg",required = false) String msg,
                       @RequestAttribute(value = "code",required = false) Integer code,
                       HttpServletRequest request){
        Object obj1 =  request.getAttribute("msg");
        Map<String,Object> map = new HashMap<>();
        map.put("reqMethod_msg",obj1);
        map.put("msg",msg);
        map.put("code",code);
        Object hello = request.getAttribute("hello");
        Object world = request.getAttribute("world");
        Object message = request.getAttribute("message");
        map.put("hello",hello);
        map.put("world",world);
        map.put("message",message);
        return map;
    }

    @GetMapping("/params")
    public String testParam(Map<String,Object> map,
                            Model model,
                            HttpServletRequest request,
                            HttpServletResponse response){
        map.put("hello","world6666");
        model.addAttribute("world","hello666");
        request.setAttribute("message","HelloWorld");

        Cookie cookie = new Cookie("c1","v1");
        //cookie.setDomain("localhost");
        response.addCookie(cookie);
        return "forward:/success";
    }
    
}
           

通路頁面:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

Map、Model類型的參數,會傳回

mavContainer.getModel();---> BindingAwareModelMap

是Model 也是Map

mavContainer.getModel(); 擷取到值的

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

4、自定義對象參數:

可以自動類型轉換與格式化,可以級聯封裝。

/**
 *     姓名: <input name="userName"/> <br/>
 *     年齡: <input name="age"/> <br/>
 *     生日: <input name="birth"/> <br/>
 *     寵物姓名:<input name="pet.name"/><br/>
 *     寵物年齡:<input name="pet.age"/>
 */
@Data
public class Person {
    private String userName;
    private Integer age;
    private Date birth;
    private Pet pet;
}
@Data
public class Pet {
    private String name;
    private Integer age;
}
           
@PostMapping("/saveuser")
    public Person saveuser(Person person){

        return person;
    }
           

index.html添加:

測試複雜類型:<hr>
測試封裝POJO:
<form action="/saveuser" method="post">
    姓名:<input name="userName" value="zhangsan"/><br>
    年齡:<input name="age" value="18"/><br>
    生日:<input name="birth" value="2019/10/10"/><br>
    寵物姓名:<input name="pet.name" value="amao"/><br>
    寵物年齡:<input name="pet.age" value="2"/><br>
    <input type="submit" value="送出"/><br>
</form>
           

測試:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

3.3、POJO封裝過程

  • ServletModelAttributeMethodProcessor

3.4、參數處理原理

  • HandlerMapping

    中找到能處理請求的

    Handler(Controller.method())

  • 為目前

    Handler

    找一個擴充卡

    HandlerAdapter

    RequestMappingHandlerAdapter

  • 擴充卡執行目标方法并确定方法參數的每一個值

1、HandlerAdapter

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

0(

RequestMappingHandlerAdapter

) - 支援方法上标注

@RequestMapping

1(

HandlerFunctionAdapter

) - 支援函數式程式設計的

xxxxxx

2、執行目标方法

package org.springframework.web.servlet;
...
public class DispatcherServlet extends FrameworkServlet {
	....
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    	....

        try {
            try {
                ModelAndView mv = null;
               
					.....
					// Actually invoke the handler.
					//DispatcherServlet -- doDispatch
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                   
        } finally {
           ....
        }
    }

}
           
//RequestMappingHandlerAdapter.handleInternal()方法
	mav = invokeHandlerMethod(request, response, handlerMethod); //執行目标方法
	
	//ServletInvocableHandlerMethod.invokeAndHandle()方法
	Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
	
	//InvocableHandlerMethod.invokeForRequest()擷取方法的參數值
	Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
           

3、參數解析器-HandlerMethodArgumentResolver

确定将要執行的目标方法的每一個參數的值是什麼;

SpringMVC目标方法能寫多少種參數類型。取決于參數解析器

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
  • 目前解析器是否支援解析這種參數(

    supportsParameter

    )
  • 支援就調用

    resolveArgument

4、傳回值處理器

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

5、如何确定目标方法每一個參數的值

package org.springframework.web.method.support;
....
public class InvocableHandlerMethod extends HandlerMethod {
	....
	protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
        MethodParameter[] parameters = this.getMethodParameters();
        if (ObjectUtils.isEmpty(parameters)) {
            return EMPTY_ARGS;
        } else {
            Object[] args = new Object[parameters.length];

            for(int i = 0; i < parameters.length; ++i) {
                MethodParameter parameter = parameters[i];
                parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
                args[i] = findProvidedArgument(parameter, providedArgs);
                if (args[i] == null) {
                    if (!this.resolvers.supportsParameter(parameter)) {
                        throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
                    }

                    try {
                        args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
                    } catch (Exception var10) {
                        if (logger.isDebugEnabled()) {
                            String exMsg = var10.getMessage();
                            if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
                                logger.debug(formatArgumentError(parameter, exMsg));
                            }
                        }

                        throw var10;
                    }
                }
            }

            return args;
        }
    }

}
           
5.1、挨個判斷所有參數解析器那個支援解析這個參數
package org.springframework.web.method.support;
....
public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
	...
	@Nullable
    private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
        HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);
        if (result == null) {
            Iterator var3 = this.argumentResolvers.iterator();

            while(var3.hasNext()) {
                HandlerMethodArgumentResolver resolver = (HandlerMethodArgumentResolver)var3.next();
                if (resolver.supportsParameter(parameter)) {
                    result = resolver;
                    this.argumentResolverCache.put(parameter, resolver);
                    break;
                }
            }
        }

        return result;
    }
}
           
5.2、解析這個參數的值
調用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法即可
           
5.3、自定義類型參數 封裝POJO

ServletModelAttributeMethodProcessor 這個參數處理器支援

是否為簡單類型。

package org.springframework.beans;
...
public abstract class BeanUtils {
	...
	public static boolean isSimpleValueType(Class<?> type) {
        return Void.class != type && Void.TYPE != type && (ClassUtils.isPrimitiveOrWrapper(type) ||
                Enum.class.isAssignableFrom(type) ||
                CharSequence.class.isAssignableFrom(type) ||
                Number.class.isAssignableFrom(type) ||
                Date.class.isAssignableFrom(type) ||
                Temporal.class.isAssignableFrom(type) ||
                URI.class == type ||
                URL.class == type ||
                Locale.class == type ||
                Class.class == type);
    }
}
           
package org.springframework.web.method.annotation;
... 
public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
	...
	 @Nullable
    public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
        Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
        Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");
        String name = ModelFactory.getNameForParameter(parameter);
        ModelAttribute ann = (ModelAttribute)parameter.getParameterAnnotation(ModelAttribute.class);
        if (ann != null) {
            mavContainer.setBinding(name, ann.binding());
        }

        Object attribute = null;
        BindingResult bindingResult = null;
        if (mavContainer.containsAttribute(name)) {
            attribute = mavContainer.getModel().get(name);
        } else {
            try {
                attribute = this.createAttribute(name, parameter, binderFactory, webRequest);
            } catch (BindException var10) {
                if (this.isBindExceptionRequired(parameter)) {
                    throw var10;
                }

                if (parameter.getParameterType() == Optional.class) {
                    attribute = Optional.empty();
                } else {
                    attribute = var10.getTarget();
                }

                bindingResult = var10.getBindingResult();
            }
        }

        if (bindingResult == null) {
            WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
            if (binder.getTarget() != null) {
                if (!mavContainer.isBindingDisabled(name)) {
                    this.bindRequestParameters(binder, webRequest);
                }

                this.validateIfApplicable(binder, parameter);
                if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) {
                    throw new BindException(binder.getBindingResult());
                }
            }

            if (!parameter.getParameterType().isInstance(attribute)) {
                attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
            }

            bindingResult = binder.getBindingResult();
        }

        Map<String, Object> bindingResultModel = bindingResult.getModel();
        mavContainer.removeAttributes(bindingResultModel);
        mavContainer.addAllAttributes(bindingResultModel);
        return attribute;
    }
}
           

WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);

WebDataBinder :web資料綁定器,将請求參數的值綁定到指定的JavaBean裡面

WebDataBinder 利用它裡面的 Converters 将請求資料轉成指定的資料類型。再次封裝到JavaBean中

GenericConversionService:在設定每一個值的時候,找它裡面的所有converter那個可以将這個資料類型(request帶來參數的字元串)轉換到指定的類型(JavaBean – Integer)

byte – > file

@FunctionalInterfacepublic interface Converter<S, T>

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

未來我們可以給WebDataBinder裡面放自己的Converter;

private static final class StringToNumber<T extends Number> implements Converter<String, T>

自定義 Converter:

在配置類添加配置方法

@Configuration(proxyBeanMethods = false)
public class WebConfig{
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return  new WebMvcConfigurer() {
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                // 設定不移除; 後面的内容。矩陣變量功能就可以生效
                urlPathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }

            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new Converter<String, Pet>() {
                    @Override
                    public Pet convert(String source) {
                        // 以逗号分割
                        if(!StringUtils.isEmpty(source)){
                            Pet pet = new Pet();
                            String[] split = source.split(",");
                            pet.setName(split[0]);
                            pet.setAge(Integer.valueOf(split[1]));
                            return pet;
                        }
                        return null;
                    }
                });
            }
        };
    }

}
           

index.html修改:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

重新開機,測試:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

6、目标方法執行完成

将所有的資料都放在

ModelAndViewContainer

;包含要去的頁面位址View。還包含Model資料

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

7、處理派發結果

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException)

;

renderMergedOutputModel(mergedModel, getRequestToExpose(request), response)

;

package org.springframework.web.servlet.view;
...
public class InternalResourceView extends AbstractUrlBasedView {
	...
	protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
        this.exposeModelAsRequestAttributes(model, request);
        this.exposeHelpers(request);
        String dispatcherPath = this.prepareForRendering(request, response);
        RequestDispatcher rd = this.getRequestDispatcher(request, dispatcherPath);
        if (rd == null) {
            throw new ServletException("Could not get RequestDispatcher for [" + this.getUrl() + "]: Check that the corresponding file exists within your web application archive!");
        } else {
            if (this.useInclude(request, response)) {
                response.setContentType(this.getContentType());
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Including [" + this.getUrl() + "]");
                }

                rd.include(request, response);
            } else {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Forwarding to [" + this.getUrl() + "]");
                }

                rd.forward(request, response);
            }

        }
    }
}
           
暴露模型作為請求域屬性
// Expose the model object as request attributes.
this.exposeModelAsRequestAttributes(model, request);
           
package org.springframework.web.servlet.view;
....
public abstract class AbstractView extends WebApplicationObjectSupport implements View, BeanNameAware {
	...
	protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
		//model中的所有資料周遊挨個放在請求域中
        model.forEach((name, value) -> {
            if (value != null) {
                request.setAttribute(name, value);
            } else {
                request.removeAttribute(name);
            }

        });
    }
}
           

4、資料響應與内容協商

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

4.1、響應JSON

1、[email protected]

// 引入依賴
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
	// spring-boot-starter-web 自帶 web場景自動引入了json場景
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-json</artifactId>
      <version>2.5.0</version>
      <scope>compile</scope>
    </dependency>
           
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

給前端自動傳回json資料;

@Controller
public class ResponseTestController {

	@ResponseBody
    @GetMapping("/test/person")
    public Person getPerson(){
        Person person = new Person();
        person.setAge(28);
        person.setUserName("zhangsan");
        return person;
    }
}
           

測試:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
傳回值解析器
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
package org.springframework.web.servlet.mvc.method.annotation;
...
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
	...
	public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
        ...
        try {
            this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);
        } catch (Exception var6) {
           ...
            }

            throw var6;
        }
    }
}
           
package org.springframework.web.method.support;
...
public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {
	...
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        HandlerMethodReturnValueHandler handler = this.selectHandler(returnValue, returnType);
        if (handler == null) {
            throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
        } else {
            handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
        }
    }
}
           
package org.springframework.web.servlet.mvc.method.annotation;
...
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
	...
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
        mavContainer.setRequestHandled(true);
        ServletServerHttpRequest inputMessage = this.createInputMessage(webRequest);
        ServletServerHttpResponse outputMessage = this.createOutputMessage(webRequest);
	
		// Try even with null return value. ResponseBodyAdvice could get involved.
        // 使用消息轉換器進行寫出操作
        this.writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
    }
}
           
傳回值解析器原理
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

1、傳回值處理器判斷是否支援這種類型傳回值 supportsReturnType

2、傳回值處理器調用 handleReturnValue 進行處理

3、RequestResponseBodyMethodProcessor 可以處理傳回值标了

@ResponseBody

注解的

利用 MessageConverters 進行處理 将資料寫為json

  • 1、内容協商(浏覽器預設會以請求頭的方式告訴伺服器他能接受什麼樣的内容類型)
  • 2、伺服器最終根據自己自身的能力,決定伺服器能生産出什麼樣内容類型的資料
  • 3、SpringMVC會挨個周遊所有容器底層的 HttpMessageConverter ,看誰能處理?

    1.得到MappingJackson2HttpMessageConverter可以将對象寫為json

    2.利用MappingJackson2HttpMessageConverter将對象轉為json再寫出去。

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

2、SpringMVC到底支援哪些傳回值

ModelAndView
Model
View
ResponseEntity 
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
有 @ModelAttribute 且為對象類型的
@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;
           

3、HTTPMessageConverter原理

1、MessageConverter規範
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

HttpMessageConverter: 看是否支援将 此 Class類型的對象,轉為MediaType類型的資料。

例子:Person對象轉為JSON。或者 JSON轉為Person

2、預設的MessageConverter
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

0 - 隻支援Byte類型的

1 - String

2 - String

3 - Resource

4 - ResourceRegion

5 - DOMSource .class \ SAXSource .class) \ StAXSource .class \StreamSource .class \Source .class

6 - MultiValueMap

7 - true

8 - true

9 - 支援注解方式xml處理的。

最終 MappingJackson2HttpMessageConverter 把對象轉為JSON(利用底層的jackson的objectMapper轉換的)

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

自定義傳回檔案資源:

@ResponseBody // RequestResponseBodyMethodProcessor -- > messageConverter
    @GetMapping("/he11")
    public FileSystemResource file(){
        // 檔案統一傳回設定
        return null;
    }
           

4.2、内容協商

根據用戶端接收能力不同,傳回不同媒體類型的資料。

1、引入xml依賴

<dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
        </dependency>
           

重新開機測試:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

2、postman分别測試傳回json和xml

隻需要改變請求頭中Accept字段。Http協定中規定的,告訴伺服器本用戶端可以接收的資料類型。

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

3、開啟浏覽器參數方式内容協商功能

為了友善内容協商,開啟基于請求參數的内容協商功能

application.yml檔案添加配置

spring:
  mvc:
    contentnegotiation:
      favor-parameter: true  #開啟請求參數内容協商模式
           

在請求位址後添加 format 參數

發請求: http://localhost:8080/test/person?format=json

http://localhost:8080/test/person?format=xml

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

debug源碼:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

确定用戶端接收什麼樣的内容類型;

1、Parameter政策優先确定是要傳回json資料(擷取請求頭中的format的值)

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

2、最終進行内容協商傳回給用戶端json即可。

4、内容協商原理

  • 1、判斷目前響應頭中是否已經有确定的媒體類型。MediaType
  • 2、擷取用戶端(PostMan、浏覽器)支援接收的内容類型。(擷取用戶端Accept請求頭字段)【application/xml】
    • contentNegotiationManager 内容協商管理器 預設使用基于請求頭的政策
    • SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
    • HeaderContentNegotiationStrategy 确定用戶端可以接收的内容類型
    • SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
  • 3、周遊循環所有目前系統的 MessageConverter,看誰支援操作這個對象(Person)
  • 4、找到支援操作Person的converter,把converter支援的媒體類型統計出來。
  • 5、用戶端需要【application/xml】。服務端能力【10種、json、xml】
  • SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
  • 6、進行内容協商的最佳比對媒體類型
  • 7、用 支援 将對象轉為 最佳比對媒體類型 的converter。調用它進行轉化 。
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

導入了jackson處理xml的包,xml的converter就會自動進來

package org.springframework.web.servlet.config.annotation;
...
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
	...
	private static final boolean jackson2XmlPresent;
	static {
        ClassLoader classLoader = WebMvcConfigurationSupport.class.getClassLoader();
        jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
        ....
    }
protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
        messageConverters.add(new ByteArrayHttpMessageConverter());
        messageConverters.add(new StringHttpMessageConverter());
        messageConverters.add(new ResourceHttpMessageConverter());
        messageConverters.add(new ResourceRegionHttpMessageConverter());
        

        Jackson2ObjectMapperBuilder builder;
        if (!shouldIgnoreXml) {
            if (jackson2XmlPresent) {
                builder = Jackson2ObjectMapperBuilder.xml();
                if (this.applicationContext != null) {
                    builder.applicationContext(this.applicationContext);
                }

                messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
            } else if (jaxb2Present) {
                messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
            }
        }

        ...

    }


}
           

5、自定義 MessageConverter

實作多協定資料相容。json、xml、x-zzp

1、

@ResponseBody

響應資料出去 調用 RequestResponseBodyMethodProcessor 處理

2、Processor 處理方法傳回值。通過 MessageConverter 處理

3、所有 MessageConverter 合起來可以支援各種媒體類型資料的操作(讀、寫)

4、内容協商找到最終的 messageConverter;

自定義響應頭資訊
步驟
1、添加一個自定義的MessageConverter進系統底層
2、系統底層就會統計所有MessageConverter能操作哪些類型
3、進行用戶端協商 [zzp  ---> zzp]
           

SpringMVC的什麼功能。一個入口給容器中添加一個 WebMvcConfigurer

@Configuration(proxyBeanMethods = false)
public class WebConfig  {

    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return  new WebMvcConfigurer() {

            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
                converters.add(new ZzpMeaageConverter());
            }
 
        };
    }
}
           

自定義的converter類:

/**
 * 自定義的converter
 */
public class ZzpMeaageConverter implements HttpMessageConverter<Person> {

    /**
     *
     * @param aClass
     * @param mediaType
     * @return
     */
    @Override
    public boolean canRead(Class<?> aClass, MediaType mediaType) {
        return aClass.isAssignableFrom(Person.class);
    }

    @Override
    public boolean canWrite(Class<?> aClass, MediaType mediaType) {
        return aClass.isAssignableFrom(Person.class);
    }

    /**
     *  伺服器統計所有MeaageConverter都能寫出哪裡内容類型
     *
     *   自定義類:application/x-zzp
     * @return
     */
    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return MediaType.parseMediaTypes("application/x-zzp");
    }

    @Override
    public Person read(Class<? extends Person> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }

    @Override
    public void write(Person person, MediaType mediaType, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
        // 自定義協定資料的寫出
        String data = person.getUserName() + ";" + person.getAge() + ";" + person.getBirth();

        // 寫出資料
        OutputStream outputMessageBody = httpOutputMessage.getBody();
        outputMessageBody.write(data.getBytes());
    }
}
           

dubug重新開機測試:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

修改format的參數,format預設為兩種:json、xml

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

基于請求位址參數的配置(format)

WebConfig添加配置,重寫WebMvcConfigurer的configureContentNegotiation的方法:

/**
             * 自定義内容協商政策
             * @param configurer
             */
            @Override
            public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
                //Map<String, MediaType> mediaTypes
                Map<String, MediaType> mediaTypes = new HashMap<>();
                //指定支援解析哪些參數對應的哪些媒體類型
                mediaTypes.put("json",MediaType.APPLICATION_JSON);
                mediaTypes.put("xml",MediaType.APPLICATION_XML);
                // 自定義 媒體類型 gg  -> application/x-zzp
                mediaTypes.put("gg",MediaType.parseMediaType("application/x-zzp"));
                ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);
                configurer.strategies(Arrays.asList(parameterStrategy));
            }
           

重新開機,測試:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

dubug截圖:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

基于請求頭參數的配置(Accept)

WebConfig添加配置,重寫WebMvcConfigurer的configureContentNegotiation的方法:

/**
             * 自定義内容協商政策
             * @param configurer
             */
            @Override
            public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
                //Map<String, MediaType> mediaTypes
                Map<String, MediaType> mediaTypes = new HashMap<>();
                //指定支援解析哪些參數對應的哪些媒體類型
                mediaTypes.put("json",MediaType.APPLICATION_JSON);
                mediaTypes.put("xml",MediaType.APPLICATION_XML);
                // 自定義 媒體類型 gg  -> application/x-zzp
                mediaTypes.put("gg",MediaType.parseMediaType("application/x-zzp"));
                ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);

                // 基于請求頭參數配置
                HeaderContentNegotiationStrategy headerStrategy = new HeaderContentNegotiationStrategy();

                configurer.strategies(Arrays.asList(parameterStrategy,headerStrategy));
            }
           

重新開機測試:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

有可能我們添加的自定義的功能會覆寫預設很多功能,導緻一些預設的功能失效。

大家考慮,上述功能除了我們完全自定義外?SpringBoot有沒有為我們提供基于配置檔案的快速修改媒體類型功能?怎麼配置呢?【提示:參照SpringBoot官方文檔web開發内容協商章節】

5、視圖解析與模闆引擎

視圖解析:SpringBoot預設不支援 JSP,需要引入第三方模闆引擎技術實作頁面渲染。

5.1、視圖解析

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

1、視圖解析原理流程

1、目标方法處理的過程中,所有資料都會被放在 ModelAndViewContainer 裡面。包括資料和視圖位址

2、方法的參數是一個自定義類型對象(從請求參數中确定的),把他重新放在 ModelAndViewContainer

3、任何目标方法執行完成以後都會傳回 ModelAndView(資料和視圖位址)。

4、processDispatchResult 處理派發結果(頁面改如何響應)

  • 1、render(mv, request, response); 進行頁面渲染邏輯
    • 1、根據方法的String傳回值得到 View 對象【定義了頁面的渲染邏輯】
      • 1、所有的視圖解析器嘗試是否能根據目前傳回值得到View對象
      • 2、得到了 redirect:/main.html --> Thymeleaf new RedirectView()
      • 3、ContentNegotiationViewResolver 裡面包含了下面所有的視圖解析器,内部還是利用下面所有視圖解析器得到視圖對象。
      • 4、view.render(mv.getModelInternal(), request, response); 視圖對象調用自定義的render進行頁面渲染工作
        • RedirectView 如何渲染【重定向到一個頁面】
        • 1、擷取目标url位址
        • 2、response.sendRedirect(encodedURL);

視圖解析:

  • 傳回值以 forward: 開始: new InternalResourceView(forwardUrl); --> 轉發request.getRequestDispatcher(path).forward(request, response);
  • 傳回值以 redirect: 開始: new RedirectView() --》 render就是重定向
  • 傳回值是普通字元串: new ThymeleafView()—>

自定義視圖解析器+自定義視圖; 大廠學院。

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

5.2、模闆引擎-Thymeleaf

1、thymeleaf簡介

thymeleaf官網

Thymeleaf is a modern server-side Java template engine for both web and standalone environments, capable of processing HTML, XML, JavaScript, CSS and even plain text.

現代化、服務端Java模闆引擎

2、基本文法

1、表達式:
表達式名字 文法 用途
變量取值 ${…} 擷取請求域、session域、對象等值
選擇變量 *{…} 擷取上下文對象值
消息 #{…} 擷取國際化等值
連結 @{…} 生成連結
片段表達式 ~{…} jsp:include 作用,引入公共頁面片段
2、字面量:

文本值: ‘one text’ , ‘Another one!’ ,…數字: 0 , 34 , 3.0 , 12.3 ,…布爾值: true , false

空值: null

變量: one,two,… 變量不能有空格

3、文本操作:

字元串拼接: +

變量替換: |The name is ${name}|

4、數學運算:

運算符: + , - , * , / , %

5、布爾運算:

運算符: and , or

一進制運算: ! , not

6、比較運算:

比較: > , < , >= , <= ( gt , lt , ge , le )等式: == , != ( eq , ne )

7、條件運算:

If-then: (if) ? (then)

If-then-else: (if) ? (then) : (else)

Default: (value) ?: (defaultvalue)

8、特殊操作:

無操作:_

3、設定屬性值-th:attr

設定單個值

<form action="subscribe.html" th:attr="[email protected]{/subscribe}">
  <fieldset>
    <input type="text" name="email" />
    <input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>
  </fieldset>
</form>
           

設定多個值

<img src="../../images/gtvglogo.png"  th:attr="[email protected]{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />
           

以上兩個的代替寫法 th:xxxx

<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>
<form action="subscribe.html" th:action="@{/subscribe}">
           

所有h5相容的标簽寫法

使用文檔

4、疊代

<tr th:each="prod : ${prods}">
        <td th:text="${prod.name}">Onions</td>
        <td th:text="${prod.price}">2.41</td>
        <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
           
<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
  <td th:text="${prod.name}">Onions</td>
  <td th:text="${prod.price}">2.41</td>
  <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
           

5、條件運算

<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}">view</a>
           
<div th:switch="${user.role}">
  <p th:case="'admin'">User is an administrator</p>
  <p th:case="#{roles.manager}">User is a manager</p>
  <p th:case="*">User is some other thing</p>
</div>
           

6、屬性優先級

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

5.3、thymeleaf使用

1、引入Starter

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

2、自動配置好了thymeleaf

package org.springframework.boot.autoconfigure.thymeleaf;
...

@Configuration(
    proxyBeanMethods = false
)
@EnableConfigurationProperties({ThymeleafProperties.class})
@ConditionalOnClass({TemplateMode.class, SpringTemplateEngine.class})
@AutoConfigureAfter({WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class})
public class ThymeleafAutoConfiguration {}
           

自動配好的政策:

  • 1、所有thymeleaf的配置值都在 ThymeleafProperties
  • 2、配置好了 SpringTemplateEngine
  • 3、配好了 ThymeleafViewResolver
  • 4、我們隻需要直接開發頁面
package org.springframework.boot.autoconfigure.thymeleaf;
...
@ConfigurationProperties(
    prefix = "spring.thymeleaf"
)
public class ThymeleafProperties {
	...
	public static final String DEFAULT_PREFIX = "classpath:/templates/";
    public static final String DEFAULT_SUFFIX = ".html"; //預設轉跳xxx.html
}
           

3、頁面開發

在resources目錄templates檔案夾下添加success.html檔案

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"> <!-- http://www.thymeleaf.org 核心連接配接 -->
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1 th:text="${msg}">哈哈</h1>
<h2>
    <a href="www.hao123.com" th:href="${link}">去百度</a>  <br/>
    <a href="www.hao123.com" th:href="@{link}">去百度2</a>
</h2>
</body>
</html>
           

控制類:

@Controller
public class ViewTestController {

    @GetMapping("/zzp")
    public String zzp(Model model){
        // model 中的資料會被放在請求域中 request.setAttribute("a",aa)
        model.addAttribute("msg","您好 zzp!");
        model.addAttribute("link","https://www.baidu.com/");
        return "success";
    }
}
           

啟動,測試通路:http://localhost:8080/zzp

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

原頁面:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

5.4、建構背景管理系統

1、項目建立

建立新的項目:boot-01-web-admin

基本依賴:thymeleaf、web-starter、devtools、lombok

2、靜态資源處理

自動配置好,我們隻需要把所有靜态資源放到 static 檔案夾下

3、路徑建構

th:action="@{/login}"

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

4、模闆抽取

th:insert/replace/include

5、頁面跳轉

@Controller
public class IndexController {


    @PostMapping("/login")
    public String main(User user, HttpSession session, Model model){
        if(StringUtils.hasLength(user.getUserName()) && "123456".equals(user.getPassword())){
            //把登陸成功的使用者儲存起來
            session.setAttribute("loginUser",user);
            //登入成功重定向到main.html; 重定向防止表單重複送出
            return "redirect:/main.html";
        }else {
            model.addAttribute("msg","賬号密碼錯誤");
            //回到登入頁面
            return "login";
        }
    }
}

           

thymeleaf行内寫法 “[[${xx.xx}]]”

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

測試:截圖

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

6、資料渲染

請求位址參數:

@GetMapping("/dynamic_table")
    public String dynamic_table(Model model){
        //表格内容的周遊
        List<User> users = Arrays.asList(new User("zhangsan", "123456"),
                new User("lisi", "123444"),
                new User("haha", "aaaaa"),
                new User("hehe ", "aaddd"));
        model.addAttribute("users",users);

        return "table/dynamic_table";
    }
           

修改dynamic_table.html檔案

<table class="display table table-bordered" id="hidden-table-info">
        <thead>
        <tr>
            <th>#</th>
            <th>使用者名</th>
            <th>密碼</th>
        </tr>
        </thead>
        <tbody>
        <tr class="gradeX" th:each="user,stats:${users}">
            <td th:text="${stats.count}">Trident</td>
            <td th:text="${user.userName}">Internet</td>
            <td >[[${user.password}]]</td>
        </tr>
        </tbody>
        </table>
           

6、攔截器

6.1、HandlerInterceptor 接口

/**
 * 登入檢查
 * 1、配置好攔截器要攔截哪些請求
 * 2、把這些配置放在容器中
 */
@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 {
        String requestURI = request.getRequestURI();
        log.info("preHandle攔截的請求路徑是{}",requestURI);
        //登入檢查邏輯
        HttpSession session = request.getSession();
        Object loginUser = session.getAttribute("loginUser");

        if(loginUser != null){
            //放行
            return true;
        }
        //攔截住。未登入。跳轉到登入頁
//        session.setAttribute("msg","請先登入");
//        response.sendRedirect("/");
        request.setAttribute("msg","請先登入");
        request.getRequestDispatcher("/").forward(request,response);
        return false;
    }

    /**
     * 目标方法執行完成以後
     * @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);
    }
}

           

6.2、配置攔截器

/**
 * 1、編寫一個攔截器實作HandlerInterceptor接口
 * 2、攔截器注冊到容器中(實作WebMvcConfigurer的addInterceptors)
 * 3、指定攔截規則【如果是攔截所有,靜态資源也會被攔截】
 */
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")  //所有請求都被攔截包括靜态資源
                .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**", "/js/**"); //放行的請求
    }
}
           
/**
     * 去main頁面
     * @return
     */
    @GetMapping("/main.html")
    public String mainPage(HttpSession session,Model model){
        log.info("目前方法是:{}","mainPage");
        //是否登入。  攔截器,過濾器
//        Object loginUser = session.getAttribute("loginUser");
//        if(loginUser != null){
//            return "main";
//        }else {
//            //回到登入頁面
//            model.addAttribute("msg","請重新登入");
//            return "login";
//        }

        return "main";
    }
           

測試:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

6.3、攔截器原理

1、根據目前請求,找到HandlerExecutionChain【可以處理請求的handler以及handler的所有 攔截器】(

mappedHandler = this.getHandler(processedRequest);

2、先來順序執行 所有攔截器的 preHandle方法

  • 1、如果目前攔截器prehandler傳回為true。則執行下一個攔截器的preHandle
  • 2、如果目前攔截器傳回為false。直接 倒序執行所有已經執行了的攔截器的 afterCompletion;
  • 3、如果任何一個攔截器傳回false。直接跳出不執行目标方法(

    if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; }

    )
  • 4、所有攔截器都傳回True。執行目标方法
  • 5、倒序執行所有攔截器的postHandle方法。
  • 6、前面的步驟有任何異常都會直接倒序觸發 afterCompletion (

    catch (Exception var22) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);

    )
  • 7、頁面成功渲染完成以後,也會倒序觸發 afterCompletion(

    if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); }

    )

源碼:

package org.springframework.web.servlet;
...
public class HandlerExecutionChain {
	...
	boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {
            HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
            //1、如果目前攔截器prehandler傳回為true。則執行下一個攔截器的preHandle 
            if (!interceptor.preHandle(request, response, this.handler)) {
            	//2、如果目前攔截器傳回為false。直接 倒序執行所有已經執行了的攔截器的 afterCompletion;
                this.triggerAfterCompletion(request, response, (Exception)null);
                return false;
            }
        }

        return true;
    }
    //5、倒序執行所有攔截器的postHandle方法
    void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
        for(int i = this.interceptorList.size() - 1; i >= 0; --i) {
            HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
            interceptor.postHandle(request, response, this.handler, mv);
        }

    }
   
    //org.springframework.web.servlet.DispatcherServlet
    // 前面的步驟有任何異常都會直接倒序觸發 afterCompletion
     private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, Exception ex) throws Exception {
        if (mappedHandler != null) {
            mappedHandler.triggerAfterCompletion(request, response, ex);
        }

        throw ex;
    }
    // 7、頁面成功渲染完成以後,也會倒序觸發 afterCompletion
    void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
        for(int i = this.interceptorIndex; i >= 0; --i) {
            HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);

            try {
                interceptor.afterCompletion(request, response, this.handler, ex);
            } catch (Throwable var7) {
                logger.error("HandlerInterceptor.afterCompletion threw exception", var7);
            }
        }

    }
    //直接 倒序執行所有已經執行了的攔截器的 afterCompletion;
    void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
        for(int i = this.interceptorIndex; i >= 0; --i) {
            HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);

            try {
                interceptor.afterCompletion(request, response, this.handler, ex);
            } catch (Throwable var7) {
                logger.error("HandlerInterceptor.afterCompletion threw exception", var7);
            }
        }

    }
}
           
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

7、檔案上傳

7.1、頁面表單

templates/form/form_layouts.html檔案

<form role="form" th:action="@{/upload}" method="post" enctype="multipart/form-data">
  <div class="form-group">
    <label for="exampleInputEmail1">郵箱</label>
    <input type="email" name="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">
  </div>
  <div class="form-group">
    <label for="exampleInputPassword1">名字</label>
    <input type="text" name="username" class="form-control" id="exampleInputPassword1" placeholder="Password">
  </div>
  <div class="form-group">
    <label for="exampleInputFile">頭像</label>
    <input type="file" name="headerImg" id="exampleInputFile">
  </div>
  <div class="form-group">
    <label for="exampleInputFile">生活照</label>
    <input type="file" name="photos" multiple>
  </div>
  <div class="checkbox">
    <label>
      <input type="checkbox"> Check me out
    </label>
  </div>
  <button type="submit" class="btn btn-primary">送出</button>
</form>                     
           

7.2、檔案上傳代碼

/**
 * 檔案上傳測試
 */
@Slf4j
@Controller
public class FormTestController {

    @GetMapping("/form_layouts")
    public String form_layouts(){
        return "form/form_layouts";
    }


    /**
     *  MultipartFile 自動封裝上傳過來的檔案
     * @param email
     * @param username
     * @param headerImg
     * @param photos
     * @return
     */
    @PostMapping("/upload")
    public String upload(@RequestParam("email") String email,
                         @RequestParam("username") String username,
                         @RequestPart("headerImg") MultipartFile headerImg,
                         @RequestPart("photos") MultipartFile[] photos) throws IOException {

        log.info("上傳的資訊:email={},username={},headerImg.getSize={},photos.length={}",
                email,username,headerImg.getSize(),photos.length);
        if(!headerImg.isEmpty()){
            String originalFilename = headerImg.getOriginalFilename();
            int i = originalFilename.lastIndexOf("\\");
            headerImg.transferTo(new File("F:\\data\\" + originalFilename.substring(i +1)));
        }
        if(photos.length > 0){
            for(MultipartFile file : photos){
                if(!file.isEmpty()){
                    String originalFilename = file.getOriginalFilename();
                    int i = originalFilename.lastIndexOf("\\");
                    file.transferTo(new File("F:\\data\\" + originalFilename.substring(i +1)));
                }
            }
        }
        return "main";
    }

}
           

自定義檔案大小上傳配置:

spring:
  servlet:
    multipart:
      max-file-size: 10MB # 單個上傳檔案大小
      max-request-size: 100MB # 整體檔案上傳大小
           

測試:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

7.3、自動配置原理

檔案上傳自動配置類-MultipartAutoConfiguration-MultipartProperties

  • 自動配置好了 StandardServletMultipartResolver 【檔案上傳解析器】
  • 原理步驟
    • 1、請求進來使用檔案上傳解析器判斷(isMultipart)并封裝(resolveMultipart,傳回MultipartHttpServletRequest)檔案上傳請求
    • 2、參數解析器來解析請求中的檔案内容封裝成MultipartFile
    • 3、将request中檔案資訊封裝為一個Map;MultiValueMap<String, MultipartFile>

      FileCopyUtils。實作檔案流的拷貝

@PostMapping("/upload")
    public String upload(@RequestParam("email") String email,
                         @RequestParam("username") String username,
                         @RequestPart("headerImg") MultipartFile headerImg,
                         @RequestPart("photos") MultipartFile[] photos)
           
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

8、異常處理

官方文檔

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

8.1、錯誤處理

1、預設規則

  • 預設情況下,Spring Boot提供 /error 處理所有錯誤的映射
  • 對于機器用戶端,它将生成JSON響應,其中包含錯誤,HTTP狀态和異常消息的詳細資訊。對于浏覽器用戶端,響應一個“ whitelabel”錯誤視圖,以HTML格式呈現相同的資料
  • SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
    SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
  • 測試異常情況:
  • SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
    SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
    SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
  • 要對其進行自定義,添加View解析為error
  • 要完全替換預設行為,可以實作 ErrorController 并注冊該類型的Bean定義,或添加ErrorAttributes類型的元件以使用現有機制但替換其内容。
  • error/ 下的4xx,5xx頁面會被自動解析;
    • SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
      測試效果:
      SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
      SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

2、定制錯誤處理邏輯

  • 自定義錯誤頁
    • error/404.html error/5xx.html;有精确的錯誤狀态碼頁面就比對精确,沒有就找 4xx.html;如果都沒有就觸發白頁
      SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
  • @[email protected]處理全局異常;底層是 ExceptionHandlerExceptionResolver 支援的

添加一個全局異常處理類:

/**
 * 處理整個web controller的異常
 */
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler({ArithmeticException.class,NullPointerException.class})  //處理異常
    public String handleArithException(Exception e){

        log.error("異常是:{}",e);
        return "login"; //視圖位址
    }
}
           

測試:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
  • @ResponseStatus+自定義異常

    ;底層是 ResponseStatusExceptionResolver ,把responsestatus注解的資訊組裝成 ModelAndView傳回;底層調用 response.sendError(statusCode, resolvedReason);tomcat發送的 /error

異常處理類:

@ResponseStatus(value= HttpStatus.FORBIDDEN,reason = "使用者數量太多")
public class UserTooManyException extends RuntimeException {

    public  UserTooManyException(){

    }
    public  UserTooManyException(String message){
        super(message);
    }
}
           
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

測試:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
  • Spring底層的異常,如 參數類型轉換異常;DefaultHandlerExceptionResolver 處理架構底層的異常。
    • response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
    • SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
  • 自定義實作 HandlerExceptionResolver 處理異常;可以作為預設的全局異常處理規則
    • SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
      自定義解析異常類:
/**
 * 自定義解析異常
 */
@Order(value= Ordered.HIGHEST_PRECEDENCE)  //優先級,數字越小優先級越高
@Component
public class CustomerHandlerExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request,
                                         HttpServletResponse response,
                                         Object handler, Exception ex) {
        try {
            response.sendError(511,"我喜歡的錯誤");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new ModelAndView();
    }
}
           
  • ErrorViewResolver 實作自定義處理異常;
    • response.sendError 。error請求就會轉給controller
    • 你的異常沒有任何人能處理。tomcat底層 response.sendError。error請求就會轉給controller
    • basicErrorController 要去的頁面位址是 ErrorViewResolver ;

3、異常處理自動配置原理

  • ErrorMvcAutoConfiguration類 自動配置異常處理規則
    • 容器中的元件:類型:DefaultErrorAttributes -> id:errorAttributes
    • SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
      • public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver
      • DefaultErrorAttributes:定義錯誤頁面中可以包含哪些資料。
      • SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
      • SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
    • 容器中的元件:類型:BasicErrorController --> id:basicErrorController(json+白頁 适配響應)
      • 處理預設 /error 路徑的請求;頁面響應 new ModelAndView(“error”, model);(

        @RequestMapping({"${server.error.path:${error.path:/error}}"})

        )
      • 容器中有元件 View->id是error;(響應預設錯誤頁)
      • SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
      • 容器中放元件 BeanNameViewResolver(視圖解析器);按照傳回的視圖名作為元件的id去容器中找View對象。
      • SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
    • 容器中的元件(錯誤視圖解析器):類型:DefaultErrorViewResolver -> id:conventionErrorViewResolver
      • 如果發生錯誤,會以HTTP的狀态碼 作為視圖頁位址(viewName),找到真正的頁面
      • error/404、5xx.html
      • SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
      • SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

如果想要傳回頁面;就會找error視圖【StaticView】。(預設是一個白頁)

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

4、異常處理步驟流程

1、執行目标方法,目标方法運作期間有任何異常都會被catch、而且标志目前請求結束webRequest.requestCompleted();并且用 dispatchException

2、進入視圖解析流程(頁面渲染?)

this.processDispatchResult(processedRequest, response, mappedHandler,

mv

,

(Exception)dispatchException

);

3、mv = processHandlerException;處理handler發生的異常,處理完成傳回ModelAndView;

  • 1、周遊所有的 handlerExceptionResolvers,看誰能處理目前異常【HandlerExceptionResolver處理器異常解析器】
  • SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
  • 2、系統預設的 異常解析器;
  • SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
    • 1、DefaultErrorAttributes先來處理異常。把異常資訊儲存到rrequest域,并且傳回null;
    • SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
    • 2、預設沒有任何人能處理異常,是以異常會被抛出
      • 1、如果沒有任何人能處理最終底層就會發送 /error 請求。會被底層的BasicErrorController處理
      • 2、解析錯誤視圖;周遊所有的 ErrorViewResolver 看誰能解析。
      • SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
      • 3、預設的 DefaultErrorViewResolver ,作用是把響應狀态碼作為錯誤頁的位址,error/500.html
      • SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
      • 4、模闆引擎最終響應這個頁面 error/500.html
      • SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

9、Web原生元件注入(Servlet、Filter、Listener)

9.1、使用Servlet API

@ServletComponentScan

(basePackages = “com.zzp.boot”) :指定原生Servlet元件都放在那裡

@WebServlet

(urlPatterns = “/my”):效果:直接響應,沒有經過Spring的攔截器

配置類:

@WebServlet(urlPatterns = "/my")
public class MyServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("66666");
    }
}
           

主啟動類添加

@ServletComponentScan

注解

@ServletComponentScan(basePackages = "com.zzp.boot")
@SpringBootApplication
public class MainAppliction {}
           

測試:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

@WebFilter

(urlPatterns={"/css/","/images/"})

配置類:

@Slf4j
@WebFilter(urlPatterns={"/css/*","/images/*"}) //攔截靜态資源
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("MyFilter初始化完成");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("MyFilter工作");
        chain.doFilter(request,response);
    }

    @Override
    public void destroy() {
        log.info("MyFilter銷毀");
    }
}
           

測試:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

@WebListene

配置類:

@Slf4j
@WebListener
public class MySwervletContextListener implements ServletContextListener {


    @Override
    public void contextInitialized(ServletContextEvent sce) {
        log.info("MySwervletContextListener監聽到項目初始化完成");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        log.info("MySwervletContextListener監聽到項目銷毀");
    }
}
           

啟動,日記輸出:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

推薦可以這種方式;

擴充:DispatchServlet 如何注冊進來

  • 容器中自動配置了 DispatcherServlet 屬性綁定到 WebMvcProperties;對應的配置檔案配置項是 spring.mvc。
  • 通過 ServletRegistrationBean 把 DispatcherServlet 配置進來。
  • 預設映射的是 / 路徑。
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

Tomcat-Servlet;

多個Servlet都能處理到同一層路徑,精确優選原則

A: /my/

B: /my/1

9.2、使用RegistrationBean

ServletRegistrationBean, FilterRegistrationBean, and ServletListenerRegistrationBean

配置類:

/**
 * 1、MyServlet --> /my
 * 2、DispatcherServlet --> /
 */
// (proxyBeanMethods = true):保證依賴的元件始終是單執行個體的
@Configuration(proxyBeanMethods = true)
public class MyRegistConfig {

    @Bean
    public ServletRegistrationBean myServlet(){
        MyServlet myServlet = new MyServlet();

        return new ServletRegistrationBean(myServlet,"/my","/my02");
    }


    @Bean
    public FilterRegistrationBean myFilter(){

        MyFilter myFilter = new MyFilter();
//        return new FilterRegistrationBean(myFilter,myServlet());
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));
        return filterRegistrationBean;
    }

    @Bean
    public ServletListenerRegistrationBean myListener(){
        MySwervletContextListener mySwervletContextListener = new MySwervletContextListener();
        return new ServletListenerRegistrationBean(mySwervletContextListener);
    }
}
           

10、嵌入式Servlet容器

官網文檔

10.1、切換嵌入式Servlet容器

  • 預設支援的webServer
    • Tomcat, Jetty, or Undertow
    • ServletWebServerApplicationContext 容器啟動尋找ServletWebServerFactory 并引導建立伺服器
  • 切換伺服器
    SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
    引用undertow服務
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-undertow</artifactId>
        </dependency>
           

啟動日記:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
  • 原理
    • SpringBoot應用啟動發現目前是Web應用。web場景包-導入tomcat
    • web應用會建立一個web版的ioc容器 ServletWebServerApplicationContext
    • ServletWebServerApplicationContext 啟動的時候尋找 ServletWebServerFactory(Servlet 的web伺服器工廠—> Servlet 的web伺服器)
    • SpringBoot底層預設有很多的WebServer工廠;TomcatServletWebServerFactory, JettyServletWebServerFactory, or UndertowServletWebServerFactory
    • 底層直接會有一個自動配置類。ServletWebServerFactoryAutoConfiguration
    • ServletWebServerFactoryAutoConfiguration導入了ServletWebServerFactoryConfiguration(配置類)
    • ServletWebServerFactoryConfiguration 配置類 根據動态判斷系統中到底導入了那個Web伺服器的包。(預設是web-starter導入tomcat包),容器中就有 TomcatServletWebServerFactory
    • TomcatServletWebServerFactory 建立出Tomcat伺服器并啟動;TomcatWebServer 的構造器擁有初始化方法initialize—this.tomcat.start();
    • 内嵌伺服器,就是手動把啟動伺服器的代碼調用(tomcat核心jar包存在)

10.2、定制Servlet容器

  • 實作 WebServerFactoryCustomizer
    • 把配置檔案的值和ServletWebServerFactory 進行綁定
  • 修改配置檔案 server.xxx
  • 直接自定義 ConfigurableServletWebServerFactory

xxxxxCustomizer:定制化器,可以改變xxxx的預設規則

import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;

@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {

    @Override
    public void customize(ConfigurableServletWebServerFactory server) {
        server.setPort(9000);
    }

}
           

11、定制化原理

11.1、定制化的常見方式

  • 修改配置檔案;
  • xxxxxCustomizer;
  • 編寫自定義的配置類

    xxxConfiguration;+ @Bean

    替換、增加容器中預設元件;視圖解析器
  • Web應用 編寫一個配置類實作 WebMvcConfigurer 即可定制化web功能;+ @Bean給容器中再擴充一些元件
/**
 * 1、編寫一個攔截器實作HandlerInterceptor接口
 * 2、攔截器注冊到容器中(實作WebMvcConfigurer的addInterceptors)
 * 3、指定攔截規則【如果是攔截所有,靜态資源也會被攔截】
 *
 *  @EnableWebMvc: 全面接管mvc
 *      1、靜态資源?視圖解析器?歡迎頁... 全部失效
 */
//@EnableWebMvc
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {


    /**
     * 定義靜态資源行為
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry){
        /**
         * 通路 /aa/** 所有請求都去 classpath:/static/ 下面進行比對
         */
        registry.addResourceHandler("/aa/**")
                .addResourceLocations("classpath:/static/");
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")  //所有請求都被攔截包括靜态資源
                .excludePathPatterns("/","/login","/css/**","/fonts/**",
                        "/images/**", "/js/**","/aa/**"); //放行的請求
    }


//    @Bean
//    public WebMvcRegistrations webMvcRegistrations(){
//        return new WebMvcRegistrations() {
//            @Override
//            public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
//                return WebMvcRegistrations.super.getRequestMappingHandlerMapping();
//            }
//        };
//    }


}
           
  • @EnableWebMvc + WebMvcConfigurer

    ——

    @Bean

    可以全面接管SpringMVC

    ,所有規則全部自己重新配置; 實作定制和擴充功能
    • 原理
    • 1、WebMvcAutoConfiguration 預設的SpringMVC的自動配置功能類。靜态資源、歡迎頁…
    • 2、一旦使用

      @EnableWebMvc

      、。會

      @Import(DelegatingWebMvcConfiguration.class)

    • 3、DelegatingWebMvcConfiguration 的 作用,隻保證SpringMVC最基本的使用
      • 把所有系統中的 WebMvcConfigurer 拿過來。所有功能的定制都是這些 WebMvcConfigurer 合起來一起生效
      • 自動配置了一些非常底層的元件。RequestMappingHandlerMapping、這些元件依賴的元件都是從容器中擷取
      • public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
    • 4、WebMvcAutoConfiguration 裡面的配置要能生效 必須

      @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)

      沒有這個元件才生效
    • 5、

      @EnableWebMvc

      導緻了 WebMvcAutoConfiguration 沒有生效。
  • … …

11.2、原理分析套路

場景starter - xxxxAutoConfiguration - 導入xxx元件 - 綁定xxxProperties – 綁定配置檔案項

三、資料通路

1、SQL

1.1、資料源的自動配置-HikariDataSource

1、導入JDBC場景

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
        </dependency>
           
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

資料庫驅動?

為什麼導入JDBC場景,官方不導入驅動?

官方不知道我們接下要操作什麼資料庫。

資料庫版本和驅動版本對應

預設版本:<mysql.version>8.0.25</mysql.version>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <!--            <version>5.1.49</version>-->
        </dependency>
       
 想要修改版本
1、直接依賴引入具體版本(maven的就近依賴原則)
2、重新聲明版本(maven的屬性的就近優先原則)
    <properties>
        <java.version>1.8</java.version>
        <mysql.version>5.1.49</mysql.version>
    </properties>
           

2、分析自動配置

2.1、自動配置的類
  • DataSourceAutoConfiguration : 資料源的自動配置
    • 修改資料源相關的配置:spring.datasource
    • 資料庫連接配接池的配置,是自己容器中沒有DataSource才自動配置的
    • 底層配置好的連接配接池是:HikariDataSource
package org.springframework.boot.autoconfigure.jdbc;
...
@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@ConditionalOnMissingBean(
    type = {"io.r2dbc.spi.ConnectionFactory"}
)
@EnableConfigurationProperties({DataSourceProperties.class})
@Import({DataSourcePoolMetadataProvidersConfiguration.class, InitializationSpecificCredentialsDataSourceInitializationConfiguration.class, SharedCredentialsDataSourceInitializationConfiguration.class})
public class DataSourceAutoConfiguration {
	....
	@Configuration(proxyBeanMethods = false )
    @Conditional({DataSourceAutoConfiguration.PooledDataSourceCondition.class})
    @ConditionalOnMissingBean({DataSource.class, XADataSource.class})
    @Import({Hikari.class, Tomcat.class, Dbcp2.class, OracleUcp.class, Generic.class, DataSourceJmxConfiguration.class})
    protected static class PooledDataSourceConfiguration {
        protected PooledDataSourceConfiguration() {
        }
    }

}
           
  • DataSourceTransactionManagerAutoConfiguration: 事務管理器的自動配置
  • JdbcTemplateAutoConfiguration: JdbcTemplate的自動配置,可以來對資料庫進行crud
    • 可以修改這個配置項

      @ConfigurationProperties(prefix = "spring.jdbc")

      來修改JdbcTemplate
    • @[email protected] JdbcTemplate

      ;容器中有這個元件
  • JndiDataSourceAutoConfiguration: jndi的自動配置(web配置)
  • XADataSourceAutoConfiguration: 分布式事務相關的

3、修改配置項

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/db_account?useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
  jdbc:
    template:
      query-timeout: 3 # 設定連接配接數控庫逾時資料 機關:秒
           

4、測試

@Slf4j
@SpringBootTest
class Boot05WebAdminApplicationTests {

    @Autowired
    JdbcTemplate jdbcTemplate;

    @Test
    void contextLoads() {
        //        jdbcTemplate.queryForObject("select * from account_tbl");
        //        jdbcTemplate.queryForList("select * from account_tbl",)
        Long aLong = jdbcTemplate.queryForObject("select count(*) from account_tbl", Long.class);
        log.info("記錄總數:{}",aLong);
    }
    
}


控制台輸出:記錄總數:2
           

1.2、使用Druid資料源

1、druid官方github位址

https://github.com/alibaba/druid

整合第三方技術的兩種方式:

  • 自定義
  • 找starter

2、自定義方式

2.1、建立資料源

pom檔案引用依賴:

<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.17</version>
        </dependency>
           

xml檔案:

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
    destroy-method="close">
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
    <property name="maxActive" value="20" />
    <property name="initialSize" value="1" />
    <property name="maxWait" value="60000" />
    <property name="minIdle" value="1" />
    <property name="timeBetweenEvictionRunsMillis" value="60000" />
    <property name="minEvictableIdleTimeMillis" value="300000" />
    <property name="testWhileIdle" value="true" />
    <property name="testOnBorrow" value="false" />
    <property name="testOnReturn" value="false" />
    <property name="poolPreparedStatements" value="true" />
    <property name="maxOpenPreparedStatements" value="20" />
           
2.2、StatViewServlet
StatViewServlet的用途包括:
- 提供監控資訊展示的html頁面
- 提供監控資訊的JSON API
           
<servlet>
		<servlet-name>DruidStatView</servlet-name>
		<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>DruidStatView</servlet-name>
		<url-pattern>/druid/*</url-pattern>
	</servlet-mapping>
           
2.3、StatFilter
用于統計監控資訊;如SQL監控、URI監控
           
需要給資料源中配置如下屬性;可以允許多個filter,多個用,分割;如:

<property name="filters" value="stat,slf4j" />
           

系統中所有filter:

别名 Filter類名
default com.alibaba.druid.filter.stat.StatFilter
stat com.alibaba.druid.filter.stat.StatFilter
mergeStat com.alibaba.druid.filter.stat.MergeStatFilter
encoding com.alibaba.druid.filter.encoding.EncodingConvertFilter
log4j com.alibaba.druid.filter.logging.Log4jFilter
log4j2 com.alibaba.druid.filter.logging.Log4j2Filter
slf4j com.alibaba.druid.filter.logging.Slf4jLogFilter
commonlogging om.alibaba.druid.filter.logging.CommonsLogFilter

慢SQL記錄配置

<bean id="stat-filter" class="com.alibaba.druid.filter.stat.StatFilter">
    <property name="slowSqlMillis" value="10000" />
    <property name="logSlowSql" value="true" />
</bean>

使用 slowSqlMillis 定義慢SQL的時長
           

代碼示範:

自定義資料源:

@Configuration
public class MyDataSourceConfig {

    // 預設的自動配置是判斷容器中沒有才會配 @ConditionalOnMissingBean(DataSource.class)
    @ConfigurationProperties("spring.datasource")  //從配置檔案擷取
    @Bean
    public DataSource dataSource() throws SQLException {
        DruidDataSource druidDataSource = new DruidDataSource();

//        druidDataSource.setUrl();
//        druidDataSource.setUsername();
//        druidDataSource.setPassword();

        return druidDataSource;
    }
}
           

測試:

@Autowired
    JdbcTemplate jdbcTemplate;

    @Autowired
    DataSource dataSource;

    @Test
    void contextLoads() {
        //        jdbcTemplate.queryForObject("select * from account_tbl");
        //        jdbcTemplate.queryForList("select * from account_tbl",)
        Long aLong = jdbcTemplate.queryForObject("select count(*) from account_tbl", Long.class);

        log.info("記錄總數:{}",aLong);

        log.info("資料源類型:{}",dataSource.getClass());
    }
    
控制台輸出:
資料源類型:class com.alibaba.druid.pool.DruidDataSource
           

引入監控功能:

pom檔案添加依賴:

<!--        引入監控功能-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
           

配置類:

@Configuration
public class MyDataSourceConfig {

    // 預設的自動配置是判斷容器中沒有才會配 @ConditionalOnMissingBean(DataSource.class)
    @ConfigurationProperties("spring.datasource")  //從配置檔案擷取
    @Bean
    public DataSource dataSource() throws SQLException {
        DruidDataSource druidDataSource = new DruidDataSource();

//        druidDataSource.setUrl();
//        druidDataSource.setUsername();
//        druidDataSource.setPassword();
        //加入監控功能
        druidDataSource.setFilters("stat,wall");//監控,防火牆
//        druidDataSource.setMaxActive(10); // 設定最大活躍線程數
        return druidDataSource;
    }

    /**
     * 配置 druid的監控頁功能
     * @return
     */
    @Bean
    public ServletRegistrationBean statViewServlet(){
        StatViewServlet statViewServlet = new StatViewServlet();
        ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");

        //設定賬号密碼
        registrationBean.addInitParameter("loginUsername","admin");
        registrationBean.addInitParameter("loginPassword","123456");

        return registrationBean;
    }

    /**
     * WebStatFilter 用于采集web-jdbc關聯監控的資料。
     */
    @Bean
    public FilterRegistrationBean webStatFilter(){
        WebStatFilter webStatFilter = new WebStatFilter();

        FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(webStatFilter);
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
        filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");

        return filterRegistrationBean;
    }

}
           

添加接口通路,友善看監控頁面:

@Autowired
    JdbcTemplate jdbcTemplate;

    @ResponseBody
    @GetMapping("/sql")
    public String queryFromDb(){
        Long aLong = jdbcTemplate.queryForObject("select count(*) from account_tbl", Long.class);
        return aLong.toString();
    }
           

登入:http://localhost:8080/druid

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

通路sql接口:http://localhost:8080/sql

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

監控一些功能的配置也可以寫配置檔案中,因為MyDataSourceConfig類的dataSource方法的注解

@ConfigurationProperties("spring.datasource") //從配置檔案擷取

spring:
  datasource:
    filters: stat,wall //開啟監控,防火牆
    max-active: 12 	  // 設定線程活躍最大數量
           

3、使用官方starter方式

3.1、引入druid-starter
<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.17</version>
        </dependency>
           
3.2、分析自動配置
  • 擴充配置項 spring.datasource.druid
package com.alibaba.druid.spring.boot.autoconfigure;
...
@Configuration
@ConditionalOnClass({DruidDataSource.class})
@AutoConfigureBefore({DataSourceAutoConfiguration.class})
@EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})
@Import({DruidSpringAopConfiguration.class, 
		DruidStatViewServletConfiguration.class, 
		DruidWebStatFilterConfiguration.class, 
		DruidFilterConfiguration.class})
public class DruidDataSourceAutoConfigure {}
           
  • @Import(

    DruidSpringAopConfiguration.class, 監控SpringBean的;配置項: spring.datasource.druid.aop-patterns
  • DruidStatViewServletConfiguration.class, 監控頁的配置:spring.datasource.druid.stat-view-servlet;預設開啟
  • DruidWebStatFilterConfiguration.class, web監控配置;spring.datasource.druid.web-stat-filter;預設開啟
  • DruidFilterConfiguration.class}) 所有Druid自己filter的配置
package com.alibaba.druid.spring.boot.autoconfigure.stat;
...
public class DruidFilterConfiguration {
    private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat";
    private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config";
    private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding";
    private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j";
    private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j";
    private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2";
    private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log";
    private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filter.wall";
    private static final String FILTER_WALL_CONFIG_PREFIX = "spring.datasource.druid.filter.wall.config";
    	...
}
           
3.3、配置示例
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/db_account?useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver

    druid:
      aop-patterns: com.zzp.boot.*  #springbean監控
      filters: stat,wall,slf4j  # 底層開啟功能,stat(sql監控),wall(防火牆)

      stat-view-servlet:  # 配置監控頁功能
        enabled: true
        login-username: admin  # 登入使用者
        login-password: admin  # 登入密碼
        resetEnable: false  # 重置按鈕

      web-stat-filter:  # web監控
        enabled: true
        urlPattern: /*  # 比對路徑
        exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' # 排除

      filter:
        stat:   # 對上面filters裡面的stat的詳細配置
          slow-sql-millis: 1000 # 慢查詢sql時間
          logSlowSql: true
          enabled: true
        wall: #防火牆
          enabled: true
          config:
            drop-table-allow: false # 不允許删表操作
           

SpringBoot配置示例

https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter

配置項清單

https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8

1.3、整合MyBatis操作

https://github.com/mybatis

starter

SpringBoot官方的Starter:spring-boot-starter-*

第三方的: *-spring-boot-starter

<dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>
           
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

1、配置模式

  • 全局配置檔案
  • SqlSessionFactory: 自動配置好了
  • SqlSession:自動配置了 SqlSessionTemplate 組合了SqlSession
  • @Import(AutoConfiguredMapperScannerRegistrar.class)

  • Mapper: 隻要我們寫的操作MyBatis的接口标準了

    @Mapper

    就會被自動掃描進來
package org.mybatis.spring.boot.autoconfigure;
...
@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties({MybatisProperties.class}) //MyBatis配置項綁定類
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
public class MybatisAutoConfiguration implements InitializingBean {...}


@ConfigurationProperties(
    prefix = "mybatis"
)
public class MybatisProperties {}
           

mybatis文檔

可以修改配置檔案中 mybatis 開始的所有;

application.yml添加配置:

# 配置mybatis規則
mybatis:
  config-location: classpath:mybatis/mybatis-config.xml  #全局配置檔案位置
  mapper-locations: classpath:mybatis/mapper/*.xml  #sql映射檔案位置
           

結構圖:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

mybatis-config.xml檔案:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 開啟駝峰命名自動映射 -->
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
</configuration>
           

AccountMapper.xml檔案:

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

    <!-- Account getAccount(Long id); -->
    <select id="getAccount" resultType="com.zzp.boot.bean.Account">
        select * from account_tbl where id = #{id}
    </select>
</mapper>
           

編寫測試代碼:

@Data
public class Account {
    private Long id;
    private String userId;
    private Long money;
}


@Mapper
public interface AccountMapper {
    Account getAccount(Long id);
}

@Service
public class AccountService {

    @Autowired
    private AccountMapper accountMapper;

    public Account getAcctByid(Long id){
        return accountMapper.getAccount(id);
    }
}



    @Autowired
    private AccountService accountService;

    @ResponseBody
    @GetMapping("/acct")
    public Account getById(@RequestParam("id") Long id){
        return accountService.getAcctByid(id);
    }


           

測試:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

配置 private Configuration configuration; mybatis.configuration下面的所有,就是相當于改mybatis全局配置檔案中的值

# 配置mybatis規則
mybatis:
#  config-location: classpath:mybatis/mybatis-config.xml   #全局配置檔案位置
  mapper-locations: classpath:mybatis/mapper/*.xml        #sql映射檔案位置
  configuration:  # 指定mybatis全局配置檔案中的相關配置項
      map-underscore-to-camel-case: true
#  可以不寫全局;配置檔案,所有全局配置檔案的配置都放在configuration配置項中即可
           
  • 導入mybatis官方starter
  • 編寫mapper接口。标準

    @Mapper

    注解
  • 編寫sql映射檔案并綁定mapper接口
  • 在application.yaml中指定Mapper配置檔案的位置,以及指定全局配置檔案的資訊 (建議;配置在mybatis.configuration)

2、注解模式

city表建立sql

CREATE TABLE `city` (
  `id` int(19) NOT NULL AUTO_INCREMENT COMMENT '自增ID',
  `name` varchar(64)NOT NULL DEFAULT '' COMMENT '名稱',
  `state` varchar(30)NOT NULL DEFAULT '' COMMENT '狀态',
  `country` varchar(30)NOT NULL DEFAULT '' COMMENT '國家',
   PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='城市表';
           
@Mapper
public interface CityMapper {

    @Select("select * from city where id=#{id}")
    City getById(Long id);

    @Insert("insert into city(`name`,`state`,`country`) values (#{name},#{state},#{country})")
    @Options(useGeneratedKeys = true,keyProperty = "id")
    void insert(City city);
}
           

3、混合模式

@Mapper
public interface CityMapper {

    @Select("select * from city where id=#{id}")
    public City getById(Long id);

    public void insert(City city);

}
           

CityMapper.xml檔案

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

    <!--  void insert(City city); -->
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        insert into city(`name`,`state`,`country`) values
                        (#{name},#{state},#{country});
    </insert>
</mapper>
           
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

最佳實戰:

  • 引入mybatis-starter
  • 配置application.yaml中,指定mapper-location位置即可
  • 編寫Mapper接口并标注

    @Mapper注解

  • 簡單方法直接注解方式
  • 複雜方法編寫mapper.xml進行綁定映射
  • 主啟動類添加

    @MapperScan("com.atguigu.admin.mapper")

    注解 簡化,其他的接口就可以不用标注

    @Mapper注解

// An highlighted block
var foo = 'bar';
           

1.4、整合 MyBatis-Plus 完成CRUD

1、什麼是MyBatis-Plus

MyBatis-Plus(簡稱 MP)是一個 MyBatis 的增強工具,在 MyBatis 的基礎上隻做增強不做改變,為簡化開發、提高效率而生。

mybatis plus 官網

建議安裝 MybatisX 插件

MyBatis-Plus quick-start代碼執行個體

2、整合MyBatis-Plus

pom檔案依賴

<dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>
           

自動配置:

  • MybatisPlusAutoConfiguration 配置類,MybatisPlusProperties 配置項綁定。mybatis-plus:xxx 就是對mybatis-plus的定制
  • SqlSessionFactory 自動配置好。底層是容器中預設的資料源
  • mapperLocations 自動配置好的。有預設值。 classpath*:/mapper*.xml;任意包的類路徑下的所有mapper檔案夾下任意路徑下的所有xml都是sql映射檔案。 建議以後sql映射檔案,放在 mapper下

  • 容器中也自動配置好了 SqlSessionTemplate
  • @Mapper

    标注的接口也會被自動掃描;建議直接 主啟動類添加

    @MapperScan("com.atguigu.admin.mapper")

    批量掃描就行

優點:

  • 隻需要我們的Mapper繼承 BaseMapper 就可以擁有crud能力
/**
 * 繼承BaseMapper 就可以擁有crud能力
 */
public interface UserMapper extends BaseMapper<User> {

}
           
@Data
public class User {

    /**
     * 所有屬性都應該在資料庫中
     */
    @TableField(exist = false) //在表中字段不存在
    private String userName;
    @TableField(exist = false)
    private String password;

    //以下是資料庫字段
    private Long id;
    private String name;
    private Integer age;
    private String email;

}

           

測試:

@Autowired
    UserMapper userMapper;

    @Test
    void testUserMapper(){
        User user = userMapper.selectById(1L);
        log.info("使用者資訊:{}",user);
    }

控制台輸出:使用者資訊:User(userName=null, password=null, id=1, name=Jone, age=18, email=[email protected].com)
           

3、CRUD功能

/**
 *  Service 的CRUD也不用寫了
 */
public interface UserService extends IService<User> {

}

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {


}
           
@GetMapping("/user/delete/{id}")
    public String deleteUser(@PathVariable("id") Long id,
                             @RequestParam(value = "pn",defaultValue = "1")Integer pn,
                             RedirectAttributes ra){

        userService.removeById(id);

        ra.addAttribute("pn",pn);
        return "redirect:/dynamic_table";
    }

    @GetMapping("/dynamic_table")
    public String dynamic_table(@RequestParam(value="pn",defaultValue = "1") Integer pn,Model model){
        //表格内容的周遊
//        List<User> users = Arrays.asList(new User("zhangsan", "123456"),
//                new User("lisi", "123444"),
//                new User("haha", "aaaaa"),
//                new User("hehe ", "aaddd"));
//        model.addAttribute("users",users);
//        if(users.size() > 3){
//            throw new UserTooManyException();
//        }

        //從資料庫中查出user表中的使用者進行展示
//        List<User> list = userService.list();
//        model.addAttribute("users",list);

        //構造分頁參數
        Page<User> userPage = new Page<>(pn, 2);
        //調用page進行分頁
        Page<User> page = userService.page(userPage, null);

//        List<User> records = page.getRecords();
//        long current = page.getCurrent();
//        long pages = page.getPages();
//        long total = page.getTotal();

        model.addAttribute("page",userPage);

        return "table/dynamic_table";
    }
           

2、NoSQL

Redis 中文官網網址

Redis 是一個開源(BSD許可)的,記憶體中的資料結構存儲系統,它可以用作資料庫、緩存和消息中間件。 它支援多種類型的資料結構,如 字元串(strings), 散列(hashes), 清單(lists), 集合(sets), 有序集合(sorted sets) 與範圍查詢,bitmaps, hyperloglogs 和 地理空間(geospatial) 索引半徑查詢。 Redis 内置了 複制(replication),LUA腳本(Lua scripting), LRU驅動事件(LRU eviction),事務(transactions)和不同級别的 磁盤持久化(persistence) , 并通過 Redis哨兵(Sentinel)和自動分區(Cluster)提供高可用性(high availability)。

2.1、Redis自動配置

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
           
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

自動配置:

  • RedisAutoConfiguration 自動配置類。RedisProperties 屬性類 --> spring.redis.xxx是對redis的配置
  • 連接配接工廠是準備好的。LettuceConnectionConfiguration、JedisConnectionConfiguration
  • 自動注入了RedisTemplate<Object, Object> : xxxTemplate;
  • 自動注入了StringRedisTemplate;k:v都是String

    key:value

  • 底層隻要我們使用 StringRedisTemplate、RedisTemplate就可以操作redis

redis環境搭建

1、阿裡雲按量付費redis。經典網絡

2、申請redis的公網連接配接位址

3、修改白名單 允許0.0.0.0/0 通路

2.2、RedisTemplate與Lettuce

配置資訊:

sring:
  redis:
    host: 127.0.0.1    # 單機模式-host
    port: 6379              # 單機模式-端口
    password: 123456  # 密碼
           
@Test
    void testRedis(){
        ValueOperations<String, String> operations = redisTemplate.opsForValue();

        operations.set("hello","world");

        String hello = operations.get("hello");
        System.out.println(hello);
    }
輸出:world
           

2.3、切換至jedis

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

<!--        導入jedis-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>
           

yaml配置

spring:
  redis:
    host: 127.0.0.1    # 單機模式-host
    port: 6379              # 單機模式-端口
    password: 123456  # 密碼
    client-type: jedis # 指定連接配接的是 jedis
    jedis:
      pool:
        max-active: 10 # 最大線程連接配接數
        min-idle: 5
           

操作統計通路次數實驗:

編寫攔截器:

@Component
public class RedisUrlCountInterceptor implements HandlerInterceptor {

    @Autowired
    StringRedisTemplate redisTemplate;


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String uri = request.getRequestURI();

        //預設每次通路目前uri就會計數+1
        redisTemplate.opsForValue().increment(uri);

        return true;
    }
}
           

添加元件中:

public class AdminWebConfig implements WebMvcConfigurer {

    /**
     * Filter、Interceptor 幾乎擁有相同的功能?
     * 1、Filter是Servlet定義的原生元件。好處,脫離Spring應用也能使用
     * 2、Interceptor是Spring定義的接口。可以使用Spring的自動裝配等功能
     *
     */
    @Autowired
    RedisUrlCountInterceptor redisUrlCountInterceptor;


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")  //所有請求都被攔截包括靜态資源
                .excludePathPatterns("/","/login","/css/**","/fonts/**",
                        "/images/**", "/js/**","/aa/**"); //放行的請求


        registry.addInterceptor(redisUrlCountInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**",
                        "/js/**","/aa/**");
    }

}

           

控制層

@Autowired
    StringRedisTemplate redisTemplate;
    /**
     * 去main頁面
     * @return
     */
    @GetMapping("/main.html")
    public String mainPage(HttpSession session,Model model){
        log.info("目前方法是:{}","mainPage");
        ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();

        String s = opsForValue.get("/main.html");
        String s1 = opsForValue.get("/sql");


        model.addAttribute("mainCount",s);
        model.addAttribute("sqlCount",s1);

        return "main";
    }
           

main.html修改:

<div class="state-value">
   <div class="value" th:text="${mainCount}">230</div>
   <div class="title">/main.html</div>
</div> 		
<div class="state-value">
   <div class="value" th:text="${sqlCount}">3490</div>
   <div class="title">/sql</div>
</div> 							
           

測試效果:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

四、單元測試

1、JUnit5 的變化

Spring Boot 2.2.0 版本開始引入 JUnit 5 作為單元測試預設庫

作為最新版本的JUnit架構,JUnit5與之前版本的Junit架構有很大的不同。由三個不同子項目的幾個不同子產品組成。

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

JUnit Platform: Junit Platform是在JVM上啟動測試架構的基礎,不僅支援Junit自制的測試引擎,其他測試引擎也都可以接入。

JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的程式設計模型,是JUnit5新特性的核心。内部 包含了一個測試引擎,用于在Junit Platform上運作。

JUnit Vintage: 由于JUint已經發展多年,為了照顧老的項目,JUnit Vintage提供了相容JUnit4.x,Junit3.x的測試引擎。

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

注意:

SpringBoot 2.4 以上版本移除了預設對 Vintage 的依賴。如果需要相容junit4需要自行引入(不能使用junit4的功能 @Test)

JUnit 5’s Vintage Engine Removed from spring-boot-starter-test,如果需要繼續相容junit4需要自行引入vintage

<dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
           
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

pom檔案依賴

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

現在版本:

@SpringBootTest
class Boot05WebAdminApplicationTests {


    @Test
    void contextLoads() {

    }
}
           

以前:

@SpringBootTest + @RunWith(SpringTest.class)

SpringBoot整合Junit以後:(

@SpringBootTest

注解标注的類)

  • 編寫測試方法:@Test标注(注意需要使用junit5版本的注解)
  • Junit類具有Spring的功能,

    @Autowired

    、比如

    @Transactional

    标注測試方法,測試完成後自動復原

2、JUnit5常用注解

JUnit5的注解與JUnit4的注解有所變化

https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations

  • @Test :表示方法是測試方法。但是與JUnit4的@Test不同,他的職責非常單一不能聲明任何屬性,拓展的測試将會由Jupiter提供額外測試
  • @ParameterizedTest :表示方法是參數化測試,下方會有詳細介紹
  • @RepeatedTest :表示方法可重複執行,下方會有詳細介紹
  • @DisplayName :為測試類或者測試方法設定展示名稱
  • @BeforeEach :表示在每個單元測試之前執行
  • @AfterEach :表示在每個單元測試之後執行
  • @BeforeAll :表示在所有單元測試之前執行
  • @AfterAll :表示在所有單元測試之後執行
  • @Tag :表示單元測試類别,類似于JUnit4中的@Categories
  • @Disabled :表示測試類或測試方法不執行,類似于JUnit4中的@Ignore
  • @Timeout :表示測試方法運作如果超過了指定時間将會傳回錯誤
  • @ExtendWith :為測試類或測試方法提供擴充類引用
import org.junit.jupiter.api.Test; //注意這裡使用的是jupiter的Test注解!!

/**
 * @BootstrapWith(SpringBootTestContextBootstrapper.class)
 * @ExtendWith(SpringExtension.class)
 */
//@SpringBootTest
@DisplayName("junit5功能測試類")
public class Junit5Test {


    @Autowired
    JdbcTemplate jdbcTemplate;

    @DisplayName("測試displayname注解")
    @Test
    void testDisplayName() {
        System.out.println("testDisplayName().....");
        System.out.println(jdbcTemplate);
    }

    @Disabled
    @DisplayName("測試方法2")
    @Test
    void test2() {
        System.out.println("test2...");
    }

    @RepeatedTest(5) //重複測試
    @Test
    void test3() {
        System.out.println("test3...");
    }

    /**
     * 規定方法逾時時間。超出時間測試出異常
     *
     * @throws InterruptedException
     */
    @Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
    @Test
    void testTimeout() throws InterruptedException {
        Thread.sleep(600);
    }

    @BeforeEach
    void testBeforeEach() {
        System.out.println("測試就要開始了...");
    }

    @AfterEach
    void testAfterEach() {
        System.out.println("測試結束了...");
    }

    @BeforeAll
    static void testBeforeAll() {
        System.out.println("所有測試就要開始了...");
    }

    @AfterAll
    static void testAfterAll() {
        System.out.println("所有測試以及結束了...");
    }
}

           

3、斷言(assertions)

斷言(assertions)是測試方法中的核心部分,用來對測試需要滿足的條件進行驗證。這些斷言方法都是 org.junit.jupiter.api.Assertions 的靜态方法。JUnit 5 内置的斷言可以分成如下

幾個類别:

檢查業務邏輯傳回的資料是否合理。

所有的測試運作結束以後,會有一個詳細的測試報告;

3.1、簡單斷言

用來對單個值進行簡單的驗證。如:

方法 說明
assertEquals 判斷兩個對象或兩個原始類型是否相等
assertNotEquals 判斷兩個對象或兩個原始類型是否不相等
assertSame 判斷兩個對象引用是否指向同一個對象
assertNotSame 判斷兩個對象引用是否指向不同的對象
assertTrue 判斷給定的布爾值是否為 true
assertFalse 判斷給定的布爾值是否為 false
assertNull 判斷給定的對象引用是否為 null
assertNotNull 判斷給定的對象引用是否不為 null
@Test
@DisplayName("simple assertion")
public void simple() {
     assertEquals(3, 1 + 2, "simple math");
     assertNotEquals(3, 1 + 1);

     assertNotSame(new Object(), new Object());
     Object obj = new Object();
     assertSame(obj, obj);

     assertFalse(1 > 2);
     assertTrue(1 < 2);

     assertNull(null);
     assertNotNull(new Object());
}
           

3.2、數組斷言

通過 assertArrayEquals 方法來判斷兩個對象或原始類型的數組是否相等

@Test
    @DisplayName("array assertion")
    void array() {
        assertArrayEquals(new int[]{1, 2}, new int[]{1, 2}, "數組内容不相等");
    }
           

3.3、組合斷言

assertAll 方法接受多個 org.junit.jupiter.api.Executable 函數式接口的執行個體作為要驗證的斷言,可以通過 lambda 表達式很容易的提供這些斷言

@Test
    @DisplayName("組合斷言")
    void all() {
        /**
         * 所有斷言全部需要成功
         */
        assertAll("test",
                () -> assertTrue(true && true, "結果不為true"),
                () -> assertEquals(1, 2, "結果不是1"));

        System.out.println("=====");
    }
           

3.4、異常斷言

在JUnit4時期,想要測試方法的異常情況時,需要用@Rule注解的ExpectedException變量還是比較麻煩的。而JUnit5提供了一種新的斷言方式Assertions.assertThrows() ,配合函數式程式設計就可以進行使用。

@DisplayName("異常斷言")
    @Test
    void testException() {
        //斷定業務邏輯一定出現異常
        assertThrows(ArithmeticException.class, () -> {
            int i = 10 / 2;
        }, "業務邏輯居然正常運作?");
    }
           

3.5、逾時斷言

@Test
@DisplayName("逾時測試")
public void timeoutTest() {
    //如果測試方法時間超過1s将會異常
    Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}
           

3.6、快速失敗

通過 fail 方法直接使得測試失敗

@DisplayName("快速失敗")
    @Test
    void testFail(){
        //xxxxx
        if(1 == 2){
            fail("測試失敗");
        }
    }
           

4、前置條件(assumptions)

JUnit 5 中的前置條件(assumptions【假設】)類似于斷言,不同之處在于不滿足的斷言會使得測試方法失敗,而不滿足的前置條件隻會使得測試方法的執行終止。前置條件可以看成是測試方法執行的前提,當該前提不滿足時,就沒有繼續執行的必要。

@DisplayName("前置條件")
	public class AssumptionsTest {
	 private final String environment = "DEV";
	 
	 @Test
	 @DisplayName("simple")
	 public void simpleAssume() {
	    assumeTrue(Objects.equals(this.environment, "DEV"));
	    assumeFalse(() -> Objects.equals(this.environment, "PROD"));
	 }
	 
	 @Test
	 @DisplayName("assume then do")
	 public void assumeThenDo() {
	    assumingThat(
	       Objects.equals(this.environment, "DEV"),
	       () -> System.out.println("In DEV")
	    );
	 }
	}


    /**
     * 測試前置條件
     */
    @DisplayName("測試前置條件")
    @Test
    void testassumptions(){
        Assumptions.assumeTrue(false,"結果不是true");
        System.out.println("111111");

    }
           

assumeTrue 和 assumFalse 確定給定的條件為 true 或 false,不滿足條件會使得測試執行終止。assumingThat 的參數是表示條件的布爾值和對應的 Executable 接口的實作對象。隻有條件滿足時,Executable 對象才會被執行;當條件不滿足時,測試執行并不會終止。

5、嵌套測試

JUnit 5 可以通過 Java 中的内部類和

@Nested

注解實作嵌套測試,進而可以更好的把相關的測試方法組織在一起。在内部類中可以使用

@BeforeEach

@AfterEach

注解,而且嵌套的層次沒有限制。

@DisplayName("嵌套測試")
public class TestingAStackDemo {

    Stack<Object> stack;
    
    @Test
    @DisplayName("new Stack()")
    void isInstantiatedWithNew() {
        new Stack<>();
        //嵌套測試情況下,外層的Test不能驅動内層的Before(After)Each/All之類的方法提前/之後運作
        assertNull(stack);
    }

    @Nested
    @DisplayName("when new")
    class WhenNew {

        @BeforeEach
        void createNewStack() {
            stack = new Stack<>();
        }

        @Test
        @DisplayName("is empty")
        void isEmpty() {
            assertTrue(stack.isEmpty());
        }

        @Test
        @DisplayName("throws EmptyStackException when popped")
        void throwsExceptionWhenPopped() {
            assertThrows(EmptyStackException.class, stack::pop);
        }

        @Test
        @DisplayName("throws EmptyStackException when peeked")
        void throwsExceptionWhenPeeked() {
            assertThrows(EmptyStackException.class, stack::peek);
        }

        @Nested
        @DisplayName("after pushing an element")
        class AfterPushing {

            String anElement = "an element";

            @BeforeEach
            void pushAnElement() {
                stack.push(anElement);
            }

            /**
             * 内層的Test可以驅動外層的Before(After)Each/All之類的方法提前/之後運作
             */
            @Test
            @DisplayName("it is no longer empty")
            void isNotEmpty() {
                assertFalse(stack.isEmpty());
            }

            @Test
            @DisplayName("returns the element when popped and is empty")
            void returnElementWhenPopped() {
                assertEquals(anElement, stack.pop());
                assertTrue(stack.isEmpty());
            }

            @Test
            @DisplayName("returns the element when peeked but remains not empty")
            void returnElementWhenPeeked() {
                assertEquals(anElement, stack.peek());
                assertFalse(stack.isEmpty());
            }
        }
    }
}
           

6、參數化測試

參數化測試是JUnit5很重要的一個新特性,它使得用不同的參數多次運作測試成為了可能,也為我們的單元測試帶來許多便利。

利用

@ValueSource

等注解,指定入參,我們将可以使用不同的參數進行多次單元測試,而不需要每新增一個參數就新增一個單元測試,省去了很多備援代碼。

@ValueSource: 為參數化測試指定入參來源,支援八大基礎類以及String類型,Class類型

@NullSource: 表示為參數化測試提供一個null的入參

@EnumSource: 表示為參數化測試提供一個枚舉入參

@CsvFileSource:表示讀取指定CSV檔案内容作為參數化測試入參

@MethodSource:表示讀取指定方法的傳回值作為參數化測試入參(注意方法傳回需要是一個流)

當然如果參數化測試僅僅隻能做到指定普通的入參還達不到讓我覺得驚豔的地步。讓我真正感到他的強大之處的地方在于他可以支援外部的各類入參。如:CSV,YML,JSON 檔案甚至方法的傳回值也可以作為入參。隻需要去實作ArgumentsProvider接口,任何外部檔案都可以作為它的入參。
@ParameterizedTest
    @DisplayName("參數化測試")
    @ValueSource(ints = {1,2,3,4,5})
    void testParameterized(int i){
        System.out.println(i);
    }


    @ParameterizedTest
    @DisplayName("參數化測試")
    @MethodSource("stringProvider")
    void testParameterized2(String i){
        System.out.println(i);
    }

    static Stream<String> stringProvider() {
        return Stream.of("apple", "banana","ZZP");
    }
           

7、遷移指南

在進行遷移的時候需要注意如下的變化:

  • 注解在

    org.junit.jupiter.api

    包中,斷言在

    org.junit.jupiter.api.Assertions

    類中,前置條件在

    org.junit.jupiter.api.Assumptions

    類中。
  • @Before

    @After

    替換成

    @BeforeEach

    @AfterEach

    @BeforeClass

    @AfterClass

    替換成

    @BeforeAll

    @AfterAll

  • @Ignore

    替換成

    @Disabled

  • @Category

    替換成

    @Tag

  • @RunWith

    @Rule

    @ClassRule

    替換成

    @ExtendWith

五、名額監控

1、SpringBoot Actuator

官網文檔

1.1、簡介

未來每一個微服務在雲上部署以後,我們都需要對其進行監控、追蹤、審計、控制等。SpringBoot就抽取了Actuator場景,使得我們每個微服務快速引用即可獲得生産級别的應用監控、審計等功能。

<!--        引入監控功能-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
           
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

1.2、1.x與2.x的不同

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

1.3、如何使用

  • 引入場景
  • 通路 http://localhost:8080/actuator/**
  • 暴露所有監控資訊為HTTP
management:
  endpoints:
    enabled-by-default: true #暴露所有端點資訊
    web:
      exposure:
        include: '*'  #以web方式暴露
           
  • 測試

    http://localhost:8080/actuator/beans

    http://localhost:8080/actuator/configprops

    http://localhost:8080/actuator/metrics

    http://localhost:8080/actuator/metrics/jvm.gc.pause

    http://localhost:8080/actuator/endpointName/detailPath

    。。。。。。

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

1.4、可視化

https://github.com/codecentric/spring-boot-admin

2、Actuator Endpoint

2.1、最常使用的端點

ID 描述
auditevents 暴露目前應用程式的稽核事件資訊。需要一個AuditEventRepository元件。
beans 顯示應用程式中所有Spring Bean的完整清單。
caches 暴露可用的緩存。
conditions 顯示自動配置的所有條件資訊,包括比對或不比對的原因。
configprops 顯示所有@ConfigurationProperties。
env 暴露Spring的屬性ConfigurableEnvironment
flyway

顯示已應用的所有Flyway資料庫遷移。

需要一個或多個Flyway元件。

health 顯示應用程式運作狀況資訊。
httptrace 顯示HTTP跟蹤資訊(預設情況下,最近100個HTTP請求-響應)。需要一個HttpTraceRepository元件。
info 顯示應用程式資訊。
integrationgraph 顯示Spring integrationgraph 。需要依賴spring-integration-core。
loggers 顯示和修改應用程式中日志的配置。
liquibase 顯示已應用的所有Liquibase資料庫遷移。需要一個或多個Liquibase元件。
metrics 顯示目前應用程式的“名額”資訊。
mappings 顯示所有@RequestMapping路徑清單。
scheduledtasks 顯示應用程式中的計劃任務。
sessions 允許從Spring Session支援的會話存儲中檢索和删除使用者會話。需要使用Spring Session的基于Servlet的Web應用程式。
shutdown 使應用程式正常關閉。預設禁用。
startup 顯示由ApplicationStartup收集的啟動步驟資料。需要使用SpringApplication進行配置BufferingApplicationStartup。
threaddump 執行線程轉儲。

如果您的應用程式是Web應用程式(Spring MVC,Spring WebFlux或Jersey),則可以使用以下附加端點:

ID 描述
heapdump 傳回hprof堆轉儲檔案。
jolokia 通過HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依賴jolokia-core。
logfile 傳回日志檔案的内容(如果已設定logging.file.name或logging.file.path屬性)。支援使用HTTPRange标頭來檢索部分日志檔案的内容
prometheus 以Prometheus伺服器可以抓取的格式公開名額。需要依賴micrometer-registry-prometheus。

最常用的Endpoint:

  • Health:監控狀況
  • Metrics:運作時名額
  • Loggers:日志記錄

2.2、Health Endpoint

健康檢查端點,我們一般用于在雲平台,平台會定時的檢查應用的健康狀況,我們就需要Health Endpoint可以為平台傳回目前應用的一系列元件健康狀況的集合。

重要的幾點:

  • health endpoint傳回的結果,應該是一系列健康檢查後的一個彙總報告
  • 很多的健康檢查預設已經自動配置好了,比如:資料庫、redis等
  • 可以很容易的添加自定義的健康檢查機制

添加對某個端點的具體配置

# management 是所有actuator的配置
# management.endpoint.端點名.xxxx  對某個端點的具體配置
management:
  endpoints:
    enabled-by-default: true  #預設開啟所有監控端點  true
    web:
      exposure:
        include: '*' # 以web方式暴露所有端點

  endpoint: #對某個端點的具體配置
    health:
      show-details: always
           
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

2.3、Metrics Endpoint

提供詳細的、層級的、空間名額資訊,這些資訊可以被pull(主動推送)或者push(被動擷取)方式得到;

  • 通過Metrics對接多種監控系統
  • 簡化核心Metrics開發
  • 添加自定義Metrics或者擴充已有Metrics
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

2.4、管理Endpoints

1、開啟與禁用Endpoints

  • 預設所有的Endpoint除過shutdown都是開啟的。
  • 需要開啟或者禁用某個Endpoint。配置模式為 management.endpoint.<endpointName>.enabled = true
management:
  endpoint:
    beans:
      enabled: true 
           
  • 或者禁用所有的Endpoint然後手動開啟指定的Endpoint
management:
  endpoints:
    enabled-by-default: false
  endpoint:
    beans:
      enabled: true
    health:
      enabled: true
           

2、暴露Endpoints

支援的暴露方式

  • HTTP:預設隻暴露health和info Endpoint
  • JMX:預設暴露所有Endpoint
  • 除過health和info,剩下的Endpoint都應該進行保護通路。如果引入SpringSecurity,則會預設配置安全通路規則
ID JMX Web
auditevents Yes No
beans Yes No
caches Yes No
conditions Yes No
configprops Yes No
env Yes No
flyway Yes No
health Yes No
heapdump N/A No
httptrace Yes No
info Yes No
integrationgraph Yes No
jolokia N/A No
logfile N/A No
loggers Yes No
liquibase Yes No
metrics Yes No
mappings Yes No
prometheus N/A No
scheduledtasks Yes No
sessions Yes No
shutdown Yes No
startup Yes No
threaddump Yes No

3、定制 Endpoint

3.1、定制 Health 資訊

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

@Component
public class MyHealthIndicator implements HealthIndicator {

    @Override
    public Health health() {
        int errorCode = check(); // perform some specific health check
        if (errorCode != 0) {
            return Health.down().withDetail("Error Code", errorCode).build();
        }
        return Health.up().build();
    }

}

建構Health
Health build = Health.down()
                .withDetail("msg", "error service")
                .withDetail("code", "500")
                .withException(new RuntimeException())
                .build();
           
management:
    health:
      enabled: true
      show-details: always #總是顯示詳細資訊。可顯示每個子產品的狀态資訊
           
@Component
public class MyComHealthIndicator extends AbstractHealthIndicator {

    /**
     * 真實的檢查方法
     * @param builder
     * @throws Exception
     */
    @Override
    protected void doHealthCheck(Health.Builder builder) throws Exception {
        //mongodb。  擷取連接配接進行測試
        Map<String,Object> map = new HashMap<>();
        // 檢查完成
        if(1 == 1){
//            builder.up(); //健康
            builder.status(Status.UP);
            map.put("count",1);
            map.put("ms",100);
        }else {
//            builder.down(); // 不健康
            builder.status(Status.OUT_OF_SERVICE);
            map.put("err","連接配接逾時");
            map.put("ms",3000);
        }

        builder.withDetail("code",100)
                .withDetails(map);

    }
}
           

測試:通路 http://localhost:8080/actuator/health

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

3.2、定制info資訊

常用兩種方式

1、編寫配置檔案

info:
  appName: boot-admin
  appVersion: 1.0.0
  mavenProjectName: @[email protected]
  mavenProjectVersion: @[email protected]
           

測試:通路 http://localhost:8080/actuator/info

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

2、編寫InfoContributor

@Component
public class AppInfoInfoContributor implements InfoContributor {
    
    @Override
    public void contribute(Info.Builder builder) {
        builder.withDetail("msg","你好")
                .withDetail("hello","zzp")
                .withDetails(Collections.singletonMap("world","6666"));
    }
}
           
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

http://localhost:8080/actuator/info會輸出以上方式傳回的所有info資訊

3.3、定制Metrics資訊

1、SpringBoot支援自動适配的Metrics

  • JVM metrics, report utilization of:
    • Various memory and buffer pools
    • Statistics related to garbage collection
    • Threads utilization

      Number of classes loaded/unloaded

  • CPU metrics
  • File descriptor metrics
  • Kafka consumer and producer metrics
  • Log4j2 metrics: record the number of events logged to Log4j2 at each level
  • Logback metrics: record the number of events logged to Logback at each level
  • Uptime metrics: report a gauge for uptime and a fixed gauge representing the application’s absolute start time
  • Tomcat metrics (server.tomcat.mbeanregistry.enabled must be set to true for all Tomcat metrics to be registered)
  • Spring Integration metrics

2、增加定制Metrics

@Service
public class CityServiceImpl implements CityService {

    @Autowired
    CityMapper cityMapper;

    Counter counter;

    public CityServiceImpl(MeterRegistry meterRegistry){
        counter = meterRegistry.counter("cityService.saveCity.count");
    }
    
    @Override
    public void saveCity(City city) {
        counter.increment();
        cityMapper.insert(city);

    }
}



//也可以使用下面的方式
@Bean
MeterBinder queueSize(Queue queue) {
    return (registry) -> Gauge.builder("queueSize", queue::size).register(registry);
}
           

測試:http://localhost:8080/actuator/metrics

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

http://localhost:8080/actuator/metrics/cityService.saveCity.count

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

3.4、定制Endpoint

@Component
@Endpoint(id = "myservice")
public class MyServiceEndPoint {

    @ReadOperation
    public Map getDockerInfo(){
        //端點的讀操作  http://localhost:8080/actuator/myservice
        return Collections.singletonMap("dockerInfo","docker started.....");
    }

    @WriteOperation
    public void stopDocker(){
        System.out.println("docker stopped.....");
    }

}
           

測試:http://localhost:8080/actuator

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

http://localhost:8080/actuator/myservice

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

場景:開發ReadinessEndpoint來管理程式是否就緒,或者LivenessEndpoint來管理程式是否存活;

當然,這個也可以直接使用 https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-kubernetes-probes

六、原了解析

1、Profile功能

為了友善多環境适配,springboot簡化了profile功能。

1.1、application-profile功能

  • 預設配置檔案 application.yaml;任何時候都會加載
  • 指定環境配置檔案 application-{env}.yaml
  • 激活指定環境
    • 配置檔案激活
    • 指令行激活:java -jar xxx.jar --spring.profiles.active=prod --person.name=haha
      • 修改配置檔案的任意值,指令行優先
  • 預設配置與環境配置同時生效
  • 同名配置項,profile配置優先

綁定屬性配置類:

@Data
@Component
@ConfigurationProperties("person")
public interface Person {}
           

yaml檔案配置:

person:
  xxx: xxx
           

1.2、@Profile條件裝配功能

@Configuration(proxyBeanMethods = false)
@Profile("production")
public class ProductionConfiguration {

    // ...

}
           

1.3、profile分組

# 指定激活的環境。預設配置檔案和指定環境的配置檔案都會生效。
spring.profiles.active=myprod 

spring.profiles.group.myprod[0]=ppd

spring.profiles.group.myprod[1]=prod

spring.profiles.group.mytest[0]=test
           

2、外部化配置

https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config

1、Default properties (specified by setting SpringApplication.setDefaultProperties).

2、@PropertySource annotations on your @Configuration classes. Please note that such property sources are not added to the Environment until the application context is being refreshed. This is too late to configure certain properties such as

logging.*

and

spring.main.*

which are read before refresh begins.

3、Config data (such as application.properties files)

4、A RandomValuePropertySource that has properties only in random.*.

5、OS environment variables.

6、Java System properties (System.getProperties()).

7、JNDI attributes from

java:comp/env

.

8、ServletContext init parameters.

9、ServletConfig init parameters.

10、Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property).

11、Command line arguments.

12、properties attribute on your tests. Available on @SpringBootTest and the test annotations for testing a particular slice of your application.

13、@TestPropertySource annotations on your tests.

14、Devtools global settings properties in the

$HOME/.config/spring-boot

directory when devtools is active.

2.1、外部配置源

常用:Java屬性檔案、YAML檔案、環境變量、指令行參數;

2.2、配置檔案查找位置

(1) classpath 根路徑

(2) classpath 根路徑下config目錄

(3) jar包目前目錄

(4) jar包目前目錄的config目錄

(5) /config子目錄的直接子目錄

2.3、配置檔案加載順序:

1、目前jar包内部的application.properties和application.yml

2、目前jar包内部的application-{profile}.properties 和 application-{profile}.yml

3、引用的外部jar包的application.properties和application.yml

 

4、引用的外部jar包的application-{profile}.properties 和 application-{profile}.yml

2.3、指定環境優先,外部優先,後面的可以覆寫前面的同名配置項

3、自定義starter

3.1、starter啟動原理

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
  • autoconfigure包中配置使用 META-INF/spring.factories 中 EnableAutoConfiguration 的值,使得項目啟動加載指定的自動配置類
  • 編寫自動配置類 xxxAutoConfiguration -> xxxxProperties
    • @Configuration
    • @Conditional
    • @EnableConfigurationProperties
    • @Bean

引入starter — xxxAutoConfiguration — 容器中放入元件 ---- 綁定xxxProperties ---- 配置項

3.2、自定義starter

zzp-hello-spring-boot-starter(啟動器)空項目

pom檔案:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.zzp</groupId>
    <artifactId>zzp-hello-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- 依賴 自動配置包 -->
        <dependency>
            <groupId>com.zzp</groupId>
            <artifactId>zzp-hello-spring-boot-starter-autoconfigure</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>
           
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

zzp-hello-spring-boot-starter-autoconfigure(自動配置包)項目

pom檔案:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 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.4.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.zzp</groupId>
    <artifactId>zzp-hello-spring-boot-starter-autoconfigure</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>zzp-hello-spring-boot-starter-autoconfigure</name>
    <description>Demo project for Spring Boot</description>


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

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

    </dependencies>

</project>
           

服務類:

/**
 * 預設不要放在容器中
 */
public class HelloService {

    @Autowired
    HelloProperties helloProperties;

    public String sayHello(String userName){
        return helloProperties.getPrefix() + ":"+userName+"》"+helloProperties.getSuffix();
    }
}
           

配置類:

@ConfigurationProperties("zzp.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;
    }
}
           

自動配置類:

@Configuration
@EnableConfigurationProperties(HelloProperties.class)  //預設HelloProperties放在容器中
public class HelloServiceAutoConfiguration{

    @ConditionalOnMissingBean(HelloService.class)
    @Bean
    public HelloService helloService(){
        HelloService helloService = new HelloService();
        return helloService;
    }

}
           

META-INF/spring.factories 檔案中 EnableAutoConfiguration 的值,使得項目啟動加載指定的自動配置類

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.zzp.hello.auto.HelloServiceAutoConfiguration
           

項目結構

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

最後,idea的Maven菜單 clean --> install 添加到本地倉庫中

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

建立測試項目boot-09-hello-test服務

pom檔案依賴:

<dependency>
            <groupId>org.zzp</groupId>
            <artifactId>zzp-hello-spring-boot-starter</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
           

application.properties配置檔案:

zzp.hello.prefix=zzp
zzp.hello.suffix=666
           

測試類:

@RestController
public class HelloController {

    @Autowired
    HelloService helloService;

    @GetMapping("/hello")
    public String sayHello(){
        String zhangsan = helloService.sayHello("張三");
        return zhangsan;
    }
}
           

測試,通路 http://localhost:8080/hello

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析

4、SpringBoot原理

Spring原理【Spring注解】、SpringMVC原理、自動配置原理、SpringBoot原理

4.1、SpringBoot啟動過程

  • 1、建立 SpringApplication
    • 儲存一些資訊。
    • 判定目前應用的類型。ClassUtils。Servlet
    • bootstrappers:初始啟動引導器(List<Bootstrapper>):去spring.factories檔案中找 org.springframework.boot. Bootstrapper
    • 找 ApplicationContextInitializer;去spring.factories找 ApplicationContextInitializer
      • List<ApplicationContextInitializer<?>> initializers
    • 找 ApplicationListener ;應用監聽器。去spring.factories找 ApplicationListener
      • List<ApplicationListener<?>> listeners
  • 2、運作 SpringApplication
    • StopWatch
    • 記錄應用的啟動時間 –

      stopWatch.start()

    • 建立引導上下文(Context環境)

      createBootstrapContext()

      • 擷取到所有之前的 bootstrappers 挨個執行 intitialize() 來完成對引導啟動器上下文環境設定
    • 讓目前應用進入headless模式。java.awt.headless
    • 擷取所有 RunListener(運作監聽器)【為了友善所有Listener進行事件感覺】
      • getSpringFactoriesInstances 去spring.factories找 SpringApplicationRunListener.
    • 周遊 SpringApplicationRunListener 調用 starting 方法;
      • 相當于通知所有感興趣系統正在啟動過程的人,項目正在 starting。
    • 儲存指令行參數;ApplicationArguments
    • 準備環境 prepareEnvironment();
      • 傳回或者建立基礎環境資訊對象。StandardServletEnvironment
      • 配置環境資訊對象。
        • 讀取所有的配置源的配置屬性值。
      • 綁定環境資訊
      • 監聽器調用 listener.environmentPrepared();通知所有的監聽器目前環境準備完成
    • 建立IOC容器(

      createApplicationContext()

      • 根據項目類型(Servlet)建立容器
      • 目前會建立 AnnotationConfigServletWebServerApplicationContext
    • 準備ApplicationContext IOC容器的基本資訊 prepareContext()
      • 儲存環境資訊
      • IOC容器的後置處理流程。

        postProcessApplicationContext()

      • 應用初始化器;

        applyInitializers

        • 周遊所有的 ApplicationContextInitializer 。調用 **initialize.。來對ioc容器進行初始化擴充功能**
        • 周遊所有的 listener 調用 **contextPrepared。EventPublishRunListenr;通知所有的監聽器contextPrepared**
      • 所有的監聽器 調用 contextLoaded。通知所有的監聽器 contextLoaded
    • 重新整理IOC容器。

      refreshContex

      • 建立容器中的所有元件(Spring注解)
    • 容器重新整理完成後工作?

      afterRefresh

    • 所有監聽 器 調用

      listeners.**started**(context);

      通知所有的監聽器 started
    • 調用所有runners;

      callRunners()

      • 擷取容器中的 ApplicationRunner
      • 擷取容器中的 CommandLineRunner
      • 合并所有runner并且按照@Order進行排序
      • 周遊所有的runner。調用 run 方法
    • 如果以上有異常,
      • 調用Listener 的 failed
    • 調用所有監聽器的 running 方法

      listeners.running(context);

      通知所有的監聽器 running
    • running如果有問題。繼續通知 failed 。調用所有 Listener 的 failed;通知所有的監聽器 failed
package org.springframework.boot;

public interface Bootstrapper {
    void intitialize(BootstrapRegistry registry);
}
           
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
package org.springframework.boot;

@FunctionalInterface
public interface ApplicationRunner {
   /**
   * Callback used to run the bean.
   * @param args incoming application arguments
   * @throws Exception on error
   */
    void run(ApplicationArguments args) throws Exception;
}
           
package org.springframework.boot;

@FunctionalInterface
public interface CommandLineRunner {
  	/**
  	 * Callback used to run the bean.
  	 * @param args incoming main method arguments
  	 * @throws Exception on error
  	 */
    void run(String... args) throws Exception;
}
           

4.2、Application Events and Listeners

https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-application-events-and-listeners

ApplicationContextInitializer

ApplicationListener

SpringApplicationRunListener

4.3、ApplicationRunner 與 CommandLineRunner

自定義監聽:

public class MyApplicationContextInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("MyApplicationContextInitializer ....initialize.... ");
    }
}
           
public class MyApplicationListener implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println("MyApplicationListener.....onApplicationEvent...");
    }
}
           
public class MySpringApplicationRunListener implements SpringApplicationRunListener {

    private SpringApplication application;
    public MySpringApplicationRunListener(SpringApplication application, String[] args){
        this.application = application;
    }

    @Override
    public void starting(ConfigurableBootstrapContext bootstrapContext) {
        System.out.println("MySpringApplicationRunListener....starting....");
    }


    @Override
    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
        System.out.println("MySpringApplicationRunListener....environmentPrepared....");
    }


    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        System.out.println("MySpringApplicationRunListener....contextPrepared....");

    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        System.out.println("MySpringApplicationRunListener....contextLoaded....");
    }

    @Override
    public void started(ConfigurableApplicationContext context) {
        System.out.println("MySpringApplicationRunListener....started....");
    }

    @Override
    public void running(ConfigurableApplicationContext context) {
        System.out.println("MySpringApplicationRunListener....running....");
    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        System.out.println("MySpringApplicationRunListener....failed....");
    }
}
           
@Order(1)
@Component
public class MyApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("MyApplicationRunner...run...");
    }
}
           
/**
 * 應用啟動做一個一次性事情
 */
@Order(2)
@Component
public class MyCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("MyCommandLineRunner....run....");
    }
}
           

resources目錄下建立META-INF檔案夾添加spring.factories檔案

org.springframework.context.ApplicationContextInitializer=\
  com.zzp.boot.listener.MyApplicationContextInitializer

org.springframework.context.ApplicationListener=\
  com.zzp.boot.listener.MyApplicationListener

org.springframework.boot.SpringApplicationRunListener=\
  com.zzp.boot.listener.MySpringApplicationRunListener
           

測試:

SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析
SpringBoot2 核心一、配置檔案二、Web開發三、資料通路四、單元測試五、名額監控六、原了解析