天天看點

SpringBoot 2.X快速掌握

0、重寫博文的原因

  • 當初我的SpringBoot系列的知識是采用分節來寫的,即:每一個知識點為一篇博文,但是:最近我黴到家了,我發現有些博文神奇般地打不開了,害我去找當初的markdown筆記,但是友善的話還是線上版舒服,隻要有網就可以通路,是以昨天晚上東拼西湊搞出了這篇SpringBoot基礎系列知識

1、什麼是SpringBoot?

1.1、百度百科一下

SpringBoot 2.X快速掌握

2、對SpringBoot快速上手

2.1、通過官網來建立 - 了解

這裡面的建立方式不做過多說明,隻需要在官網裡面建立好了,然後下載下傳解壓,就可以了,我這裡直接使用編輯器建立

springboot搭建項目官網

SpringBoot 2.X快速掌握

2.2、使用IDEA編輯器建立

SpringBoot 2.X快速掌握
SpringBoot 2.X快速掌握
SpringBoot 2.X快速掌握

選完之後,idea就會去拉取相應的jar包,建立結果如下:

SpringBoot 2.X快速掌握
啟動項目
SpringBoot 2.X快速掌握
SpringBoot 2.X快速掌握
編寫controller
SpringBoot 2.X快速掌握

重新啟動主方法,輸入請求

SpringBoot 2.X快速掌握

這樣就建立成功了一個springboot項目

3、小彩蛋 - banner

SpringBoot 2.X快速掌握

上面這玩意兒,我不想看到它

  • 推薦一個網址:https://www.bootschool.net/ascii-art
SpringBoot 2.X快速掌握
SpringBoot 2.X快速掌握
  • 在項目的resources資源目錄下,建立一個banner檔案
    SpringBoot 2.X快速掌握
  • 再運作項目主方法
    SpringBoot 2.X快速掌握
    • 至于這個banner為什麼可以啟動,在下一篇小部落格中說SpringBoot的原理圖時,裡面有

4、了解yml文法

  • 這玩意兒的文法就像如下圖的類與屬性的關系一樣,層層遞進的( 注意:使用yml文法時,每句的結尾别有空格,容易出問題,另外:IDEA中采用tab縮進沒問題,但是:在其他地方,如:linux中,使用yml文法時,别用tab縮進,也容易導緻程式啟動不起來 )
SpringBoot 2.X快速掌握

4.1、使用yml給實體類指派

  • 準備工作:導入依賴
<!--這個jar包就是為了實體類中使用@ConfigurationProperties(prefix = "person")這個注解而不報紅-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

           
  • 使用

    @ConfigurationProperties

    注解實作給實體類屬性指派
    SpringBoot 2.X快速掌握
SpringBoot 2.X快速掌握
  • 測試
    SpringBoot 2.X快速掌握

5、jsr303檢驗

  • jsr303這是資料檢驗的規範,基于這個的實作方式有好幾個,自行百度一下,然後注解含義都是和下面列出來的差不多
依賴
<!--JSR303校驗的依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

           
使用jsr303檢驗
SpringBoot 2.X快速掌握
  • 可以搭配的注解如下:
空檢查
	@Null 驗證對象是否為null
	@NotNull 驗證對象是否不為null, 無法查檢長度為0的字元串
	@NotBlank 檢查限制字元串是不是Null還有被Trim的長度是否大于0,隻對字元串,且會去掉前後空格.
	@NotEmpty 檢查限制元素是否為NULL或者是EMPTY.


Booelan檢查
	@AssertTrue 驗證 Boolean 對象是否為 true
	@AssertFalse 驗證 Boolean 對象是否為 false


長度檢查
	@Size(min=, max=) 驗證對象(Array,Collection,Map,String)長度是否在給定的範圍之内
	@Length(min=, max=) Validates that the annotated string is between min and max included.


日期檢查
	@Past 驗證 Date 和 Calendar 對象是否在目前時間之前,驗證成立的話被注釋的元素一定是一個過去的日期
	@Future 驗證 Date 和 Calendar 對象是否在目前時間之後 ,驗證成立的話被注釋的元素一定是一個将來的日期
	@Pattern 驗證 String 對象是否符合正規表達式的規則,被注釋的元素符合制定的正規表達式,regexp:正規表達式 flags: 指定 Pattern.Flag 的數組,表示正規表達式的相關選項。


數值檢查
	建議使用在Stirng,Integer類型,不建議使用在int類型上,因為表單值為“”時無法轉換為int,但可以轉換為Stirng為”“,Integer為null
	@Min 驗證 Number 和 String 對象是否大等于指定的值
	@Max 驗證 Number 和 String 對象是否小等于指定的值
	@DecimalMax 被标注的值必須不大于限制中指定的最大值. 這個限制的參數是一個通過BigDecimal定義的最大值的字元串表示.小數存在精度
	@DecimalMin 被标注的值必須不小于限制中指定的最小值. 這個限制的參數是一個通過BigDecimal定義的最小值的字元串表示.小數存在精度
	@Digits 驗證 Number 和 String 的構成是否合法
	@Digits(integer=,fraction=) 驗證字元串是否是符合指定格式的數字,interger指定整數精度,fraction指定小數精度。
	@Range(min=, max=) 被指定的元素必須在合适的範圍内
	@Range(min=10000,max=50000,message=”range.bean.wage”)
	@Valid 遞歸的對關聯對象進行校驗, 如果關聯對象是個集合或者數組,那麼對其中的元素進行遞歸校驗,如果是一個map,則對其中的值部分進行校驗.(是否進行遞歸驗證)
	@CreditCardNumber信用卡驗證
	@Email 驗證是否是郵件位址,如果為null,不進行驗證,算通過驗證。
	@ScriptAssert(lang= ,script=, alias=)
	@URL(protocol=,host=, port=,regexp=, flags=)

           

6、yml多環境配置

SpringBoot 2.X快速掌握
SpringBoot 2.X快速掌握

還有一種标準的配置,即:采用多個yml檔案,如:

application-test.yml 就是測試環境的配置

appilication-dev.yml 就是開發環境的配置

appilication-pro.yml 就是生産環境配置

7、設定預設首頁

  • 這是SpringBoot + thmeleaf響應式程式設計的技術,現在前後端分離,這種東西其實沒什麼鳥用

7.1、頁面在static目錄中時

  • 直接在controller中編寫跳轉位址即可
    SpringBoot 2.X快速掌握

7.2、頁面在templates模闆引擎中時

SpringBoot 2.X快速掌握
這種需要導入相應的啟動器
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

           
編寫controller
SpringBoot 2.X快速掌握
測試
SpringBoot 2.X快速掌握

8、簡單認識thymeleaf

  • 這是SpringBoot + thmeleaf響應式程式設計的技術,現在前後端分離,這種東西其實沒什麼鳥用
官網學習位址
  • https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html

8.1、什麼是thymeleaf?

一張圖看明白:

  • SpringBoot 2.X快速掌握

解讀:

  • 前端交給我們的頁面,是html頁面。如果是我們以前開發,我們需要把他們轉成jsp頁面,jsp好處就是當我們查出一些資料轉發到JSP頁面以後,我們可以用jsp輕松實作資料的顯示,及互動等
  • jsp支援非常強大的功能,包括能寫Java代碼,但是,SpringBoot是以jar的方式,不是war,第二,我們用的還是嵌入式的Tomcat,是以,springboot現在預設是不支援jsp的
  • 那不支援jsp,如果我們直接用純靜态頁面的方式,那給我們開發會帶來非常大的麻煩,那怎麼辦?

SpringBoot推薦使用模闆引擎:

  • 模闆引擎,jsp就是一個模闆引擎,還有用的比較多的FreeMaker、Velocity,再多的模闆引擎,他們的思想都是一樣的,SpringBoot推薦使用thymeleaf
    • 模闆引擎的作用就是我們來寫一個頁面模闆,比如有些值,是動态的,我們寫一些表達式。而這些值從哪來?就是我們在背景封裝一些資料。然後把這個模闆和這個資料交給模闆引擎,模闆引擎按照我們封裝的資料把這表達式解析出來、填充到我們指定的位置,然後把這個資料最終生成一個我們想要的内容進而最後顯示出來,這就是模闆引擎。
    • 不管是jsp還是其他模闆引擎,都是這個思想。隻不過,不同模闆引擎之間,他們可能文法有點不一樣。其他的就不介紹了,這裡主要介紹一下SpringBoot給我們推薦的Thymeleaf模闆引擎,這模闆引擎,是一個進階語言的模闆引擎,他的這個文法更簡單。而且功能更強大

8.2、thymeleaf的取資料方式

  • 官網中有說明
    SpringBoot 2.X快速掌握

提取出來看一下,進而在springboot中示範一下

  • 簡單的表達:
    • 變量表達式: ${...}
    • 選擇變量表達式: *{...}
    • 消息表達: #{...}
    • 連結 URL 表達式: @{...}
    • 片段表達式: ~{...}

8.3、在springboot中使用thymeleaf

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

           
怎麼使用thymeleaf?
  • 這個問題換言之就是:html檔案應該放到什麼目錄下
    • 前面我們已經導入了依賴,那麼按照springboot的原理( 本篇部落格結尾附有原理連結 ),底層會幫我們導入相應的東西,并做了相應的配置,那麼就去看一下源碼,進而知道我們應該把檔案放在什麼地方( 注:springboot中和配置相關的都在xxxxxProperties檔案中,是以:去看一下thymeleaf對應的thymeleafProperties檔案 )
SpringBoot 2.X快速掌握
  • 那就來建一個
SpringBoot 2.X快速掌握
  • 編寫controller,讓其跳到templates目錄的頁面中去
    SpringBoot 2.X快速掌握
  • 測試
    SpringBoot 2.X快速掌握
  • 成功跳過去了

8.4、延伸:傳輸資料

8.4.1、開胃菜

  • 參照官網來( 這裡隻示範 變量表達式: ${...},其他的都是一樣的原理 )
SpringBoot 2.X快速掌握
編寫背景,存入資料
SpringBoot 2.X快速掌握
在前台擷取資料
SpringBoot 2.X快速掌握
表空間限制連結如下,這個在thymeleaf官網中有
xmlns:th="http://www.thymeleaf.org"

           
測試:
SpringBoot 2.X快速掌握

8.4.2、開整

SpringBoot 2.X快速掌握
背景
SpringBoot 2.X快速掌握
前台
SpringBoot 2.X快速掌握
測試
SpringBoot 2.X快速掌握

其他的玩法都差不多

9、靜态資源處理方式

  • 在前面玩了thymeleaf,在resources中還有一個目錄是static
SpringBoot 2.X快速掌握
  • 那麼就來研究一下靜态資源:靜态資源,springboot底層是怎麼去裝配的?
    • 都在WebMvcAutoConfiguration有答案,去看一下
SpringBoot 2.X快速掌握
  • 通過上述的源碼發現兩個東西:

    webjars

    getStaticLocations()

9.1、webjars的方式處理靜态資源

  • webjars的官網:https://www.webjars.org/all
  • 進去之後:裡面就是各種各樣的jar包
使用jQuery做示範
  • 導入jQuery的依賴
<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.4.1</version>
</dependency>

           
SpringBoot 2.X快速掌握
  • 導入之後:發現多了這麼一個jar包,現在我們去直接通路一下
SpringBoot 2.X快速掌握
  • 是可以直接通路的,為什麼?
SpringBoot 2.X快速掌握
getStaticLocations(),點進去看一下

發現是如下這麼一個方法

public String[] getStaticLocations() {
            return this.staticLocations;
        }

           
  • 檢視

    staticLocations

SpringBoot 2.X快速掌握
SpringBoot 2.X快速掌握
"classpath:/META-INF/resources/",   <!--這個就不多說明,指的就是再建一個META-INF檔案夾,裡面再建一個resources目錄,參照Java基礎中的web項目目錄-->

"classpath:/resources/",

"classpath:/static/",

"classpath:/public/"

           
  • 發現有四種方式可以放靜态資源,那就來測試一下

9.1.1、resources/ static/ public的優先級

SpringBoot 2.X快速掌握
測試
SpringBoot 2.X快速掌握
  • 發現resources下的優先級最高
删掉resources中的資源檔案,繼續測試
SpringBoot 2.X快速掌握
SpringBoot 2.X快速掌握
  • 發現static目錄其次

9.1.1.1、總結:resources、static、public優先級

  • resources目錄下的優先級最高
  • 其次是static
  • 最後是public

資源放置建議:

  • public放置公有的資源,如:img、js、css....
  • static放置靜态通路的頁面,如:登入、注冊....
  • resources,應該說是templates,放置動态資源,如:使用者管理.....

10、整合jdbc、druid、druid實作日志監控

10.1、整合jdbc、druid

依賴
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>


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

           
編寫application.yml
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis_spring?useUnicode=true&characterEncoding=utf-8
    username: root
    password: "072413"

           
測試
SpringBoot 2.X快速掌握

10.2、整合druid

依賴
<!--要玩druid的話,需要導入下面這個依賴 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>

           
修改yml檔案
SpringBoot 2.X快速掌握
測試
SpringBoot 2.X快速掌握

10.3、druid實作日志監控

  • 注意點:需要web啟動器支援
<!--
	玩druid實作監控日志,需要web啟動器支援,因為:druid的statViewServlet本質是繼承了servlet
	是以:需要web的依賴支援 / servlet支援
-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

           
編寫配置
package cn.xiegongzi.config;

// 這個類是為了延伸druid的強大功能 ————— 監控背景
// 注意:這個需要spring的web啟動器支援,即:這個監控背景的本質StatViewServlet就是servlet,是以需要servlet支援

import com.alibaba.druid.support.http.StatViewServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;

@Configuration
public class DruidConfig {

    @Bean
    public ServletRegistrationBean StatViewServlet() {

        ServletRegistrationBean bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");

        HashMap<String, String> initParameters = new HashMap<>();

    // 下面這些參數可以在 com.alibaba.druid.support.http.StatViewServlet
    // 的父類 com.alibaba.druid.support.http.ResourceServlet 中找到
        initParameters.put("loginUsername", "zixieqing");  // 登入日志監控的使用者名
        initParameters.put("loginPassword", "072413");    // 登入密碼

        initParameters.put("allow", "`localhost`");    // 運作誰可以通路日志監控

        bean.setInitParameters(initParameters);
        return bean;
    }
}

           
測試
SpringBoot 2.X快速掌握
SpringBoot 2.X快速掌握

11、整合mybatis

  • 注:複雜sql使用xml,簡單sql使用注解

11.1、xml版

導入依賴
<!--
        mybatis-spring-boot-starter是第三方( mybatis )jar包,不是spring官網的
        spring自己的生态是:spring-boot-stater-xxxx
-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>

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

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

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

           
編寫實體
package cn.xiegongzi.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {

    private Integer id;
    private String username;
    private String password;
}

           
編寫dao / mapper層
package cn.xiegongzi.mapper;

import cn.xiegongzi.entity.User;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

/*
*   @Mapper 這個注解是mybati-spring提供的,即:和自動裝配是一樣的效果
*   還可以用:
*       @Repository   是spring本身提供的
* 
*   以及:在啟動類( main )中使用@mapperScan掃包
* */

@Mapper

public interface IUserMapper {

    List<User> findALLUser();
}

           
編寫xml的sql語句
SpringBoot 2.X快速掌握
  • 注意點:dao層 / mapper和xml的同包同名問題
編寫yml
# 編寫連接配接池
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis_spring?useUnicode=true&characterEncoding=utf-8
    username: root
    password: "072413"
    type: com.alibaba.druid.pool.DruidDataSource
# 把實作類xml檔案添加進來
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: cn.xiegongzi.entity   # 給實體類配置别名
  configuration:
    map-underscore-to-camel-case: true    # 開啟駝峰命名映射

           
SpringBoot 2.X快速掌握
測試
SpringBoot 2.X快速掌握

11.2、注解版

  • 和ssm整合中的玩法一樣,隻改動一個地方即可,就是不需要xml了
  • 直接在dao層 / mapper的接口方法頭上用

    @insert()

    @delete()

    @update()

    @select()

    注解,然後小括号中編寫sql字元串即可
SpringBoot 2.X快速掌握
  • 當然:也可以給日志設定級别
SpringBoot 2.X快速掌握

12、整合pageHelper分頁插件

依賴
<dependency>
	<groupId>com.github.pagehelper</groupId>
	<artifactId>pagehelper-spring-boot-starter</artifactId>
	<version>1.2.5</version>
</dependency>


           
測試
SpringBoot 2.X快速掌握

13、內建swagger

  • 理論知識濾過,自行百度百科swagger是什麼
  • swagger的常見注解和解讀網址:http://c.biancheng.net/view/5533.html

13.1、快速上手

導入依賴
<!--swagger所需要的依賴-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.8.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.8.0</version>
        </dependency>
        <!--這個依賴是為了渲染swagger文檔頁面的( 為了好看一點罷了 ) ,swagger真正的依賴是上面兩個-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>swagger-bootstrap-ui</artifactId>
            <version>1.8.5</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.75</version>
        </dependency>

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

           
編寫swagger配置檔案
SpringBoot 2.X快速掌握
package cn.xiegongzi.config;


	import org.springframework.context.annotation.Bean;
	import org.springframework.context.annotation.Configuration;
	import springfox.documentation.builders.ApiInfoBuilder;
	import springfox.documentation.builders.PathSelectors;
	import springfox.documentation.builders.RequestHandlerSelectors;
	import springfox.documentation.service.ApiInfo;
	import springfox.documentation.service.Contact;
	import springfox.documentation.spi.DocumentationType;
	import springfox.documentation.spring.web.plugins.Docket;
	import springfox.documentation.swagger2.annotations.EnableSwagger2;

	@Configuration      // 表明目前類是一個配置類,并把目前類丢到spring容器中去
	@EnableSwagger2     // 開啟swagger功能
	public class SwaggerConfig {

		@Bean
		public Docket createRestApi() {
			// http://ip位址:端口/項目名/swagger-ui.html#/
			ApiInfo apiInfo = new ApiInfoBuilder()
					// 網站标題    即:生成的文檔網址标題
					.title( "悠忽有限公司" )
					 // 網站描述     即:對生成文檔的描述
					.description( "這是一個很nice的接口文檔" )
					 // 版本
					.version( "9.0" )
					 // 聯系人
					.contact( new Contact("紫邪情","https://www.cnblogs.com/xiegongzi/","110" ) )
					 // 協定  http / https都可以
					.license( "tcp" )
					// 協定url 即:進入到swagger文檔頁面的位址
					.licenseUrl( "http://localhost:8080/" )
					.build();

			// swagger版本
			return new Docket( DocumentationType.SWAGGER_2 )
					// 請求映射路徑  就是:controller中有一個接口,然後前台通路的那個接口路徑
					// 這個可以在生成的文檔中進行調試時看到
					.pathMapping( "/" )
					 // 根據pathMapping去進行查詢( 做相應的操作 )
					.select()
					// 掃描包   即:哪些地方可以根據我們的注解配置幫我們生成文檔
					.apis( RequestHandlerSelectors.basePackage( "cn.xiegongzi" ) )
					.paths( PathSelectors.any() )
					.build()
					.apiInfo( apiInfo );
		}

	}

           
編寫yml檔案
spring:
	  datasource:
	  	# 注意這裡加了cj啊,即:MySQL驅動用的是8.x的
		driver-class-name: com.mysql.cj.jdbc.Driver
		url: jdbc:mysql://localhost:3306/mybatis_spring?useUnicode=true&characterEncoding=utf-8
		username: root
		# 注意:在yml中,這種自己寫的内容最好用字元串寫法,以後玩Redis也是一樣,不然有時出現坑,即:密碼無效 / 這裡面填入的值沒有解析出來,不比對
		password: "072413"

           
編寫實體類
package cn.xiegongzi.entity;

	import io.swagger.annotations.ApiModel;
	import io.swagger.annotations.ApiModelProperty;
	import lombok.AllArgsConstructor;
	import lombok.Data;
	import lombok.NoArgsConstructor;

	import java.io.Serializable;

	@Data
	@AllArgsConstructor
	@NoArgsConstructor

/**
 * @ApiModel(description = "描述")
 * 表明這個實體類就是需要的資料名和類型
 * 背景接收前端的參數是一個對象時使用( controller寫的是@RequestBody OrderPaidDTO orderPaid ),即:後端接收參數封裝成了一個xxxxDTO( PO、BO、Entity、DTO、DAO含義和關系是什麼,自行百度 )
 * 這個東西可以先不加,在做增加、修改時可以用這個測試一下,進而去swagger中看效果
*/
	@ApiModel(description = "使用者資訊")
	public class User implements Serializable {

		 // 資料屬性配置,這裡面可以跟幾個屬性,常見的是value、required、dataType、hidden,在待會後面附加的連結中有解釋
		@ApiModelProperty
		private Integer id;

		@ApiModelProperty
		private String username;

		@ApiModelProperty
		private String phone;
	}

           
編寫mapper
package cn.xiegongzi.mapper;

	import cn.xiegongzi.entity.User;
	import org.apache.ibatis.annotations.Mapper;
	import org.apache.ibatis.annotations.Select;

	import java.util.List;

	@Mapper
	public interface IUserMapper {

		@Select("select * from user")
		List<User> findAllUser();
	}

           
編寫service接口和實作類
SpringBoot 2.X快速掌握
編寫controller
SpringBoot 2.X快速掌握
package cn.xiegongzi.controller;

	import cn.xiegongzi.service.IUserService;
	import com.alibaba.fastjson.JSON;
	import io.swagger.annotations.Api;
	import io.swagger.annotations.ApiOperation;
	import org.springframework.beans.factory.annotation.Autowired;
	import org.springframework.web.bind.annotation.GetMapping;
	import org.springframework.web.bind.annotation.RestController;

	@RestController
	/*
		@Api
			表示目前類可以被生成一個swagger文檔
			可以跟參數tags,參數表示:這整個接口的名字( 前端是接口,後端是controller控制層 )
	*/
	@Api(tags = "使用者管理接口集")
	public class UserController {

		@Autowired
		private IUserService userService;


		/*
			@ApiImplicitParam 這個注解是對請求參數做限制用的,如:請求時要求前台傳遞一個id,那麼:在這個注解裡面:就可以聲明這個參數的類型( 對象類型中要求屬性限制,可以使用@ApiModelProperty 也可以使用 符合jsr303規範的資料檢驗方式 )
		*/
		// 遵循restful風格  要是使用@RequestMapping的話,會生成多個接口swagger文檔( 即:對應post、get.... )
		@GetMapping("/swaggger/doc")
		// value這個接口的名字;notes 對這個接口的描述
		@ApiOperation(value = "擷取全部使用者接口" , notes = "擷取全部的使用者")
		public String findAllUser() {

			return JSON.toJSONString( userService.findAllUser() );
		}
	}

           
測試
SpringBoot 2.X快速掌握
SpringBoot 2.X快速掌握

13.2、結語

  • 以上的内容是入門,其他的注解開發時自行摸索吧!
  • 還有一種,比swagger更好,就是:Apifox / ApiPost / eolink,自行下載下傳安裝包,安裝之後玩一下

14、內建JPA

資料庫表字段資訊
SpringBoot 2.X快速掌握
導入依賴
<!--導入jpa需要的依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>


        <!--項目需要的依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

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

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.75</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

           
編寫yml檔案
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis_spring?useUnicode=true&characterEncoding=utf-8
    username: root
    password: "072413"

  jpa:
# 這裡可以不用hibernate,還可以用hikari( 這個在前面整合jdbc時見過,就是當時輸出的那句話 )
    hibernate:
# 指定為update,每次啟動項目檢測表結構有變化的時候會新增字段,表不存在時會建立表
      ddl-auto: update
# 如果指定create,則每次啟動項目都會清空資料并删除表,再建立
# 這裡面還可以跟:create-drop/create/none
      naming:
        # 指定jpa的自動表生成政策,駝峰自動映射為下劃線格式
        implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl  # 預設就是這個
#        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
# 注掉的這種是:不用駝峰名字,直接把實體類的大寫字母變小寫就完了

    show-sql: true		# 在控制台顯示sql語句( 不是真的sql語句,而是相當于:說明 ),預設是false
# 使用INNODB引擎
    properties.hibernate.dialect: org.hibernate.dialect.MySQL55Dialect
    database-platform: org.hibernate.dialect.MySQL55Dialect
# 使用JPA建立表時,預設使用的存儲引擎是MyISAM,通過指定資料庫版本,可以使用InnoDB
           
編寫實體類
package cn.xiegongzi.entity;

import lombok.Data;
import org.springframework.data.annotation.Id;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import java.io.Serializable;


@Data
// @AllArgsConstructor
// @NoArgsConstructor

@Entity
/** @Entity
 * 表明:目前類和資料庫中的這個同類名的資料庫表形成ORM映射關系
 * 要是資料庫中沒有這個表,那麼:根據yml配置的ddl-auto: update 就會自動幫我們生成
*/
public class ZiXieQing implements Serializable {

    @javax.persistence.Id
    @Id     // 表明這個屬性是資料庫表中的主鍵
    @GeneratedValue(strategy = GenerationType.IDENTITY)     // 表示:自增  預設是auto,即:和資料庫中的auto_increment是一樣的
    private int id;

    @Column( length = 15 )          // 生成資料庫中的列字段,裡面的參數不止這些,還可以用其他的,對應資料庫列字段的那些操作
                                    // 可以點進源碼看一下
    private String name;

    // public ZiXieQing() {
    // }


    public ZiXieQing(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

           

附:

@Column

注解中可以支援的屬性

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    String name() default "";

    boolean unique() default false;

    boolean nullable() default true;

    boolean insertable() default true;

    boolean updatable() default true;

    String columnDefinition() default "";

    String table() default "";

    int length() default 255;

    int precision() default 0;

    int scale() default 0;
}

           
編寫mapper
package cn.xiegongzi.mapper;

import cn.xiegongzi.entity.ZiXieQing;
import org.springframework.data.jpa.repository.JpaRepository;

@Repository
/**
 * 注:這裡别用@Mapper這個注解,因為:@mapper是mybatis提供的注解
 * JpaRepository相對mybatis來說就是是外部的東西。是以:并不能支援@mapper注解
 */
public interface ZiXieQingMapper extends JpaRepository<ZiXieQing , Integer> {

    /*
		JpaRepository這裡面有預設的一些方法,即:增删查改...
    	JpaRepository<ZiXieQing , Integer> 本來樣子是:JpaRepository<T , ID>
    	T  表示:自己編寫的實體類 類型
    	ID  表示: 實體類中id字段的類型      注:本示例中,實體類中id是int 因為要弄自增就必須為int,不然和資料庫映射時對不上
    */

}

           

附:

JpaRepository

中提供的方法

SpringBoot 2.X快速掌握
編寫service接口和實作類
SpringBoot 2.X快速掌握
編寫controller
SpringBoot 2.X快速掌握
測試
SpringBoot 2.X快速掌握
現在去看一下資料庫
SpringBoot 2.X快速掌握

生成出來了,完成

15、內建mybatis-plus

  • mybatis-plus官網位址:https://baomidou.com/guide/
導入依賴
<!--mybatis-plus需要的依賴-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.2</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>

           
編寫yml
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis_spring?useUnicode=true&characterEncoding=utf-8
    username: root
    password: "072413"
    type: com.alibaba.druid.pool.DruidDataSource

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl   # mybatis-plus配置日志
    map-underscore-to-camel-case: true    # 開啟駝峰映射 即:實體類屬性名和資料庫字段采用駝峰映射
    auto-mapping-behavior: full     # 自動映射字段
  mapper-locations: classpath:mapper/*.xml    # 如果使用了mybatis和mybatis-plus 那麼這裡就可以把mybatis的實作類xml內建進來
    # 但是:最好别這種做,用了mybatis就别用mybatis-plus,二者最好隻用其一

           

注:别把mybatis和mybatis-plus一起內建到spring中,否則:很容易出問題,雖然:mybatis-plus是mybatis的增強版,既然是增強版,那麼就不會抛棄它原有的東西,隻會保留原有的東西,然後新增功能,但是:mybatis和mybatis-plus內建到一起之後很容易造成版本沖突,是以:對于單個系統子產品 / 單個系統來說,建議二者隻選其一內建 ( PS:當然事情不是絕對的 我說的是萬一,隻是操作不當很容易觸發錯誤而已,但是:二者一起內建也是可以的,當出現報錯時可以解決掉,不延伸了 ,這不是這裡該做的事情 )

編寫實體類
package cn.xiegongzi.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "user")      // 表名注解
public class User implements Serializable {

    @TableId(type = IdType.AUTO)        // 表示主鍵,這個主鍵是一個Long類型的值( 即:snowflake雪花算法 )
    private Integer id;
    @TableField("username")         // 資料庫字段名   就是:當實體類中的字段和資料庫字段不一樣時可以使用
    private String name;
    private String phone;
}

           
編寫mapper
package cn.xiegongzi.mapper;

import cn.xiegongzi.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

@Mapper         // 不想在每個mapper層都寫這個注解,那把@MapperScan("cn.xiegongzi.mapper") 在啟動類中加入這個注解也可以實作
public interface IUserMapper extends BaseMapper<User> {

    /*
		BaseMapper 和 JPA一樣,内部有很多方法 , 即:CRUD.....,還有分頁( 分頁就是page()這個方法 )
		BaseMapper原來的樣子是:BaseMapper<T>  T表示實體類 類型
	*/

}

           

附:

BaseMapper<T>

提供的方法如下:

SpringBoot 2.X快速掌握
測試
SpringBoot 2.X快速掌握

其他的知識,在mybatis-plus官網中都有

15、分布式本地緩存技術ehcache

  • 還有一種比較流行的是

    Caffeine

    這個東西要更簡單一點( Caffeine本地緩存的學習網址:http://www.mydlq.club/article/56/ 這個網址中的第二種內建方式和下面玩的Ehcache注解的含義一樣 ),而且不需要借助xml檔案,而ehcache需要借助xml檔案

15.1、Ehcache介紹

  • Ehacahe是一個比較成熟的Java緩存架構,最早從hibernate發展而來,是程序中的緩存系統,它提供了用記憶體、磁盤檔案存儲,以及分布式存儲方式等多種靈活的cache管理方案

15.2、ehcache常用注解

15.2.1、@CacheConfig注解

  • 用于标注在類上,可以存放該類中所有緩存的公有屬性( 如:設定緩存名字 )
@CacheConfig(cacheNames = "users")
public class UserService{

}

           
  • 當然:這個注解其實可以使用

    @Cacheable

    來代替

15.2.2、@Cacheable注解( 讀資料時 ) - 用得最多

  • 應用到讀取資料的方法上,如:查找資料的方法,使用了之後可以做到先從本地緩存中讀取資料,若是沒有在調用此注解下的方法去資料庫中讀取資料,當然:還可以将資料庫中讀取的資料放到用此注解配置的指定緩存中
@Cacheable(value = "user", key = "#userId")
User selectUserById( Integer userId );

           
@Cacheable注解的屬性
  • value、cacheNames
    • 這兩個參數其實是等同的( cacheNames為Spring 4新增的,作為value的别名 )
    • 這兩個屬性的作用:用于指定緩存存儲的集合名
  • key
    • 作用:緩存對象存儲在Map集合中的key值
  • condition
    • 作用:緩存對象的條件,即:隻有滿足這裡面配置的表達式條件的内容才會被緩存,如:@Cache( key = "#userId",condition="#userId.length() < 3" 這個表達式表示隻有當userId長度小于3的時候才會被緩存
  • unless
    • 作用:另外一個緩存條件,它不同于condition參數的地方在于此屬性的判斷時機( 此注解中編寫的條件時在函數被

      調用之後

      才做判斷,是以:這個屬性可以通過封裝的result進行判斷 )
  • keyGenerator
    • 作用:用于指定key生成器,若需要綁定一個自定義的key生成器,我們需要去實作

      org.springframewart.cahce.intercceptor.KeyGenerator

      接口,并使用改參數來綁定
    • 注意點:改參數與上面的key屬性是互斥的
  • cacheManager
    • 作用:指定使用哪個緩存管理器,也就是當有多個緩存器時才需要使用
  • cacheResolver
    • 作用:指定使用哪個緩存解析器
    • 需要通過

      org.springframewaork.cache.interceptor.CacheResolver

      接口來實作自己的緩存解析器

15.2.3、@CachePut注解 ( 寫資料時 )

  • 用在

    寫資料

    的方法上,如:新增 / 修改方法,調用方法時會自動把對應的資料放入緩存,

    @CachePut

    的參數和

    @Cacheable

    差不多
@CachePut(value="user", key = "#userId")
public User save(User user) {
	users.add(user);
	return user;
}

           

15.2.4、@CacheEvict注解 ( 删除資料時 )

  • 用在删除資料的方法上,調用方法時會從緩存中移除相應的資料
@CacheEvict(value = "user", key = "#userId")
void delete( Integer userId);

           
  • 這個注解除了和

    @Cacheable

    一樣的參數之外,還有另外兩個參數:
    • allEntries:預設為false,當為true時,會移除緩存中該注解該屬性所在的方法的所有資料
    • beforeInvocation:預設為false,在調用方法之後移除資料,當為true時,會在調用方法之前移除資料

15.2.5、@Cacheing組合注解 - 推薦

@Caching(
	put = {
		@CachePut(value = "user", key = "#userId"),
		@CachePut(value = "user", key = "#username"),
		@CachePut(value = "user", key = "#userAge"),
	}
)

           
  • 指的是:将userId、username、userAge放到名為user的緩存中存起來

15.3、SpringBoot內建Ehcache

15.3.1、配置Ehache

依賴
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
        </dependency>

           

application.yml

配置檔案中加入配置
cache:
		ehcache:
			# 配置ehcache.xml配置檔案所在地
			config: class:ehcache.xml

           
在主啟動類開啟緩存功能
@SpringBootAllication
@EnableCaching
public class Starter {
	public static void main(String[] args) {
		SpringApplication.run(Starter.class);
	}
}

           
編寫

ehcache.xml

配置檔案
  • resources

    目錄下建立

    ehcache.xml

    ,并編寫如下内容:
<ehcache name="myCache">
    <!--緩存磁盤儲存路徑-->
    <diskStore path = "D:/test/cache"/>

    <!--預設的緩存配置
        maxElementsInMemory 緩存最大數目
        eternal 對象是否永久有效 一旦設定了,那麼timeout将不再起作用
        timeToIdleSeconds 設定對象在實效前能允許的閑置時間( 機關:秒 ),預設值是0,即:可閑置時間無窮大
                            僅當eternal=“false"對象不是永久有效時使用
        timeToLiveSeconds 設定對象失效前能允許的存活時間( 機關:秒 )
                             最大時間介于建立時間和失效時間之間
        maxElementsOnDisk 磁盤最大緩存個數
        diskExpiryThreadIntervalSeconds 磁盤失效時,線程運作時間間隔,預設是120秒
        memoryStoreEvictionPolicy 當達到設定的maxElementsInMemory限制時,Ehcache将會根據指定的政策去清理記憶體
                                    預設政策是LRU( 即:最近最少使用政策 )
                                    還可以設定的政策:
                                        FIFO    先進先出政策
                                        LFU     最近最少被通路政策
                                        LRU     最近最少使用政策
                                                    緩存的元素有一個時間戳,當緩存容量滿了,同時又需要騰出地方來緩存新的元素時,
                                                    那麼現有緩存元素中的時間戳 離 目前時間最遠的元素将被清出緩存

    -->
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            maxElementsOnDisk="10000000"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"/>

    <!--下面的配置是自定義緩存配置,可以複制粘貼,用多套
        name 起的緩存名
        overflowToDisk 當系統當機時,資料是否儲存到上面配置的<diskStore path = "D:/test/cache"/>磁盤中
        diskPersistent 是否緩存虛拟機重新開機期資料

        另外的配置項:
            clearOnFlush  記憶體數量最大時是否清除
            diskSpoolBufferSizeMB 設定diskStore( 即:磁盤緩存 )的緩沖區大小,預設是30MB
                                    每個Cache都應該有自己的一個緩沖區
    -->
    <cache
        name="users"
        eternal="false"
        maxElementsInMemory="100"
        overflowToDisk="false"
        diskPersistent="false"
        timeToIdleSeconds="0"
        timeToLiveSeconds="300"
        memoryStoreEvictionPolicy="LRU"
    />
</ehcache>

           

15.3.2、在項目中使用ehcahe

  • 使用常用的

    @Cacheable

    注解舉例
查詢條件是單個時( service實作類中直接開注解 )
// 這裡的value值就是前面xml中配置的哪個cache name值
@Cacheable(value="users", key = "#username")
public User queryUserByUsername(String username) {
	return userMapper.selectUserByUsername(username);
}

           
查詢條件是多個時( service實作類中直接開注解 )
  • 本質:字元串的拼接
// 這裡的UserDAO.username+就是封裝的UserDAO,裡面的屬性有username、userage、userPhone
@Cache(value="users", key = "#UserDAO.username+'-'+#UserDAO.userage+'-'+#UserDAO.userPhone")
public User queryUserByUsername(UserDAO userDAO) {
	return userMapper.selectUserByUserDAO(userDAO);
}


           

其他的注解也都是差不多的

16、定時任務

16.1、小頂堆資料結構

  • 就是一個完全二叉樹,同時這個二叉樹遵循一個規則,根節點存的值永遠小于兩個子節點存的值
SpringBoot 2.X快速掌握
  • 樹結構隻是一種邏輯結構,是以:資料還是要存起來的,而這種小頂堆就是采用了數組
SpringBoot 2.X快速掌握
  • 即:數組下标為0的位置不放值,然後把樹結構的資料放在對應位置
    • 樹結構資料轉成數組資料的規律:從上到下、從左到右 - 即:根節點、左孩子節點、右孩子節點( 對照上面兩個圖來看 )
    • 這種存儲方式找父節點也好找,就是數組中( 目前數值的下标值 % 2 ) ,這種算法的原理:就是利用二叉樹的深度 和 存放資料個數的關系( 數列 ),即:頂層最多可以放多少個資料?2的0次方;第二層最多可以存放多少個資料?2的1次方...........
  • 這種小頂堆需要明白三個點:
    • 存資料的方式: 上述提到了
    • 取資料的方式:從底向上。即:從最底層開始,若比找尋的值小,那就找父節點,父節點也比所找尋數值小,繼續找父節點的父節點.,要是比父節點大,那就找相鄰兄弟節點嘛.........依次類推,最後就可以找到所找尋的資料了
    • 存資料的方式:自底向上、逐漸上浮。即:從最底層開始,要存的值 和 父節點相比,比父節點小的話,那存的值就和父節點存的值進行換位.....以此類推

16.2、時間輪算法

SpringBoot 2.X快速掌握

16.3、基礎型時間輪

  • 模仿時鐘,24個刻度( 數組,每一個刻度作為數組的下标 ),每一個刻度後面就是一個連結清單,這個連結清單中放對應的定時任務,到了指定時間點就把後面連結清單中的任務全部周遊出來執行
  • 缺點:當要弄年、月、秒這種就又要再加輪子,這樣就很複雜了,是以:此種方式隻适合記一天24小時的定時任務,涉及到年月秒就不行了

16.4、round型時間輪

  • 在前面基礎型時間輪的基礎上,在每一個刻度的位置再加一個round值( 每個刻度後面還是一個連結清單存定時任務 ),round值記錄的就是實際需求的值,如:一周,那round值就為7,當然這個round值可以是1,也可以是30....,每一次周遊時鐘數組的那24個刻度時,周遊到某一個刻度,那麼就讓round值減1,直到round值為0時,就表示24數組中目前這個刻度存的定時任務該執行了
  • 缺點:需要讓round值減1,那麼就是需要對時間輪進行周遊,如:定時任務應該是4号執行,但是3号周遊時間輪時,定時任務并不執行,而此時也需要周遊時間輪進而讓round值減1,這浪費了性能

16.5、分量時間輪

  • 後續的定時任務架構就是基于這個做的,如:Spring中有一個@Scheduled( cron = "x x x x ...." )注解,它的這個cron時間表達式就是基于這種分量時間輪
  • 使用多個輪子
    • 如:一個時間輪記錄小時0 - 24,而另一個輪子記錄天數0 - 30天
    • 先周遊天倫中的刻度,若今天是0 -30中要執行定時任務的那一天,那麼天輪的刻度指向的就是時輪
    • 然後再去周遊時輪中對應的那個刻度,進而找到這個刻度後面的連結清單,将連結清單周遊出來,執行定時任務

16.6、Timer定時任務

  • 底層原理就是:小頂堆,隻是它的底層用了一個taskQueue任務隊列來充當小頂堆中的哪個數組,存取找的邏輯都是和小頂堆一樣的
  • 有着弊端:
    • schedule() API 真正的執行時間 取決上一個任務的結束時間 - 會出現:少執行了次數
    • scheduleAtFixedRate() API 想要的是嚴格按照預設時間 12:00:00 12:00:02 12:00:04,但是最終結果是:執行時間會亂
    • 底層調的是

      run()

      ,也就是單線程 - 缺點:任務阻塞( 阻塞原因:任務逾時 )
SpringBoot 2.X快速掌握
package com.tuling.timer;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class TimerTest {

    public static void main(String[] args) {
        Timer t = new Timer();// 任務啟動
        for (int i=0; i<2; i++){
            TimerTask task = new FooTimerTask("foo"+i);
            t.scheduleAtFixedRate(task,new Date(),2000);// 任務添加   10s 5次   4 3
            // 預設的執行時間nextExecutorTime 12:00:00   12:00:02  12:00:04
            // schedule  真正的執行時間 取決上一個任務的結束時間  ExecutorTime   03  05  08  丢任務(少執行了次數)
            // scheduleAtFixedRate  嚴格按照預設時間 12:00:00   12:00:02  12:00:04(執行時間會亂)
            // 單線程  任務阻塞  任務逾時
        }
    }
}

class FooTimerTask extends TimerTask {

    private String name;

    public FooTimerTask(String name) {
        this.name = name;
    }

    public void run() {
        try {
            System.out.println("name="+name+",startTime="+new Date());
            Thread.sleep(3000);
            System.out.println("name="+name+",endTime="+new Date());

            // 因為是單線程,是以解決辦法:使用線程池執行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


           

16.7、定時任務線程池

  • 底層原理就是timer + 線程池來做到的
  • 如下的

    Executors.newScheduledThreadPool(5);

    這種建立線程池的方法在高并發情況下,最好别用(阿裡巴巴開發手冊上有規定)
SpringBoot 2.X快速掌握
package com.tuling.pool;

import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduleThreadPoolTest {

    public static void main(String[] args) {
        // 這種線程池叫做垃圾 - 了解即可
        // 缺點:允許的請求隊列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,進而導緻 OOM
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        for (int i=0;i<2;i++){

            scheduledThreadPool.scheduleAtFixedRate(new Task("task-"+i),0,2, TimeUnit.SECONDS);
        }
    }
}
class Task implements Runnable{

    private String name;

    public Task(String name) {
        this.name = name;
    }

    public void run() {
        try {
            System.out.println("name="+name+",startTime="+new Date());
            Thread.sleep(3000);
            System.out.println("name="+name+",endTime="+new Date());

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

           

16.8、@Scheduled注解實作

  • 這玩意兒是Spring提供的
  • 缺點就是其定時間不能動态更改,它适用于具有固定任務周期的任務
  • 注意點:要在相應的代碼中使用

    @Scheduled

    注解來進行任務配置,那麼就需要在主啟動類上加上

    @EnableScheduling // 開啟定時任務

    注解
這個注解的幾個屬性
  • fixedRate 表示任務執行之間的時間間隔,具體是指兩次任務的開始時間間隔,即第二次任務開始

    時,第一次任務可能還沒結束

  • fixedDelay 表示任務執行之間的時間間隔,具體是指本次任務結束到下次任務開始之間的時間間

  • initialDelay 表示首次任務啟動的延遲時間
  • cron 表達式:秒 分 小時 日 月 周 年
cron表達式說明
SpringBoot 2.X快速掌握
  • 上圖通配符含義
通配符 意義

?

表示不指定值,即不關心某個字段的取值時使用

需要注意的是,月份中的日期和星期可能會起沖突,是以在配置時這兩個得有一個是

?

*

表示所有值,例如:在秒的字段上設定 * ,表示每一秒都會觸發

,

用來分開多個值,例如在周字段上設定 "MON,WED,FRI" 表示周一,周三和周五觸發

-

表示區間,例如在秒上設定 "10-12",表示 10,11,12秒都會觸發

/

用于遞增觸發,如在秒上面設定"5/15" 表示從5秒開始,每增15秒觸發(5,20,35,50)

#

序号(表示每月的第幾個周幾),例如在周字段上設定"6#3"表示在每月的第三個周六,(用

在母親節和父親節再合适不過了)

L

表示最後的意思

在日字段設定上,表示當月的最後一天(依據目前月份,如果是二月還會自動判斷是否是潤年

在周字段上表示星期六,相當于"7"或"SAT"(注意周日算是第一天)

如果在"L"前加上數字,則表示該資料的最後一個。例如在周字段上設定"6L"這樣的格式,則表

示"本月最後一個星期五"

W

表示離指定日期的最近工作日(周一至周五)

例如在日字段上設定"15W",表示離每月15号最近的那個工作日觸發。如果15号正好是周六,則找最近的周五(14号)觸發, 如果15号是周未,則找最近的下周一(16号)觸發,如果15号正好在工作日(周一至周五),則就在該天觸發

如果指定格式為 "1W",它則表示每月1号往後最近的工作日觸發。如果1号正是周六,則将在3号下周一觸發。(注,"W"前隻能設定具體的數字,不允許區間"-")

L

W

組合
如果在日字段上設定"LW",則表示在本月的最後一個工作日觸發(一般指發工資 )

周字段的設定

若使用英文字母是不區分大小寫的 ,即 MON 與mon相同
cron表達式舉例
“0 0 12 * * ?”				每天中午12點觸發

“0 15 10 ? * *”				每天上午10:15觸發
“0 15 10 * * ?”
“0 15 10 * * ? *”

“0 15 10 * * ? 2005”		2005年的每天上午10:15 觸發

“0 0/5 14 * * ?”			在每天下午2點到下午2:55期間的每5分鐘觸發

“0 0-5 14 * * ?”			在每天下午2點到下午2:05期間的每1分鐘觸發

“0 10,44 14 ? 3 WED”		每年三月的星期三的下午2:10和2:44觸發

“0 15 10 ? * MON-FRI”		周一至周五的上午10:15觸發

“0 15 10 ? * 6L”			每月的最後一個星期五上午10:15觸發

“0 15 10 ? * 6L 2002-2005”	2002年至2005年的每月的最後一個星期五上午10:15觸發

“0 15 10 ? * 6#3”			每月的第三個星期五上午10:15觸發

0 23-7/2,8 * * *		   晚上11點到早上8點之間每兩個小時,早上八點

0 11 4 * 1-3				每個月的4号和每個禮拜的禮拜一到禮拜三的早上11點

           

16.9、Redis實作 - 分布式定時任務

  • 前面的方式都是單機的

16.8.1、zset實作

邏輯
  • 将定時任務存放到 ZSet 集合中,并且将過期時間存儲到 ZSet 的 Score 字段中
  • 通過一個無線循環來判斷目前時間内是否有需要執行的定時任務,如果有則進行執行
import redis.clients.jedis.Jedis;
import utils.JedisUtils;
import java.time.Instant;
import java.util.Set;

public class DelayQueueExample {
    // zset key
    private static final String _KEY = "myTaskQueue";

    public static void main(String[] args) throws InterruptedException {
        Jedis jedis = JedisUtils.getJedis();
        // 30s 後執行
        long delayTime = Instant.now().plusSeconds(30).getEpochSecond();
        jedis.zadd(_KEY, delayTime, "order_1");
        // 繼續添加測試資料
        jedis.zadd(_KEY, Instant.now().plusSeconds(2).getEpochSecond(), "order_2");
        jedis.zadd(_KEY, Instant.now().plusSeconds(2).getEpochSecond(), "order_3");
        jedis.zadd(_KEY, Instant.now().plusSeconds(7).getEpochSecond(), "order_4");
        jedis.zadd(_KEY, Instant.now().plusSeconds(10).getEpochSecond(), "order_5");
        // 開啟定時任務隊列
        doDelayQueue(jedis);
    }

    /**
     * 定時任務隊列消費
     * @param jedis Redis 用戶端
     */
    public static void doDelayQueue(Jedis jedis) throws InterruptedException {
        while (true) {
            // 目前時間
            Instant nowInstant = Instant.now();
            long lastSecond = nowInstant.plusSeconds(-1).getEpochSecond(); // 上一秒時間
            long nowSecond = nowInstant.getEpochSecond();
            // 查詢目前時間的所有任務
            Set<String> data = jedis.zrangeByScore(_KEY, lastSecond, nowSecond);
            for (String item : data) {
                // 消費任務
                System.out.println("消費:" + item);
            }
            // 删除已經執行的任務
            jedis.zremrangeByScore(_KEY, lastSecond, nowSecond);
            Thread.sleep(1000); // 每秒查詢一次
        }
    }
}

           

16.8.2、鍵空間實作

邏輯
  • 給所有的定時任務設定一個過期時間
  • 等到了過期之後,我們通過訂閱過期消息就能感覺到定時任務需要被執行了,此時我們執行定時任務即可
  • 注意點:預設情況下 Redis 是不開啟鍵空間通知的,需要我們通過

    config set notify-keyspace-events Ex

    的指令手動開啟,開啟之後定時任務的代碼如下
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;
import utils.JedisUtils;

public class TaskExample {
    public static final String _TOPIC = "__keyevent@0__:expired"; // 訂閱頻道名稱
    public static void main(String[] args) {
        Jedis jedis = JedisUtils.getJedis();
        // 執行定時任務
        doTask(jedis);
    }

    /**
     * 訂閱過期消息,執行定時任務
     * @param jedis Redis 用戶端
     */
    public static void doTask(Jedis jedis) {
        // 訂閱過期消息
        jedis.psubscribe(new JedisPubSub() {
            @Override
            public void onPMessage(String pattern, String channel, String message) {
                // 接收到消息,執行定時任務
                System.out.println("收到消息:" + message);
            }
        }, _TOPIC);
    }
}

           

16.8、Quartz任務排程

  • 組成結構圖如下:需要時自行摸索即可
SpringBoot 2.X快速掌握

16.8.1.簡單示例

依賴
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>

           
定義job
public class MyJob implements Job {
    private Logger log = LoggerFactory.getLogger(MyJob.class);

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {

        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        TriggerKey triggerKey = jobExecutionContext.getTrigger().getKey();

        log.info("觸發器:{},所屬組:{},執行時間:{},執行任務:{}",
                triggerKey.getName(),triggerKey.getGroup(),dateFormat.format(new Date()),"hello SpringBoot Quartz...");
    }
}

           
編寫QuartzConfig
public class QuartzConfig {

    @Bean
    public JobDetail jobDetail() {
        return JobBuilder.newJob(MyJob.class)
                .storeDurably()
                .build();
    }

    @Bean
    public Trigger trigger01() {
        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                // 每一秒執行一次
                .withIntervalInSeconds(1)
                // 永久重複,一直執行下去
                .repeatForever();

        return TriggerBuilder.newTrigger()
                // 參數1、trigger名字;參數2、目前這個trigger所屬的組 - 參考時間輪存儲任務,那個刻度後面是怎麼存的任務
                .withIdentity("trigger01", "group1")
                .withSchedule(scheduleBuilder)
                // 哪一個job,上一個方法中bean注入
                .forJob("jobDetail")
                .build();
    }

    /**
     * 每兩秒觸發一次任務
     */
    @Bean
    public Trigger trigger02() {
        return TriggerBuilder
                .newTrigger()
                .withIdentity("triiger02", "group1")
                // cron時間表達式
                .withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ? *"))
                .forJob("jobDetail")
                .build();
    }
}

           

17、附加:SpringBoot線程池

17.1、場景

  • 提高一下插入表的性能優化,兩張表,先插舊的表,緊接着插新的表,若是一萬多條資料就有點慢了

17.2、使用步驟

  • 用Spring提供的對

    ThreadPoolExecutor

    封裝的線程池

    ThreadPoolTaskExecutor

    ,直接使用注解啟用
配置
@Configuration
@EnableAsync
public class ExecutorConfig {

    private static final Logger logger = LoggerFactory.getLogger(ExecutorConfig.class);

    @Value("${async.executor.thread.core_pool_size}")
    private int corePoolSize;
    @Value("${async.executor.thread.max_pool_size}")
    private int maxPoolSize;
    @Value("${async.executor.thread.queue_capacity}")
    private int queueCapacity;
    @Value("${async.executor.thread.name.prefix}")
    private String namePrefix;

    @Bean(name = "asyncServiceExecutor")
    public Executor asyncServiceExecutor() {
        logger.info("start asyncServiceExecutor");
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 配置核心線程數
        executor.setCorePoolSize(corePoolSize);
        // 配置最大線程數
        executor.setMaxPoolSize(maxPoolSize);
        // 配置隊列大小
        executor.setQueueCapacity(queueCapacity);
        // 配置線程池中的線程的名稱字首
        executor.setThreadNamePrefix(namePrefix);

        // rejection-policy:當pool已經達到max size的時候,如何處理新任務
        // CALLER_RUNS:不在新線程中執行任務,而是有調用者所在的線程來執行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //執行初始化
        executor.initialize();
        return executor;
    }
}

           
  • @Value

    取值配置是在

    application.properties

    中的
# 異步線程配置
# 配置核心線程數
async.executor.thread.core_pool_size = 5
# 配置最大線程數
async.executor.thread.max_pool_size = 5
# 配置隊列大小
async.executor.thread.queue_capacity = 99999
# 配置線程池中的線程的名稱字首
async.executor.thread.name.prefix = async-service-

           
Demo測試
  • Service接口
public interface AsyncService {

    /**
     * 執行異步任務
     * 可以根據需求,自己加參數拟定
     */
    void executeAsync();
}

           
  • Service實作類
@Service
public class AsyncServiceImpl implements AsyncService {

    private static final Logger logger = LoggerFactory.getLogger(AsyncServiceImpl.class);

    @Override
    @Async("asyncServiceExecutor")
    public void executeAsync() {
        logger.info("start executeAsync");

        System.out.println("異步線程要做的事情");
        System.out.println("可以在這裡執行批量插入等耗時的事情");

        logger.info("end executeAsync");
    }
}

           
  • 在Controller層注入剛剛的Service即可
@Autowired
private AsyncService asyncService;

@GetMapping("/async")
public void async(){
    asyncService.executeAsync();
}

           
  • 使用測試工具測試即可看到相應的列印結果

17.3、摸索一下

  • 弄清楚線程池當時的情況,有多少線程在執行,多少在隊列中等待?
  • 建立一個

    ThreadPoolTaskExecutor

    的子類,在每次送出線程的時候都将目前線程池的運作狀況列印出來
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.concurrent.ListenableFuture;

import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;

public class VisiableThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {


    private static final Logger logger = LoggerFactory.getLogger(VisiableThreadPoolTaskExecutor.class);

    private void showThreadPoolInfo(String prefix) {
        ThreadPoolExecutor threadPoolExecutor = getThreadPoolExecutor();

        if (null == threadPoolExecutor) {
            return;
        }

        logger.info("{}, {},taskCount [{}], completedTaskCount [{}], activeCount [{}], queueSize [{}]",
                this.getThreadNamePrefix(),
                prefix,
                threadPoolExecutor.getTaskCount(),
                threadPoolExecutor.getCompletedTaskCount(),
                threadPoolExecutor.getActiveCount(),
                threadPoolExecutor.getQueue().size());
    }

    @Override
    public void execute(Runnable task) {
        showThreadPoolInfo("1. do execute");
        super.execute(task);
    }

    @Override
    public void execute(Runnable task, long startTimeout) {
        showThreadPoolInfo("2. do execute");
        super.execute(task, startTimeout);
    }

    @Override
    public Future<?> submit(Runnable task) {
        showThreadPoolInfo("1. do submit");
        return super.submit(task);
    }

    @Override
    public <T> Future<T> submit(Callable<T> task) {
        showThreadPoolInfo("2. do submit");
        return super.submit(task);
    }

    @Override
    public ListenableFuture<?> submitListenable(Runnable task) {
        showThreadPoolInfo("1. do submitListenable");
        return super.submitListenable(task);
    }

    @Override
    public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
        showThreadPoolInfo("2. do submitListenable");
        return super.submitListenable(task);
    }
}

           
  • 進過測試發現:

    showThreadPoolInfo

    方法中将任務總數、已完成數、活躍線程數,隊列大小都列印出來了,然後Override了父類的execute、submit等方法,在裡面調用showThreadPoolInfo方法,這樣每次有任務被送出到線程池的時候,都會将目前線程池的基本情況列印到日志中
  • 現在修改

    ExecutorConfig.java

    asyncServiceExecutor

    方法,将

    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor()

    改為

    ThreadPoolTaskExecutor executor = new VisiableThreadPoolTaskExecutor()

@Bean(name = "asyncServiceExecutor")
    public Executor asyncServiceExecutor() {
        logger.info("start asyncServiceExecutor");
        // 在這裡進行修改
        ThreadPoolTaskExecutor executor = new VisiableThreadPoolTaskExecutor();
        // 配置核心線程數
        executor.setCorePoolSize(corePoolSize);
        // 配置最大線程數
        executor.setMaxPoolSize(maxPoolSize);
        // 配置隊列大小
        executor.setQueueCapacity(queueCapacity);
        // 配置線程池中的線程的名稱字首
        executor.setThreadNamePrefix(namePrefix);

        // rejection-policy:當pool已經達到max size的時候,如何處理新任務
        // CALLER_RUNS:不在新線程中執行任務,而是有調用者所在的線程來執行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //執行初始化
        executor.initialize();
        return executor;
    }

           
  • 經最後測試得到的結果:送出任務到線程池的時候,調用的是

    submit(Callable task)

    這個方法,目前已經送出了3個任務,完成了3個,目前有0個線程在處理任務,還剩0個任務在隊列中等待

18、結語

最後:SpringBoot還有很多内容,那些也就是整合各種架構而已,到了現在整合了這麼多,那也有點門路了,其實都差不多是同樣的套路:1、引入SpringBoot整合的對應架構依賴;2、編寫對應的配置;3、使用對應的注解 / 編寫業務邏輯。差不多都是這樣的套路,是以:到時需要時直接面向百度程式設計即可

另外:SpringBoot原理篇連結:https://www.cnblogs.com/xiegongzi/p/15522405.html