SpringBoot項(xiàng)目的流程是怎樣的

SpringBoot項(xiàng)目的流程是怎樣的,相信很多沒(méi)有經(jīng)驗(yàn)的人對(duì)此束手無(wú)策,為此本文總結(jié)了問(wèn)題出現(xiàn)的原因和解決方法,通過(guò)這篇文章希望你能解決這個(gè)問(wèn)題。

我們一直強(qiáng)調(diào)成都網(wǎng)站制作、成都網(wǎng)站設(shè)計(jì)、外貿(mào)營(yíng)銷網(wǎng)站建設(shè)對(duì)于企業(yè)的重要性,如果您也覺(jué)得重要,那么就需要我們慎重對(duì)待,選擇一個(gè)安全靠譜的網(wǎng)站建設(shè)公司,企業(yè)網(wǎng)站我們建議是要么不做,要么就做好,讓網(wǎng)站能真正成為企業(yè)發(fā)展過(guò)程中的有力推手。專業(yè)網(wǎng)站建設(shè)公司不一定是大公司,成都創(chuàng)新互聯(lián)作為專業(yè)的網(wǎng)絡(luò)公司選擇我們就是放心。

前言

在我的工作中,我從零開(kāi)始搭建了不少軟件項(xiàng)目,其中包含了基礎(chǔ)代碼框架和持續(xù)集成基礎(chǔ)設(shè)施等,這些內(nèi)容在敏捷開(kāi)發(fā)中通常被稱為“第0個(gè)迭代”要做的事情。但是,當(dāng)項(xiàng)目運(yùn)行了一段時(shí)間之后再來(lái)反觀,我總會(huì)發(fā)現(xiàn)一些不足的地方,要么測(cè)試分類沒(méi)有分好,要么基本的編碼架子沒(méi)有考慮周全。

另外,我在工作中也會(huì)接觸到很多既有項(xiàng)目,公司內(nèi)部和外部的都有,多數(shù)項(xiàng)目的編碼實(shí)踐我都是不滿意的。比如,我曾經(jīng)新加入一個(gè)項(xiàng)目的時(shí)候,前前后后請(qǐng)教了3位同事才把該項(xiàng)目在本地運(yùn)行起來(lái);又比如在另一項(xiàng)目中,我發(fā)現(xiàn)前端請(qǐng)求對(duì)應(yīng)的Java類命名規(guī)范不統(tǒng)一,有被后綴為Request的,也有被后綴為Command的。

再者,工作了這么多年之后,我越來(lái)越發(fā)現(xiàn)基礎(chǔ)知識(shí)以及系統(tǒng)性學(xué)習(xí)的重要性。誠(chéng)然,技術(shù)框架的發(fā)展使得我們可以快速地實(shí)現(xiàn)業(yè)務(wù)功能,但是當(dāng)軟件出了問(wèn)題之后有時(shí)卻需要將各方面的知識(shí)融會(huì)貫通并在大腦里綜合反應(yīng)才能找到解決思路。

基于以上,我希望整理出一套公共性的項(xiàng)目模板出來(lái),旨在盡量多地包含日常開(kāi)發(fā)之所需,減少開(kāi)發(fā)者的重復(fù)性工作以及提供一些最佳實(shí)踐。對(duì)于后端開(kāi)發(fā)而言,我選擇了當(dāng)前被行業(yè)大量使用的Spring Boot。

本文以一個(gè)簡(jiǎn)單的電商訂單系統(tǒng)為例,源代碼請(qǐng)?jiān)L問(wèn):

git clone https://github.com/e-commerce-sample/order-backend
git checkout a443dace

所使用的技術(shù)棧主要包括:Spring Boot、Gradle、MySQL、Junit 5、Rest Assured、Docker等。

從寫好README開(kāi)始

一份好的README可以給人以項(xiàng)目全景概覽,可以使新人快速上手項(xiàng)目,可以降低溝通成本。同時(shí),README應(yīng)該簡(jiǎn)明扼要,條理清晰,建議包含以下方面:

  • 項(xiàng)目簡(jiǎn)介:用一兩句話簡(jiǎn)單描述該項(xiàng)目所實(shí)現(xiàn)的業(yè)務(wù)功能;

  • 技術(shù)選型:列出項(xiàng)目的技術(shù)棧,包括語(yǔ)言、框架和中間件等;

  • 本地構(gòu)建:列出本地開(kāi)發(fā)過(guò)程中所用到的工具命令;

  • 領(lǐng)域模型:核心的領(lǐng)域概念,比如對(duì)于示例電商系統(tǒng)來(lái)說(shuō)有Order、Product等;

  • 測(cè)試策略:自動(dòng)化測(cè)試如何分類,哪些必須寫測(cè)試,哪些沒(méi)有必要寫測(cè)試;

  • 技術(shù)架構(gòu):技術(shù)架構(gòu)圖;

  • 部署架構(gòu):部署架構(gòu)圖;

  • 外部依賴:項(xiàng)目運(yùn)行時(shí)所依賴的外部集成方,比如訂單系統(tǒng)會(huì)依賴于會(huì)員系統(tǒng);

  • 環(huán)境信息:各個(gè)環(huán)境的訪問(wèn)方式,數(shù)據(jù)庫(kù)連接等;

  • 編碼實(shí)踐:統(tǒng)一的編碼實(shí)踐,比如異常處理原則、分頁(yè)封裝等;

  • FAQ:開(kāi)發(fā)過(guò)程中常見(jiàn)問(wèn)題的解答。

需要注意的是,README中的信息可能隨著項(xiàng)目的演進(jìn)而改變(比如引入了新的技術(shù)?;蛘呒尤肓诵碌念I(lǐng)域模型),因此也是需要持續(xù)更新的。雖然我們知道,軟件文檔的一個(gè)痛點(diǎn)便是無(wú)法與項(xiàng)目實(shí)際進(jìn)展保持同步,但是就README這點(diǎn)信息來(lái)講,還是建議開(kāi)發(fā)者們不要吝嗇那一點(diǎn)點(diǎn)敲鍵盤的時(shí)間。

此外,除了保持README的持續(xù)更新,一些重要的架構(gòu)決定可以通過(guò)示例代碼的形式記錄在代碼庫(kù)中,新開(kāi)發(fā)者可以通過(guò)直接閱讀這些示例代碼快速了解項(xiàng)目的通用實(shí)踐方式以及架構(gòu)選擇,請(qǐng)參考:

https://www.thoughtworks.com/radar/techniques/lightweight-architecture-decision-records

一鍵式本地構(gòu)建

為了避免諸如前文中所提到的“請(qǐng)教了3位同事才本地構(gòu)建成功”的尷尬,為了減少“懶惰”的程序員們的手動(dòng)操作,也為了為所有開(kāi)發(fā)者提供一種一致的開(kāi)發(fā)體驗(yàn),我們希望用一個(gè)命令就可以完成所有的事情。這里,對(duì)于不同的場(chǎng)景我總結(jié)出了以下命令:

  • 生成IDE工程:idea.sh,生成IntelliJ工程文件并自動(dòng)打開(kāi)IntelliJ

  • 本地運(yùn)行:run.sh,本地啟動(dòng)項(xiàng)目,自動(dòng)啟動(dòng)本地?cái)?shù)據(jù)庫(kù),監(jiān)聽(tīng)調(diào)試端口5005

  • 本地構(gòu)建:local-build.sh,只有本地構(gòu)建成功才能提交代碼

以上3個(gè)命令基本上可以完成日常開(kāi)發(fā)之所需,此時(shí),對(duì)于新人的開(kāi)發(fā)流程大致為:

  • 拉取代碼;

  • 運(yùn)行idea.sh,自動(dòng)打開(kāi)IntelliJ;

  • 編寫代碼,包含業(yè)務(wù)代碼和自動(dòng)化測(cè)試;

  • 運(yùn)行run.sh,進(jìn)行本地調(diào)試或必要的手動(dòng)測(cè)試(本步驟不是必需);

  • 運(yùn)行l(wèi)ocal-build.sh,完成本地構(gòu)建;

  • 再次拉取代碼,保證local-build.sh成功,提交代碼。

事實(shí)上,這些命令腳本的內(nèi)容非常簡(jiǎn)單,比如run.sh文件內(nèi)容為:

#!/usr/bin/env bash./gradlew clean bootRun

然而,這種顯式化的命令卻可以減少新人的恐懼感,因?yàn)樗麄冎恍枰肋\(yùn)行這3個(gè)命令就可以搞開(kāi)發(fā)了。另外,一個(gè)小小的細(xì)節(jié):本地構(gòu)建的local-build.sh命令本來(lái)可以重命名為更簡(jiǎn)單的build.sh,但是當(dāng)我們?cè)诿钚兄惺褂肨ab鍵自動(dòng)補(bǔ)全的時(shí)候,會(huì)發(fā)現(xiàn)自動(dòng)補(bǔ)全到了build目錄,而不是build.sh命令,并不方便,因此命名為了local-build.sh。

細(xì)節(jié)雖小,但是卻體現(xiàn)了一個(gè)宗旨,即我們希望給開(kāi)發(fā)者一種極簡(jiǎn)的開(kāi)發(fā)體驗(yàn),我把這些看似微不足道的東西稱作是對(duì)程序員的“人文關(guān)懷”。

目錄結(jié)構(gòu)

Maven所提倡的目錄結(jié)構(gòu)當(dāng)前已經(jīng)成為事實(shí)上的行業(yè)標(biāo)準(zhǔn),Gradle在默認(rèn)情況下也采用了Maven的目錄結(jié)構(gòu),這對(duì)于多數(shù)項(xiàng)目來(lái)說(shuō)已經(jīng)足夠了。此外,除了Java代碼,項(xiàng)目中還存在其他類型的文件,比如Gradle插件的配置、工具腳本和部署配置等。無(wú)論如何,項(xiàng)目目錄結(jié)構(gòu)的原則是簡(jiǎn)單而有條理,不要隨意地增加多余的文件夾,并且也需要及時(shí)重構(gòu)。

在示例項(xiàng)目中,頂層只有2個(gè)文件夾,一個(gè)是用于放置Java源代碼和項(xiàng)目配置的src文件夾,另一個(gè)是用于放置所有Gradle配置的gradle文件夾,此外,為了方便開(kāi)發(fā)人員使用,將上文提到的3個(gè)常用腳本直接放到根目錄下:

└── order-backend    ├── gradle // 文件夾,用于放置所有Gradle配置    ├── src // 文件夾,Java源代碼    ├── idea.sh //生成IntelliJ工程    ├── local-build.sh // 提交之前的本地構(gòu)建    └── run.sh // 本地運(yùn)行

對(duì)于gradle而言,我們刻意地將Gradle插件腳本與插件配置放到了一起,比如Checkstyle:

├── gradle│   ├── checkstyle│   │   ├── checkstyle.gradle│   │   └── checkstyle.xml

事實(shí)上,在默認(rèn)情況下Checkstyle插件會(huì)從項(xiàng)目根目錄下的config目錄查找checkstyle.xml配置文件,但是這一方面增加了多余的文件夾,另一方面與該插件相關(guān)的設(shè)施分散在了不同的地方,違背了廣義上的內(nèi)聚原則。

基于業(yè)務(wù)分包

早年的Java分包方式通常是基于技術(shù)的,比如與domain包平級(jí)的有controller包、service包和infrastructure包等。這種方式當(dāng)前并不被行業(yè)所推崇,而是應(yīng)該首先基于業(yè)務(wù)分包。

比如,在訂單示例項(xiàng)目中,有兩個(gè)重要的領(lǐng)域?qū)ο驩rder和Product(在DDD中稱為聚合根),所有的業(yè)務(wù)都圍繞它們展開(kāi),因此分別創(chuàng)建order包和product包,再分別在包下創(chuàng)建與之相關(guān)的各個(gè)子包。此時(shí)的order包如下:

├── order│   ├── OrderApplicationService.java│   ├── OrderController.java│   ├── OrderNotFoundException.java│   ├── OrderRepository.java│   ├── OrderService.java│   └── model│       ├── Order.java│       ├── OrderFactory.java│       ├── OrderId.java│       ├── OrderItem.java│       └── OrderStatus.java

可以看到,在order包下我們直接放置了OrderController和OrderRepository等類,而沒(méi)有必要再為這些類劃分單獨(dú)的子包。而對(duì)于領(lǐng)域模型Order來(lái)講,由于包含了多個(gè)對(duì)象,因此基于內(nèi)聚性原則將它們歸到model包中。但是這并不是一個(gè)必須,如果業(yè)務(wù)足夠簡(jiǎn)單,我們甚至可以將所有類直接放到業(yè)務(wù)包下,product包便是如此:

└── product    ├── Product.java    ├── ProductApplicationService.java    ├── ProductController.java    ├── ProductId.java    └── ProductRepository.java

在編碼實(shí)踐中,我們總是基于一個(gè)業(yè)務(wù)用例來(lái)實(shí)現(xiàn)代碼,在技術(shù)分包場(chǎng)景下,我們需要在分散的各包中來(lái)回切換,增加了代碼導(dǎo)航的成本;另外,代碼提交的變更內(nèi)容也是散落的,在查看代碼提交歷史時(shí),無(wú)法直觀的看出該次提交是關(guān)于什么業(yè)務(wù)功能的。

在業(yè)務(wù)分包下,我們只需要在單個(gè)統(tǒng)一的包下修改代碼,減少了代碼導(dǎo)航成本;另外一個(gè)好處是,如果哪天我們需要將某個(gè)業(yè)務(wù)遷移到另外的項(xiàng)目(比如識(shí)別出了獨(dú)立的微服務(wù)),那么直接整體移動(dòng)業(yè)務(wù)包即可。

當(dāng)然,基于業(yè)務(wù)分包并不意味著所有的代碼都必須囿于業(yè)務(wù)包下,這里的邏輯是:優(yōu)先進(jìn)行業(yè)務(wù)分包,然后對(duì)于一些不隸屬于任何業(yè)務(wù)的代碼可以單獨(dú)分包,比如一些util類、公共配置等。比如我們依然可以創(chuàng)建一個(gè)common包,下面放置了Spring公共配置、異常處理框架和日志等子包:

└── common    ├── configuration    ├── exception    ├── loggin    └── utils

自動(dòng)化測(cè)試分類

在當(dāng)前的微服務(wù)和前后端分離的開(kāi)發(fā)模式下,后端項(xiàng)目?jī)H提供純粹的業(yè)務(wù)API,而不包含UI邏輯,因此后端項(xiàng)目不會(huì)再包含諸如WebDriver的重量級(jí)端到端測(cè)試。同時(shí),后端項(xiàng)目作為向外提供業(yè)務(wù)功能的獨(dú)立運(yùn)行單元,在API級(jí)別也應(yīng)該有相應(yīng)的測(cè)試。

此外,程序中有些框架性代碼,要么是諸如Controller之類的技術(shù)性框架代碼,要么是基于某種架構(gòu)風(fēng)格的代碼(比如DDD實(shí)踐中的ApplicationService),這些代碼一方面并不包含業(yè)務(wù)邏輯,一方面是很薄的一個(gè)抽象層(即實(shí)現(xiàn)相對(duì)簡(jiǎn)單),用單元測(cè)試來(lái)覆蓋顯得沒(méi)有必要,因此筆者的觀點(diǎn)是可以不為此編寫單獨(dú)的單元測(cè)試。歡迎關(guān)注公眾號(hào)"Java學(xué)習(xí)之道",查看更多干貨!

再者,程序中有些重要的組件性代碼,比如訪問(wèn)數(shù)據(jù)庫(kù)的Repository或者分布式鎖,使用單元測(cè)試實(shí)際上“測(cè)不到點(diǎn)上”,而使用API測(cè)試又顯得在分類邏輯上不合理,為此我們可以專門創(chuàng)建一種測(cè)試類型謂之組件測(cè)試。

基于以上,我們可以對(duì)自動(dòng)化測(cè)試做個(gè)分類:

  • 單元測(cè)試:核心的領(lǐng)域模型,包括領(lǐng)域?qū)ο?比如Order類),F(xiàn)actory類,領(lǐng)域服務(wù)類等;

  • 組件測(cè)試:不適合寫單元測(cè)試但是又必須測(cè)試的類,比如Repository類,在有些項(xiàng)目中,這種類型測(cè)試也被稱為集成測(cè)試;

  • API測(cè)試:模擬客戶端測(cè)試各個(gè)API接口,需要啟動(dòng)程序。

Gradle在默認(rèn)情況下只提供src/test/java目錄用于測(cè)試,對(duì)于以上3種類型的測(cè)試,我們需要將它們分開(kāi)以便于管理(也是職責(zé)分離的體現(xiàn))。為此,可以通過(guò)Gradle提供的SourceSets對(duì)測(cè)試代碼進(jìn)行分類:

sourceSets {    componentTest {        compileClasspath += sourceSets.main.output + sourceSets.test.output        runtimeClasspath += sourceSets.main.output + sourceSets.test.output    }
   apiTest {        compileClasspath += sourceSets.main.output + sourceSets.test.output        runtimeClasspath += sourceSets.main.output + sourceSets.test.output    }}

到此,3種類型的測(cè)試可以分別編寫在以下目錄:

  • 單元測(cè)試:src/test/java

  • 組件測(cè)試:src/componentTest/java

  • API測(cè)試:src/apiTest/java

需要注意的是,這里的API測(cè)試更多強(qiáng)調(diào)的是對(duì)業(yè)務(wù)功能的測(cè)試,有些項(xiàng)目中可能還會(huì)存在契約測(cè)試和安全測(cè)試等,雖然從技術(shù)上講都是對(duì)API的訪問(wèn),但是這些測(cè)試都是單獨(dú)的關(guān)注點(diǎn),因此建議分開(kāi)對(duì)待。

值得一提的是,由于組件測(cè)試和API測(cè)試需要啟動(dòng)程序,也即需要準(zhǔn)備好本地?cái)?shù)據(jù)庫(kù),我們采用了Gradle的docker-compose插件(或者jib插件),該插件會(huì)在運(yùn)行測(cè)試之前自動(dòng)運(yùn)行Docker容器(比如MySQL):

apply plugin: 'docker-compose'
dockerCompose {    useComposeFiles = ['docker/mysql/docker-compose.yml']}
bootRun.dependsOn composeUpcomponentTest.dependsOn composeUpapiTest.dependsOn composeUp

更多的測(cè)試分類配置細(xì)節(jié),比如JaCoCo測(cè)試覆蓋率配置等,請(qǐng)參考本文的示例項(xiàng)目代碼。對(duì)Gradle不熟悉的讀者可以參考:

https://www.cnblogs.com/CloudTeng/p/3417762.html

日志處理

在日志處理中,除了完成基本配置外,還有2個(gè)需要考慮的點(diǎn):

1、在日志中加入請(qǐng)求標(biāo)識(shí),便于鏈路追蹤。在處理一個(gè)請(qǐng)求的過(guò)程中有時(shí)會(huì)輸出多條日志,如果每條日志都共享統(tǒng)一的請(qǐng)求ID,那么在日志追蹤時(shí)會(huì)更加方便。此時(shí),可以使用Logback原生提供的MDC(Mapped Diagnostic Context)功能,創(chuàng)建一個(gè)RequestIdMdcFilter:

 protected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain)            throws ServletException, IOException {    //request id in header may come from Gateway, eg. Nginx    String headerRequestId = request.getHeader(HEADER_X_REQUEST_ID);    MDC.put(REQUEST_ID, isNullOrEmpty(headerRequestId) ? newUuid() : headerRequestId);    try {        filterChain.doFilter(request, response);    } finally {        clearMdc();    }    }

2、集中式日志管理,在多節(jié)點(diǎn)部署的場(chǎng)景下,各個(gè)節(jié)點(diǎn)的日志是分散的,為此可以引入諸如ELK之類的工具將日志統(tǒng)一輸出到ElasticSearch中。本文的示例項(xiàng)目使用了redisAppender將日志輸出到Logstash:

<appender name="REDIS" class="com.cwbase.logback.RedisAppender">    <tags>ecommerce-order-backend-${ACTIVE_PROFILE}</tags>    <host>elk.yourdomain.com</host>    <port>6379</port>    <password>whatever</password>    <key>ecommerce-ordder-log</key>    <mdc>true</mdc>    <type>redis</type></appender>

當(dāng)然,統(tǒng)一日志的方案還有很多,比如Splunk和Graylog等。

異常處理

在設(shè)計(jì)異常處理的框架時(shí),需要考慮以下幾點(diǎn):

  • 向客戶端提供格式統(tǒng)一的異常返回

  • 異常信息中應(yīng)該包含足夠多的上下文信息,最好是結(jié)構(gòu)化的數(shù)據(jù)以便于客戶端解析

  • 不同類型的異常應(yīng)該包含唯一標(biāo)識(shí),以便客戶端精確識(shí)別

異常處理通常有兩種形式,一種是層級(jí)式的,即每種具體的異常都對(duì)應(yīng)了一個(gè)異常類,這些類最終繼承自某個(gè)父異常;另一種是單一式的,即整個(gè)程序中只有一個(gè)異常類,再以一個(gè)字段來(lái)區(qū)分不同的異常場(chǎng)景。

層級(jí)式異常的好處是能夠顯式化異常含義,但是如果層級(jí)設(shè)計(jì)不好可能導(dǎo)致整個(gè)程序中充斥著大量的異常類;單一式的好處是簡(jiǎn)單,而其缺點(diǎn)在于表意性不夠。

本文的示例項(xiàng)目使用了層級(jí)式異常,所有異常都繼承自一個(gè)AppException:

public abstract class AppException extends RuntimeException {    private final ErrorCode code;    private final Map<String, Object> data = newHashMap();}

這里,ErrorCode枚舉中包含了異常的唯一標(biāo)識(shí)、HTTP狀態(tài)碼以及錯(cuò)誤信息;而data字段表示各個(gè)異常的上下文信息。

在示例系統(tǒng)中,在沒(méi)有找到訂單時(shí)拋出異常:

public class OrderNotFoundException extends AppException {    public OrderNotFoundException(OrderId orderId) {        super(ErrorCode.ORDER_NOT_FOUND, ImmutableMap.of("orderId", orderId.toString()));    }}

在返回異常給客戶端時(shí),通過(guò)一個(gè)ErrorDetail類來(lái)統(tǒng)一異常格式:

public final class ErrorDetail {    private final ErrorCode code;    private final int status;    private final String message;    private final String path;    private final Instant timestamp;    private final Map<String, Object> data = newHashMap();}

最終返回客戶端的數(shù)據(jù)為:

{  requestId: "d008ef46bb4f4cf19c9081ad50df33bd",  error: {    code: "ORDER_NOT_FOUND",    status: 404,    message: "沒(méi)有找到訂單",    path: "/order",    timestamp: 1555031270087,    data: {      orderId: "123456789"    }  }}

可以看到,ORDER_NOT_FOUND與data中的數(shù)據(jù)結(jié)構(gòu)是一一對(duì)應(yīng)的,也即對(duì)于客戶端來(lái)講,如果發(fā)現(xiàn)了ORDER_NOT_FOUND,那么便可確定data中一定存在orderId字段,進(jìn)而完成精確的結(jié)構(gòu)化解析。

后臺(tái)任務(wù)與分布式鎖

除了即時(shí)完成客戶端的請(qǐng)求外,系統(tǒng)中通常會(huì)有一些定時(shí)性的例行任務(wù),比如定期地向用戶發(fā)送郵件或者運(yùn)行數(shù)據(jù)報(bào)表等;另外,有時(shí)從設(shè)計(jì)上我們會(huì)對(duì)請(qǐng)求進(jìn)行異步化處理。此時(shí),我們需要搭建后臺(tái)任務(wù)相關(guān)基礎(chǔ)設(shè)施。Spring原生提供了任務(wù)處理(TaskExecutor)和任務(wù)計(jì)劃(TaskSchedulor)機(jī)制;而在分布式場(chǎng)景下,還需要引入分布式鎖來(lái)解決并發(fā)沖突,為此我們引入一個(gè)輕量級(jí)的分布式鎖框架ShedLock。

啟用Spring任務(wù)配置如下:

@Configuration@EnableAsync@EnableSchedulingpublic class SchedulingConfiguration implements SchedulingConfigurer {
   @Override    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {        taskRegistrar.setScheduler(newScheduledThreadPool(10));    }
   @Bean(destroyMethod = "shutdown")    @Primary    public TaskExecutor taskExecutor() {        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();        executor.setCorePoolSize(2);        executor.setMaxPoolSize(5);        executor.setQueueCapacity(10);        executor.setTaskDecorator(new LogbackMdcTaskDecorator());        executor.initialize();        return executor;    }
}

然后配置Shedlock:

@Configuration@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")public class DistributedLockConfiguration {
   @Bean    public LockProvider lockProvider(DataSource dataSource) {        return new JdbcTemplateLockProvider(dataSource);    }
   @Bean    public DistributedLockExecutor distributedLockExecutor(LockProvider lockProvider) {        return new DistributedLockExecutor(lockProvider);    }
}

實(shí)現(xiàn)后臺(tái)任務(wù)處理:

 @Scheduled(cron = "0 0/1 * * * ?")    @SchedulerLock(name = "scheduledTask", lockAtMostFor = THIRTY_MIN, lockAtLeastFor = ONE_MIN)    public void run() {        logger.info("Run scheduled task.");    }為了支持代碼直接調(diào)用分布式鎖,基于Shedlock的LockProvider創(chuàng)建DistributedLockExecutor:
public class DistributedLockExecutor {    private final LockProvider lockProvider;
   public DistributedLockExecutor(LockProvider lockProvider) {        this.lockProvider = lockProvider;    }
   public <T> T executeWithLock(Supplier<T> supplier, LockConfiguration configuration) {        Optional<SimpleLock> lock = lockProvider.lock(configuration);        if (!lock.isPresent()) {            throw new LockAlreadyOccupiedException(configuration.getName());        }
       try {            return supplier.get();        } finally {            lock.get().unlock();        }    }
}

使用時(shí)在代碼中直接調(diào)用:

   public String doBusiness() {        return distributedLockExecutor.executeWithLock(() -> "Hello World.",                new LockConfiguration("key", Instant.now().plusSeconds(60)));    }

本文的示例項(xiàng)目使用了基于JDBC的分布式鎖,事實(shí)上任何提供原子操作的機(jī)制都可用于分布式鎖,Shedlock還提供基于Redis、ZooKeeper和Hazelcast等的分布式鎖實(shí)現(xiàn)機(jī)制。

統(tǒng)一代碼風(fēng)格

除了Checkstyle統(tǒng)一代碼格式之外,項(xiàng)目中有些通用的公共的編碼實(shí)踐方式也需要在整個(gè)開(kāi)發(fā)團(tuán)隊(duì)中進(jìn)行統(tǒng)一,包括但不限于以下方面:

  • 客戶端的請(qǐng)求數(shù)據(jù)類統(tǒng)一使用相同后綴,比如Command

  • 返回給客戶端的數(shù)據(jù)統(tǒng)一使用相同后綴,比如Represetation

  • 統(tǒng)一對(duì)請(qǐng)求處理的流程框架,比如采用傳統(tǒng)的3層架構(gòu)或者DDD戰(zhàn)術(shù)模式

  • 提供一致的異常返回(請(qǐng)參考“異常處理”小節(jié))

  • 提供統(tǒng)一的分頁(yè)結(jié)構(gòu)類

  • 明確測(cè)試分類以及統(tǒng)一的測(cè)試基礎(chǔ)類(請(qǐng)參考“自動(dòng)化測(cè)試分類”小節(jié))

靜態(tài)代碼檢查

靜態(tài)代碼檢查主要包含以下Gradle插件,具體配置請(qǐng)參考本文示例代碼:

  • Checkstyle:用于檢查代碼格式,規(guī)范編碼風(fēng)格

  • Spotbugs:Findbugs的繼承者

  • Dependency check:OWASP提供的Java類庫(kù)安全性檢查

  • Sonar:用于代碼持續(xù)改進(jìn)的跟蹤

健康檢查

健康檢查主要用于以下場(chǎng)景:

  • 我們希望初步檢查程序是否運(yùn)行正常

  • 有些負(fù)載均衡軟件會(huì)通過(guò)一個(gè)健康檢查URL判斷節(jié)點(diǎn)的可達(dá)性

此時(shí),可以實(shí)現(xiàn)一個(gè)簡(jiǎn)單的API接口,該接口不受權(quán)限管控,可以公開(kāi)訪問(wèn)。如果該接口返回HTTP的200狀態(tài)碼,便可初步認(rèn)為程序運(yùn)行正常。此外,我們還可以在該API中加入一些額外的信息,比如提交版本號(hào)、構(gòu)建時(shí)間、部署時(shí)間等。

啟動(dòng)本文的示例項(xiàng)目:

./run.sh

然后訪問(wèn)健康檢查API:http://localhost:8080/about,結(jié)果如下:

{  requestId: "698c8d29add54e24a3d435e2c749ea00",  buildNumber: "unknown",  buildTime: "unknown",  deployTime: "2019-04-11T13:05:46.901+08:00[Asia/Shanghai]",  gitRevision: "unknown",  gitBranch: "unknown",  environment: "[local]"}

以上接口在示例項(xiàng)目中用了一個(gè)簡(jiǎn)單的Controller實(shí)現(xiàn),事實(shí)上Spring Boot的Acuator框架也能夠提供相似的功能。

API文檔

軟件文檔的難點(diǎn)不在于寫,而在于維護(hù)。多少次,當(dāng)我對(duì)照著項(xiàng)目文檔一步一步往下走時(shí),總得不到正確的結(jié)果,問(wèn)了同事之后得到回復(fù)“哦,那個(gè)已經(jīng)過(guò)時(shí)了”。本文示例項(xiàng)目所采用的Swagger在一定程度上降低了API維護(hù)的成本,因?yàn)镾wagger能自動(dòng)識(shí)別代碼中的方法參數(shù)、返回對(duì)象和URL等信息,然后自動(dòng)地實(shí)時(shí)地創(chuàng)建出API文檔。

配置Swagger如下:

@Configuration@EnableSwagger2@Profile(value = {"local", "dev"})public class SwaggerConfiguration {
   @Bean    public Docket api() {        return new Docket(SWAGGER_2)                .select()                .apis(basePackage("com.ecommerce.order"))                .paths(any())                .build();    }}
啟動(dòng)本地項(xiàng)目,訪問(wèn)http://localhost:8080/swagger-ui.html:
SpringBoot項(xiàng)目的流程是怎樣的

數(shù)據(jù)庫(kù)遷移

在傳統(tǒng)的開(kāi)發(fā)模式中,數(shù)據(jù)庫(kù)由專門的運(yùn)維團(tuán)隊(duì)或者DBA來(lái)維護(hù),要對(duì)數(shù)據(jù)庫(kù)進(jìn)行修改需要向DBA申請(qǐng),告之遷移內(nèi)容,最后由DBA負(fù)責(zé)數(shù)據(jù)庫(kù)變更實(shí)施。在持續(xù)交付和DevOps運(yùn)動(dòng)中,這些工作逐步提前到開(kāi)發(fā)過(guò)程,當(dāng)然并不是說(shuō)不需要DBA了,而是這些工作可以由開(kāi)發(fā)者和運(yùn)維人員一同完成。

另外,在微服務(wù)場(chǎng)景下,數(shù)據(jù)庫(kù)被包含在單個(gè)服務(wù)的邊界之內(nèi),因此基于內(nèi)聚性原則(咦,這好像是本文第三次提到內(nèi)聚原則了,可見(jiàn)其在軟件開(kāi)發(fā)中的重要性),數(shù)據(jù)庫(kù)的變更最好也與項(xiàng)目代碼一道維護(hù)在代碼庫(kù)中。

本文的示例項(xiàng)目采用了Flyway作為數(shù)據(jù)庫(kù)遷移工具,加入了Flyway依賴后,在src/main/sources/db/migration目錄下創(chuàng)建遷移腳本文件即可:

resources/├── db│   └── migration│       ├── V1__init.sql│       └── V2__create_product_table.sql

遷移腳本的命名需要遵循一定的規(guī)則以保證腳本執(zhí)行順序,另外遷移文件生效之后不要任意修改,因?yàn)镕lyway會(huì)檢查文件的checksum,如果checksum不一致將導(dǎo)致遷移失敗。

多環(huán)境構(gòu)建

在軟件的開(kāi)發(fā)流程中,我們需要將軟件部署到多個(gè)環(huán)境,經(jīng)過(guò)多輪驗(yàn)證后才能最終上線。在不同的階段中,軟件的運(yùn)行態(tài)可能是不一樣的,比如本地開(kāi)發(fā)時(shí)可能將所依賴的第三方系統(tǒng)stub掉;持續(xù)集成構(gòu)建時(shí)可能使用的是測(cè)試用的內(nèi)存數(shù)據(jù)庫(kù)等等。為此,本文的示例項(xiàng)目推薦采用以下環(huán)境:

  • local:用于開(kāi)發(fā)者本地開(kāi)發(fā)

  • ci:用于持續(xù)集成

  • dev:用于前端開(kāi)發(fā)聯(lián)調(diào)

  • qa:用于測(cè)試人員

  • uat:類生產(chǎn)環(huán)境,用于功能驗(yàn)收(有時(shí)也稱為staging環(huán)境)

  • prod:正式的生產(chǎn)環(huán)境

CORS

在前后端分離的系統(tǒng)中,前端單獨(dú)部署,有時(shí)連域名都和后端不同,此時(shí)需要進(jìn)行跨域處理。傳統(tǒng)的做法可以通過(guò)JSONP,但這是一種比較“trick”的做法,當(dāng)前更通用的實(shí)踐是采用CORS機(jī)制,在Spring Boot項(xiàng)目中,啟用CORS配置如下:

@Configurationpublic class CorsConfiguration {    @Bean    public WebMvcConfigurer corsConfigurer() {        return new WebMvcConfigurer() {            @Override            public void addCorsMappings(CorsRegistry registry) {                registry.addMapping("/**");            }        };    }}
對(duì)于使用Spring Security的項(xiàng)目,需要保證CORS工作于Spring Security的過(guò)濾器之前,為此Spring Security專門提供了相應(yīng)配置:
@EnableWebSecuritypublic class WebSecurityConfig extends WebSecurityConfigurerAdapter {
   @Override    protected void configure(HttpSecurity http) throws Exception {        http            // by default uses a Bean by the name of corsConfigurationSource            .cors().and()            ...    }
   @Bean    CorsConfigurationSource corsConfigurationSource() {        CorsConfiguration configuration = new CorsConfiguration();        configuration.setAllowedOrigins(Arrays.asList("https://example.com"));        configuration.setAllowedMethods(Arrays.asList("GET","POST"));        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();        source.registerCorsConfiguration("/**", configuration);        return source;    }}
常用第三方類庫(kù):

這里列出一些比較常見(jiàn)的第三方庫(kù),開(kāi)發(fā)者們可以根據(jù)項(xiàng)目所需引入:

  • Guava:來(lái)自Google的常用類庫(kù)

  • Apache Commons:來(lái)自Apache的常用類庫(kù)

  • Mockito:主要用于單元測(cè)試的mock

  • DBUnit:測(cè)試中管理數(shù)據(jù)庫(kù)測(cè)試數(shù)據(jù)

  • Rest Assured:用于Rest API測(cè)試

  • Jackson 2:Json數(shù)據(jù)的序列化和反序列化

  • jjwt:Jwt token認(rèn)證

  • Lombok:自動(dòng)生成常見(jiàn)Java代碼,比如equals()方法,getter和setter等;

  • Feign:聲明式Rest客戶端

  • Tika:用于準(zhǔn)確檢測(cè)文件類型

  • itext:生成Pdf文件等

  • zxing:生成二維碼

  • Xstream:比Jaxb更輕量級(jí)的XML處理庫(kù)

最后,需要提醒的是,本文提到的實(shí)踐方式只是一個(gè)參考,一方面依然存在考慮不周的地方,另一方面示例項(xiàng)目中用到的技術(shù)工具還存在其他替代方案,請(qǐng)根據(jù)自己項(xiàng)目的實(shí)際情況進(jìn)行取舍。

看完上述內(nèi)容,你們掌握SpringBoot項(xiàng)目的流程是怎樣的的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!

分享題目:SpringBoot項(xiàng)目的流程是怎樣的
轉(zhuǎn)載來(lái)源:http://bm7419.com/article30/ipoiso.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供營(yíng)銷型網(wǎng)站建設(shè)、域名注冊(cè)、關(guān)鍵詞優(yōu)化、移動(dòng)網(wǎng)站建設(shè)、建站公司、App設(shè)計(jì)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

外貿(mào)網(wǎng)站建設(shè)