現(xiàn)在的潮流是前端承擔(dān)越來越多的責(zé)任:MVC中的V和C,后端只需要負責(zé)提供數(shù)據(jù)M,但是后端有更重要的任務(wù):高并發(fā)、提供各個維度的擴展能力(負載均衡、數(shù)據(jù)表切分、服務(wù)分離)、更清晰的API設(shè)計。Spring Boot框架提供的機制便于工程師實現(xiàn)標準的RESTful接口,本文主要討論如何編寫Controller代碼,另外還涉及了MySQL的數(shù)據(jù)庫操作,之前我也寫過一篇關(guān)于Mysql的文章link,但是這篇文章加上了CRUD的操作。
先回顧下之前的文章中我們用到的例子:圖書信息管理系統(tǒng),主要的領(lǐng)域?qū)ο笥衎ook、author、publisher和reviewer。
首先我們要在pom文件中添加對應(yīng)的starter,即spring-boot-starter-web
,對應(yīng)的xml代碼示例為:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
然后我們要創(chuàng)建控制器(Controller),先在項目根目錄下創(chuàng)建controller包,一般為每個實體類對象創(chuàng)建一個控制器,例如BookController。
@RestController注解是@Controller和@ResponseBody的合集,表示這是個控制器bean,并且是將函數(shù)的返回值直接填入HTTP響應(yīng)體中,是REST風(fēng)格的控制器。@RequestMapping("/books")表示該控制器處理所有“/books”的URL請求,具體由那個函數(shù)處理,要根據(jù)HTTP的方法來區(qū)分:GET表示查詢、POST表示提交、PUT表示更新、DELETE表示刪除。
{
"message": "get all books",
"book": [
{
"isbn": "9781-1234-5678",
"title": "你愁啥",
"description": "這是一本奇怪的書",
"author": {
"firstName": "馮",
"lastName": "pp"
},
"publisher": {
"name": "大錘出版社"
},
"reviewers": []
},
{
"isbn": "9781-1234-1111",
"title": "別吵吵",
"description": "哈哈哈",
"author": {
"firstName": "杜琪",
"lastName": "琪"
},
"publisher": {
"name": "大錘出版社"
},
"reviewers": []
}
]
}
{
"message": "get book with isbn(9781-1234-5678)",
"book": {
"isbn": "9781-1234-5678",
"title": "你愁啥",
"description": "這是一本奇怪的書",
"author": {
"firstName": "馮",
"lastName": "pp"
},
"publisher": {
"name": "大錘出版社"
},
"reviewers": []
}
}
book.setTitle(title)
更新book信息,然后調(diào)用bookRepository.save(book)更新該對象的信息,通過@PathVariable修飾的參數(shù)title與URL中用“{title}”的值對應(yīng)。最后,放上完整的Controller代碼:
package com.test.bookpub.controller;
import com.alibaba.fastjson.JSONObject;
import com.test.bookpub.domain.Author;
import com.test.bookpub.domain.Book;
import com.test.bookpub.domain.Publisher;
import com.test.bookpub.repository.AuthorRepository;
import com.test.bookpub.repository.BookRepository;
import com.test.bookpub.repository.PublisherRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.*;
/**
* @author duqi
* @create 2015-12-02 18:18
*/
@RestController
@RequestMapping("/books")
public class BookController {
private static final Logger logger = LoggerFactory.getLogger(BookController.class);
@Autowired
private BookRepository bookRepository;
@Autowired
public AuthorRepository authorRepository;
@Autowired
public PublisherRepository publisherRepository;
@RequestMapping(method = RequestMethod.GET)
public Iterable<Book> getAllBooks() {
return bookRepository.findAll();
}
@RequestMapping(value = "/{isbn}", method = RequestMethod.GET)
public Map<String, Object> getBook(@PathVariable String isbn) {
Book book = bookRepository.findBookByIsbn(isbn);
Map<String, Object> response = new LinkedHashMap<>();
response.put("message", "get book with isbn(" + isbn +")");
response.put("book", book);
return response;
}
@RequestMapping(method = RequestMethod.POST)
public Map<String, Object> addBook(@RequestBody JSONObject bookJson) {
JSONObject authorJson = bookJson.getJSONObject("author");
Author author = new Author(authorJson.getString("firstName"), authorJson.getString("lastName"));
authorRepository.save(author);
String isbn = bookJson.getString("isbn");
JSONObject publisherJson = bookJson.getJSONObject("publisher");
Publisher publisher = new Publisher(publisherJson.getString("name"));
publisherRepository.save(publisher);
String title = bookJson.getString("title");
String desc = bookJson.getString("desc");
Book book = new Book(author, isbn, publisher, title);
book.setDescription(desc);
bookRepository.save(book);
Map<String, Object> response = new LinkedHashMap<>();
response.put("message", "book add successfully");
response.put("book", book);
return response;
}
@RequestMapping(value = "/{isbn}", method = RequestMethod.DELETE)
public Map<String, Object> deleteBook(@PathVariable String isbn) {
Map<String, Object> response = new LinkedHashMap<>();
try {
bookRepository.deleteBookByIsbn(isbn);
} catch (NullPointerException e) {
logger.error("the book is not in database");
response.put("message", "delete failure");
response.put("code", 0);
}
response.put("message", "delete successfully");
response.put("code", 1);
return response;
}
@RequestMapping(value = "/{isbn}/{title}", method = RequestMethod.PUT)
public Map<String, Object> updateBookTitle(@PathVariable String isbn, @PathVariable String title) {
Map<String, Object> response = new LinkedHashMap<>();
Book book = null;
try {
book = bookRepository.findBookByIsbn(isbn);
book.setTitle(title);
bookRepository.save(book);
} catch (NullPointerException e) {
response.put("message", "can not find the book");
return response;
}
response.put("message", "book update successfully");
response.put("book", book);
return response;
}
}
有三個問題需要補充探討
現(xiàn)在我要說下Controller的角色,大家可以看到,我這里將很多業(yè)務(wù)代碼混淆在Controller的代碼中。實際上,根據(jù)程序員必知之前端演進史一文所述Controller層應(yīng)該做的事是: 處理請求的參數(shù) 渲染和重定向 選擇Model和Service 處理Session和Cookies,我基本上認同這個觀點,最多再加上OAuth驗證(利用攔截器實現(xiàn)即可)。而真正的業(yè)務(wù)邏輯應(yīng)該單獨分處一層來處理,即常見的service層;
今天遇到一個類似參考資料2中的錯誤,我經(jīng)過查找后發(fā)現(xiàn)是Jakson解析我的對象的時候出現(xiàn)了無限遞歸解析,究其原因,是因為外鏈:解析book的時候,需要解析author,但是在author中又有books選項,所以造成死循環(huán),解決的辦法就是在author中的books屬性上加上注解:@JsonBackReference;同樣需要在Publisher類中的books屬性加上@JsonBackReference注解。
上述演示的Controller代碼還有兩個問題:返回值形式不統(tǒng)一;并沒有遵循標準的API設(shè)計(例如update方法實際上應(yīng)該由客戶端返回更新過的完整對象,這樣就可以直接調(diào)用save方法),后續(xù),我會參考RESTful API 設(shè)計指南進行學(xué)習(xí),對API的設(shè)計進行自己的學(xué)習(xí)總結(jié),讀者朋友,你也需要自己實踐和學(xué)習(xí)哦,有問題的可以找我討論。