Spring Boot實(shí)戰(zhàn):Restful API的構(gòu)建

在現(xiàn)在的開發(fā)流程中夫偶,為了最大程度實(shí)現(xiàn)前后端的分離,通常后端接口只提供數(shù)據(jù)接口岛请,由前端通過(guò)Ajax請(qǐng)求從后端獲取數(shù)據(jù)并進(jìn)行渲染再展示給用戶沫浆。我們用的最多的方式就是后端會(huì)返回給前端一個(gè)JSON字符串,前端解析JSON字符串生成JavaScript的對(duì)象斩萌,然后再做處理缝裤。

本文就來(lái)演示一下Spring boot如何實(shí)現(xiàn)這種模式,本文重點(diǎn)會(huì)講解如何設(shè)計(jì)一個(gè)Restful的API颊郎,并通過(guò)Spring boot來(lái)實(shí)現(xiàn)相關(guān)的API。不過(guò)霎苗,為了大家更好的了解Restful風(fēng)格的API姆吭,我們先設(shè)計(jì)一個(gè)傳統(tǒng)的數(shù)據(jù)返回接口,這樣大家可以對(duì)比著來(lái)理解唁盏。

一内狸、非Restful接口的支持

我們這里以文章列表為例,實(shí)現(xiàn)一個(gè)返回文章列表的接口厘擂,代碼如下:

@Controller

@RequestMapping("/article")

public class ArticleController {

? ?@Autowired

? ?private ArticleService articleService;

? ?@RequestMapping("/list.json")

? ?@ResponseBody

? ?public List<Article> listArticles(String title, Integer pageSize, Integer pageNum) {

? ? ? ?if (pageSize == null) {

? ? ? ? ? ?pageSize = 10;

? ? ? ?}

? ? ? ?if (pageNum == null) {

? ? ? ? ? ?pageNum = 1;

? ? ? ?}

? ? ? ?int offset = (pageNum - 1) * pageSize;

? ? ? ?return articleService.getArticles(title, 1L, offset, pageSize);

? ?}

}

這個(gè)ArticleService的實(shí)現(xiàn)很簡(jiǎn)單昆淡,就是簡(jiǎn)單的封裝了ArticleMapper的操作,ArticleMapper的內(nèi)容大家可以參考上一篇的文章刽严,ArticleService的實(shí)現(xiàn)類如下:

@Service

public class ArticleServiceImpl implements ArticleService {

? ?@Autowired

? ?private ArticleMapper articleMapper;

? ?@Override

? ?public Long saveArticle(@RequestBody Article article) {

? ? ? ?return articleMapper.insertArticle(article);

? ?}

? ?@Override

? ?public List<Article> getArticles(String title,Long userId,int offset,int pageSize) {

? ? ? ?Article article = new Article();

? ? ? ?article.setTitle(title);

? ? ? ?article.setUserId(userId);

? ? ? ?return articleMapper.queryArticlesByPage(article,offset,pageSize);

? ?}

? ?@Override

? ?public Article getById(Long id) {

? ? ? ?return articleMapper.queryById(id);

? ?}

? ?@Override

? ?public void updateArticle(Article article) {

? ? ? ?article.setUpdateTime(new Date());

? ? ? ?articleMapper.updateArticleById(article);

? ?}

}

運(yùn)行Application.java這個(gè)類昂灵,然后訪問(wèn):http://locahost:8080/article/list.json,就可以看到如下的結(jié)果:

ArticleServiceImpl這個(gè)類是一個(gè)很普通的類,只有一個(gè)Spring的注解@Service眨补,標(biāo)識(shí)為一個(gè)bean以便于通過(guò)Spring IoC容器來(lái)管理管削。我們?cè)賮?lái)看看ArticleController這個(gè)類,其實(shí)用過(guò)Spring MVC的人應(yīng)該都熟悉這幾個(gè)注解撑螺,這里簡(jiǎn)單解釋一下:

@Controller 標(biāo)識(shí)一個(gè)類為控制器含思。

@RequestMapping URL的映射。

@ResponseBody 返回結(jié)果轉(zhuǎn)換為JSON字符串甘晤。

@RequestBody 表示接收J(rèn)SON格式字符串參數(shù)含潘。

通過(guò)這個(gè)三個(gè)注解,我們就能輕松的實(shí)現(xiàn)通過(guò)URL給前端返回JSON格式數(shù)據(jù)的功能线婚。不過(guò)大家肯定有點(diǎn)疑惑调鬓,這不都是Spring MVC的東西嗎?跟Spring boot有什么關(guān)系酌伊?其實(shí)Spring boot的作用就是為我們省去了配置的過(guò)程腾窝,其他功能確實(shí)都是Spring與Spring MVC來(lái)為我們提供的,大家應(yīng)該記得Spring boot通過(guò)各種starter來(lái)為我們提供自動(dòng)配置的服務(wù)居砖,我們的工程里面之前引入過(guò)這個(gè)依賴:

<dependency>

? ? ?<groupId>org.springframework.boot</groupId>

? ? ?<artifactId>spring-boot-starter-web</artifactId>

</dependency>

這個(gè)是所有Spring boot的web工程都需要引入的jar包虹脯,也就是說(shuō)只要是Spring boot的web的工程,都默認(rèn)支持上述的功能奏候。這里我們進(jìn)一步發(fā)現(xiàn)循集,通過(guò)Spring boot來(lái)開發(fā)web工程,確實(shí)為我們省了許多配置的工作蔗草。

二咒彤、Restful API設(shè)計(jì)

好了,我們現(xiàn)在再來(lái)看看如何實(shí)現(xiàn)Restful API咒精。實(shí)際上Restful本身不是一項(xiàng)什么高深的技術(shù)镶柱,而只是一種編程風(fēng)格,或者說(shuō)是一種設(shè)計(jì)風(fēng)格模叙。在傳統(tǒng)的http接口設(shè)計(jì)中歇拆,我們一般只使用了get和post兩個(gè)方法,然后用我們自己定義的詞匯來(lái)表示不同的操作范咨,比如上面查詢文章的接口故觅,我們定義了article/list.json來(lái)表示查詢文章列表,可以通過(guò)get或者post方法來(lái)訪問(wèn)渠啊。

而Restful API的設(shè)計(jì)則通過(guò)HTTP的方法來(lái)表示CRUD相關(guān)的操作输吏。因此,除了get和post方法外替蛉,還會(huì)用到其他的HTTP方法贯溅,如PUT拄氯、DELETE、HEAD等盗迟,通過(guò)不同的HTTP方法來(lái)表示不同含義的操作坤邪。下面是我設(shè)計(jì)的一組對(duì)文章的增刪改查的Restful API:

這里可以看出,URL僅僅是標(biāo)識(shí)資源的路勁罚缕,而具體的行為由HTTP方法來(lái)指定艇纺。

三、Restful API實(shí)現(xiàn)

現(xiàn)在我們?cè)賮?lái)看看如何實(shí)現(xiàn)上面的接口邮弹,其他就不多說(shuō)黔衡,直接看代碼:

@RestController

@RequestMapping("/rest")

public class ArticleRestController {

? ?@Autowired

? ?private ArticleService articleService;

? ?@RequestMapping(value = "/article", method = POST, produces = "application/json")

? ?public WebResponse<Map<String, Object>> saveArticle(@RequestBody Article article) {

? ? ? ?article.setUserId(1L);

? ? ? ?articleService.saveArticle(article);

? ? ? ?Map<String, Object> ret = new HashMap<>();

? ? ? ?ret.put("id", article.getId());

? ? ? ?WebResponse<Map<String, Object>> response = WebResponse.getSuccessResponse(ret);

? ? ? ?return response;

? ?}

? ?@RequestMapping(value = "/article/{id}", method = DELETE, produces = "application/json")

? ?public WebResponse<?> deleteArticle(@PathVariable Long id) {

? ? ? ?Article article = articleService.getById(id);

? ? ? ?article.setStatus(-1);

? ? ? ?articleService.updateArticle(article);

? ? ? ?WebResponse<Object> response = WebResponse.getSuccessResponse(null);

? ? ? ?return response;

? ?}

? ?@RequestMapping(value = "/article/{id}", method = PUT, produces = "application/json")

? ?public WebResponse<Object> updateArticle(@PathVariable Long id, @RequestBody Article article) {

? ? ? ?article.setId(id);

? ? ? ?articleService.updateArticle(article);

? ? ? ?WebResponse<Object> response = WebResponse.getSuccessResponse(null);

? ? ? ?return response;

? ?}

? ?@RequestMapping(value = "/article/{id}", method = GET, produces = "application/json")

? ?public WebResponse<Article> getArticle(@PathVariable Long id) {

? ? ? ?Article article = articleService.getById(id);

? ? ? ?WebResponse<Article> response = WebResponse.getSuccessResponse(article);

? ? ? ?return response;

? ?}

}

我們?cè)賮?lái)分析一下這段代碼,這段代碼和之前代碼的區(qū)別在于:

我們使用的是@RestController這個(gè)注解腌乡,而不是@Controller盟劫,不過(guò)這個(gè)注解同樣不是Spring boot提供的,而是Spring?MVC4中的提供的注解与纽,表示一個(gè)支持Restful的控制器侣签。

這個(gè)類中有三個(gè)URL映射是相同的,即都是/article/{id}急迂,這在@Controller標(biāo)識(shí)的類中是不允許出現(xiàn)的影所。這里的可以通過(guò)method來(lái)進(jìn)行區(qū)分,produces的作用是表示返回結(jié)果的類型是JSON僚碎。

@PathVariable這個(gè)注解猴娩,也是Spring MVC提供的,其作用是表示該變量的值是從訪問(wèn)路徑中獲取勺阐。

所以看來(lái)看去卷中,這個(gè)代碼還是跟Spring boot沒(méi)太多的關(guān)系,Spring boot也僅僅是提供自動(dòng)配置的功能渊抽,這也是Spring boot用起來(lái)很舒服的一個(gè)很重要的原因蟆豫,因?yàn)樗那秩胄苑浅7浅P。慊靖杏X(jué)不到它的存在腰吟。

四无埃、測(cè)試

代碼寫完了,怎么測(cè)試毛雇?除了GET的方法外,都不能直接通過(guò)瀏覽器來(lái)訪問(wèn)侦镇,當(dāng)然灵疮,我們可以直接通過(guò)postman來(lái)發(fā)送各種http請(qǐng)求。不過(guò)我還是比較支持通過(guò)單元測(cè)試類來(lái)測(cè)試各個(gè)方法壳繁。這里我們就通過(guò)Junit來(lái)測(cè)試各個(gè)方法:

@RunWith(SpringJUnit4ClassRunner.class)

@SpringBootTest(classes = Application.class)

public class ArticleControllerTest {

? ?@Autowired

? ?private ArticleRestController restController;

? ?private MockMvc mvc;

? ?@Before

? ?public void setUp() throws Exception {

? ? ? ?mvc = MockMvcBuilders.standaloneSetup(restController).build();

? ?}

? ?@Test

? ?public void testAddArticle() throws Exception {

? ? ? ?Article article = new Article();

? ? ? ?article.setTitle("測(cè)試文章000000");

? ? ? ?article.setType(1);

? ? ? ?article.setStatus(2);

? ? ? ?article.setSummary("這是一篇測(cè)試文章");

? ? ? ?Gson gosn = new Gson();

? ? ? ?RequestBuilder builder = MockMvcRequestBuilders

? ? ? ? ? ? ? ?.post("/rest/article")

? ? ? ? ? ? ? ?.accept(MediaType.APPLICATION_JSON)

? ? ? ? ? ? ? ?.contentType(MediaType.APPLICATION_JSON_UTF8)

? ? ? ? ? ? ? ?.content(gosn.toJson(article));

? ? ? ?MvcResult result = mvc.perform(builder).andReturn();

? ? ? ?System.out.println(result.getResponse().getContentAsString());

? ?}

? ?@Test

? ?public void testUpdateArticle() throws Exception {

? ? ? ?Article article = new Article();

? ? ? ?article.setTitle("更新測(cè)試文章");

? ? ? ?article.setType(1);

? ? ? ?article.setStatus(2);

? ? ? ?article.setSummary("這是一篇更新測(cè)試文章");

? ? ? ?Gson gosn = new Gson();

? ? ? ?RequestBuilder builder = MockMvcRequestBuilders

? ? ? ? ? ? ? ?.put("/rest/article/1")

? ? ? ? ? ? ? ?.accept(MediaType.APPLICATION_JSON)

? ? ? ? ? ? ? ?.contentType(MediaType.APPLICATION_JSON_UTF8)

? ? ? ? ? ? ? ?.content(gosn.toJson(article));

? ? ? ?MvcResult result = mvc.perform(builder).andReturn();

? ?}

? ?@Test

? ?public void testQueryArticle() throws Exception {

? ? ? ?RequestBuilder builder = MockMvcRequestBuilders

? ? ? ? ? ? ? ?.get("/rest/article/1")

? ? ? ? ? ? ? ?.accept(MediaType.APPLICATION_JSON)

? ? ? ? ? ? ? ?.contentType(MediaType.APPLICATION_JSON_UTF8);

? ? ? ?MvcResult result = mvc.perform(builder).andReturn();

? ? ? ?System.out.println(result.getResponse().getContentAsString());

? ?}

? ?@Test

? ?public void testDeleteArticle() throws Exception {

? ? ? ?RequestBuilder builder = MockMvcRequestBuilders

? ? ? ? ? ? ? ?.delete("/rest/article/1")

? ? ? ? ? ? ? ?.accept(MediaType.APPLICATION_JSON)

? ? ? ? ? ? ? ?.contentType(MediaType.APPLICATION_JSON_UTF8);

? ? ? ?MvcResult result = mvc.perform(builder).andReturn();

? ?}

}

執(zhí)行結(jié)果這里就不給大家貼了震捣,大家有興趣的話可以自己實(shí)驗(yàn)一下荔棉。整個(gè)類要說(shuō)明的點(diǎn)還是很少,主要這些東西都與Spring boot沒(méi)關(guān)系蒿赢,支持這些操作的原因還是上一篇文章中提到的引入對(duì)應(yīng)的starter:

<dependency>

? ? ?<groupId>org.springframework.boot</groupId>

? ? ?<artifactId>spring-boot-starter-test</artifactId>

? ? ?<scope>test</scope>

</dependency>

因?yàn)橐獔?zhí)行HTTP請(qǐng)求润樱,所以這里使用了MockMvc,ArticleRestController通過(guò)注入的方式實(shí)例化羡棵,不能直接new壹若,否則ArticleRestController就不能通過(guò)Spring IoC容器來(lái)管理,因而其依賴的其他類也無(wú)法正常注入皂冰。通過(guò)MockMvc我們就可以輕松的實(shí)現(xiàn)HTTP的DELETE/PUT/POST等方法了店展。

五、總結(jié)

本文講解了如果通過(guò)Spring boot來(lái)實(shí)現(xiàn)Restful的API秃流,其實(shí)大部分東西都是Spring和Spring MVC提供的赂蕴,Spring boot只是提供自動(dòng)配置的功能。但是舶胀,正是這種自動(dòng)配置概说,為我們減少了很多的開發(fā)和維護(hù)工作,使我們能更加簡(jiǎn)單嚣伐、高效的實(shí)現(xiàn)一個(gè)web工程糖赔,從而讓我們能夠更加專注于業(yè)務(wù)本身的開發(fā),而不需要去關(guān)心框架的東西纤控。這篇文章中我們提到了可以通過(guò)postman和junit的方式來(lái)訪問(wèn)Restful 接口挂捻,下篇文章我們會(huì)介紹另外一種方式來(lái)訪問(wèn),有興趣的可以繼續(xù)關(guān)注一下船万。

擴(kuò)展閱讀

springboot知識(shí)點(diǎn)整理

Springboot Mybatis整合(完整版)

Spring Boot配置隨機(jī)數(shù)那些小技巧

給你一份詳細(xì)的Spring Boot知識(shí)清單

Spring MVC到Spring BOOT的簡(jiǎn)化之路

來(lái)源:https://www.cnblogs.com/paddix/p/8215245.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末刻撒,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子耿导,更是在濱河造成了極大的恐慌声怔,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舱呻,死亡現(xiàn)場(chǎng)離奇詭異醋火,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)箱吕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門芥驳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人茬高,你說(shuō)我怎么就攤上這事兆旬。” “怎么了怎栽?”我有些...
    開封第一講書人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵丽猬,是天一觀的道長(zhǎng)宿饱。 經(jīng)常有香客問(wèn)我爹橱,道長(zhǎng)毙玻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任圆雁,我火速辦了婚禮由桌,結(jié)果婚禮上为黎,老公的妹妹穿的比我還像新娘。我一直安慰自己沥寥,他們只是感情好碍舍,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著邑雅,像睡著了一般片橡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上淮野,一...
    開封第一講書人閱讀 52,736評(píng)論 1 312
  • 那天捧书,我揣著相機(jī)與錄音,去河邊找鬼骤星。 笑死经瓷,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的洞难。 我是一名探鬼主播舆吮,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼队贱!你這毒婦竟也來(lái)了色冀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤柱嫌,失蹤者是張志新(化名)和其女友劉穎锋恬,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體编丘,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡与学,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嘉抓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片索守。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖抑片,靈堂內(nèi)的尸體忽然破棺而出蕾盯,到底是詐尸還是另有隱情,我是刑警寧澤蓝丙,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布级遭,位于F島的核電站,受9級(jí)特大地震影響渺尘,放射性物質(zhì)發(fā)生泄漏挫鸽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一鸥跟、第九天 我趴在偏房一處隱蔽的房頂上張望丢郊。 院中可真熱鬧,春花似錦医咨、人聲如沸枫匾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)干茉。三九已至,卻和暖如春很泊,著一層夾襖步出監(jiān)牢的瞬間角虫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工委造, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留戳鹅,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓昏兆,卻偏偏與公主長(zhǎng)得像枫虏,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子爬虱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361