先說一些廢話
雖然我的工作中更多的是與數(shù)據(jù)庫打交道,但是作為一個(gè)Coder,我覺得掌握前后端的Web技術(shù)來說是非常有必要的。
不僅可以幫助我們?cè)诠ぷ髦懈玫睦斫馄渌麔徫慌c你對(duì)接的人他的工作痛點(diǎn),也能在公司需要人手的時(shí)候成為一個(gè)有力的應(yīng)急幫手,比如之前公司的數(shù)據(jù)中臺(tái)我就參與架構(gòu)和部分開發(fā)任務(wù),更重要的是我私下里可以運(yùn)用一些快速框架來搭建一些有意思的網(wǎng)站,比如我的個(gè)人主頁和個(gè)人博客都是我自學(xué)java和js所做出來的作品。
所以今天我希望整合一些我以往的經(jīng)驗(yàn)和看過的教程文檔,來寫一篇文章,幫助你在一天之內(nèi)通過這篇文章快速學(xué)習(xí)SpringBoot框架以及各種開發(fā)必備的工具與插件?。?!
MVC
什么是MVC
- MVC三層架構(gòu)是指:視圖層 View、服務(wù)層 Service,與持久層 Dao,它們分別完成不同的功能
- View 層:用于接收用戶提交請(qǐng)求的代碼在這里編寫
- Service 層:系統(tǒng)的業(yè)務(wù)邏輯主要在這里完成
- Dao 層:直接操作數(shù)據(jù)庫的代碼在這里編寫
- 為了更好的降低各層間的耦合度,在三層架構(gòu)程序設(shè)計(jì)中,采用面向抽象編程,即上層對(duì)下層的調(diào)用,是通過接口實(shí)現(xiàn)的,而下層對(duì)上層的真正服務(wù)提供者,是下層接口的實(shí)現(xiàn)類
- 服務(wù)標(biāo)準(zhǔn)(接口)是相同的,服務(wù)提供者(實(shí)現(xiàn)類)可以更換,這就實(shí)現(xiàn)了層間解耦合
MVC 架構(gòu)程序的工作流程
- 用戶通過 View 頁面向服務(wù)端提出請(qǐng)求,可以是表單請(qǐng)求、超鏈接請(qǐng)求、AJAX 請(qǐng)求等
- 服務(wù)端 Controller 控制器接收到請(qǐng)求后對(duì)請(qǐng)求進(jìn)行解析,找到相應(yīng)的 Model 對(duì)用戶請(qǐng)求進(jìn)行處理
- Model 處理后,將處理結(jié)果再交給 Controller
- Controller 在接到處理結(jié)果后,根據(jù)處理結(jié)果找到要作為向客戶端發(fā)回的響應(yīng) View 頁面,頁面經(jīng)渲染(數(shù)據(jù)填充)后,再發(fā)送給客戶端
使用xml還是注解
- 應(yīng)用的基本配置使用xml,比如數(shù)據(jù)源和資源文件等
- 業(yè)務(wù)開發(fā)使用注解,比如service注入bean
- 但是xml越來越多導(dǎo)致越來越臃腫,最終發(fā)展到使用完全基于注解開發(fā)
注解
聲明Bean注解
@Component 組件沒有明確規(guī)定其角色,作用在類級(jí)別上聲明當(dāng)前類為一個(gè)業(yè)務(wù)組件,被
Spring IOC 容器維護(hù)
@Service 在業(yè)務(wù)邏輯層(Service)類級(jí)別進(jìn)行聲明
@Registory 在數(shù)據(jù)訪問層(Dao)類級(jí)別進(jìn)行聲明
@Controller 在展現(xiàn)層(MVC)使用,標(biāo)注當(dāng)前類為一個(gè)控制器
注入Bean注解
@Autowired 它可以對(duì)類成員變量、方法及構(gòu)造函數(shù)進(jìn)行標(biāo)注,完成自動(dòng)裝配的工作,通過
@Autowired的使用來消除set、get方法
@Inject 作用同上,是JSR-330 標(biāo)準(zhǔn)
@Resource 作用同上,是JSR-250 標(biāo)準(zhǔn)
以上三種注解在Set方法或?qū)傩陨下暶鳎话闱闆r下更習(xí)慣聲明在屬性上,代碼簡潔清晰
配置與獲取Bean注解
@Configuration 將當(dāng)前類聲明為一個(gè)配置類,相當(dāng)于一個(gè)xml配置文件
@ComponentScan 自動(dòng)掃描包下標(biāo)注有@Repository @Service @Controller
@Component 注解的類并有Spring IOC 容器進(jìn)行實(shí)例化和維護(hù)
@Bean 作用于方法上,聲明當(dāng)前方法的返回值是一個(gè)Bean對(duì)象,相當(dāng)于xml文件中<bean>聲明當(dāng)前方法返回一個(gè)bean對(duì)象
@Value 獲取properties文件指定的key/value
pom.xml
作用是添加坐標(biāo)相關(guān)配置,主要是各種依賴jar包
組合注解和元注解
所謂元注解其實(shí)就是可以注解到別的注解上的注解,被注解的注解稱之為組合注解,組合注解具備元注解的功能,主要的作用是消除重復(fù)注解
自定義注解
個(gè)性化的定義自己所需要的功能并聲明一個(gè)注解,簡化工程,可以參考文章————SPRINGBOOT自定義注解學(xué)習(xí)
常用注解
可以參考文章————SpringBoot常用注解集合詳細(xì)學(xué)習(xí),這里后期會(huì)補(bǔ)上說明
@RestController、@ResponseBody、@RequestBody
- 相當(dāng)于
@Controller + @ResponseBody兩個(gè)注解的結(jié)合,返回JSON數(shù)據(jù)不需要在方法前面加@ResponseBody注解了,
但使用@RestController這個(gè)注解,就不能返回jsp、html頁面,視圖解析器無法解析jsp、html頁面v -
@ResponseBody表示該方法的返回結(jié)果直接寫入HTTP response body中,一般在異步獲取數(shù)據(jù)時(shí)使用(也就是AJAX),
在使用@RequestMapping后,返回值通常解析為跳轉(zhuǎn)路徑,但是加上@ResponseBody后返回結(jié)果不會(huì)被解析為跳轉(zhuǎn)路徑,而是直接寫入HTTP response body中,
比如異步獲取JSON數(shù)據(jù),加上@ResponseBody后,會(huì)直接返回JSON數(shù)據(jù) -
@RequestBody將 HTTP 請(qǐng)求正文插入方法中,使用適合的 HttpMessageConverter 將請(qǐng)求體寫入某個(gè)對(duì)象
@MapperScan、@Mapper
- @Mapper注解:
- 作用:在接口類上添加了@Mapper,在編譯之后會(huì)生成相應(yīng)的接口實(shí)現(xiàn)類
- 添加位置:接口類上面
- 如果想要每個(gè)接口都要變成實(shí)現(xiàn)類,那么需要在每個(gè)接口類上加上
@Mapper注解,比較麻煩,解決這個(gè)問題用@MapperScan注解
- @MapperScan注解:
- 作用:指定要變成實(shí)現(xiàn)類的接口所在的包,然后包下面的所有接口在編譯之后都會(huì)生成相應(yīng)的實(shí)現(xiàn)類
- 添加位置:是在Springboot啟動(dòng)類上面添加
- 添加
@MapperScan("com.winter.da")注解以后,com.winter.dao包下面的接口類,在編譯之后都會(huì)生成相應(yīng)的實(shí)現(xiàn)類
習(xí)慣大于配置目標(biāo)
Spring Boot 的目標(biāo)是快速運(yùn)行,快速創(chuàng)建web應(yīng)用,并獨(dú)立機(jī)型部署(jar包方式,war包方式),相比于Spring框架是全新重寫的框架
核心配置
修改Banner圖標(biāo)
主要是通過修改/src/main/resources目錄下的banner.txt文件,如果沒有則默認(rèn)使用SpringBoot初始Banner
可以個(gè)性化制作Banner的網(wǎng)站制定相應(yīng)的txt文件
全局配置
默認(rèn)是application.properties或者application.yml
坐標(biāo)依賴都配置在pom.xml中,如果添加了依賴以后標(biāo)紅可以使用Maven -> Reload project即可
入口類依靠組合注解@SpringBootApplication
@SpringBootConfiguration 本身是一個(gè)配置類,啟動(dòng)類啟動(dòng)的時(shí)候會(huì)加載
@EnableAutoConfiguration 組合了@AutoConfigurationPackage&@Import(AutoConfigurationImportSelector.class)
@AutoConfigurationPackage 底層是一個(gè)@Import(AutoConfigurationPackage.Registrar.class),其會(huì)把啟動(dòng)類的包下組合都掃描到Spring容器中
@AutoConfigurationImportSelector 讀取大量的自動(dòng)配置類,完成自動(dòng)配置,其讀取的是classpath下的META-INF/spring.factories下的配置文件
@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}
)}
)
Profile配置————區(qū)分生產(chǎn)和開發(fā)環(huán)境
通過在application.yml中設(shè)置spring.profiles.active=test/dev/prod來動(dòng)態(tài)切換不同環(huán)境,例如:
# 開發(fā)環(huán)境配置文件
application-dev.yml
server:
prot: 8098
# 測(cè)試環(huán)境配置文件
application-test.yml
server:
prot: 8097
# 生產(chǎn)環(huán)境配置文件
application-prod.yml
server:
prot: 8099
# 主配置文件
application.yml
spring:
profiles:
active: dev
日志配置
SpringBoot默認(rèn)使用LogBack日志系統(tǒng),一般主流的日志都是用log4j日志系統(tǒng)
如果重復(fù)啟動(dòng)Spring項(xiàng)目,可能會(huì)有端口占用的報(bào)錯(cuò)
- 思路是殺死占用端口的進(jìn)程即可,主要是下面兩個(gè)命令
- 使用
netstat -aon|findstr "被占用的端口"或者tasklist |findstr "進(jìn)程名稱"查詢到端口的進(jìn)程號(hào) - 使用
taskkill /f /t /im "進(jìn)程名稱"或者taskkill /f /t /pid "進(jìn)程PID"殺死進(jìn)程即可
事務(wù)控制
聲明式事務(wù)
可以參考文章————SpringBoot聲明式事務(wù)的簡單運(yùn)用詳細(xì)學(xué)習(xí),這里后期會(huì)補(bǔ)上說明
主要應(yīng)用在新增修改刪除上,應(yīng)用注解即可
全局異常
使用@ControllerAdvice配合@ExceptionHandler
可以參考文章————Springboot系列-@ControllerAdvice使用詳細(xì)學(xué)習(xí),這里后期會(huì)補(bǔ)上說明
此注解其實(shí)是一個(gè)增強(qiáng)的Controller,使用這個(gè)Controller,可實(shí)現(xiàn)三個(gè)方面的功能,因?yàn)檫@是SpringMVC提供的功能,所以可以在springboot中直接使用
- 全局異常處理 (@ExceptionHandler)
- 全局?jǐn)?shù)據(jù)綁定 (@InitBinder)
- 全局?jǐn)?shù)據(jù)預(yù)處理 (@ModelAttribute)
package com.fx67ll.springboot.exceptions;
import com.fx67ll.springboot.po.vo.ResultInfo;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
public class TestGlobalExceptionHandler {
@ExceptionHandler(value = Exception.class)
@ResponseBody
public ResultInfo exceptionHandler(Exception exception) {
ResultInfo resultInfo = new ResultInfo();
resultInfo.setCode(978);
resultInfo.setMsg("全局異常攔截,操作失??!");
// if (exception instanceof ParamsException) {
// ParamsException paramsException = (ParamsException) exception;
// resultInfo.setMsg(paramsException.getMsg());
// resultInfo.setCode(paramsException.getCode());
// }
return resultInfo;
}
}
數(shù)據(jù)校驗(yàn)
為什么要進(jìn)行后端數(shù)據(jù)校驗(yàn)
數(shù)據(jù)的校驗(yàn)是交互式網(wǎng)站一個(gè)不可或缺的功能,前端的js校驗(yàn)可以涵蓋大部分的校驗(yàn)職責(zé),如用戶名唯一性,生日格式,郵箱格式校驗(yàn)等等常用的校驗(yàn)。
但是一般前端傳來的數(shù)據(jù)是不可信的,前端校驗(yàn)過了,后端也應(yīng)該重新校驗(yàn),因?yàn)椴慌懦脩衾@過瀏覽器直接通過Http工具向后端請(qǐng)求的情況。
所以服務(wù)端的數(shù)據(jù)校驗(yàn)也是必要的,可以防止臟數(shù)據(jù)落到數(shù)據(jù)庫中,如果數(shù)據(jù)庫中出現(xiàn)一個(gè)非法的郵箱格式,也會(huì)讓運(yùn)維人員頭疼不已。
如何進(jìn)行后端數(shù)據(jù)校驗(yàn)
-
SpringBoot中一般使用Spring Validation來進(jìn)行后端數(shù)據(jù)校驗(yàn),它是對(duì)Hibernate Validation進(jìn)行了二次封裝,
在SpringMVC模塊中添加了自動(dòng)校驗(yàn),并將校驗(yàn)信息封裝進(jìn)了特定的類中 - 在使用時(shí)我們只需要引入
spring-boot-starter-web依賴即可,該模塊會(huì)自動(dòng)依賴spring-boot-starter-validation
Spring Validation 常用注解
@Null:被注釋的元素必須為
null
@NotNull:被注釋的元素不能為null,可以為空字符串
@AssertTrue:被注釋的元素必須為true
@AssertFalse:被注釋的元素必須為false
@Min(value):被注釋的元素必須是一個(gè)數(shù)字,其值必須大于等于指定的最小值
@Max(value):被注釋的元素必須是一個(gè)數(shù)字,其值必須小于等于指定的最大值
@DecimalMin(value):被注釋的元素必須是一個(gè)數(shù)字,其值必須大于等于指定的最小值
@DecimalMax(value):被注釋的元素必須是一個(gè)數(shù)字,其值必須小于等于指定的最大值
@Size(max,min):被注釋的元素的大小必須在指定的范圍內(nèi)
@Digits(integer,fraction):被注釋的元素必須是一個(gè)數(shù)字,其值必須在可接受的范圍內(nèi)
@Past:被注釋的元素必須是一個(gè)過去的日期
@Future:被注釋的元素必須是一個(gè)將來的日期
@Pattern(value):被注釋的元素必須符合指定的正則表達(dá)式
@Email:被注釋的元素必須是電子郵件地址
@Length:被注釋的字符串的大小必須在指定的范圍內(nèi)
@Range:被注釋的元素必須在合適的范圍內(nèi)
@URL:被注解的元素必須是一個(gè)URL
@NotEmpty:用在集合類上,不能為null,并且長度必須大于0
@NotBlank:只能作用在String上,不能為null,而且調(diào)用trim()后,長度必須大于0
自定義注解
可以參考文章————Spring自定義注解(validation)詳細(xì)學(xué)習(xí),這里后期會(huì)補(bǔ)上說明
示例代碼
-
/com/fx67ll/springboot/controller/UserController.java在傳參的位置添加@Vaild注解表示這里的參數(shù)需要校驗(yàn),需要注意JSON格式和表單格式傳過來的參數(shù)異常會(huì)有些區(qū)別,需要在后面注意// 添加用戶 @PutMapping("/adduser") public ResultInfo saveUser(@RequestBody @Valid User user) { ResultInfo resultInfo = new ResultInfo(); userService.saveUser(user); return resultInfo; } - 在
Bean文件/com/fx67ll/springboot/dao/User.java中私有字段上使用注解來校驗(yàn),不貼所有代碼了,僅貼部分重點(diǎn)代碼@NotBlank(message = "用戶名稱不能為空!") private String userName; @NotBlank(message = "用戶密碼不能為空!") @Length(min = 6, max = 20, message = "密碼長度最少六位且最多二十位!") private String userPwd; - 在全局自定義異常攔截中
/com/fx67ll/springboot/exceptions/TestGlobalExceptionHandler.java向用戶返回錯(cuò)誤代碼和信息package com.fx67ll.springboot.exceptions; import com.fx67ll.springboot.po.vo.ResultInfo; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; @ControllerAdvice public class TestGlobalExceptionHandler { @ExceptionHandler(value = Exception.class) @ResponseBody public ResultInfo exceptionHandler(Exception exception) { ResultInfo resultInfo = new ResultInfo(); resultInfo.setCode(978); resultInfo.setMsg("全局異常攔截,操作失??!"); // 全局?jǐn)?shù)據(jù)校驗(yàn),注意!?。∈褂?json 請(qǐng)求體調(diào)用接口,校驗(yàn)異常拋出 MethodArgumentNotValidException if (exception instanceof MethodArgumentNotValidException) { MethodArgumentNotValidException methodArgumentNotValidException = (MethodArgumentNotValidException) exception; resultInfo.setCode(1023); resultInfo.setMsg(methodArgumentNotValidException.getBindingResult().getFieldError().getDefaultMessage()); } return resultInfo; } }
靜態(tài)資源
默認(rèn)配置下,我們可以在resources資源目錄下存放web應(yīng)用靜態(tài)資源文件
自定義靜態(tài)資源路徑,可以通過在spring.resources.static-locations后面追加一個(gè)配置classpath:/你自定義的配置目錄/,例如:
# application.yml
spring:
resources:
# 多個(gè)目錄使用逗號(hào)隔開
static-loaction: classpath:/public/,classpath:/static/,classpath:/fx67ll/
打包和部署
jar包
- 一般用于編寫依賴工具包
- 打包
- 在IDEA
Run/Debug Configurations下Command line配置clean complie package -Dmaven.test.skip=true執(zhí)行打包命令 -
target目錄得到待部署的項(xiàng)目文件
- 在IDEA
- 部署
- 在dos窗口中,執(zhí)行命令
java -jar jar包所在的本地目錄
- 在dos窗口中,執(zhí)行命令
war包
- 在生產(chǎn)環(huán)境中最為常見的部署方式
- 修改
pom.xml,設(shè)置打包模式為war包<groupId>com.fx67ll</groupId> <artifactId>springboot-quickstart</artifactId> <version>0.1.0</version> <!--設(shè)置為war包模式--> <packaging>war</packaging> - 忽略內(nèi)嵌Tomcat
<!--設(shè)置為外部已提供,表示忽略--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency> - 配置生成的war包名稱
<build> <!--設(shè)置war包名稱--> <finalName>fx67ll-springboot-quickstart-test</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> - 修改
Starter類,添加容器啟動(dòng)加載文件(類似讀取web.xml文件)- 這里通過繼承
SpringBootServletInitiallizer類并重寫configure方法來實(shí)現(xiàn) - 在部署項(xiàng)目的時(shí)候指定外部Tomcat讀取項(xiàng)目入口方法
@SpringBootApplication public class Starter extends SpringBootServletInitializer { public static void main(String[] args) { SpringApplication.run(Starter.class); } @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(Starter.class); } } - 這里通過繼承
- 打包
- 在IDEA
Run/Debug Configurations下Command line配置clean complie package -Dmaven.test.skip=true執(zhí)行打包命令 -
target目錄得到待部署的項(xiàng)目文件
- 在IDEA
- 部署并訪問
- 放置到外部tomcat中,執(zhí)行bin目錄下start腳本即可
熱部署
熱部署,就是在應(yīng)用正在運(yùn)行的時(shí)候升級(jí)軟件,卻不需要重新啟動(dòng)應(yīng)用,主要應(yīng)用在開發(fā)過程中
熱部署原理
-
spring-boot-devtools是一個(gè)為開發(fā)者服務(wù)的一個(gè)模塊,其中最重要的功能就是自動(dòng)應(yīng)用代碼更改到最新的App上面去,
原理是在發(fā)現(xiàn)代碼有更改之后,重新啟動(dòng)應(yīng)用,但是速度比手動(dòng)停止后再啟動(dòng)還要更快,更快指的不是節(jié)省出來的手工操作的時(shí)間 - 其深層原理是使用了兩個(gè)
ClassLoader,一個(gè)Classloader加載那些不會(huì)改變的類(第三方Jar包),另一個(gè)ClassLoader加載會(huì)更改的類,稱為restart ClassLoader,
這樣在有代碼更改的時(shí)候,原來的restart ClassLoader被丟棄,重新創(chuàng)建一個(gè)restart ClassLoader,由于需要加載的類相比較少,所以實(shí)現(xiàn)了較快的重啟時(shí)間,大概在5秒以內(nèi)
devtools原理
- devtools會(huì)監(jiān)聽classpath下的文件變動(dòng),并且會(huì)立即重啟應(yīng)用(發(fā)生在保存時(shí)機(jī))注意:因?yàn)槠洳捎玫奶摂M機(jī)機(jī)制,該項(xiàng)重啟是很快的
- devtools可以實(shí)現(xiàn)頁面熱部署(即頁面修改后會(huì)立即生效,這個(gè)可以直接在
application文件中配置spring.thymeleaf.cache=false來實(shí)現(xiàn) 注意:不同的模板配置不一樣
熱部署主要步驟
- 在
pom.xml中添加依賴,同時(shí)添加devtools生效標(biāo)志插件<!--熱部署插件devtools--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <!--表示當(dāng)前這個(gè)項(xiàng)目被繼承之后,這個(gè)不向下傳遞--> <optional>true</optional> </dependency> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <!--在原有的基礎(chǔ)上添加--> <configuration> <!--如果沒有該配置,熱部署插件devtools不生效--> <fork>true</fork> </configuration> </plugin> - 修改
application.yml全局配置文件,在application.yml中配置spring.devtools.restart.enable=false,此時(shí)restart類加載器還會(huì)初始化,但不會(huì)監(jiān)視文件更新spring: # 熱部署配置 devtools: restart: enabled: true # 設(shè)置重啟的目錄,添加目錄的文件需要restart additional-paths: src/main/java # 解決項(xiàng)目啟動(dòng)重新編譯后接口報(bào)404的問題 poll-interval: 3000 quiet-period: 1000 - 修改 IDEA 配置
- 修改了java類之后,IDEA 默認(rèn)是不自動(dòng)編譯的,而
spring-boot-devtools又是監(jiān)測(cè)classpath下的文件發(fā)生變化才會(huì)重啟應(yīng)用,所以需要設(shè)置 IDEA 的自動(dòng)編譯 - 設(shè)置自動(dòng)配置
File -> Settings -> Build -> Complier -> Build Project automatically 修改Register屬性,執(zhí)行快捷鍵ctrl + shift + alt + /,選擇Register,勾上Complier autoMake allow when app running-
注意 IDEA 2021.2.3 版本中沒有上面的選項(xiàng),遷移到了
File -> Settings -> Tools -> Advanced Settings -> Complier -> Allow auto-make to start......
- 修改了java類之后,IDEA 默認(rèn)是不自動(dòng)編譯的,而
- 配置完需要重啟一下,然后有修改的話項(xiàng)目會(huì)自動(dòng)更新,但是如果是自動(dòng)觸發(fā)的話,會(huì)造成頻繁更新,對(duì)硬件有一定的負(fù)擔(dān),所以可以改成手動(dòng)觸發(fā)模式
- 點(diǎn)擊右上角
Run/Debug Configurations - 選擇下拉
Configuration -> Spring Boot -> Running Application Update Policies -> On 'Update' action - 選擇
Update classes and resources - 如果有更新可以,使用快捷鍵
Ctrl + F10重新編譯
- 點(diǎn)擊右上角
- 快捷鍵
Ctrl + F9,使用熱部署重新啟動(dòng)
單元測(cè)試
依賴
<!--單元測(cè)試-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
Service業(yè)務(wù)層————業(yè)務(wù)邏輯方法測(cè)試
需要注意的是:
- 如果在和
main文件夾平級(jí)的test文件夾下新建了java文件夾,但是無法新建java class文件 - 那么就需要右鍵文件夾
Mark Directory as -> Test Sources Root之后,文件夾變綠即可
# 示例代碼
package com.fx67ll.springboot.service;
import com.fx67ll.springboot.Starter;
import com.fx67ll.springboot.po.User;
import com.fx67ll.springboot.query.UserQuery;
import com.fx67ll.springboot.srevice.UserService;
import com.github.pagehelper.PageInfo;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
/**
* Service業(yè)務(wù)方法測(cè)試
*
* Junit中的RunWith注解 表示該類是單元測(cè)試的執(zhí)行類
* SpringRunner 是 spring-test 提供的測(cè)試執(zhí)行單元類(是Spring單元測(cè)試中SpringJUnit4ClassRunner的新名字)
* SpringBootTest注解 是執(zhí)行測(cè)試程序的引導(dǎo)類
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {Starter.class})
public class TestUserService {
// 日志的使用
private Logger logger = LoggerFactory.getLogger(TestUserService.class);
@Resource
private UserService userService;
@Before
public void before() {
logger.info("單元測(cè)試開始......");
}
@Test
public void testQueryUserById() {
logger.info("測(cè)試根據(jù)用戶id查詢......");
User user = userService.queryUserById(1);
logger.info("用戶記錄: {}", user.toString());
}
@Test
public void testSelectUserListByParams() {
logger.info("測(cè)試根據(jù)分頁條件查詢用戶列表......");
UserQuery userQuery = new UserQuery();
PageInfo<User> pageInfo = userService.selectUserListByParams(userQuery);
logger.info(pageInfo.toString());
}
@After
public void after() {
logger.info("單元測(cè)試結(jié)束......");
}
}
controller控制層————接口方法測(cè)試
使用MockMVC進(jìn)行測(cè)試
MockMvc是由spring-test包提供,實(shí)現(xiàn)了對(duì)Http請(qǐng)求的模擬,能夠直接使用網(wǎng)絡(luò)的形式,轉(zhuǎn)換到Controller的調(diào)用,使得測(cè)試速度快、不依賴網(wǎng)絡(luò)環(huán)境。
同時(shí)提供了一套驗(yàn)證的工具,結(jié)果的驗(yàn)證十分方便
什么是Mock
在面向?qū)ο蟮某绦蛟O(shè)計(jì)中,模擬對(duì)象mock object是以可控的方式模擬真實(shí)對(duì)象行為的假對(duì)象。
在編程過程中,通常通過模擬一些輸入數(shù)據(jù),來驗(yàn)證程序是否達(dá)到預(yù)期結(jié)果
接口MockMvcBuilder
提供一個(gè)唯一的build方法,用來構(gòu)造MockMvc。
主要有兩個(gè)實(shí)現(xiàn):StandaloneMockMvcBuilder和DefaultMockMvcBuilder,分別對(duì)應(yīng)兩種測(cè)試方式,
即獨(dú)立安裝和集成Web環(huán)境測(cè)試(并不會(huì)集成真正的web環(huán)境,而是通過相應(yīng)的Mock API進(jìn)行模擬測(cè)試,無須啟動(dòng)服務(wù)器)。
MockMvcBuilders提供了對(duì)應(yīng)的創(chuàng)建方法standaloneSetup方法和webAppContextSetup方法,在使用時(shí)直接調(diào)用即可。
# 示例代碼
# PS:雖然提示測(cè)試通過,但是控制臺(tái)一直沒有打印出返回信息的記錄,后期有空看看
package com.fx67ll.springboot.controller;
import com.fx67ll.springboot.Starter;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {Starter.class})
@AutoConfigureMockMvc
public class TestUserController {
// 日志的使用
private Logger logger = LoggerFactory.getLogger(TestUserController.class);
@Autowired
private MockMvc mockMvc;
/**
* 模擬測(cè)試用戶列表查詢
* 其實(shí)就在模擬真實(shí)環(huán)境下前端對(duì)后端發(fā)起的請(qǐng)求
*/
@Test
public void apiTestSelectUserListByParams() throws Exception {
logger.info("開始模擬發(fā)送查詢用戶列表的請(qǐng)求......");
// 構(gòu)建請(qǐng)求
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get("/user/list")
.contentType("text/html") // 設(shè)置請(qǐng)求頭信息
.accept(MediaType.APPLICATION_JSON); // 設(shè)置請(qǐng)求Accept頭信息
// 發(fā)送請(qǐng)求
ResultActions perform = mockMvc.perform(requestBuilder);
// 校驗(yàn)請(qǐng)求結(jié)果
perform.andExpect(MockMvcResultMatchers.status().isOk());
// 獲取執(zhí)行完成后返回的結(jié)果
MvcResult mvcResult = perform.andReturn();
// 得到執(zhí)行后的響應(yīng)
MockHttpServletResponse response = mvcResult.getResponse();
// 打印結(jié)果
logger.info(String.valueOf(response.getContentLength()));
logger.info("響應(yīng)狀態(tài): ", response.getStatus());
logger.info("響應(yīng)信息: ", response.getContentAsString());
logger.info("結(jié)束模擬發(fā)送查詢用戶列表的請(qǐng)求......");
}
@Test
public void apiTestQueryUserByUsername() throws Exception {
logger.info("開始模擬根據(jù)用戶名查詢用戶記錄的請(qǐng)求......");
// 構(gòu)建請(qǐng)求并發(fā)送
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/user/name/admin"))
.andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
// 打印結(jié)果
logger.info("響應(yīng)狀態(tài): ", mvcResult.getResponse().getStatus());
logger.info("響應(yīng)信息: ", mvcResult.getResponse().getContentAsString());
logger.info("結(jié)束模擬根據(jù)用戶名查詢用戶記錄的請(qǐng)求......");
}
}
Swagger2文檔工具
依賴
在pom.xml中添加以下代碼
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
常用注解
可以參考文章————swagger2 注解說明詳細(xì)學(xué)習(xí),這里后期會(huì)補(bǔ)上說明
@Api
主要是用在請(qǐng)求類上,用于說明該類的作用
# 示例
@Api(tags = "xx模塊")
@ApiOperation
主要是用在請(qǐng)求的方法上,說明方法的作用
# 示例
@ApiOperation(value = "xx方法的作用", notes = "xx方法的備注說明")
@ApiImplicitParams、@ApiImplicitParam
主要是用在請(qǐng)求的方法上,說明方法的參數(shù)
# 詳細(xì)參數(shù)說明
@ApiImplicitParams:用在請(qǐng)求的方法上,包含一組參數(shù)說明
@ApiImplicitParam:對(duì)單個(gè)參數(shù)的說明
name:參數(shù)名
value:參數(shù)的說明、描述
required:參數(shù)是否必須必填
paramType:參數(shù)放在哪個(gè)地方
· query --> 請(qǐng)求參數(shù)的獲?。篅RequestParam
· header --> 請(qǐng)求參數(shù)的獲?。篅RequestHeader
· path(用于restful接口)--> 請(qǐng)求參數(shù)的獲?。篅PathVariable
· body(請(qǐng)求體)--> @RequestBody User user
· form(普通表單提交)
dataType:參數(shù)類型,默認(rèn)String,其它值dataType="Integer"
defaultValue:參數(shù)的默認(rèn)值
# 單個(gè)參數(shù)示例
@ApiImplicitParam(name = "xxx", value = "xxx", required = true, paramType = "path", dataType = "String", defaultValue = "")
# 多個(gè)參數(shù)示例
@ApiImplicitParams({
@ApiImplicitParam(name = "xxxa", value = "xxxa", required = true, paramType = "body", dataType = "String", defaultValue = ""),
@ApiImplicitParam(name = "xxxb", value = "xxxb", required = true, paramType = "body", dataType = "String", defaultValue = ""),
})
@ApiResponses、@ApiResponse
主要是用在請(qǐng)求的方法上,說明錯(cuò)誤響應(yīng)的信息
# 詳細(xì)參數(shù)說明
@ApiResponses:響應(yīng)狀態(tài)的說明。是個(gè)數(shù)組,可包含多個(gè) @ApiResponse
@ApiResponse:每個(gè)參數(shù)的說明
code:數(shù)字,例如400
message:信息,例如"請(qǐng)求參數(shù)沒填好"
response:拋出異常的類
# 多個(gè)參數(shù)示例,一般響應(yīng)都是多個(gè)code,所以不寫單個(gè)參數(shù)的示例了
@ApiResponses({
@ApiResponse(code = 200, message = "請(qǐng)求成功"),
@ApiResponse(code = 578, message = "請(qǐng)求參數(shù)錯(cuò)誤"),
@ApiResponse(code = 404, message = "請(qǐng)求路徑?jīng)]有或頁面跳轉(zhuǎn)路徑不對(duì)")
})
@ApiModel、@ApiModelProperty
- @ApiModel 經(jīng)常用于請(qǐng)求的入?yún)?duì)象和響應(yīng)返回值對(duì)象的描述
- 入?yún)⑹菍?duì)象,即 @RequestBody 時(shí), 用于封裝請(qǐng)求(包括數(shù)據(jù)的各種校驗(yàn))數(shù)據(jù)
- 返回值是對(duì)象,即 @ResponseBody 時(shí),用于返回值對(duì)象的描述
- @ApiModelProperty 用于每個(gè)屬性上面,說明屬性的含義
# 示例
@ApiModel(description = "用戶實(shí)體類")
public class User {
@ApiModelProperty(value = "用戶名", required = true, example = "0")
private Integer id;
@ApiModelProperty(value = "用戶ID", required = true, example = "fx67ll")
private String userName;
@ApiModelProperty(value = "用戶密碼", required = true, example = "xxxxxxxx")
private String userPwd;
}
分布式緩存工具Ehcache
什么是Ehcache
EhCache是一個(gè)純Java的進(jìn)程內(nèi)緩存框架,具有快速、精干等特點(diǎn),是Hibernate中默認(rèn)CacheProvider。
Ehcache是一種廣泛使用的開源Java分布式緩存,主要面向通用緩存,Java EE和輕量級(jí)容器。
它具有內(nèi)存和磁盤存儲(chǔ),緩存加載器,緩存擴(kuò)展,緩存異常處理程序,一個(gè)gzip緩存servlet過濾器,支持REST API和SOAP API等特點(diǎn)。
SpringCache相關(guān)注解
SpringBoot緩存實(shí)現(xiàn)內(nèi)部使用SpringCache實(shí)現(xiàn)緩存控制,這里集成Ehcache實(shí)際上是對(duì)SpringCache抽象的一種實(shí)現(xiàn)
可以參考文章————Spring Cache 簡介詳細(xì)學(xué)習(xí),這里后期會(huì)補(bǔ)上說明
@EnableCaching
開啟緩存功能,一般放在啟動(dòng)類上
@CacheConfig
當(dāng)我們需要緩存的地方越來越多,你可以使用@CacheConfig(cacheNames = {"cacheName"})注解在Class之上來統(tǒng)一指定value的值,
這時(shí)可省略value,如果你在你的方法依舊寫上了value,那么依然以方法的value值為準(zhǔn)
@Cacheable
根據(jù)方法對(duì)其返回結(jié)果進(jìn)行緩存,下次請(qǐng)求時(shí),如果緩存存在,則直接讀取緩存數(shù)據(jù)返回;如果緩存不存在,則執(zhí)行方法,并把返回的結(jié)果存入緩存中,一般用在查詢方法上
注意value后面要使用ehcache.xml文件中所列的cache.name
# 單個(gè)參數(shù)示例代碼
@Cacheable(value = "fx67llCache", key = "#xxx")
# 多個(gè)參數(shù)示例,采用拼接的方式
@Cacheable(value = "fx67llCache", key = "#xxx.xxx + '-' + #xxx.xxx + '-' + #xxx.xxx")
@CachePut
使用該注解標(biāo)志的方法,每次都會(huì)執(zhí)行,并將結(jié)果存入指定的緩存中。其他方法可以直接從響應(yīng)的緩存中讀取緩存數(shù)據(jù),而不需要再去查詢數(shù)據(jù)庫,一般用在新增方法上
# 示例代碼
@CachePut(value = "fx67llCache", key = "#xxx.xxx")
@CacheEvict
使用該注解標(biāo)志的方法,會(huì)清空指定的緩存,一般用在更新或者刪除方法上
# 示例代碼
@CacheEvict(value = "fx67llCache", key = "#xxx")
@Caching
該注解可以實(shí)現(xiàn)同一個(gè)方法上同時(shí)使用多種注解
Ehcache的使用
- 在
pom.xml添加依賴<!--Ehcache工具依賴--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> </dependency> - 添加
ehcache.xml文件<?xml version="1.0" encoding="UTF-8"?> <ehcache name="fx67llCache"> <!-- diskStore:為緩存路徑,ehcache分為內(nèi)存和磁盤兩級(jí),此屬性定義磁盤的緩存位置。參數(shù)解釋如下: user.home – 用戶主目錄 user.dir – 用戶當(dāng)前工作目錄 java.io.tmpdir – 默認(rèn)臨時(shí)文件路徑 --> <diskStore path="D:\Java\test-ehcache-cache"/> <!-- defaultCache:默認(rèn)緩存策略,當(dāng)ehcache找不到定義的緩存時(shí),則使用這個(gè)緩存策略。只能定義一個(gè)。 --> <!-- name:緩存名稱。 maxElementsInMemory:緩存最大數(shù)目 maxElementsOnDisk:硬盤最大緩存?zhèn)€數(shù)。 eternal:對(duì)象是否永久有效,一但設(shè)置了,timeout將不起作用。 overflowToDisk:是否保存到磁盤,當(dāng)系統(tǒng)當(dāng)機(jī)時(shí) timeToIdleSeconds:設(shè)置對(duì)象在失效前的允許閑置時(shí)間(單位:秒)。僅當(dāng)eternal=false對(duì)象不是永久有效時(shí)使用,可選屬性,默認(rèn)值是0,也就是可閑置時(shí)間無窮大。 timeToLiveSeconds:設(shè)置對(duì)象在失效前允許存活時(shí)間(單位:秒)。最大時(shí)間介于創(chuàng)建時(shí)間和失效時(shí)間之間。僅當(dāng)eternal=false對(duì)象不是永久有效時(shí)使用,默認(rèn)是0.,也就是對(duì)象存活時(shí)間無窮大。 diskPersistent:是否緩存虛擬機(jī)重啟期數(shù)據(jù) Whether the disk store persists between restarts of the Virtual Machine. The default value is false. diskSpoolBufferSizeMB:這個(gè)參數(shù)設(shè)置DiskStore(磁盤緩存)的緩存區(qū)大小。默認(rèn)是30MB。每個(gè)Cache都應(yīng)該有自己的一個(gè)緩沖區(qū)。 diskExpiryThreadIntervalSeconds:磁盤失效線程運(yùn)行時(shí)間間隔,默認(rèn)是120秒。 memoryStoreEvictionPolicy:當(dāng)達(dá)到maxElementsInMemory限制時(shí),Ehcache將會(huì)根據(jù)指定的策略去清理內(nèi)存。默認(rèn)策略是LRU(最近最少使用)。你可以設(shè)置為FIFO(先進(jìn)先出)或是LFU(較少使用)。 clearOnFlush:內(nèi)存數(shù)量最大時(shí)是否清除。 memoryStoreEvictionPolicy:可選策略有:LRU(最近最少使用,默認(rèn)策略)、FIFO(先進(jìn)先出)、LFU(最少訪問次數(shù))。 FIFO,first in first out,這個(gè)是大家最熟的,先進(jìn)先出。 LFU, Less Frequently Used,就是上面例子中使用的策略,直白一點(diǎn)就是講一直以來最少被使用的。如上面所講,緩存的元素有一個(gè)hit屬性,hit值最小的將會(huì)被清出緩存。 LRU,Least Recently Used,最近最少使用的,緩存的元素有一個(gè)時(shí)間戳,當(dāng)緩存容量滿了,而又需要騰出地方來緩存新的元素的時(shí)候,那么現(xiàn)有緩存元素中時(shí)間戳離當(dāng)前時(shí)間最遠(yuǎn)的元素將被清出緩存。 --> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" maxElementsOnDisk="10000000" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"/> <cache name="fx67llCache" eternal="false" maxElementsInMemory="100" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="0" timeToLiveSeconds="300" memoryStoreEvictionPolicy="LRU"/> </ehcache> - 在
application.yml添加緩存配置# Ehcache 緩存配置 cache: ehcache: config: classpath:ehcache.xml - 在入口類添加
@EnableCaching注解,表示開啟緩存 - Java Bean 對(duì)象實(shí)現(xiàn)序列化,
public class User implements Serializable - 在需要使用的地方使用現(xiàn)關(guān)注解,實(shí)現(xiàn)緩存可以減少從數(shù)據(jù)庫查詢的次數(shù)
定時(shí)調(diào)度工具Quartz
可以參考文章————Quartz定時(shí)調(diào)度詳細(xì)學(xué)習(xí),這里后期會(huì)補(bǔ)上說明
什么是Quartz
在日常項(xiàng)目運(yùn)行中,我們總會(huì)有需求在某一時(shí)間段周期性的執(zhí)行某個(gè)動(dòng)作,比如每天在某個(gè)時(shí)間段導(dǎo)出報(bào)表,或者每隔多久統(tǒng)計(jì)一次現(xiàn)在在線的用戶量等。
在SpringBoot中有Java自帶的java.util.Timer類,也可以在啟動(dòng)類添加@EnableScheduling注解引入定時(shí)任務(wù)環(huán)境
Quartz的使用
- 在
pom.xml添加依賴<!--Quartz工具依賴--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> - 添加
job包并編寫job任務(wù),實(shí)現(xiàn)job接口,并在execute方法中實(shí)現(xiàn)自己的業(yè)務(wù)邏輯package com.fx67ll.springboot.jobs; import org.quartz.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.text.SimpleDateFormat; import java.util.Date; public class TestQuartzJob implements Job { private Logger logger = LoggerFactory.getLogger(TestQuartzJob.class); @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { // 獲取整理好的日期時(shí)間 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 查詢觸發(fā)器名稱和觸發(fā)器屬于哪個(gè)分組 TriggerKey triggerKey = jobExecutionContext.getTrigger().getKey(); //打印日志 logger.info("當(dāng)前觸發(fā)器是: " + triggerKey.getName() + ",它所屬的組別是: " + triggerKey.getGroup() + "----------觸發(fā)時(shí)間: " + simpleDateFormat.format(new Date()) + "-->" + "Hello fx67ll Spring Boot Quartz......"); } } - 構(gòu)建調(diào)度配置類,創(chuàng)建JobDetail實(shí)例并定義Trigger注冊(cè)到scheduler,啟動(dòng)scheduler開啟調(diào)度
package com.fx67ll.springboot.conf; import com.fx67ll.springboot.jobs.TestQuartzJob; import org.quartz.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class QuartzCOnf { @Bean /** * 具體的可以被執(zhí)行的調(diào)度程序 */ public JobDetail jobDetailTestQuartz(){ return JobBuilder.newJob(TestQuartzJob.class).storeDurably().build(); } @Bean /** * 第一個(gè)測(cè)試觸發(fā)器,主要是配置參數(shù)提示什么時(shí)候調(diào)用 * 應(yīng)用場景有比如定時(shí)發(fā)送郵件之類的 */ public Trigger triggerTestQuartzFirst(){ SimpleScheduleBuilder simpleScheduleBuilder = SimpleScheduleBuilder.simpleSchedule() // 每五秒執(zhí)行一次 .withIntervalInSeconds(1) // 永久重復(fù),一直執(zhí)行下去 .repeatForever(); return TriggerBuilder.newTrigger() // 設(shè)置觸發(fā)器名稱和分組 .withIdentity("triggerTestQuartzFirst","groupTestQuartz") .withSchedule(simpleScheduleBuilder) .forJob(jobDetailTestQuartz()) .build(); } @Bean /** * 第二個(gè)測(cè)試觸發(fā)器 */ public Trigger triggerTestQuartzSecond(){ return TriggerBuilder.newTrigger() // 設(shè)置觸發(fā)器名稱和分組 .withIdentity("triggerTestQuartzSecond","groupTestQuartz") // 這里是通過定義表達(dá)式來表示每5秒執(zhí)行一次,后續(xù)再深入研究下 .withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ? *")) .forJob(jobDetailTestQuartz()) .build(); } }
附錄
操作代碼目錄說明
| springboot-quickstart | springboot-mybatis | springboot-mybatis-crud | springboot-mybatis-crud-prod |
|---|---|---|---|
| 快速入門 | 整合mybatis | 整套crud操作 | 生產(chǎn)環(huán)境開發(fā) |
操作代碼資源地址
參考資料
- 參考教程 ———— 兩天搞定SpringBoot框架
- 參考文檔 ———— JavaSpringBoot 中 @Autowired用法
- 參考文檔 ———— SpringBoot - @Configuration、@Bean注解的使用詳解(配置類的實(shí)現(xiàn))
- 參考文檔 ———— 【Spring Boot】Spring基礎(chǔ) —— 組合注解與元注解
- 參考文檔 ———— @RestController 和 @Controller 的區(qū)別
- 參考文檔 ———— MapperScan注解詳解
- 參考文檔 ———— Mapper.xml詳解
- 參考文檔 ———— MVC三層架構(gòu)(詳解)
- 參考文檔 ———— 配置devtools熱部署
- 參考文檔 ———— (十三)SpringBoot2.0熱部署Devtools原理
- 參考文檔 ———— 2021版IDEA沒有compiler.automake.allow.when.app.running
- 參考文檔 ———— SpringBoot基礎(chǔ)之MockMvc單元測(cè)試
- 參考文檔 ———— Ehcache詳細(xì)解讀
- 參考文檔 ———— spring boot接入ehcache
- 參考文檔 ———— SpringBoot(十二): validation常用注解
- 參考文檔 ———— SpringBoot之——Validator校驗(yàn)相關(guān)的注解
- 參考文檔 ———— 強(qiáng)悍的Spring之spring validation
- json格式校驗(yàn)并顯示錯(cuò)誤_使用 Spring Validation 優(yōu)雅地進(jìn)行參數(shù)校驗(yàn)
我是 fx67ll.com,如果您發(fā)現(xiàn)本文有什么錯(cuò)誤,歡迎在評(píng)論區(qū)討論指正,感謝您的閱讀!
如果您喜歡這篇文章,歡迎訪問我的 本文github倉庫地址,為我點(diǎn)一顆Star,Thanks~ :)
轉(zhuǎn)發(fā)請(qǐng)注明參考文章地址,非常感謝?。?!