引言
不知道大家是不是也有一種想法,就是喜歡用新的東西。比如:手機系統出現新版本就趕緊升級、軟件出現新版本也會在第一時間進行升級。反正我是有這種想法,比較喜歡新的東西,因為新的東西會有更 cool 的特性,可以給人心理、生理上一種舒適感(生理舒適感???)。
背景介紹
公司不同項目使用的 SpringBoot 版本是不同的,最近在做的項目使用的是比較新的版本,2.x。該項目開發過程中,所有對外提供的服務,都會有一個接口層和接口實現層。為了讓接口的職責更加的清晰明瞭,大部分信息都在接口層進行定義。比如:接口的請求映射路徑,接口方法接收參數的方式@RequestBody 以及參數校驗方式@Valid 等等。後來又去另一個項目組去開發,接到一個需要提供服務接口的任務,當時也沒在意,覺得很簡單,分分鐘就可以完成,悲劇也就在此時此刻發生了。
接下來請大家先看看項目實例代碼,在實例中會為大家重現錯誤,並提供解決方案以及分析出現該問題的原理是為什麼。
項目實例
第一步:我們需要定義服務的接口
<code>@RequestMapping("/persons")
public interface PersonApi {
/**
* add
*
* @param person
* @return
*/
@PostMapping("/")
List<person> add(@Valid @RequestBody Person person);
/**
* update
*
* @param person
* @return
*/
@PutMapping("/")
List<person> update(@Valid @RequestBody Person person);
}
/<person>/<person>/<code>
第二步:有了服務接口,那麼肯定會有服務接口的實現
<code>@RestController
public class PersonController implementsPersonApi{
private static List<person> personList = new ArrayList<>;
static {
personList.add(new Person(10001, "test1"));
personList.add(new Person(10002, "test2"));
personList.add(new Person(10003, "test3"));
personList.add(new Person(10004, "test4"));
personList.add(new Person(10005, "test5"));
}
@Override
public List<person> add(Person person) {
personList.add(person);
return personList;
}
@Override
public List<person> update(Person person) {
personList.removeIf(p -> Objects.equals(p.getId, person.getId));
personList.add(person);
return personList;
}
}
/<person>/<person>/<person>/<code>
第三步:服務接口編寫完成,就需要自己調用接口進行測試,接下來就進行接口測試
可以看到接口可以正常調用,說明接口上使用@RequestBody 起了作用。當然,本文到此還沒有結束,好戲還在後面
第五步:開始展示真正的本領了,修改 SpringBoot 的版本號,將版本號修改為 1.5.8.RELEASE
第六步:再次接口調用
可以很神奇的看到,接口請求的參數並沒有和我們的接口參數對象進行綁定,也就是我們的@RequestBody 註解不生效了。此時的我整個人都不好了,上一個項目寫的好好的接口,到這裡就不行了?難道是水土不服?於是打開了另一個項目,一個字母一個字母進行對照,生怕錯誤了什麼,最終還是以我失敗宣告結束。
嘗試方法
當時是不想看源碼的,因為看源碼好累,於是我就在實現方法上也加上了同樣的註解,準備嘗試一下。
<code>@Override
public List<person> add(@RequestBody Person person) {
personList.add(person);
return personList;
}
/<person>/<code>
不抱希望的嘗試請求接口
當返回結果出現在我眼前的時候,只有“我靠”兩個字能形容我的心情。突然有一種山重水複疑無路,柳暗花明又一村的感覺,整個世界又變的美好了。
雖然問題得到了完美解決,但是內心深處還是想了解下到底是為什麼?只有弄清原理,下次在遇到此問題的時候,我們才可能迎刃而解,不費吹灰之力。接下來就為大家揭開這層神秘的面紗!
疑惑解答
SpringBooot1.5.8.RELEASE 源碼分析
打開 RequestMappingHandlerAdapter#invokeHandlerMethod,找到如下代碼
<code>modelFactory.initModel(webRequest, mavContainer, invocableMethod);
/<code>
一直到 MethodParameter#getParameterAnnotations
如上代碼獲取當前方法參數的註解信息,最終返回,重點:此處獲取的是實現方法上的註解,並不會獲取接口方法上的註解,所以必須在實現方法上
加上註解。SpringBooot2.2.0.RELEASE 源碼分析
跟著上面的思路找到獲取參數註解的方法
可以很清楚的看到升級後的 SpringBoot 的 HandlerMethodParameter 中重寫了獲取參數註解的方法,重寫方法中調用了父類獲取參數註解的方法,並且在自己的實現中又去獲取了接口上的註解,然後進行組合。因此升級後的項目中可以在接口上定義註解,同樣也可以在實現方法上定義註解。
此次的此已經結束了,後續還會繼續為大家帶來精彩對決的吐血經歷,讓擼友多點發質,多點開心。
參考文獻
搭上 SpringBoot 請求處理源碼分析專車
搭上 SpringBoot 參數解析返回值處理源碼分析專車
———— e n d ————
快年底了,師長為大家準備了三份面試寶典:
《java面試寶典5.0》
《350道Java面試題:整理自100+公司》
《資深java面試寶典-視頻版》
分別適用於初中級,中高級,以及資深級工程師的面試複習。
內容包含java基礎、javaweb、各個性能優化、JVM、鎖、高併發、反射、Spring原理、微服務、Zookeeper、數據庫、數據結構、限流熔斷降級等等。
一、初中級《java面試寶典5.0》,對標8-13K
二、中高級《350道Java面試題:整理自100+公司》,對標12-20K
三、資深《java面試突擊-視頻版》,對標20K+
閱讀更多 Java進階架構師 的文章