SpringBoot教程——檢視閱讀

卡斯特梅的雨傘發表於2020-05-21

SpringBoot教程——檢視閱讀

參考

SpringBoot教程——一點——藍本——springboot2.1.1

SpringBoot教程——易百——springboo2.0.5.RELEASE

SpringBoot教程——w3c——springboot1.3.3.RELEASE

SpringBoot教程——C語言網——springboot2.1.6

SpringBoot教程部落格——純潔的微笑

SpringBoot教程——javaschool

SpringBoot官網

Spring Boot參考指南——翻譯

SpringBoot教程彙總——部落格專案索引

SpringBoot教程彙總——部落格專案中文索引

Spring Boot 基礎——IBM

SpringBoot教程——極客——雜亂講不好——springboo2.1.5.RELEASE

SpringBoot教程——jc2182——參考——springboo2.3.0.BUILD-SNAPSHOT

  • 基於Spring Boot框架:Spring Boot 2.1.11.RELEASE

略讀

一點

基於spring的知識點基礎上講springboot,只說了最簡單的使用和常用的與其他框架如redis、mybatis的整合。缺點是沒有對springboot與spring原來的細節對比。

C語言中午網

講得比較詳細,有深入到springboot最重要的兩點COC和spring-boot-starter 自動配置依賴模組常用的操作,以及springboot與dubbo構建微服務的操作。

W3C

不是很好,學習起來操作不夠清晰。

javaschool

只有這個好點

spring-boot-starter 起步依賴模組列舉

SpringBoot2.02官方參考指南

沒翻譯完,質量一般,還不如直接看官網

易百

仔細講解了springboot裡的一些使用,還結合了些springcloud的東西,但比較少說與其他框架如redis、mybatis的整合。總體來說這個教程並不好,有點雜亂無章。

IBM

最簡單的hello world。

Spring Boot starter 參考頁面  :列出了其他許多 starter。

spring-boot-starter-web。基於這個 starter,Spring Boot 形成了該應用程式的以下依賴:

  • 使用 Tomcat 作為嵌入式 Web 伺服器容器
  • 使用 Hibernate 進行物件-關係對映 (ORM)
  • 使用 Apache Jackson 繫結 JSON
  • 使用 Spring MVC 作為 REST 框架

如果我們不想用tomcat,可以更改 POM 來使用 Jetty 代替 Tomcat。如果不想用hibernate,改用mybatis,也可以這樣操作。

<dependencies>
    <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-jetty</artifactId>
    </dependency>
</dependencies>

Spring Boot基礎入門

什麼是Spring Boot

Spring Boot概述

Spring Boot 是所有基於 Spring Framework 5.0 開發的專案的起點。Spring Boot 的設計是為了讓你儘可能快的跑起來 Spring 應用程式並且儘可能減少你的配置檔案。

簡化了使用Spring的難度,簡省了繁重的配置,提供了各種啟動器,開發者能快速上手。

Spring Boot的優點

  • 使用 Spring 專案引導頁面可以在幾秒構建一個專案
  • 方便對外輸出各種形式的服務,如 REST API、WebSocket、Web、Streaming、Tasks
  • 非常簡潔的安全策略整合
  • 支援關聯式資料庫和非關聯式資料庫
  • 支援執行期內嵌容器,如 Tomcat、Jetty
  • 強大的開發包,支援熱啟動
  • 自動管理依賴自帶應用監控
  • 支援各種 IDE,如 IntelliJ IDEA 、NetBeans

Spring Boot核心功能

起步依賴

起步依賴本質上是一個Maven專案物件模型(Project Object Model,POM),定義了對其他庫的傳遞依賴,這些東西加在一起即支援某項功能。

自動配置

Spring Boot的自動配置是一個執行時(更準確地說,是應用程式啟動時)的過程,考慮了眾多因素,才決定Spring配置應該用哪個,不該用哪個。該過程是Spring自動完成的。

Spring Boot快速入門

步驟:

  1. 建立一個普通的maven專案。
  2. pom.xml匯入起步依賴 。
  3. 編寫引導類

示例:

pom.xml

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

    <groupId>com.self</groupId>
    <artifactId>hellospringboot</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- 匯入springboot父工程. 注意:任何的SpringBoot工程都必須有的!!! -->
    <!-- 父工程的作用:鎖定起步的依賴的版本號,並沒有真正到依賴 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.11.RELEASE</version>
    </parent>

    <dependencies>
        <!--web起步依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

</project>

引導類,或者說叫啟動類

@SpringBootApplication
public class MyBootApplication {

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

@Controller
public class HelloController {

    @RequestMapping("/hello")
    @ResponseBody
    public String sayHello(){
        return "Hello Spring Boot!";
    }
}

請求:http://localhost:8080/hello

輸出:

Spring Boot配置檔案

Spring Boot的核心是自動配置(或者叫預設配置),通過自動配置大大減少Spring專案的配置編寫。但是在實際開發中,我們仍然需要根據需求來適當修改某些必要的引數配置,這時Spring Boot提供了兩種格式的配置方便開發者進行修改。

  • applicaiton*.properties
  • application.yml(或者application.yaml)

application*.properties

Spring Boot使用了一個全域性的配置檔案application.properties,放在src/main/resources目錄下或者src/main/resources/config下。在 src/main/resources/config 下的優先順序高 。Sping Boot的全域性配置檔案的作用是對一些預設配置的配置值進行修改。

Spring Boot內建屬性

示例:

編寫修改Tomcat埠屬性 :

application.properties

server.port=9000

Spring Boot內建屬性參考

自定義屬性

application.properties

server.port=9000

#自定義型別
#基本型別
name=Amy
age=21

#JavaBean型別
user.name=Amy
user.age=21

#陣列/List集合
user.list=Amy,Jack,Roy
#或者
user.list[0]=Amy
user.list[1]=Jack
user.list[2]=Roy

#Map集合
user.map={name:"Amy",age:21}
#或者
user.map.name=Amy
user.map.age=21

自定義配置怎麼取值使用,什麼場景使用?

Profile多環境配置

當應用程式需要部署到不同執行環境時,一些配置細節通常會有所不同,最簡單的比如日誌,生產日誌會將日誌級別設定為WARN或更高階別,並將日誌寫入日誌檔案,而開發的時候需要日誌級別為DEBUG,日誌輸出到控制檯即可。如果按照以前的做法,就是每次釋出的時候替換掉配置檔案,這樣太麻煩了,Spring Boot的Profile就給我們提供瞭解決方案,命令帶上引數就搞定。

步驟:

  • 建立不同環境的application.properties檔案
  • 每個檔案裡面的環境配置變數不同

示例:

application.properties

#啟用prod生產環境
spring.profiles.active= prod
#當profiles不同環境變數檔案裡有配置值時,application.properties裡配置的變數是會被啟用的環境如application-prod.properties裡的值所覆蓋的。
server.port=9000

application-prod.properties

server.port=9004

輸出:

application*.yml

YAML(/ˈjæməl/,尾音類似camel駱駝)是一個可讀性高,用來表達資料序列化的格式。

yml或yaml所表示的YAML Ain’t Markup Language,YAML是一種簡潔的非標記語言,檔名字尾為yml,java中經常用它描述配置檔案application.yml。YAML以資料為中心,比json/xml等更適合做配置檔案。使用空白,縮排,分行組織資料,從而使得表示更加簡潔易讀。

在yml之前使用最多的配置檔案形式是xml和properties檔案。xml檔案太過繁瑣,看過的人都知道,想要新加一個配置節點的話還需要包含在<>標籤裡;而properties配置檔案沒有了標籤,不過當你的配置有很多層級的時候,寫完之後你會發現會有大量重複的程式碼。而yml/yaml檔案結合了兩者的優勢,當你新增節點配置的時候,不需要標籤,在寫多層級配置的時候也不會產生重複程式碼。

yml格式書寫規則

  1. 大小寫敏感
  2. 使用縮排表示層級關係
  3. 禁止使用tab縮排,只能使用空格鍵
  4. 縮排長度沒有限制,只要元素對齊就表示這些元素屬於一個層級。
  5. 使用#表示註釋
  6. 字串可以不用引號標註

Spring Boot內建屬性

注意:

當application.properties和application.yml同時存在時,生效的是application.properties。

#yml檔案在寫時有提示,友好,優先選擇使用
#修改埠
server:
  port: 9090
#基本型別 注意:屬性值大小寫敏感
name: Bruce Wayne
age: 29
#JavaBean型別
user:
  name: Bruce Wayne
  age: 29
#陣列/List集合
#user:層級只能指定一個,後面的如果是在user層級下的只需要tab空格就可以了,再寫user層級則會報錯
#user:
  list: eric,jack,rose
  #下面這種寫法用@Value註解解析不了
  list:
  - Jack
  - Rose
  - Jerry
#Map集合
#user:
#yml語法格式要求key如map: 後面對應的value值必需在冒號:後面空格,否則格式錯誤
  map: {name: Bruce Wayne,age: 29}

Profile多環境配置

application.yml

#啟用test測試環境
#當profiles不同環境變數檔案裡有配置值時,application.yml裡配置的變數是會被啟用的環境如application-test.yml裡的值所覆蓋的。
#還有一點需要特別注意的是當存在application-test.properties與application-test.yml兩個並行時,生效的是application-test.properties
spring:
  profiles:
    active: test

application-test.yml

server:
  port: 9092

Spring Boot讀取配置檔案(properties和yml處理是一樣的)

Spring Boot裡面有兩個註解可以讀取application.properties或application.yml檔案的屬性值。

  1. @Value
  2. @ConfigurationProperties

注意:

1、不能配置user.name=Amy屬性配置,因為取不到Amy的值,取到的是計算機的使用者名稱,在這臺電腦裡我的使用者名稱是Castamere。應該是個系統預設保留的取值配置,這裡沒有深入去研究。

2、不能配置userName=Amy這個key為userName或者username的基本型別配置,否則取到的是還是計算機的使用者名稱。

@Value

基本型別

application.yml

#基本型別 
firstName: Bruce Wayne1111
age: 29

讀取:

@Controller
public class ConfigController {

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

    @RequestMapping("/show")
    @ResponseBody
    public String showConfig() {
        return name + " : " + age;
    }
}

JavaBean型別

#JavaBean型別
user:
  first: Bruce Wayne
  age: 31

讀取:

@Controller
public class ConfigController {
    @Value("${user.firstName}")
    private String firstName;
    @Value("${user.age}")
    private Integer age;

    @RequestMapping("/show")
    @ResponseBody
    public String showConfig() {
        return firstName + " : " + age;
    }
}

陣列/List集合

user:
  list: Jack,Rose,Jerry

讀取:

@Value("#{'${user.list}'.split(',')}")
private List<String> list;

@RequestMapping("/show")
@ResponseBody
public String showConfig() {
    return JSON.toJSONString(list);
}

Map集合

user:
#yml語法格式要求key如map: 後面對應的value值必需在冒號:後面空格,否則格式錯誤
#讀取的時候要加引號""是給@Value註解用的麼?
# map: {nickname: erci,age: 20}
  map: "{name: 'SuperMan',age: 28}"

讀取:

@Value("#{${user.map}}")
private Map<String,Object> map;

@RequestMapping("/show")
@ResponseBody
public String showConfig() {
    return JSON.toJSONString(map);
}

注意,不加引號會報錯.

#報錯:Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'user.map' in value "#{${user.map}}"
user:
  map: {name: 'SuperMan',age: 28}

@ConfigurationProperties

注意以下幾點:

  • prefix:代表屬性的字首,如果user.nickname字首就是user

  • 屬性名稱必須和properties檔案的屬性名保持一致

  • 屬性必須提供setter方法來注入檔案的屬性值

    基本型別

    firstName: Bruce Wayne
    age: 30

讀取:

@Controller
@ConfigurationProperties
public class ConfigurationController {

    private String firstName;
    private Integer age;

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @RequestMapping("/show")
    @ResponseBody
    public String showConfig() {
        return firstName + " : " + age;
    }
}

輸出:

//成功
Bruce Wayne : 30
//沒有setter方法來注入檔案的屬性值,不會報錯,但是沒有賦值
null : null

JavaBean型別

#JavaBean型別
user:
  firstName: Bruce
  age: 31

讀取:

@Controller
//兩種配置方式都可以
@ConfigurationProperties("user")
//@ConfigurationProperties(prefix = "user")
public class ConfigurationController {

    private String firstName;
    private Integer age;

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @RequestMapping("/show")
    @ResponseBody
    public String showConfig() {
        return firstName + " : " + age;
        //return JSON.toJSONString(list);
        //return JSON.toJSONString(map);
    }
}

Bruce : 31

陣列/List集合

#陣列/List集合
#user:層級只能指定一個,後面的如果是在user層級下的只需要tab空格就可以了,再寫user層級則會報錯
#user:
#兩種list表達方式都可以,傾下第一種
  list: Jack,Rose,Jerry
  list2:
  - Jack
  - Morty
  - Jerry

讀取:

@Controller
//兩種配置方式都可以
@ConfigurationProperties("user")
//@ConfigurationProperties(prefix = "user")
public class ConfigurationController {

    private List<String> list;

    public void setList(List<String> list) {
        this.list = list;
    }

    @RequestMapping("/show")
    @ResponseBody
    public String showConfig() {
        return JSON.toJSONString(list);
    }
}

Map集合

#Map集合
#yml語法格式要求key如map: 後面對應的value值必需在冒號:後面空格,否則格式錯誤
#讀取的時候要加引號""是給@Value註解用的麼?@ConfigurationProperties("user")讀取map不需要加引號,否則報錯,說明兩種讀取方式不同
#報錯:Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'user.map' in value "#{${user.map}}"
  map: {name: 'SuperMan',age: 28}
#  map: "{name: 'SuperMan',age: 28}"

讀取:

@Controller
//兩種配置方式都可以
@ConfigurationProperties("user")
//@ConfigurationProperties(prefix = "user")
public class ConfigurationController {
    private Map<String,Object> map;

    public void setMap(Map<String, Object> map) {
        this.map = map;
    }
    
    @RequestMapping("/show")
    @ResponseBody
    public String showConfig() {
        return JSON.toJSONString(map);
    }
}

{"name":"SuperMan","age":28}

Spring Boot熱部署

什麼是熱部署

無法熱部署的缺點:

  • 在實際開發過程中,每次修改程式碼就得將專案重啟,重新部署,對於一些大型應用來說,重啟時間需要花費大量的時間成本。
  • 程式設計師開發過程中重啟緩慢影響開發。在 Java 開發領域,熱部署一直是一個難以解決的問題,目前的 Java 虛擬機器只能實現方法體的修改熱部署,對於整個類的結構修改,仍然需要重啟虛擬機器,對類重新載入才能完成更新操作。

熱部署原理

深層原理是使用了兩個ClassLoader,一個Classloader載入那些不會改變的類(第三方Jar包),另一個ClassLoader載入會更改的類,稱為restart ClassLoader,這樣在有程式碼更改的時候,原來的restart ClassLoader 被丟棄,重新建立一個restart ClassLoader,由於需要載入的類相比較少,所以實現了較快的重啟時間。

Spring Boot熱部署實現方式

Spring Boot有3種熱部署方式:

  1. 使用springloaded配置pom.xml檔案,使用mvn spring-boot:run啟動
  2. 使用springloaded本地載入啟動,配置jvm引數
  3. 使用devtools工具包,操作簡單,但是每次需要重新部署

Spring Boot使用devtools工具包實現熱部署

需要說明以下4點:

  1. devtools可以實現頁面熱部署(即頁面修改後會立即生效,這個可以直接在application.properties檔案中配置spring.thymeleaf.cache=false來實現)
  2. 實現類檔案熱部署(類檔案修改後不會立即生效,過會兒生效)
  3. 實現對屬性檔案的熱部署。即devtools會監聽classpath下的檔案變動,並且會立即重啟應用(發生在儲存的時候)。這裡可能有疑問,為什麼還要重啟?這樣就不是熱部署啦!注意:因為其採用的虛擬機器機制,該項重啟比正常重啟會快非常多!
  4. scope配置為true,在修改java檔案後就立即熱啟動,而且會清空Session中的資料。如果有使用者登陸的話,專案重啟後需要重新登陸。

示例:

pom.xml新增依賴:

<!--devtools熱部署-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
    <scope>true</scope>
</dependency>

application.yml新增devtools的配置 :

spring:
 devtools:
   restart:
     enabled: true  #設定開啟熱部署
     additional-paths: src/main/java #重啟目錄
     exclude: WEB-INF/**
 freemarker:
   cache: false    #頁面不載入快取,修改即時生效

修改了類檔案後,IDEA不會自動編譯,必須修改IDEA設定。

1、
File-Settings-Compiler-Build Project automatically 
2、
ctrl + shift + alt + / ,選擇Registry,勾上 Compiler autoMake allow when app running

Spring Boot訪問靜態資源

Spring Boot預設靜態資源目錄

在Spring Boot應用啟動過程中,會讀取載入一個靜態資原始檔載入路徑這個屬性

# 預設值為
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/

這個屬性的預設值代表靜態資源掃描目錄:

classpath:/META-INF/resources/ 
classpath:/resources/
classpath:/static/ 
classpath:/public/
/:當前專案的根路徑

這意味著我們可以只要把靜態資原始檔存放在以上目錄,即可以被訪問到!

需注意優先順序問題:根據前後關係確定優先順序,配置路徑在前面會優先訪問前面的靜態資源。也就是說如果classpath:/resources/目錄和classpath:/public/都有一個test.html,那麼根據預設的優先順序,會去訪問classpath:/resources/下的資源。

示例:分別建立了public、resources、static目錄,在目錄下建立html靜態頁面。專案啟動後,我們都可以直接訪問這些頁面 。

//請求
http://localhost:8080/index.html
http://localhost:8080/img/test.gif
http://localhost:8080/hello.html //在public和resources資料夾下都有,優先訪問resources下的靜態資源
http://localhost:8080/success.jsp//jsp檔案訪問會直接下載,而不會去解析。

修改Spring Boot靜態資源路徑

我們可以在application.yml檔案中修改靜態資源路徑,如:

# 修改靜態資源載入路徑
spring:
  resources:
    static-locations: classpath:/download,classpath:/static/,classpath:/public/

注意:

如果按照以上寫法會覆蓋Spring Boot的預設路徑。如果希望保留預設路徑,那就要先寫上之前所有值,再最後加上新的路徑。

Spring Boot進階

Spring Boot異常處理

有5種處理方式:

  1. Spring Boot預設異常提示。
  2. 自定義error的錯誤頁面。
  3. @ExceptionHandler註解(Controller中自定義)。
  4. @ControllerAdvice註解 加 @ExceptionHandler註解 抽取所以共用的異常處理方法。
  5. 實現HandlerExceptionResovler。

Spring Boot預設異常提示

在Spring Boot應用執行過程中難免會出現各種錯誤,預設情況下,只要出現異常,就會跳轉到Spring Boot預設錯誤提示頁面,如下:

為了給使用者更好的體驗,我們可以使用以下四種手段來優化異常捕獲的情況。

自定義error的錯誤頁面

SpringBoot應用預設已經提供一套錯誤處理機制:就是把所有後臺錯誤統一交給error請求,然後跳轉到了本身自己的錯誤提示頁面。這時,我們利用springboot的錯誤處理機制,重新建立了一個新的error.html,該頁面必須放在resources的templates目錄下 。

示例:

pom.xml ——頁面用到了Thymeleaf,所以專案中需要匯入Thymeleaf的依賴。如果沒有加依賴則頁面載入失敗繼續調整到第一種springboot預設異常提示頁面上。

error.html

<head>
    <meta charset="UTF-8">
    <title th:text="${title}"></title>
</head>
<body>
<div >
    <div>
        <div>
            <p><span>頁面出現</span><span class="code" th:text="${status}"></span>錯誤,非常抱歉!</p>
            <a href="/" class="btn-back common-button">您可以點選返回首頁</a>
            <div >
                <div th:text="${#dates.format(timestamp,'yyyy-MM-dd HH:mm:ss')}"></div>
                <div>錯誤原因:</div>
                <div th:text="${message}"></div>
                <div th:text="${error}"></div>
            </div>
        </div>
    </div>
</div>
</body>

異常展示:

@ExceptionHandler註解

 /**@ExceptionHandler 註解只能作用為物件的方法上,並且在執行時有效,value() 可以指定異常類。由該註解注*釋的方法可以具有靈活的輸入引數。
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
    Class<? extends Throwable>[] value() default {};
}

示例:

@Controller
public class HelloController {

    @Autowired
    private User user;

    @RequestMapping("/hello")
    @ResponseBody
    public String sayHello() {
        throw new NullPointerException();
        //return "Hello Spring Boot!";
    }

    @RequestMapping("/user")
    @ResponseBody
    public String helloUser() {
        int i = 10 / 0;
        return JSON.toJSONString(user);
    }

    // 處理java.lang.ArithmeticException
    @ExceptionHandler(ArithmeticException.class)
    @ResponseBody
    public String handlerArithmeticException(Exception e) {
        return "數學運算錯誤:" + e.getMessage();
    }

    // 處理java.lang.NullPointerException
    @ExceptionHandler(value = {NullPointerException.class})
    @ResponseBody
    public String handlerNullPointerException(Exception e) {
        // e:該物件包含錯誤資訊
        return "空指標錯誤:" + e;
    }
}

輸出:

@ControllerAdvice註解

剛才的@ExceptionHandler註解是用在控制器類裡面的,這樣每個控制器都需要定義相關方法,比較繁瑣。這時可以使用@ControllerAdvice來抽取所有共同的@ExceptionHandler方法,從而簡化異常方法的定義。

注意:

當業務Controller有自己的@ExceptionHandler註解處理方法時,生效的是Controller上的異常處理方法,@ControllerAdvice裡的不會生效。

示例:

@ControllerAdvice
public class CommonExceptionHandler {

    // 處理java.lang.ArithmeticException
    @ExceptionHandler(ArithmeticException.class)
    @ResponseBody
    public String handlerArithmeticException(Exception e) {
        return "數學運算錯誤1:" + e.getMessage();
    }

    // 處理java.lang.NullPointerException
    @ExceptionHandler(value = {NullPointerException.class})
    @ResponseBody
    public String handlerNullPointerException(Exception e) {
        // e:該物件包含錯誤資訊
        return "空指標錯誤1:" + e;
    }
}

輸出:

HandlerExceptionResovler

注意:

異常處理優先順序順序:Controller層異常 > @ControllerAdvice > HandlerExceptionResovler

示例:

@Configuration
public class CommonHandlerExceptionResolver implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        ModelAndView mv = new ModelAndView();
        //判斷不同異常型別,做不同處理
        if(e instanceof ArithmeticException){
            mv.setViewName("error1");
        }
        if(e instanceof NullPointerException){
            mv.setViewName("error2");
        }
        mv.addObject("error", e.toString());
        return mv;
    }
}

輸出:

Spring Boot表單資料驗證

在Spring Boot中我們經常需要對錶單資料進行合法性驗證。下面講解如何在Spring Boot中進行表單驗證。

更多校驗規則參考Spring,兩者是一樣的。

示例:

pom.xml

<dependencies>
        <!--web起步依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 匯入thymeleaf座標 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
    </dependencies>

Pojo,新增驗證註解

public class User {

    private Integer id;

    @NotEmpty(message = "姓名不能為空")
    private String name;
    @Min(1)
    private Integer age;
    @Email(message = "郵箱地址不正確")
    private String email;
    @NotEmpty(message = "描述不能為空")
    @Length(min = 5, max = 100, message = "描述必須在5-100個字之間")
    private String desc;
    //...}

@Controller
@RequestMapping("/user")
public class UserController {
    /**
     * 跳轉到add.html
     * @return
     */
    @RequestMapping("/toAdd")
    public String toAdd() {
        return "add";
    }
    /**
     * 使用者新增
     * BindingResult: 用於封裝驗證物件(user)裡面的驗證結果
     */
    @RequestMapping("/add")
    public String add(@Valid User user, BindingResult result) {
        if (result.hasErrors()) {
            return "add";
        }
        //save
        System.out.println("儲存使用者:" + JSON.toJSONString(user));
        return "success";
    }
}

設計頁面,回顯錯誤資訊

add.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>使用者新增</title>
</head>
<body>
<h3>使用者新增</h3>
<form action="/user/add" method="post">
    使用者名稱:<input type="text" name="name"/><font color="red" th:errors="${user.name}"></font><br/>
    描述:<input type="text" name="desc"/><font color="red" th:errors="${user.desc}"></font><br/>
    年齡:<input type="text" name="age"/><font color="red" th:errors="${user.age}"></font><br/>
    郵箱:<input type="text" name="email"/><font color="red" th:errors="${user.email}"></font><br/>
    <input type="submit" value="儲存"/>
</form>
</body>
</html>

success.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>提示頁面</title>
</head>
<body>
儲存成功
</body>
</html>

儲存使用者:{"age":25,"desc":"黃金腦殿下","email":"roy@inc.com","name":"艾米"}

Spring Boot檔案上傳

示例:

\resources\static\upload.html——在靜態資原始檔夾下這樣我們就可以直接訪問靜態資源。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>檔案上傳頁面</title>
</head>
<body>
檔案上傳頁面

<hr/>
<form action="/upload" method="post" enctype="multipart/form-data">
    請選擇檔案:<input type="file" name="fileName"/><br/>
    <input type="submit" value="開始上傳"/>
</form>
</body>
</html>

@Controller
public class UploadController {

    @RequestMapping("/upload")
    public String upload(MultipartFile fileName, HttpServletRequest request){
        //處理檔案
        System.out.println("檔案原名稱:"+fileName.getOriginalFilename());
        System.out.println("檔案型別:"+fileName.getContentType());
        String upload = UploadController.class.getResource("/").getFile()+"/upload";
        File file = new File(upload);
        if (!file.exists()) {
            file.mkdir();
        }
        //目標檔案傳入地址路徑+名稱
        try {
            fileName.transferTo(new File(upload + "/" + fileName.getOriginalFilename()));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "success";
    }
}

Spring Boot檔案下載

示例:

\resources\static\download.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>檔案下載</title>
</head>
<body>
<h3>檔案下載</h3>
<a href="/download">下載</a>
</body>
</html>

@Controller
public class DownloadController {

    @RequestMapping("/download")
    public void download(HttpServletResponse response) throws IOException {
        InputStream inputStream = new FileInputStream(DownloadController.class.getResource("/").getFile()+"/static/img/test.gif");
        //2.輸出檔案
        //設定響應頭
        response.setHeader("Content-Disposition","attachment;filename=export.gif");
        OutputStream outputStream = response.getOutputStream();
        byte[] buff = new byte[1024];
        int lenth = 0;
        while ((lenth= inputStream.read(buff))!= -1){
            outputStream.write(buff,0,lenth);
        }
        //3.關閉資源
        outputStream.close();
        inputStream.close();
    }
}

Spring Boot原理分析

@SpringBootApplication

首先,我從引導類開始:

@SpringBootApplication
public class MyBootApplication {

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

}

引導類程式碼很簡單,但可以看出最關鍵的是@SpringBootApplication註解以及在main方法中執行的SpringAppliation.run()了,我們進去@SpringBootApplication的原始碼:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
  ......
}

我們看到@SpringBootApplication其實是一個複合的註解,它就是由@SpringBootConfiguration、@EnableAutoConfiguration以及@ComponentScan 三個註解組成,所以如果我們把SpringBoot啟動類改寫成如下方式,整個SpringBoot應用依然可以與之前的啟動類功能一樣:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public class MyBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyBootApplication.class, args);
    }
}

因為我們每次新建專案時都要寫上三個註解來完成配置,這顯然太繁瑣了,SpringBoot就為我們提供了@SpringBootApplication這樣註解來簡化我們的操作。接著,我們重點分析這三個註解的作用。

@SpringBootConfiguration

我們來看@SpringBootConfiguration註解的原始碼:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

我們可以看到,SpringBoot為了區別@Configuration而新提供的專屬於SpringBoot的註解,功能其實和@Configuration一模一樣。而這裡的@Configuration註解對於我們來說並不陌生,它就是是個IoC容器的配置類。看到這裡,我們其實可以把SpringBoot的啟動類這樣來看就清楚了:

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class MyBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyBootApplication.class, args);
    }
}

啟動類MyBootApplication其實就是一個標準的Spring純註解下的啟動類,也並沒有什麼特殊。

@EnableAutoConfiguration

看到這個註解,我們不禁聯想出Spring 中很多以“@Enable”開頭的註解,比如:@EnableScheduling、@EnableCaching以及@EnableMBeanExport等,@EnableAutoConfiguration註解的理念和工作原理和它們其實一脈相承。簡單的來說,就是該註解藉助@Import註解的支援,Spring的IoC容器收集和註冊特定場景相關的Bean定義:

  • @EnableScheduling是通過@Import將Spring排程框架相關的bean都載入到IoC容器。
  • @EnableMBeanExport是通過@Import將JMX相關的bean定義載入到IoC容器。

而@EnableAutoConfiguration註解也是藉助@Import將所有複合配置條件的bean定義載入到IoC容器,僅此而已!@EnableAutoConfiguration註解的原始碼如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    ...
}

這其中最關鍵的就是@Import(AutoConfigurationImportSelector.class)了,它藉助AutoConfigurationImportSelector.class可以幫助SpringBoot應用將所有符合條件的@Configuration配置類都載入到當前SpringBoot建立並使用的IoC容器,就像下圖一樣。

下面我們給出AutoConfigurationImportSelector.java的部分原始碼,來解釋和驗證上圖:

public class AutoConfigurationImportSelector
        implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered {
    protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
        return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class,
                this.beanClassLoader);
    }
    protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
        return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class,
                this.beanClassLoader);
    }
}

以上原始碼可以看出,@EnableAutoConfiguration正是藉助SpringFactoriesLoader的支援,才能完成所有配置類的載入!

SpringFactoriesLoader

SpringFactoriesLoader屬於Spring框架專屬的一種擴充套件方案(其功能和使用方式類似於Java的SPI方案:java.util.ServiceLoader),它的主要功能就是從指定的配置檔案META-INF/spring.factories中載入配置,spring.factories是一個非常經典的java properties檔案,內容格式是Key=Value形式,只不過這Key以及Value都非常特殊,為Java類的完整類名(Fully Qualified Name),比如:

org.springframework.context.ApplicationListener=org.springframework.boot.autoconfigure.BackgroundPreinitializer

然後Spring框架就可以根據某個型別作為Key來查詢對應的型別名稱列表了,SpringFactories原始碼如下:

public abstract class SpringFactoriesLoader {

    private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);

    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

    public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader){
        ...
    }

    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        ...
    }
    // ...
}

對於@EnableAutoConfiguraion來說,SpringFactoriesLoader的用途和其本意稍微不同,它本意是為了提供SPI擴充套件,而在@EnableAutoConfiguration這個場景下,它更多的是提供了一種配置查詢的功能的支援,也就是根據@EnableAutoConfiguration的完整類名org.springframework.boot.autoconfigure.EnableAutoConfiguration作為Key來獲取一組對應的@Configuration類:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration

在SpringBoot的autoconfigure依賴包中的META-INF檔案下的spring.factories檔案中,我們可以找到以上內容。

總結來說,@EnableAutoConfiguration能實現自動配置的原理就是:SpringFactoriesLoader從classpath中搜尋所有META-INF/spring.fatories檔案,並將其中Key[org.springframework.boot.autoconfigure.EnableAutoConfiguration]對應的Value配置項通過反射的方式例項化為對應的標註了@Configuration的JavaConfig形式的IoC容器配置類,然後彙總到當前使用的IoC容器中。

@ComponentScan

@ComponentScan註解在Spring Boot啟動的時候其實不是必需的!因為我們知道作為Spring框架裡的老成員,@ComponentScan的功能就是自動掃描並載入複合條件的元件或Bean定義,最終將這些Bean定義載入到當前使用的容器中。這個過程,我們可以手工單個進行註冊,不是一定要通過這個註解批量掃描和註冊,所以說@ComponentScan是非必需的。

所以,如果我們當前應用沒有任何Bean定義需要通過@ComponentScan載入到當前SpringBoot應用對應的IoC容器,那麼,去掉@ComponentScan註解,當前的SpringBoot應用依舊可以完美執行!

Spring Boot整合其他技術

Spring Boot整合Servlet

在Spring Boot應用如何我們需要編寫Servlet相關元件(包括Servlet、Filter、Listener),需要怎麼做呢?Spring Boot提供了兩種使用Servlet元件的方式:

  1. 使用@ServletComponentScan註解註冊
  2. 使用@Bean註解註解

@ServletComponentScan

注意:在引導類類必須新增@ServletComponentScan註解,該註解用於掃描應用中Servlet相關元件。

示例:

/**
 * 等同於web.xml配置
 *     <servlet>
 *         <servlet-name>helloServlet</servlet-name>
 *         <servlet-class>com.yiidian.controller.HelloServlet</servlet-class>
 *     </servlet>
 *     <servlet-mapping>
 *            <servlet-name>helloServlet</servlet-name>
 *         <url-pattern>/helloServlet</url-pattern>
 *     </servlet-mapping>
 *
 */
// @WebServlet:宣告該類為Servlet程式
@WebServlet("/hi")
//@WebServlet(name="helloServlet",urlPatterns="/hi")
public class HelloServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("HelloServlet");
        String docType = "<!DOCTYPE html> \n";
        String top = "Hello SpringBoot";
        resp.getWriter().println(docType +
                "<html>\n" +
                "<head><title>" + top + "</title></head>\n" +
                "<body bgcolor=\"#f0f0f0\">\n" +
                "<h1 align=\"center\">" + top + "</h1>\n" +
                "</body></html>");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       doGet(req,resp);
    }
}

//表示對所有servlet都執行過濾操作
//@WebFilter
//表示對路徑為/ 的servlet執行過濾操作
//@WebFilter("/")
//表示對路徑為/hi 的servlet執行過濾操作,兩種寫法都可以
@WebFilter("/hi")
//@WebFilter(filterName="HiFilter",urlPatterns="/hi")
//定義Filter過濾器
public class HiFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("HiFilter init");

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("do HiFilter before");
        //放行執行目標servlet資源
        filterChain.doFilter(servletRequest,servletResponse);
        System.out.println("do HiFilter after");
    }

    @Override
    public void destroy() {
        System.out.println("HiFilter destroy");
    }
}

//定義Listener監聽器
@WebListener
public class HiListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("ServletContext物件建立了");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("ServletContext物件消耗了");
    }
}

引導類

@SpringBootApplication
@ServletComponentScan //註冊Servlet元件
public class MyBootApplication {

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

}

輸出:

ServletContext物件建立了
HiFilter init
do HiFilter before
HelloServlet
do HiFilter after

注意:在測試時發現按照正確配置去寫程式碼但是一直訪問不成功。可能的原因有兩個:

  • IDEA的問題,一直重啟卻識別不了@ServletComponentScan去掃描servlet,導致失敗。
  • 啟動類的@ServletComponentScan掃描預設是掃描與該啟動類同包以及其子包下的類。

@Bean

第二種方式的程式碼和第一種幾乎一樣,就是引導類的差別,引導類改為使用@Bean註解來註解Servlet、Filter和Listener。

示例:

@SpringBootApplication
public class MyBeanBootApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyBeanBootApplication.class,args);
    }
    //註冊Servlet程式
    @Bean
    public ServletRegistrationBean getServletRegistrationBean(){
        ServletRegistrationBean bean = new ServletRegistrationBean(new HelloServlet());
        bean.addUrlMappings("/hi");
        return bean;
    }
    //註冊Filter
    @Bean
    public FilterRegistrationBean getFilterRegistrationBean(){
        FilterRegistrationBean bean = new FilterRegistrationBean(new HiFilter());
        bean.addUrlPatterns("/hi");
        return bean;
    }
    //註冊Listener
    @Bean
    public ServletListenerRegistrationBean getServletListenerRegistrationBean(){
        return new ServletListenerRegistrationBean(new HiListener());
    }
}

注意:如果兩個都配置則filter和listener會執行兩遍。當使用@Bean整合Servlet時,Servlet,Filter,Listener不需要加對應的註解,因為我們在@Bean中已經把new HelloServlet()建立出來了。

Spring Boot整合JSP

示例:

<?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>com.self</groupId>
    <artifactId>hellospringboot</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- 匯入springboot父工程. 注意:任何的SpringBoot工程都必須有的!!! -->
    <!-- 父工程的作用:鎖定起步的依賴的版本號,並沒有真正到依賴 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.11.RELEASE</version>
    </parent>

    <dependencies>
        <!--web起步依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.60</version>
        </dependency>
        <!--devtools熱部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
            <scope>true</scope>
        </dependency>
        <!-- jsp依賴 -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>
 <!--       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </dependency>-->
    </dependencies>
</project>

注意:Error resolving template [userlist], template might not exist or might not be accessible by any of the configured Template Resolvers.這是因為整合jsp就不能依賴thymeleaf。

org.thymeleaf.exceptions.TemplateInputException: Error resolving template [userlist], template might not exist or might not be accessible by any of the configured Template Resolvers
	at org.thymeleaf.engine.TemplateManager.resolveTemplate(TemplateManager.java:869) ~[thymeleaf-3.0.11.RELEASE.jar:3.0.11.RELEASE]
	at org.thymeleaf.engine.TemplateManager.parseAndProcess(TemplateManager.java:607) ~[thymeleaf-3.0.11.RELEASE.jar:3.0.11.RELEASE]



        <!-- 匯入thymeleaf座標 整合jsp就不能依賴thymeleaf,否則會因為請求不到頁面而報錯-->
<!--        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>-->

起步依賴tomcat。因為spring-boot-starter-web已經有spring-boot-starter-tomcat依賴了,如下,因此不再需要再依賴tomcat。

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

擴充套件:

pom中<scope></scope>一些理解
compile:預設值,表示當前依賴包,要參與當前專案的編譯,後續測試,執行時,打包
provided:代表在編譯和測試的時候用,執行,打包的時候不會打包進去
test:表示當前依賴包只參與測試時的工作:比如Junit
runtime:表示當前依賴包只參與執行週期,其他跳過了
system:從參與度和provided一致,不過被依賴項不會從maven遠端倉庫下載,而是從本地的系統拿。需要
systemPath屬性來定義路徑

配置application.yml

#springmvc檢視解析器配置
spring:
  mvc:
    view:
      prefix: /WEB-INF/jsp/  # 字首,注意最後的資料夾jsp後面要加斜槓/
      suffix: .jsp  # 字尾

@Controller
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/list")
    public String list(Model model) {
        //模擬使用者資料
        List<User> list = new ArrayList<User>();
        for (int i = 0; i < 3; i++) {
            User user = new User();
            user.setId(ThreadLocalRandom.current().nextInt());
            user.setAge(ThreadLocalRandom.current().nextInt(100));
            user.setName(UUID.randomUUID().toString());
            list.add(user);
        }
        //把資料存入model
        model.addAttribute("list", list);
        //跳轉到jsp頁面: userlist.jsp
        return "userlist";
    }
}

編寫userlist.jsp頁面

注意:Spring Boot專案並不會到templates目錄查詢JSP頁面,它是到/src/main/webapp目錄下查詢。所以我們需要建立webapp目錄,然後在裡面建立我們需要的目錄和JSP檔案。

<%@ page language="java" contentType="text/html; charset=utf-8"
         pageEncoding="utf-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>使用者列表展示</title>
</head>
<body>

<h3>使用者列表展示</h3>
<table border="1">
    <tr>
        <th>編號</th>
        <th>姓名</th>
        <th>年齡</th>
    </tr>
    <c:forEach items="${list}" var="user">
        <tr>
            <td>${user.id}</td>
            <td>${user.name}</td>
            <td>${user.age}</td>
        </tr>
    </c:forEach>
</table>
</body>
</html>

@SpringBootApplication
public class MyBootApplication {

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

修改專案執行目錄,經過測試,不修改也行,估計和IDEA版本有關。

Spring Boot應用預設情況下,會到應用的classes目錄下載入內容,因為JSP頁面並不在classes下面,所以需要把執行目錄手動改為應用的根目錄下,這樣才能載入到JSP頁面。 如下:

請求:http://localhost:8080/user/list

輸出:

報錯:

如下圖,是因為application.yml配置jsp頁面解析器路徑名稱弄錯。

Spring Boot整合Thymeleaf

thymeleaf怎麼讀?英 [taim li:f] 美 [taɪm lif] 。百里香葉。

關於模板引擎

  1. 市面上主流的 Java 模板引擎有:JSP、Velocity、Freemarker、Thymeleaf。
  2. JSP本質也是模板引擎,Spring Boot官方推薦使用“Thymeleaf”模板引擎。

模板引擎的原理

模板引擎原理圖如下,模板引擎的作用都是將模板(頁面)和資料進行整合然後輸出顯示,區別在於不同的模板使用不同的語法,如 JSP 的JSTL表示式,以及J SP 自己的表示式和語法,同理 Thymeleaf 也有自己的語法。

Spring Boot整合Thymeleaf

示例:

pom.xml匯入Thymeleaf的依賴.

<dependencies>
        <!--web起步依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

@Controller
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/thyme")
    public String thymeLeaf(Model model){
        model.addAttribute("message","thymeLeaf show");
        //跳轉到templates/show.html
        return "show";
    }
   }

注意以下幾點:

  • 模板檔案(即頁面)必須放在/resources/templates目錄下,否則無法渲染。
  • Thymeleaf標籤都是以th開頭的。

show.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>SpringBoot整合Thymeleaf</title>
</head>
<body>
<span th:text="${message}"></span>
</body>
</html>

輸出:

Thymeleaf基本語法

變數輸出

方法程式碼

//變數輸出
@RequestMapping("/demo2")
public String demo2(Model model){
	model.addAttribute("name", "張三");
	return "demo2";
}

頁面程式碼

<h3>變數輸出</h3>
<h4 th:text="${name}"></h4>
<h4 th:text="李四"></h4>

條件判斷及迭代遍歷、域物件使用、超連結

show.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>SpringBoot整合Thymeleaf</title>
</head>
<body>
<span th:text="${message}"></span>

<h3>條件判斷</h3>
<div th:if="${gender} == '男'">
    這是一位男性朋友
</div>
<div th:if="${gender} == '女'">
    這是一位女性朋友
</div>
<br/>
<div th:switch="${grade}">
    <span th:case="1">這是1的情況</span>
    <span th:case="2">這是2的情況</span>
    <span th:case="3">這是3的情況</span>
</div>
<h3>迭代遍歷</h3>
<table border="1">
    <tr>
        <td>編號</td>
        <td>姓名</td>
        <td>年齡</td>
    </tr>
    <tr th:each="user : ${list}">
        <td th:text="${user.id}"></td>
        <td th:text="${user.name}"></td>
        <td th:text="${user.age}"></td>
    </tr>
</table>
<h3>域物件資料的獲取</h3>
request: <span th:text="${#httpServletRequest.getAttribute('request')}"></span><br/>
session: <span th:text="${session.session}"></span><br/>
application: <span th:text="${application.application}"></span><br/>
<h3>超連結的語法</h3>
<a th:href="@{~/user/thyme}">訪問demo1</a><br/>

<a th:href="@{~/user/thyme(id=1,name=eric)}">訪問demo1,傳遞引數</a>
</body>
</html>

@Controller
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/thyme")
    public String thymeLeaf(Model model, HttpServletRequest request){
        model.addAttribute("message","thymeLeaf show");
        model.addAttribute("gender","男");
        model.addAttribute("grade",2);
        List<User> list = new ArrayList<User>();
        for (int i = 0; i < 3; i++) {
            User user = new User();
            user.setId(ThreadLocalRandom.current().nextInt());
            user.setAge(ThreadLocalRandom.current().nextInt(100));
            user.setName(UUID.randomUUID().toString());
            list.add(user);
        }
        //把資料存入model
        model.addAttribute("list", list);
        //request
        request.setAttribute("request", "request's data");
        //session
        request.getSession().setAttribute("session", "session's data");
        //application
        request.getSession().getServletContext().setAttribute("application", "application's data");
        //跳轉到templates/show.html
        return "show";
    }
  }

輸出:

Spring Boot整合FreeMarker

//跳過

Spring Boot整合MyBatis

前置條件:

CREATE TABLE `t_user` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `name` varchar(64) NOT NULL COMMENT '姓名',
  `dept` varchar(254) NOT NULL COMMENT '部門',
  `phone` varchar(16) NOT NULL COMMENT '電話',
  `height` decimal(10,2) DEFAULT NULL COMMENT '身高',
  `create_emp` bigint(20) NOT NULL COMMENT '建立人',
  `create_time` datetime DEFAULT NULL COMMENT '建立時間',
  `modify_emp` bigint(20) DEFAULT NULL COMMENT '修改人',
  `modify_time` datetime DEFAULT NULL COMMENT '修改時間',
  PRIMARY KEY (`id`),
  KEY `idx_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COMMENT='使用者表';

示例:

pom.xml——匯入mybatis和mysql驅動程式

<dependencies>
        <!--web起步依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--mybatis 起步依賴-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.1.1</version>
        </dependency>
        <!-- MySQL 連線驅動 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
    </dependencies>

application.yml——配置資料來源連線引數,及mybatis相關配置。

spring:
	datasource: #修改資料庫連線配置
        url: jdbc:mysql://localhost:3306/hello_mybatis?characterEncoding=UTF8
        driver-class-name: com.mysql.jdbc.Driver
        username: root
        password: 123456

# mybatis配置
mybatis:
  type-aliases-package: com.self.pojo # 別名目錄

public class User {

    private Integer id;

    @NotEmpty(message = "姓名不能為空")
    private String name;
    @Min(1)
    private Integer age;
    @Email(message = "郵箱地址不正確")
    private String email;
    @NotEmpty(message = "描述不能為空")
    @Length(min = 5, max = 100, message = "描述必須在5-100個字之間")
    private String desc;

    /**
     * 部門,帝國
     */
    private String dept;
    /**
     * 聯絡號碼
     */
    private String phone;
    /**
     * 身高
     */
    private BigDecimal height;
    /**
     * 建立人
     */
    private Long createEmp;
    /**
     * 建立時間
     */
    private Date createTime;
    /**
     * 修改人
     */
    private Long modifyEmp;
    /**
     * 修改時間
     */
    private Date modifyTime;
    //...
    }

//必須給Dao介面加上@Mapper,這樣Spring Boot在啟動時才能掃描到Dao介面,併為其生成代理物件。
@Mapper
//裝飾用,告訴spring這是個dao註冊bean,不加@Repository的話IDEA會提示這個bean無法@Autowired自動依賴
@Repository
public interface UserDao {

    public List<User> getUsers();
}

UserDao.xml——Dao對映配置,在Dao介面相同目錄下建立同名的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.self.dao.UserDao">

    <!-- 查詢所有使用者 -->
    <select id="getUsers" resultType="User">
        select * from t_user where 1=1
    </select>
</mapper>

@Controller
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserDao userDao;
    
    @RequestMapping("/showAll")
    @ResponseBody
    public List<User> list(){
        //模擬使用者資料
        List<User> list = userDao.getUsers();
        return list;
    }
}

啟動類不變,輸出:

整合Spring Data JPA

示例:

pom.xml——新增Spring Data JPA的依賴

<dependencies>
        <!--web起步依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- springBoot JPA 的起步依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!-- MySQL 連線驅動 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
    </dependencies>

application.yml

spring:
  datasource: #修改資料庫連線配置
    url: jdbc:mysql://localhost:3306/mybatis?characterEncoding=UTF8
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: root
# jpa配置
  jpa:
    show-sql: true #控制檯輸出生成的SQL語句
    generate-ddl: true # 自動建表

專案使用Spring Data JPA,所以在Pojo實體中必須新增Jpa的對映註解,和資料庫表進行一一對映。

如果pojo欄位不是一一對映,比如比資料庫多了欄位,則會導致執行失敗。

import javax.persistence.*;
@Entity
@Table(name="t_user")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @NotEmpty(message = "姓名不能為空")
    private String name;
    /**
     * 部門,帝國
     */
    private String dept;
    /**
     * 聯絡號碼
     */
    private String phone;
    /**
     * 身高
     */
    private BigDecimal height;
    /**
     * 建立人
     */
    private Long createEmp;
    /**
     * 建立時間
     */
    private Date createTime;
    /**
     * 修改人
     */
    private Long modifyEmp;
    /**
     * 修改時間
     */
    private Date modifyTime;
//...
}

Spring Data JPA提供了兩個核心介面,我們專案中一般選擇繼承它們:

  • JpaRepository介面:擁有CRUD,分頁,排序等方法

  • JpaSpecificationExecutor介面:擁有組合條件搜尋方法

    public interface UserDao extends JpaRepository<User, Integer>, JpaSpecificationExecutor {

    }

    @Controller
    @RequestMapping("/user")
    public class UserController {

      @Autowired
      private UserDao userDao;
    
      @RequestMapping("/showAll")
      @ResponseBody
      public List<User> list(){
          //模擬使用者資料
          List<User> list = userDao.findAll();
          return list;
      }
    }
    

輸出:

Spring Boot整合Redis

前提: 需要Windows啟動Redis Server 。

解壓redis壓縮包。

開啟一個 cmd 視窗 使用 cd 命令切換安裝目錄如 E:\redis 執行:

#啟動redis伺服器
redis-server.exe redis.windows.conf

驗證redis是否啟動成功,另外開啟一個 cmd 視窗 使用 cd 命令切換安裝目錄如 E:\redis 執行:

#啟動redis客戶端
redis-cli.exe -h 127.0.0.1 -p 6379
#執行命令
set name mike
get name

示例:

pom.xml——匯入Spring Data Redis依賴!

 <dependencies>
        <!--web起步依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 配置使用 redis 啟動器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    </dependencies>

application.yml——Spring Boot的Redis配置

#host:代表Redis服務端地址
#port:Java連線Redis的埠
#database:操作的Redis的資料庫索引
spring:
    redis:
        host: localhost # 預設localhost,需要遠端伺服器需要修改
        port: 6379  # 預設6379,如果不一致需要修改
        database: 0 # 代表連線的資料庫索引,預設為0,

在Controller注入RedisTemplate模板物件,利用它來操作Redis資料庫,這裡寫一個put方法,用於往Redis存入資料,一個get方法,從Redis獲取資料。但需要注意的時候,如果操作的Pojo物件,該Pojo必須實現java.io.Serializable介面 。

@Controller
@RequestMapping("/user")
public class UserController {

    @Autowired
    private RedisTemplate redisTemplate;

    @RequestMapping("/load")
    @ResponseBody
    public String loadUsers(){
        List<User> list = userDao.findAll();
        for (User user : list) {
            redisTemplate.opsForValue().set(user.getName(),user);
        }
        return "success";
    }

    @RequestMapping(value = "/get",method = RequestMethod.GET)
    @ResponseBody
    public User getUser(String name){
       return (User) redisTemplate.opsForValue().get(name);
    }
  }

public class User implements Serializable {
    //...
}

請求:
先快取redis
http://localhost:8080/user/load
從redis獲取快取資料
http://localhost:8080/user/get?name=艾米哈珀

輸出:

報錯:Failed to serialize object using DefaultSerializer。這是因為傳輸物件沒有實現序列號介面,無法序列號。

org.springframework.data.redis.serializer.SerializationException: Cannot serialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [com.self.pojo.User]

Spring Boot整合EhCache

EhCache簡介

EhCache 是一個純Java的程式內快取框架,具有快速、精幹等特點,是Hibernate中預設CacheProvider。Ehcache是一種廣泛使用的開源Java分散式快取。主要面向通用快取,Java EE和輕量級容器。它具有記憶體和磁碟儲存,快取載入器,快取擴充套件,快取異常處理程式,一個gzip快取servlet過濾器,支援REST和SOAP api等特點。

配置ehcache.xml——引數說明

引數名 說明
name 快取名稱
maxElementsInMemory 快取最大個數
eternal 物件是否永久有效,一但設定了,timeout將不起作用
timeToIdleSeconds 設定物件在失效前的允許閒置時間(單位:秒)。僅當eternal=false物件不是永久有效時使用,可選屬性,預設值是0,也就是可閒置時間無窮大
timeToLiveSeconds 設定物件在失效前允許存活時間(單位:秒)。最大時間介於建立時間和失效時間之間。僅當eternal=false物件不是永久有效時使用,預設是0.,也就是物件存活時間無窮大
overflowToDisk 當記憶體中物件數量達到maxElementsInMemory時,Ehcache將會物件寫到磁碟中
diskSpoolBufferSizeMB 這個引數設定DiskStore(磁碟快取)的快取區大小。預設是30MB。每個Cache都應該有自己的一個緩衝區
maxElementsOnDisk 硬碟最大快取個數
diskPersistent 是否快取虛擬機器重啟期資料
diskExpiryThreadIntervalSeconds 磁碟失效執行緒執行時間間隔,預設是120秒。
memoryStoreEvictionPolicy 當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理記憶體。預設策略是LRU(最近最少使用)。你可以設定為FIFO(先進先出)或是LFU(較少使用)
clearOnFlush 記憶體數量最大時是否清除

示例:

pom.xml

 <!--web起步依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--springboot 整合 junit 起步依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>2.1.6.RELEASE</version>
            <scope>test</scope>
        </dependency>

        <!-- 快取座標 -->
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-cache -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
            <version>2.1.11.RELEASE</version>
        </dependency>
        <!-- Ehcache支援 -->
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>2.10.6</version>
        </dependency>

ehcache.xml

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">

    <diskStore path="java.io.tmpdir"/>

    <!-- defaultCache: 預設配置 -->
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            maxElementsOnDisk="10000000"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
        <persistence strategy="localTempSwap"/>
    </defaultCache>

    <!-- 快取名稱為user的配置 -->
    <cache name="user"
           maxElementsInMemory="10000"
           eternal="false"
           timeToIdleSeconds="120"
           timeToLiveSeconds="120"
           maxElementsOnDisk="10000000"
           diskExpiryThreadIntervalSeconds="120"
           memoryStoreEvictionPolicy="LRU">
        <persistence strategy="localTempSwap"/>
    </cache>
</ehcache>

application.yml

#配置EhCache的配置spring:
cache:
  ehcache:
    config: ehcache.xml

引導類中需要新增@EnableCaching註解,開啟快取功能 。

@SpringBootApplication
@EnableCaching // 開啟快取
public class MyBootApplication {

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

@Service
public class UserService {

    @Cacheable(value = "user",key = "#id")
    public User findById(Integer id){
        System.out.println("執行了UserService獲取User");
        User user = new User();
        user.setId(5);
        user.setName("林雨裳");
        user.setDept("艾米帝國");
        user.setPhone("911119");
        return user;
    }
}

@Cacheable的屬性:

  • value:對應ehcache.xml的快取配置名稱(name屬性值)

  • key:給快取值起個key,便於Spring內部檢索不同的快取資料。#id這個語法代表把方法形參作為key。

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringBootTest(classes = MyBootApplication.class)
    public class EhCacheTest {

      @Autowired
      private UserService userService;
    
      @Test
      public void testCache(){
          //第一次
          System.out.println(JSON.toJSONString(userService.findById(5)));
          //第二次
          System.out.println(JSON.toJSONString(userService.findById(5)));
      }
    

    }

輸出:

執行了UserService獲取User
{"dept":"艾米帝國","id":5,"name":"林雨裳","phone":"911119"}
{"dept":"艾米帝國","id":5,"name":"林雨裳","phone":"911119"}

從結果可以看出,第一次呼叫Service的時候,到Service內部獲取資料。但是第二次呼叫Service時已經不需要從Service獲取資料,證明第一次查詢的時候已經把Customer物件快取到EhCache中。

EhCache常用註解

註解 說明
@Cacheable 主要針對方法配置,能夠根據方法的請求引數對其進行快取
@CacheConfig 統一配置本類的快取註解的屬性
@CachePut 保證方法被呼叫,又希望結果被快取。與@Cacheable區別在於是否每次都呼叫方法,常用於更新
@CacheEvict 清空快取

@Cacheable/@CachePut/@CacheEvict 主要的引數:

屬性名 說明
value 快取的名稱,在 spring 配置檔案中定義,必須指定至少一個 例如: @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”}
key 快取的 key,可以為空,如果指定要按照 SpEL 表示式編寫, 如果不指定,則預設按照方法的所有引數進行組合 例如: @Cacheable(value=”testcache”,key=”#id”)
condition 快取的條件,可以為空,使用 SpEL 編寫,返回 true 或者 false, 只有為 true 才進行快取/清除快取 例如:@Cacheable(value=”testcache”,condition=”#userName.length()>2”)
unless 否定快取。當條件結果為TRUE時,就不會快取。 @Cacheable(value=”testcache”,unless=”#userName.length()>2”)
allEntries (@CacheEvict ) 是否清空所有快取內容,預設為 false,如果指定為 true, 則方法呼叫後將立即清空所有快取 例如: @CachEvict(value=”testcache”,allEntries=true)
beforeInvocation (@CacheEvict) 是否在方法執行前就清空,預設為 false,如果指定為 true, 則在方法還沒有執行的時候就清空快取,預設情況下,如果方法 執行丟擲異常,則不會清空快取 例如: @CachEvict(value=”testcache”,beforeInvocation=true)

具體參考一點

Spring Boot整合Junit

示例:

pom.xml

 <!--springboot 整合 junit 起步依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>2.1.6.RELEASE</version>
            <scope>test</scope>
        </dependency>

@RunWith(SpringJUnit4ClassRunner.class)
//重點是加入@SpringBootTest註解,屬性classes用於載入引導類
@SpringBootTest(classes = MyBootApplication.class)
public class JUnitTest {

    @Test
    public void test() {
        System.out.println("Hello JUnit");
    }
}

輸出:

Hello JUnit

Spring Boot整合Quartz

Quartz(石英)簡介

Quartz 是一個完全由Java 編寫的開源任務排程的框架,通過觸發器設定作業定時執行規則,控制作業的執行時間。Quartz 定時器作用很多,比如,定時傳送資訊和定時生成報表等。 Quartz 框架主要核心元件包括排程器、觸發器和作業。排程器作為作業的總指揮,觸發器 作為作業的操作者,作業為應用的功能模組。其關係如圖:

示例:

pom.xml

  <!-- sping對schedule的支援 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
        <!--Quartz執行必須依賴到spring-tx包 注意:spring-boot-starter-web已經依賴了,所以不需要再依賴,只需要知道Quartz執行必須依賴到spring-tx包 -->
<!--        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
        </dependency>-->
        <!-- Quartz支援 -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.2.1</version>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-api</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>

Job任務類——我們想實現的定時任務業務程式碼寫在這裡。

public class CheckJob {

    public void task() {
        System.out.println("校驗任務被觸發,當前時間為:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    }

}

public class ReminderJob {

    //具體定時任務
    public void task(){
        System.out.println("呼吸提醒任務被觸發,當前時間為:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    }
}

Quartz配置類——每個排程任務都對應一個配置類

@Configuration
public class CheckQuartzConfig {

    @Bean
    public CheckJob createCheckJob() {
        return new CheckJob();
    }

    /**
     * 建立任務
     */
    @Bean("checkJobDetail")
    public MethodInvokingJobDetailFactoryBean checkJobDetailFactoryBean(CheckJob job) {
        MethodInvokingJobDetailFactoryBean detailFactoryBean = new MethodInvokingJobDetailFactoryBean();
        //設定任務物件
        detailFactoryBean.setTargetObject(job);
        //設定任務方法
        detailFactoryBean.setTargetMethod("task");
        return detailFactoryBean;
    }
    /**
     * 觸發器
     */
    @Bean("checkTrigger")
    public CronTriggerFactoryBean createTrigger(@Qualifier("checkJobDetail") MethodInvokingJobDetailFactoryBean bean) {
        CronTriggerFactoryBean triggerFactoryBean = new CronTriggerFactoryBean();
        triggerFactoryBean.setJobDetail(bean.getObject());
         //	每天11點30分觸發執行一次
        triggerFactoryBean.setCronExpression("0 30 11 * * ? *");
        return triggerFactoryBean;
    }
    /**
     * 建立Schduler
     */
    @Bean("checkScheduler")
    public SchedulerFactoryBean createSchedulerFactoryBean(@Qualifier("checkTrigger") CronTriggerFactoryBean bean){
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        //關聯trigger
        schedulerFactoryBean.setTriggers(bean.getObject());
        return schedulerFactoryBean;
    }
}

@Configuration
public class QuartzConfig {

    @Bean
    public ReminderJob createReminderJob() {
        return new ReminderJob();
    }

    @Bean("reminderJobDetail")
    public MethodInvokingJobDetailFactoryBean reminderJobDetailFactoryBean(ReminderJob job) {
        MethodInvokingJobDetailFactoryBean detailFactoryBean = new MethodInvokingJobDetailFactoryBean();
        detailFactoryBean.setTargetObject(job);
        detailFactoryBean.setTargetMethod("task");
        return detailFactoryBean;
    }


    @Bean("reminderTrigger")
    public CronTriggerFactoryBean createTrigger(@Qualifier("reminderJobDetail") MethodInvokingJobDetailFactoryBean bean) {
        CronTriggerFactoryBean triggerFactoryBean = new CronTriggerFactoryBean();
        triggerFactoryBean.setJobDetail(bean.getObject());
         //定時任務3秒執行一次
        triggerFactoryBean.setCronExpression("0/3 * * * * ? *");
        return triggerFactoryBean;
    }

    @Bean("reminderScheduler")
    public SchedulerFactoryBean createSchedulerFactoryBean(@Qualifier("reminderTrigger") CronTriggerFactoryBean bean){
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        schedulerFactoryBean.setTriggers(bean.getObject());
        return schedulerFactoryBean;
    }
}

執行引導類。

輸出:

呼吸提醒任務被觸發,當前時間為:2020-05-21 11:29:51
呼吸提醒任務被觸發,當前時間為:2020-05-21 11:29:54
呼吸提醒任務被觸發,當前時間為:2020-05-21 11:29:57
呼吸提醒任務被觸發,當前時間為:2020-05-21 11:30:00
校驗任務被觸發,當前時間為:2020-05-21 11:30:00
呼吸提醒任務被觸發,當前時間為:2020-05-21 11:30:03
呼吸提醒任務被觸發,當前時間為:2020-05-21 11:30:06
呼吸提醒任務被觸發,當前時間為:2020-05-21 11:30:09

Cron表示式擴充套件

Cron表示式線上工具

Cron表示式教程

CronTrigger

CronTriggers往往比SimpleTrigger更有用,如果您需要基於日曆的概念,而非SimpleTrigger完全指定的時間間隔,復發的發射工作的時間表。 CronTrigger,你可以指定觸發的時間表如“每星期五中午”,或“每個工作日9:30時”,甚至“每5分鐘一班9:00和10:00逢星期一上午,星期三星期五“。 即便如此,SimpleTrigger一樣,CronTrigger擁有的startTime指定的時間表時生效,指定的時間表時,應停止(可選)結束時間。

Cron表示式

cron的表示式被用來配置CronTrigger例項。 cron的表示式是字串,實際上是由七子表示式,描述個別細節的時間表。這些子表示式是分開的空白,代表:

  1. Seconds
  2. Minutes
  3. Hours
  4. Day-of-Month
  5. Month
  6. Day-of-Week
  7. Year (可選欄位)

例 "0 0 12 ? * WED" 在每星期三下午12:00 執行,

個別子表示式可以包含範圍, 例如,在前面的例子裡("WED")可以替換成 "MON-FRI", "MON, WED, FRI"甚至"MON-WED,SAT". “*” 代表整個時間段.

每一個欄位都有一套可以指定有效值,如

Seconds (秒) :可以用數字0-59 表示,

Minutes(分) :可以用數字0-59 表示,

Hours(時) :可以用數字0-23表示,

Day-of-Month(天) :可以用數字1-31 中的任一一個值,但要注意一些特別的月份

Month(月) :可以用0-11 或用字串 “JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV and DEC” 表示

Day-of-Week(每週):可以用數字1-7表示(1 = 星期日)或用字元口串“SUN, MON, TUE, WED, THU, FRI and SAT”表示

“/”:為特別單位,表示為“每”如“0/15”表示每隔15分鐘執行一次,“0”表示為從“0”分開始, “3/20”表示表示每隔20分鐘執行一次,“3”表示從第3分鐘開始執行

“?”:表示每月的某一天,或第周的某一天

“L”:用於每月,或每週,表示為每月的最後一天,或每個月的最後星期幾如“6L”表示“每月的最後一個星期五”

“W”:表示為最近工作日,如“15W”放在每月(day-of-month)欄位上表示為“到本月15日最近的工作日”

““#”:是用來指定“的”每月第n個工作日,例 在每週(day-of-week)這個欄位中內容為"6#3" or "FRI#3" 則表示“每月第三個星期五”

常用Cron表示式

0 15 10 * * ? * 每天10點15分觸發
0 15 10 * * ? 2017 2017年每天10點15分觸發
0 * 14 * * ? 每天下午的 2點到2點59分每分觸發
0 0/5 14 * * ? 每天下午的 2點到2點59分(整點開始,每隔5分觸發)
0 0/5 14,18 * * ? 每天下午的 2點到2點59分、18點到18點59分(整點開始,每隔5分觸發)
0 0-5 14 * * ? 每天下午的 2點到2點05分每分觸發
0 15 10 ? * 6L 每月最後一週的星期五的10點15分觸發
0 15 10 ? * 6#3 每月的第三週的星期五開始觸發

Spring Boot整合Task

Spring自身有一個定時任務技術,叫Spring Task,本文講解在Spring Boot應用中如何使用Spring Task。

cron表示式複習:

序號 說明 必填 允許值 萬用字元
1 秒 是 0-59 , - * /
2 分 是 0-59 , - * /
3 時 是 0-23 , - * /
4 日 是 1-31 , - * ? / L W
5 月 是 1-12 / JAN-DEC , - * /
6 周 是 1-7 or SUN-SAT , - * ? / L #
7 年 否 1970-2099 , - * /

  1. :表示匹配該域的任意值。假如在Minutes域使用, 即表示每分鐘都會觸發事件。
  2. ?:只能用在DayofMonth和DayofWeek兩個域。它也匹配域的任意值,但實際不會。因為DayofMonth和DayofWeek會相互影響。例如想在每月的20日觸發排程,不管20日到底是星期幾,則只能使用如下寫法: 13 13 15 20 * ?, 其中最後一位只能用?,而不能使用,如果使用表示不管星期幾都會觸發,實際上並不是這樣。
  3. -:表示範圍。例如在Minutes域使用5-20,表示從5分到20分鐘每分鐘觸發一次
  4. /:斜槓前面值表示起始時間開始觸發,後面值表示每隔固定時間觸發一次。例如在Minutes域使用5/20,則意味著20分鐘觸發一次,從第5分鐘開始,5,25,45等分別觸發一次.
  5. ,:表示列出列舉值。例如:在Minutes域使用5,20,則意味著在5和20分每分鐘觸發一次。
  6. L:表示最後,只能出現在DayofWeek和DayofMonth域。如果在DayofWeek域使用5L,意味著在最後的一個星期四觸發。
  7. W:表示有效工作日(週一到週五),只能出現在DayofMonth域,系統將在離指定日期的最近的有效工作日觸發事件。例如:在 DayofMonth使用5W,如果5日是星期六,則將在最近的工作日:星期五,即4日觸發。如果5日是星期天,則在6日(週一)觸發;如果5日在星期一到星期五中的一天,則就在5日觸發。另外一點,W的最近尋找不會跨過月份 。
  8. LW:這兩個字元可以連用,表示在某個月最後一個工作日,即最後一個星期五。
  9. :用於確定每個月第幾個星期幾,只能出現在DayofMonth域。例如在4#2,表示某月的第二個星期三。

示例:

pom.xml

 <dependencies>
        <!--web起步依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- sping對schedule的支援 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
    </dependencies>

//引導類必須加上@EnableScheding註解啟動SpringTask
@SpringBootApplication
@EnableScheduling // 開啟Spring Task
public class MyBootApplication {

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

}

@Component
public class WelcomeTask {
    //“5/20”表示每隔20秒執行一次,“5”表示為從“5”秒開始
    @Scheduled(cron = "5/20 * * * * ?")
    public void task(){
        System.out.println("歡迎任務被觸發,當前時間為:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    }
}

輸出:

2020-05-21 14:04:33.166  INFO 17100 --- [  restartedMain] com.self.MyBootApplication               : Started MyBootApplication in 5.693 seconds (JVM running for 6.831)//啟動成功時間33秒
歡迎任務被觸發,當前時間為:2020-05-21 14:04:45
歡迎任務被觸發,當前時間為:2020-05-21 14:05:05
歡迎任務被觸發,當前時間為:2020-05-21 14:05:25
歡迎任務被觸發,當前時間為:2020-05-21 14:05:45

spring-boot整合日誌功能(logback、log4j2)

springboot為我們已經內建了log元件 。

springboot內建log元件

application.yml

#在application.yml檔案中修改springboot內建日誌級別,預設是info
#方式一:
#debug: true
#方式二:
logging:
  level:
    root: debug

日誌級別從低到高為TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF,級別越高,列印的日誌越少。舉例說明:說將日誌級別設定為Debug,那麼DEBUG, INFO, WARN, ERROR, FATAL, OFF這幾類日誌都會列印。

application.properties檔案中日誌key的第三級的含義為"路徑":

  • 填寫root,能夠指定整個專案(包含jdk原始碼列印的日誌)的日誌級別;
  • 填寫某個包名,能夠指定該包下所有Java檔案的日誌級別,其餘為被指定的Java檔案的日誌級別為預設級別:Info
  • 甚至可以指定任意Java檔案

整合參考

日誌框架選型——都log4J2或者logback都差不多

springboot整合logback

springboot整合log4J2


springboot整合logback

springboot預設依賴了logback,所以不需要新增依賴。

基本配置:

#官方文件中有提到, SpringBoot 的 Logging 配置的級別有7個:TRACE , DEBUG , INFO , WARN , ERROR , FATAL , OFF
#root日誌以INFO級別輸出
logging.level.root=INFO
#springframework.web日誌以WARN級別輸出
logging.level.org.springframework.web=WARN
#hibernate日誌以ERROR級別輸出
logging.level.org.hibernate=ERROR

在resources下建立logback-spring.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!-- scan 配置檔案如果發生改變,將會被重新載入  scanPeriod 檢測間隔時間-->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <contextName>spring-boot-log</contextName>
    <include resource="org/springframework/boot/logging/logback/base.xml"/>
    <!-- 普通日誌 -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>log/spring-boot-log-info.log</file>
        <!-- 迴圈政策:基於時間建立日誌檔案 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日誌命名:單個檔案大於128MB 按照時間+自增i 生成log檔案 -->
            <fileNamePattern>log/spring-boot-log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>128MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!-- 最大儲存時間:30天-->
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <append>true</append>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>info</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- 錯誤日誌 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>log/spring-boot-log-error.log</file>
        <!-- 迴圈政策:基於時間建立日誌檔案 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日誌命名:單個檔案大於2MB 按照時間+自增i 生成log檔案 -->
            <fileNamePattern>log/spring-boot-log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>2MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!-- 最大儲存時間:180天-->
            <maxHistory>180</maxHistory>
        </rollingPolicy>
        <append>true</append>
        <!-- 日誌格式 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!-- 日誌級別過濾器 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 過濾的級別 -->
            <level>ERROR</level>
            <!-- 匹配時的操作:接收(記錄) -->
            <onMatch>ACCEPT</onMatch>
            <!-- 不匹配時的操作:拒絕(不記錄) -->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- 控制檯 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- 日誌格式 -->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!--此日誌appender是為開發使用,只配置最底級別,控制檯輸出的日誌級別是大於或等於此級別的日誌資訊-->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
    </appender>
    <!-- additivity 避免執行2次 -->
    <logger name="com.itstyle"  level="INFO"  additivity="false">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="INFO_FILE"/>
        <appender-ref ref="ERROR_FILE"/>
    </logger>
    <root level="INFO">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="INFO_FILE" />
        <appender-ref ref="ERROR_FILE" />
    </root>
</configuration>

springboot整合log4J2

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions><!-- 去掉springboot預設配置 -->
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency> <!-- 引入log4j2依賴 -->
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

log4j2-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--Configuration後面的status,這個用於設定log4j2自身內部的資訊輸出,可以不設定,當設定成trace時,你會看到log4j2內部各種詳細輸出-->
<!--monitorInterval:Log4j能夠自動檢測修改配置 檔案和重新配置本身,設定間隔秒數-->
<configuration monitorInterval="5">
    <!--日誌級別以及優先順序排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->

    <!--變數配置-->
    <Properties>
        <!-- 格式化輸出:%date表示日期,%thread表示執行緒名,%-5level:級別從左顯示5個字元寬度 %msg:日誌訊息,%n是換行符-->
        <!-- %logger{36} 表示 Logger 名字最長36個字元 -->
        <property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
        <!-- 定義日誌儲存的路徑 -->
        <property name="FILE_PATH" value="logs" />
        <property name="FILE_NAME" value="hellospringboot-log" />
    </Properties>

    <appenders>

        <console name="Console" target="SYSTEM_OUT">
            <!--輸出日誌的格式-->
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <!--控制檯只輸出level及其以上級別的資訊(onMatch),其他的直接拒絕(onMismatch)-->
            <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
        </console>

        <!--檔案會列印出所有資訊,這個log每次執行程式會自動清空,由append屬性決定,適合臨時測試用-->
        <File name="Filelog" fileName="${FILE_PATH}/test.log" append="false">
            <PatternLayout pattern="${LOG_PATTERN}"/>
        </File>

        <!-- 這個會列印出所有的info及以下級別的資訊,每次大小超過size,則這size大小的日誌會自動存入按年份-月份建立的資料夾下面並進行壓縮,作為存檔-->
        <RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/info.log" filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
            <!--控制檯只輸出level及以上級別的資訊(onMatch),其他的直接拒絕(onMismatch)-->
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!--interval屬性用來指定多久滾動一次,預設是1 hour-->
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="10MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy屬性如不設定,則預設為最多同一資料夾下7個檔案開始覆蓋-->
            <DefaultRolloverStrategy max="15"/>
        </RollingFile>

        <!-- 這個會列印出所有的warn及以下級別的資訊,每次大小超過size,則這size大小的日誌會自動存入按年份-月份建立的資料夾下面並進行壓縮,作為存檔-->
        <RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/warn.log" filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
            <!--控制檯只輸出level及以上級別的資訊(onMatch),其他的直接拒絕(onMismatch)-->
            <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!--interval屬性用來指定多久滾動一次,預設是1 hour-->
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="10MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy屬性如不設定,則預設為最多同一資料夾下7個檔案開始覆蓋-->
            <DefaultRolloverStrategy max="15"/>
        </RollingFile>

        <!-- 這個會列印出所有的error及以下級別的資訊,每次大小超過size,則這size大小的日誌會自動存入按年份-月份建立的資料夾下面並進行壓縮,作為存檔-->
        <RollingFile name="RollingFileError" fileName="${FILE_PATH}/error.log" filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
            <!--控制檯只輸出level及以上級別的資訊(onMatch),其他的直接拒絕(onMismatch)-->
            <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!--interval屬性用來指定多久滾動一次,預設是1 hour-->
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="10MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy屬性如不設定,則預設為最多同一資料夾下7個檔案開始覆蓋-->
            <DefaultRolloverStrategy max="15"/>
        </RollingFile>

    </appenders>

    <!--Logger節點用來單獨指定日誌的形式,比如要為指定包下的class指定不同的日誌級別等。-->
    <!--然後定義loggers,只有定義了logger並引入的appender,appender才會生效-->
    <loggers>

        <!--過濾掉spring和mybatis的一些無用的DEBUG資訊-->
        <logger name="org.mybatis" level="info" additivity="false">
            <AppenderRef ref="Console"/>
        </logger>
        <!--監控系統資訊-->
        <!--若是additivity設為false,則 子Logger 只會在自己的appender裡輸出,而不會在 父Logger 的appender裡輸出。-->
        <Logger name="org.springframework" level="info" additivity="false">
            <AppenderRef ref="Console"/>
        </Logger>

        <root level="info">
            <appender-ref ref="Console"/>
            <appender-ref ref="Filelog"/>
            <appender-ref ref="RollingFileInfo"/>
            <appender-ref ref="RollingFileWarn"/>
            <appender-ref ref="RollingFileError"/>
        </root>
    </loggers>

</configuration>

配置引數詳解

日誌級別

機制:如果一條日誌資訊的級別大於等於配置檔案的級別,就記錄。

  • trace:追蹤,就是程式推進一下,可以寫個trace輸出
  • debug:除錯,一般作為最低階別,trace基本不用。
  • info:輸出重要的資訊,使用較多
  • warn:警告,有些資訊不是錯誤資訊,但也要給程式設計師一些提示。
  • error:錯誤資訊。用的也很多。
  • fatal:致命錯誤。

輸出源

  • CONSOLE(輸出到控制檯)
  • FILE(輸出到檔案)

格式

  • SimpleLayout:以簡單的形式顯示
  • HTMLLayout:以HTML表格顯示
  • PatternLayout:自定義形式顯示

PatternLayout自定義日誌佈局:

%d{yyyy-MM-dd HH:mm:ss, SSS} : 日誌生產時間,輸出到毫秒的時間
%-5level : 輸出日誌級別,-5表示左對齊並且固定輸出5個字元,如果不足在右邊補0
%c : logger的名稱(%logger)
%t : 輸出當前執行緒名稱
%p : 日誌輸出格式
%m : 日誌內容,即 logger.info("message")
%n : 換行符
%C : Java類名(%F)
%L : 行號
%M : 方法名
%l : 輸出語句所在的行數, 包括類名、方法名、檔名、行數
hostName : 本地機器名
hostAddress : 本地ip地址

Log4j2配置詳解

1、根節點Configuration

有兩個屬性:

  • status
  • monitorinterval

有兩個子節點:

  • Appenders
  • Loggers(表明可以定義多個Appender和Logger).

status用來指定log4j本身的列印日誌的級別.

monitorinterval用於指定log4j自動重新配置的監測間隔時間,單位是s,最小是5s.

2、Appenders節點

常見的有三種子節點:Console、RollingFile、File

Console節點用來定義輸出到控制檯的Appender.

  • name:指定Appender的名字.
  • target:SYSTEM_OUT 或 SYSTEM_ERR,一般只設定預設:SYSTEM_OUT.
  • PatternLayout:輸出格式,不設定預設為:%m%n.

File節點用來定義輸出到指定位置的檔案的Appender.

  • name:指定Appender的名字.
  • fileName:指定輸出日誌的目的檔案帶全路徑的檔名.
  • PatternLayout:輸出格式,不設定預設為:%m%n.

RollingFile節點用來定義超過指定條件自動刪除舊的建立新的Appender.

  • name:指定Appender的名字.
  • fileName:指定輸出日誌的目的檔案帶全路徑的檔名.
  • PatternLayout:輸出格式,不設定預設為:%m%n.
  • filePattern : 指定當發生Rolling時,檔案的轉移和重新命名規則.
  • Policies:指定滾動日誌的策略,就是什麼時候進行新建日誌檔案輸出日誌.
  • TimeBasedTriggeringPolicy:Policies子節點,基於時間的滾動策略,interval屬性用來指定多久滾動一次,預設是1 hour。modulate=true用來調整時間:比如現在是早上3am,interval是4,那麼第一次滾動是在4am,接著是8am,12am...而不是7am.
  • SizeBasedTriggeringPolicy:Policies子節點,基於指定檔案大小的滾動策略,size屬性用來定義每個日誌檔案的大小.
  • DefaultRolloverStrategy:用來指定同一個資料夾下最多有幾個日誌檔案時開始刪除最舊的,建立新的(通過max屬性)。

Loggers節點,常見的有兩種:Root和Logger.

Root節點用來指定專案的根日誌,如果沒有單獨指定Logger,那麼就會預設使用該Root日誌輸出

  • level:日誌輸出級別,共有8個級別,按照從低到高為:All < Trace < Debug < Info < Warn < Error < AppenderRef:Root的子節點,用來指定該日誌輸出到哪個Appender.
  • Logger節點用來單獨指定日誌的形式,比如要為指定包下的class指定不同的日誌級別等。
  • level:日誌輸出級別,共有8個級別,按照從低到高為:All < Trace < Debug < Info < Warn < Error < Fatal < OFF.
  • name:用來指定該Logger所適用的類或者類所在的包全路徑,繼承自Root節點.
  • AppenderRef:Logger的子節點,用來指定該日誌輸出到哪個Appender,如果沒有指定,就會預設繼承自Root.如果指定了,那麼會在指定的這個Appender和Root的Appender中都會輸出,此時我們可以設定Logger的additivity="false"只在自定義的Appender中進行輸出。

例項:

LoggerFactory建立Logger類。

@Component
public class WelcomeTask {
    private static final Logger logger = LoggerFactory.getLogger(UserController.class);

    //“5/20”表示每隔20秒執行一次,“5”表示為從“5”秒開始
    @Scheduled(cron = "5/5 * * * * ?")
    public void task(){
        logger.debug("進入WelcomeTask 定時任務!");
        System.out.println("歡迎任務被觸發,當前時間為:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        logger.error("完成WelcomeTask 定時任務!");
    }
}

lombok工具簡化建立Logger類。

@Component
@Slf4j
public class WelcomeTask {
    //“5/20”表示每隔20秒執行一次,“5”表示為從“5”秒開始
    @Scheduled(cron = "5/5 * * * * ?")
    public void task(){
        log.debug("進入WelcomeTask 定時任務!");
        System.out.println("歡迎任務被觸發,當前時間為:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        log.error("完成WelcomeTask 定時任務!");
    }
}

lombok使用參考教程。

lombok就是一個註解工具jar包,能幫助我們省略一繁雜的程式碼。

lombok使用實踐

pom.xml依賴

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.20</version>
</dependency>

IDEA外掛裡搜尋lombok外掛安裝,重啟 。

常用註解

  1. @Setter 註解在類或欄位,註解在類時為所有欄位生成setter方法,註解在欄位上時只為該欄位生成setter方法。
  2. @Getter 使用方法同上,區別在於生成的是getter方法。
  3. @ToString 註解在類,新增toString方法。
  4. @EqualsAndHashCode 註解在類,生成hashCode和equals方法。
  5. @NoArgsConstructor 註解在類,生成無參的構造方法。
  6. @RequiredArgsConstructor 註解在類,為類中需要特殊處理的欄位生成構造方法,比如final和被@NonNull註解的欄位。
  7. @AllArgsConstructor 註解在類,生成包含類中所有欄位的構造方法。
  8. @Data 註解在類,為類的所有欄位註解@ToString、@EqualsAndHashCode、@Getter的便捷方法,同時為所有非final欄位註解@Setter。

疑問:

1、Q:軟體版本的GA 代表什麼意思?

A:GA:General Availability,正式釋出的版本,在國外都是用GA來說明release版本的 .

引申:

參考

Alpha:是內部測試版,一般不向外部發布,會有很多Bug.一般只有測試人員使用。

Beta:也是測試版,這個階段的版本會一直加入新的功能。在Alpha版之後推出。

RC:(Release Candidate) 顧名思義麼 ! 用在軟體上就是候選版本。系統平臺上就是發行候選版本。RC版不會再加入新的功能了,主要著重於除錯。

GA:General Availability,正式釋出的版本,在國外都是用GA來說明release版本的。

RTM:(Release to Manufacture)是給工廠大量壓片的版本,內容跟正式版是一樣的,不過RTM版也有出限制、評估版的。但是和正式版本的主要程式程式碼都是一樣的。

OEM:是給計算機廠商隨著計算機販賣的,也就是隨機版。只能隨機器出貨,不能零售。只能全新安裝,不能從舊有作業系統升級。包裝不像零售版精美,通常只有一面CD和說明書(授權書)。

RVL:號稱是正式版,其實RVL根本不是版本的名稱。它是中文版/英文版文件破解出來的。

EVAL:而流通在網路上的EVAL版,與“評估版”類似,功能上和零售版沒有區別。

RTL:Retail(零售版)是真正的正式版,正式上架零售版。在安裝盤的i386資料夾裡有一個eula.txt,最後有一行EULAID,就是你的版本。比如簡體中文正式版是EULAID:WX.4_PRO_RTL_CN,繁體中文正式版是WX.4_PRO_RTL_TW。其中:如果是WX.開頭是正式版,WB.開頭是測試版。PRE,代表家庭版;PRO,代表專業版。

α、β、λ常用來表示軟體測試過程中的三個階段,α是第一階段,一般只供內部測試使用;β是第二個階段,已經消除了軟體中大部分的不完善之處,但仍有可能還存在缺陷和漏洞,一般只提供給特定的使用者群來測試使用;λ是第三個階段,此時產品已經相當成熟,只需在個別地方再做進一步的優化處理即可上市發行。

2、Q:springboot的核心功能起步依賴和自動配置詳解?

3、Q: 自定義配置怎麼取值使用,什麼場景使用?

4、Q: 每次釋出的時候替換掉配置檔案,這樣太麻煩了,Spring Boot的Profile就給我們提供瞭解決方案,命令帶上引數就搞定。 是指打包命令帶上引數就能自動載入不同的環境變數配置麼?我們一般是如何打生產包或測試包部署的?怎麼通過jekeins實現不同環境的打包部署?

5、Q: yml配置檔案屬性值是大小寫敏感的麼?教程裡說是,可實際測試中並不是?怎麼理解?

#基本型別 注意:屬性值大小寫敏感
firstName: Bruce Wayne1111
age: 29

@Controller
public class ConfigController {

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

    @RequestMapping("/show")
    @ResponseBody
    public String showConfig() {
        return name + " : " + age;
    }
}

輸出:

Bruce Wayne1111 : 29

6、Q: 下面@Value取值的表示式怎麼理解符號#的作用?已經什麼時候可以用split()方法?

 @Value("#{'${user.list}'.split(',')}")
    private List<String> list;

7、Q: Spring Boot3種熱部署方式前兩種如何實現?

Spring Boot有3種熱部署方式:

  1. 使用springloaded配置pom.xml檔案,使用mvn spring-boot:run啟動
  2. 使用springloaded本地載入啟動,配置jvm引數
  3. 使用devtools工具包,操作簡單,但是每次需要重新部署

8、Q: 什麼是Thymeleaf ?

9、Q: @Configuration註解的作用?對於下面這個異常處理類的作用?

@Configuration
public class CommonHandlerExceptionResolver implements HandlerExceptionResolver {
}

10、Q: java註解和spring註解需要系統研究一遍。

11、Q: 啟動類SpringbootDemoApplication其實就是一個標準的Spring純註解下的啟動類 。怎麼理解Spring純註解下的啟動類?

12、Q: SPI 方案?

A:SPI 全稱為 (Service Provider Interface),即服務提供商介面,是JDK內建的一種服務提供發現機制。目前有不少框架用它來做服務的擴充套件發現,簡單來說,它就是一種動態替換發現服務實現者的機制。

Java擴充套件方法之SPI

其他:

1、COC:Convention over Configuration,即約定大於配置。

72法則

72法則指以1%的複利計息,72年後(72是約數,準確值是ln2/ln1.01),本金翻倍的規律。

相關文章