通過這個starter從而可以獲取一系列的依賴,如spring mvc相關,structs2的框架是幾年前項目的標配了,而現(xiàn)在大部分項目都是使用spring mvc了,spring mvc使用@Controller規(guī)定了控制層的入口,類似的為@RestController(這個注解相當于組合@ResponseBody,但是ResponseEntity對象也是不需要@ResponseBody也是可以返回實際數(shù)據(jù)的),同時通過@RequestMapping,或者@GetMapping或者PostMapping等等,標注實際映射的url,這里有一個坑,以往的系統(tǒng)系統(tǒng)動不動就用特殊的請求參數(shù)來分別功能,這在實際使用中一個是不夠直觀,一個是是控制層平白無故多出代碼,另外一個偽靜態(tài)化操作,我們現(xiàn)在普遍使用的是REST風格的url,在返回前端界面時使用返回String類型的數(shù)據(jù),并且保證標注在類上的一定是@Controller而不是@RestController,該方法上一定沒有@ResponseBody注解,return 的數(shù)據(jù)為"[或有或無的文件夾名]模板名稱[或有或無的后綴]",當然這是forward轉(zhuǎn)發(fā)請求的寫法,當需要寫重定向的時候需要在前面加上"redirect:"字符串,其實默認是"forward:",這是省略的寫法.在控制層上我們一般做獲取請求參數(shù),這些參數(shù)有點隱藏的rest里面,使用@PathVariable獲取,隱藏在parameter里面使用@Parameter獲取,隱藏在body里面,使用@RequestBody獲取,還有點可能隱藏在cookie里面,這些參數(shù)的數(shù)據(jù)都是需要控制層去獲取能封裝盡量封裝到對象里面.同時也對于可以直接校驗的對象中的參數(shù)可以使用校驗框架,在形參簽名標注@Valid注解.在控制層一般都是注入業(yè)務層的接口,有些復雜操作通常需要組合多個業(yè)務的功能才能實現(xiàn),當然這是非常司空見慣的,在一個系統(tǒng)中,通常不管如何,控制層的異常是絕對不能向上拋出的,這里涉及到向上傳參的方法,業(yè)務層如何向控制層傳遞參數(shù)呢?一個是返回值、一個是傳入引用值對象、一個是ThreadLocal對象,一個是向上拋出業(yè)務層及其下層的業(yè)務異常,我們通常規(guī)定好業(yè)務層出現(xiàn)異常通常都是向上拋出的,不做任何捕獲,直到控制層的捕獲,通常如果有異常的話最后向調(diào)用者表達出異常的詳細原因,此時一般會單獨寫一個枚舉類、寫一個常量類,當然、如果項目文檔如果足夠強大,程序員足夠細心、開發(fā)周期非常短而沒有時間寫的話,一般我們都是會去寫的,這個常量類其實是一個接口類,因為本來僅僅是只需要放置一些屬性就可以了,而接口類中默認的訪問類型就是public,而且是static修飾的.最后有一個非常重要的概念:aop,面向切面編程,在spring boot中也是基于注解實現(xiàn)的,使用的是@Advice注解,組合@PointCut等注解Spring data jpa
在現(xiàn)如今的面向?qū)ο箝_發(fā)中,提起萬事萬物皆對象,在數(shù)據(jù)庫層開發(fā)中,通常會使用hibernate,而spring data jpa是hibernate的簡化升級版,其特點為標注一個為@Repository,并且這個類需要實現(xiàn)JpaRepository<實體類型,主鍵類型>,這里的實體類在Jpa中也并發(fā)由于是hibernate就要是配置,我們使用@Entity注解標注一個類為類與表映射,使用@Table映射數(shù)據(jù)庫服務器中實際表名,使用@Id標注當前字段為主鍵,同時對于可自增的字段使用@GenereteVlaue標注,而在一些特殊操作,需要標注當前實體類為@DynamicUpdate或者@DynamicCreate,這在使用Timestampt類型的參數(shù)是比較好用,然后我也理解到timestampt是存儲庫,localDateTime是工具類,同時我的項目中使用了Lombok,這個框架可以使用注解為實體類生成getter和setter以及constructor方法.在項目初始開發(fā)階段推薦使用,同時這個需要配置ide,由于我的是idea所以配置還是比較簡單的,安裝插件即可,在實體類上加上@Data注解,對于空參構(gòu)造使用@NoArgsConstructor和@AllConstructor注解.對于實體類中屬性名和數(shù)據(jù)庫中表中列名不一致的問題,一般使用@Columne注解即可.一般主鍵的類型為String或者Long,Long的承載量更小,運算速度更快,拓展性小,可自增,而String加上UUID這種全局唯一之后實現(xiàn)承載量更大,當然對于Long可承載量來說其實也是很大的,其實一般項目使用Long其實也是可以的.Spring Data Jpa默認已經(jīng)提供好了基于直接插入整個對象、修改指定id值的對象、刪除指定Id值的對象、查找所有對象、查找指定id值的對象,同時基于查詢提供了Pageable接口的傳入?yún)?shù),在業(yè)務層可自由組合size和page實現(xiàn)分頁操作,但是有一個bug就是分頁并非那么容易的時候spring data jpa定義的pageable就不是那么容易使用了,反而mysql的limit參數(shù)確實那么的好用,同時也可以指定sort規(guī)則,這些都是簡單的,更加復雜的在于根據(jù)指定條件查找/刪除/修改,然后有些查詢的優(yōu)化在于使用@Query注解實現(xiàn),一些自定義的修改規(guī)則在于使用@Update注解,總體來說spring data jpa的使用是非常簡單的.Spring data mybatis
mybatis作為以執(zhí)行效率為主要的框架,與spring data jpa不同的是,spring data mybatis是基于具體sql語句的.依賴于具體數(shù)據(jù)庫,多個系統(tǒng)之間的dao層代碼無法融合起來,在升級數(shù)據(jù)庫的時候整個系統(tǒng)會有短暫的疲憊期.同時也會出現(xiàn)由于切換數(shù)據(jù)庫導致的數(shù)據(jù)的兼容性問題,在大數(shù)據(jù)量遷移方面也是一個比較麻煩的問題.對于開發(fā)者來說,一部分掌握sql語言能非常流暢的優(yōu)化來自sql語句的執(zhí)行效率的人來說mybatis是極為好用的,但是另外一部分不那么流暢的人來說就為困難了,當然也可以請專門的寫sql語言的dba來寫或者優(yōu)化sql語句.但是比spring data jpa這種對象關系映射的ORM框架不同的是,mybatis的操作多表的效率是非常高的.而jpa的多表會有執(zhí)行的冗雜性,同時jpa的優(yōu)化執(zhí)行語句是非常困難的.雖然jpa的@Query可以直接強制執(zhí)行對象SQL語句.spring data mybatis與直接使用mybatis不同的是,其使用起來也是以注解使用較多,雖然都是使用接口類,但是其盡量使用@Mapper類對dao類進行標注,使用@Insert、@Delete@、@Update+@Param、@Selete+@Reuslts進行crud語句的操作,至于其他的與進行jdbc語句差不多,mybatis其實也只是做了那么兩件事情,結(jié)合書寫的sql語句將參數(shù)設置進去,對查詢結(jié)果數(shù)據(jù)屬性將數(shù)據(jù)取出.通常在對于效率要求很高的項目中都是使用mybatis的,因為對于一個項目來說在現(xiàn)如今每提升一點系統(tǒng)性能對于經(jīng)濟的獲取也是非常大的,另外其實項目中是可以使用兩套持久化技術的.前期使用spring data jpa實現(xiàn),后期切換成mybatis其實也是可以的.Thymeleaf
java網(wǎng)站開發(fā)從剛一開始的將頁面冗雜到servelt中到提出mvc思想后將視圖層單獨出去,其中獲取好處也是非常多的,由于將界面層作為三分之一,這樣的前端可以直接由前端人員進行開發(fā)維護,剛開始的時候使用的是jsp技術,那個的時候的開發(fā)人員并非很多,網(wǎng)站承載量并非特別大,網(wǎng)頁前端業(yè)務也并不復雜,所以導致對性能的要求不是很高,jsp的運行流程為轉(zhuǎn)譯編譯運行,其中消耗的時間內(nèi)在當初是并非很多的,但是對于現(xiàn)在來說這種消耗是很高的,這是導致我們棄用jsp的一個原因,還有一個非常重要的原因就是jsp非常依賴java servlet,這就使得jsp只能作為后端專屬語言了,后來隨著分布的口號,我們逐漸意識到前端渲染的重要性,那個時候我們使用的是Freeemarker網(wǎng)頁靜態(tài)化技術,這在當年還有一些類似的技術,例如:velocity,但是velocity的性能不如freemarker,后來網(wǎng)頁靜態(tài)化技術成了標準技術了,網(wǎng)頁靜態(tài)化技術為將傳入數(shù)據(jù)傳入模板中通過特點標簽將數(shù)據(jù)填充到指定位置最后生成html文件,在一些高并發(fā)訪問的網(wǎng)頁使用這種靜態(tài)化技術遠遠比動態(tài)化技術要好的多,典型代表為:淘寶,當年是什么支持了淘寶在那么用戶沖擊下服務器依然很流暢,一部分是cdn一部分就是靜態(tài)化技術,另外的化就是其獨特的架構(gòu)了,要知道當前國內(nèi)第一家開源RPC框架Dubbo就是阿里的.spring團隊對市場上有先進性的技術都很感興趣.比如struts2/hibernate,這些到現(xiàn)在逐漸變成了spring mvc以及最新基于reactor異步架構(gòu)的spring wbflux和spring data jpa及其系列,甚至可以說freemarker就是thymeleaf的前生,同時spring 自家的spring mvc框架對thymleaf非常支持.同時thmeleaf對于面向?qū)ο笏枷胫С值姆浅5轿?將原本需要冗雜的標簽改為屬性,使得將html文件改為thymeleaf模板文件非常方便.Spring data redis
redis是一個單線程基于內(nèi)存支持分布式部署的服務.通常我們項目中使用redis一個是為了使用其基于內(nèi)存的特點實現(xiàn)緩存,另一個是使用其單線程及其執(zhí)行指令強原子性的特點實現(xiàn)并發(fā)鎖機制.在項目中很多地方都會使用緩存,一個是用戶登錄信息token存入redis,而這在以往稱之為單點登錄.另外為業(yè)務數(shù)據(jù)緩存,如果是自己使用StringRedisTemplate實現(xiàn)緩存操作,那么這個業(yè)務的代碼量又會平白增加很多了,在spring boot2.x中,我們通常是使用@Cache注解實現(xiàn)對緩存的操作,這個注解包含了獲取緩存,清除緩存,修改緩存(當觸發(fā)某種條件時),這種注解需要標注在方法名上,對于不同的方法實現(xiàn)不同的操作.其實redis一個更加重要的作用是作為并發(fā)鎖,通常的秒殺操作都是能夠使用這種并發(fā)鎖達到單機程序鎖所達不到的效率,因為這種鎖是不會阻塞本地線程的.實現(xiàn)原理是因為一次向redis請求的指令為一組同時存取的原子操作,一次只能有一個操作成功,同時通常會在存數(shù)據(jù)的時候會設置過期時間,而取出數(shù)據(jù)時通常會使用時間戳做鎖,將本機時間作為鎖.而解鎖的過程為將指定key的value移除掉.Spring data ElasticSearch
在項目集成了搜索引擎之后,傳統(tǒng)操作索引庫是比較復雜的,但是功能最為完善,但是其實有些時候并不是需要那么完善的功能的,比如說高亮操作,這個時候我們通常是為了簡化操作而封裝一個通用方法,其實現(xiàn)在spring data 系列已經(jīng)對es有了這種操作的支持了,使用spring data elasticsearch 可以非常簡單的實現(xiàn)對索引庫的CRUD操作,但是首先你需要定義一個文檔類,使用@Document(&#34;文檔名稱,通常類名也行&#34;), 寫一個接口類實現(xiàn)ElasticSearchRepository<文檔庫類,類中主鍵類型>.其使用方式與spring data jpa一致.Logback
對一個web項目來說,接口文檔是非常重要的.以前也有接口文檔,但是以前的文檔時有專門的維護人員或者開發(fā)人員去書寫的,只是作為有哪些對外接口的文字描述,測試的時候需要測試人員再次復制黏貼Url并填入復雜的請求參數(shù),同時這種測試是低效率的,對于多個復雜接口一起組合使用,是很實現(xiàn)的.多模塊或者多子系統(tǒng)并行開發(fā)時,這種文檔還影響著項目開發(fā)的進度,因為系統(tǒng)直接會有其他系統(tǒng)功能的依賴,swagger2打破了這一僵局,swagger2會掃描項目中所有加了@Controller或者@RestController的類,將這些類的映射路徑分別收集起來在請求swagger暴露的REST API界面時會將這些映射路徑分別隱藏在各自的Controller類中,同時可以手動指定Controller類的名稱和方法名稱以及請求參數(shù)聲明.如果點進去會進入一個具體的請求url中,swagger會展示URL調(diào)用時需要傳入哪些參數(shù),哪些是必須的,參數(shù)的基本類型,如果再點擊&#34;Try it out&#34;,會直接調(diào)用這個rest 的url,同時由于這個界面都是折疊效果做的,所以不必擔心當前測試沒有做完就去做下面或者上面的其他而導致丟失參數(shù).總結(jié)來說:swagger的使用比較簡單、而且測試非常方便Apache ab
對于項目中的注冊用戶功能,我們使用了阿里云的短信接口服務,當用戶注冊時需要先請求專門的發(fā)送短信的接口,此接口內(nèi)實現(xiàn)較為簡單,無dao層操作,無需涉及到數(shù)據(jù)庫的操作,但是需要集成阿里云短信java版的maven依賴(官網(wǎng)提示沒有依賴而是需要自己在自己的私有庫上去安裝),在封裝了簡單的發(fā)送短信的工具類后,使用發(fā)送短信是非常簡單的,在開始集成阿里云短信接口服務時,需要申請短信模塊和短信簽名,這兩個申請還是很簡單的.將參數(shù)配置進去后,將固定參數(shù)配置到application.yml中,當然也可以使用application.properties,對于這兩者的區(qū)別在于yml更加面向?qū)ο?在ide工具中,如idea中修改時很簡單的,但是在記事本或者notepad++中就不容易了.其實影響也不大.還是推薦使用application.yml.在需要使用的方式使用@Value(&#34;${xxx}&#34;)注解進行注入.如果用戶是使用了正確的手機號碼進行注冊,當然我們后臺也會進行基于正則表達式的檢查傳入的手機號碼.用戶手機會在1-2分鐘內(nèi)受到短信通知,而對于短信丟失率,官網(wǎng)給出的數(shù)據(jù)是99%.再調(diào)用短信接口時,我會先生成0-9的6位數(shù)字的隨機值,然后將此值放入redis中,因為項目中使用的是spring boot框架,所以集成一個spring data reids還會很簡單的.而通過在業(yè)務層使用@Autowired StringRedisTemplate stringRedisTemplate; 注入redis操作對象,而通過這個對象可以將手機號碼和0-9的6為隨機數(shù)字放入redis中。當然在實際項目中是這樣做的,聲明一個業(yè)務常量接口類,在里面聲明一個常量,因為interface類里面所以的屬性都是public static final 修飾的,所以可以使得更加貼合實際.我會聲明一個redis中直接存放的string的key的前綴,例如為&#39;requester_regist_&#39;;然后后面再直接拼接上手機號碼,這樣就是直接是&#39;requester_regist_123456&#39;,通過添加到redis并設置過期時間,過期時間默認為30分鐘.當用戶來注冊的時候回攜帶手機號碼,這個時候需要校驗是否有效,還是需要注冊redis操作對象并調(diào)用取數(shù)據(jù)的方法,從常量類中取出注冊前綴以及傳入的手機號碼,如果取出的值不為空并且與傳入的verificationCode一致,則給與注冊.同時對于賬號名是否有效會進行校驗,比如說是否與正則表達式符合,同時還會校驗用戶名是否已經(jīng)存在,如果存在則不給予注冊.在返回數(shù)據(jù)對象會封裝一個抽象響應對象.需要封裝一些必須的屬性,例如:響應嗎、數(shù)據(jù)(類型為泛型T,以為不管是返回單個pojo對象還是page對象還是list對象都是可行的)、消息內(nèi)容,通常還會聲明一個用戶注冊的枚舉類,該類中必須設置int code和stirng msg,對于這樣封裝,雖然是增加了代碼量,但是很好維護,代碼格式很優(yōu)美.就此客戶端,不管是pc的html還是手機app都是可以通過判斷code表示是否注冊成功.對于數(shù)據(jù)校驗成功之后就可以開始寫入數(shù)據(jù)庫了,本項目中使用的是mysql,并且將mysql是通過docker進項安裝了,在啟動時固定了映射端口為3306,其實個人感覺最好不要隨機映射端口或者ip,原因也是很簡單的,因為下次啟動的時候服務的訪問地址都改變了,對于項目來說就是災難性的了,同時在啟動容器時指定數(shù)據(jù)卷,以便數(shù)據(jù)備份導出以及持續(xù)使用.同時對于mysql鏡像一個bug就是時區(qū)問題,這是我在使用timestamp類型的列才發(fā)現(xiàn)的,通過查資料才發(fā)現(xiàn)是容器內(nèi)部時區(qū)問題,我隨即登錄宿主機使用date指令查看了宿主機時間及時區(qū),同時通過docker exec -it 容器id /bin/bash 指令進入了容器內(nèi)部,在同樣使用了date指定之后發(fā)現(xiàn)時區(qū)果然不對,相差8個小時,隨機我再次查閱官方文檔,解決方法有兩種,一種是掛載宿主機時區(qū)文件,一個是同時-e攜帶參數(shù)指定容器時區(qū).至此最終解決問題.在項目中使用的是spring data jpa,spring data系列框架都是很容易被開發(fā)人員接受的,spring data jpa 是基于hibernate 實現(xiàn)的,在拋棄了hibernte冗雜的配置文件.并基于接口類實現(xiàn)基于的crud,同時提供了高級查詢對象,在spring data jpa中,表與類的映射關系只是幾個注解接口解決.@Entity,@Table@Column@Id@GenerateValue,在接口層,異常都是需要寫一堆代碼,同時對于分頁操作也是不夠簡潔,jpa 中只是需要聲明一個接口類繼承JpaRepository<實體類類型,主鍵類型>,在實際的多條數(shù)據(jù)查詢中使用pageable對象,封住了size和page和sort對象,分頁查詢時同時指定page第幾頁,size一頁多少條,sort:以哪些字段排序以什么屬性排序.通常注冊只需要返回id即可.同時既然是注冊,密碼的安全性也是非常高的.使用3倍md5并且加鹽實現(xiàn)了加密明文操作,同時將加密方法封裝起來,因為當用戶使用明文密碼登錄是還是需要使用該加密類實現(xiàn)加密再比對才能校驗是否密碼正確.
對于項目中最基礎的登錄功能,我們實現(xiàn)的是SSO,英文名為single sign on,稱為單點登錄.當用戶首次完整登錄成功,業(yè)務層會生成全局唯一的字符串,而這個操作使用uuid即可完成,在java中生成一個UUID值是非常簡單的,使用UUID類調(diào)用randomUUID即可返回一個32+4位的字符串,同時將該字符串中所有的&#39;-&#39;替換為&#39;&#39;即可.將此字符串即token(令牌)存入redis中,至于key,還是可以像前面一樣在接口類中聲明前綴,例如&#34;requester_login_&#34;,然后再加上用戶的id,因為登錄方式可以有多種,但是id確實唯一的.同時在業(yè)務層注入redis操作類,同時調(diào)用設置值的方法將key和token存入redis中并設置默認過期時間為2個小時.因為spring data redis的stringRedsiTemplate.opsForValue的存值方法支持指定時間單位,所以可以不需要講毫秒值換算為2小時,而是2,TimeUnit.Hour即可。對于pc的html/html5可以直接通過session對象操作cookie對象即可,而對于像手機app這種,可以將token直接作為返回參數(shù)返回,html5的本地存儲,安卓的sqlite這些都是可行的存儲方案.在下一次請求登錄時,如果對于該賬號的token有效不為空,并且與傳入的token一致,即可認為是sso登錄,在多平臺的時代,sso登錄有著重要意義.
對于項目中的修改密碼,首先要確保用戶現(xiàn)在是登錄狀態(tài),然后與注冊用戶差不多,但是在使用spring data jpa進行修改操作時需要使用@Update注解標注修改,同時在@Query(&#34;sql語句&#34;)使用ddl語句進行修改指定密碼類的值.