SpringBoot RESTful實戰(zhàn)
一、目標
了解 Restful 是什么,基本概念及風格;
能使用SpringBoot 實現(xiàn)一套基礎(chǔ)的 Restful 風格接口;
利用Swagger 生成清晰的接口文檔。
二、Restful 入門
什么是REST?
摘自百科的定義:REST即表述性狀態(tài)轉(zhuǎn)移(英文:Representational State Transfer,簡稱REST) 是Roy Fielding博士(HTTP規(guī)范主要貢獻者)在2000年的論文中提出來的一種軟件架構(gòu)風格。 是一種針對網(wǎng)絡(luò)應(yīng)用的設(shè)計和開發(fā)方式,可以降低開發(fā)的復(fù)雜性,提高系統(tǒng)的可伸縮性。
通俗點說,REST就是一組架構(gòu)約束準則;在這些準則中,有不少是利用了現(xiàn)有的WEB標準能力。 而最終的目的則是簡化當前業(yè)務(wù)層的設(shè)計及開發(fā)工作。
Restful API 則是指符合REST架構(gòu)約束的API,關(guān)于這個詞在早年前其實已經(jīng)非常流行,但大多數(shù)開發(fā)者對其仍然 處于觀望狀態(tài),并不一定會立即采用。這個相信與當時技術(shù)社區(qū)的成熟度及氛圍是密切相關(guān)。 無論如何,在微服務(wù)架構(gòu)如此流行的今天,Restful API已經(jīng)成為了一種必備的的標準設(shè)計風格。
關(guān)鍵要點
理解 Restful 風格需要理解以下幾點:
資源
資源指的就是一個抽象的信息實體,可以是一個用戶、一首歌曲、一篇文章,只要是可作為引用的對象就是資源。 每個資源通常會被映射到一個URI,通過訪問這個URI可以獲取到信息。
資源的表述
資源表述(Representation)指的則是資源的外在表現(xiàn)形式 比如一個帖子,可以通過HTML格式展現(xiàn),也可以通過XML、JSON等格式輸出到客戶端。
在前面的文章(SpringBoot-Scope詳解)中提到,HTTP協(xié)議通過MIME來統(tǒng)一定義數(shù)據(jù)信息的格式標準。 通常,Accept、Content-Type可以用來指定客戶端及服務(wù)端可接受的信息格式,而這個就是資源的表述
狀態(tài)轉(zhuǎn)移
在HTTP訪問過程中,資源的狀態(tài)發(fā)生變化。這里會涉及到以下的幾個動詞:
| 名稱 | 語義 |
|---|---|
| GET | 獲取資源 |
| POST | 新建資源 |
| PUT | 更新資源 |
| DELETE | 刪除資源 |
對于不同的訪問方法,服務(wù)器會產(chǎn)生對應(yīng)的行為并促使資源狀態(tài)產(chǎn)生轉(zhuǎn)換。
關(guān)于無狀態(tài)
Restful 是無狀態(tài)的設(shè)計,這點意味著交互過程中的請求應(yīng)該能包含所有需要的信息,而不需要依賴于已有的上下文。 然而 JavaEE中存在一些違背的做法,比如Cookie中設(shè)置JSESSIONID, 在多次請求間傳遞該值作為會話唯一標識,這標識著服務(wù)端必須保存著這些會話狀態(tài)數(shù)據(jù)。
PlayFramework框架實現(xiàn)了無狀態(tài)的Session,其將會話數(shù)據(jù)經(jīng)過加密編碼并置入Cookie中, 這樣客戶端的請求將直接攜帶上全部的信息,是無狀態(tài)的請求**,這點非常有利于服務(wù)端的可擴展性。
三、SpringBoot 實現(xiàn) Restful
接下來,我們利用 SpringBoot 來實現(xiàn)一個Restful 風格的樣例。
說明基于 PetStore(寵物店) 的案例,實現(xiàn)對某顧客(Customer)名下的寵物(Pet)的增刪改查。
1. 實體定義
Customer
publicclassCustomer{? ?privateString name;? ?publicCustomer(){? ? ? ?super();? ?}? ?publicCustomer(String name){? ? ? ?super();? ? ? ?this.name = name;? ?}? ?publicString getName(){? ? ? ?return name;? ?}? ?publicvoid setName(String name){? ? ? ?this.name = name;? ?}}
Customer 只包含一個name屬性,我們假定這是唯一的標志。
Pet
publicclassPet{? ?privateString petId;? ?privateString name;? ?privateString type;? ?privateString description;? ?publicString getPetId(){? ? ? ?return petId;? ?}? ?publicvoid setPetId(String petId){? ? ? ?this.petId = petId;? ?}? ?publicString getName(){? ? ? ?return name;? ?}? ?publicvoid setName(String name){? ? ? ?this.name = name;? ?}? ?publicString getType(){? ? ? ?return type;? ?}? ?publicvoid setType(String type){? ? ? ?this.type = type;? ?}? ?publicString getDescription(){? ? ? ?return description;? ?}? ?publicvoid setDescription(String description){? ? ? ?this.description = description;? ?}}
Pet 包含了以下幾個屬性
| 屬性名 | 描述 |
|---|---|
| petId | 寵物ID編號 |
| name | 寵物名稱 |
| type | 寵物類型 |
| description | 寵物的描述 |
2. URL資源
基于Restful 的原則,我們定義了以下的一組URL:
| 接口 | 方法 | URL |
|---|---|---|
| 添加寵物 | POST | /rest/pets/{customer} |
| 獲取寵物列表 | GET | /rest/pets/{customer} |
| 獲取寵物信息 | GET | /rest/pets/{customer}/{petId} |
| 更新寵物信息 | PUT | /rest/pets/{customer}/{petId} |
| 刪除寵物 | DELETE | /rest/pets/{customer}/{petId} |
3. 數(shù)據(jù)管理
接下來實現(xiàn)一個PetManager 類,用于模擬在內(nèi)存中對Pet數(shù)據(jù)進行增刪改查 代碼如下:
@ComponentpublicclassPetManager{? ?privatestaticMap<String,Customer> customers =newConcurrentHashMap<String,Customer>();? ?privatestaticMap<String,Map<String,Pet>> pets =newConcurrentHashMap<String,Map<String,Pet>>();? ?@PostConstruct? ?publicvoid init(){? ? ? ?String[] customerNames =newString[]{"Lilei","Hanmeimei","Jim Green"};? ? ? ?for(String customerName : customerNames){? ? ? ? ? ?customers.put(customerName,newCustomer(customerName));? ? ? ?}? ?}? ?/**? ? * 獲取customer? ? *? ? * @param customer? ? * @return? ? */? ?publicCustomer getCustomer(String customer){? ? ? ?if(StringUtils.isEmpty(customer)){? ? ? ? ? ?returnnull;? ? ? ?}? ? ? ?return customers.get(customer);? ?}? ?/**? ? * 獲取customer名下的 pet 列表? ? *? ? * @param customer? ? * @return? ? */? ?publicList<Pet> getPets(String customer){? ? ? ?if(StringUtils.isEmpty(customer)){? ? ? ? ? ?returnCollections.emptyList();? ? ? ?}? ? ? ?if(!pets.containsKey(customer)){? ? ? ? ? ?returnCollections.emptyList();? ? ? ?}? ? ? ?return pets.get(customer).values().stream().collect(Collectors.toList());? ?}? ?/**? ? * 獲取某個pet? ? *? ? * @param customer? ? * @param petId? ? * @return? ? */? ?publicPet getPet(String customer,String petId){? ? ? ?if(StringUtils.isEmpty(customer)||StringUtils.isEmpty(petId)){? ? ? ? ? ?returnnull;? ? ? ?}? ? ? ?if(!pets.containsKey(customer)){? ? ? ? ? ?returnnull;? ? ? ?}? ? ? ?return pets.get(customer).get(petId);? ?}? ?/**? ? * 刪除pet? ? *? ? * @param customer? ? * @param petId? ? * @return? ? */? ?publicboolean removePet(String customer,String petId){? ? ? ?if(StringUtils.isEmpty(customer)||StringUtils.isEmpty(petId)){? ? ? ? ? ?returnfalse;? ? ? ?}? ? ? ?if(!pets.containsKey(customer)){? ? ? ? ? ?returnfalse;? ? ? ?}? ? ? ?return pets.get(customer).remove(petId)!=null;? ?}? ?/**? ? * 添加pet? ? *? ? * @param customer? ? * @param pet? ? * @return? ? */? ?publicPet addPet(String customer,Pet pet){? ? ? ?if(StringUtils.isEmpty(customer)|| pet ==null){? ? ? ? ? ?returnnull;? ? ? ?}? ? ? ?Map<String,Pet> customerPets =null;? ? ? ?if(!pets.containsKey(customer)){? ? ? ? ? ?customerPets =newLinkedHashMap<String,Pet>();? ? ? ? ? ?Map<String,Pet> previous = pets.putIfAbsent(customer, customerPets);? ? ? ? ? ?// 已經(jīng)存在? ? ? ? ? ?if(previous !=null){? ? ? ? ? ? ? ?customerPets = previous;? ? ? ? ? ?}? ? ? ?}else{? ? ? ? ? ?customerPets = pets.get(customer);? ? ? ?}? ? ? ?if(pet.getPetId()==null){? ? ? ? ? ?pet.setPetId(UUID.randomUUID().toString());? ? ? ?}? ? ? ?customerPets.put(pet.getPetId(), pet);? ? ? ?return pet;? ?}? ?/**? ? * 更新某個pet? ? *? ? * @param customer? ? * @param petPojo? ? * @return? ? */? ?publicPet updatePet(String customer,Pet petPojo){? ? ? ?if(StringUtils.isEmpty(customer)|| petPojo ==null){? ? ? ? ? ?returnnull;? ? ? ?}? ? ? ?if(petPojo.getPetId()==null){? ? ? ? ? ?returnnull;? ? ? ?}? ? ? ?Pet pet = getPet(customer, petPojo.getPetId());? ? ? ?pet.setType(petPojo.getType());? ? ? ?pet.setName(petPojo.getName());? ? ? ?pet.setDescription(petPojo.getDescription());? ? ? ?return pet;? ?}}
4. 控制層實現(xiàn)
SpringBoot 提供了 @RestController,用于快速定義一個Restful 風格的Controller類@RestController=@ResponseBody + @Controller
@RestController@RequestMapping("/rest/pets/{customer}")publicclassRestApiController{? ?@Autowired? ?privatePetManager dataManager;? ?/**? ? * 添加寵物? ? *? ? * @param customer? ? * @param pet? ? * @return? ? */? ?@PostMapping? ?publicResponseEntity<Object> addPet(@PathVariableString customer,@RequestBodyPet pet){? ? ? ?validateCustomer(customer);? ? ? ?Pet newPet = dataManager.addPet(customer, pet);? ? ? ?// 返回 201.created? ? ? ?if(newPet !=null){? ? ? ? ? ?URI location =ServletUriComponentsBuilder.fromCurrentRequest().path("/{petId}")? ? ? ? ? ? ? ? ? ?.buildAndExpand(newPet.getPetId()).toUri();? ? ? ? ? ?returnResponseEntity.created(location).build();? ? ? ?}? ? ? ?// 返回 204.noContent? ? ? ?returnResponseEntity.noContent().build();? ?}? ?/**? ? * 獲取寵物列表? ? *? ? * @param customer? ? * @return? ? */? ?@GetMapping? ?@ResponseBody? ?publicList<Pet> listPets(@PathVariableString customer){? ? ? ?validateCustomer(customer);? ? ? ?List<Pet> pets = dataManager.getPets(customer);? ? ? ?return pets;? ?}? ?/**? ? * 獲取某個寵物? ? *? ? * @param customer? ? * @param petId? ? */? ?@GetMapping("/{petId}")? ?@ResponseBody? ?publicPet getPet(@PathVariableString customer,@PathVariableString petId){? ? ? ?validateCustomer(customer);? ? ? ?validatePet(customer, petId);? ? ? ?Pet pet = dataManager.getPet(customer, petId);? ? ? ?return pet;? ?}? ?/**? ? * 更新寵物信息? ? *? ? * @param customer? ? * @param petId? ? * @param pet? ? */? ?@PutMapping("/{petId}")? ?publicResponseEntity<Object> updatePet(@PathVariableString customer,@PathVariableString petId,@RequestBodyPet pet){? ? ? ?validateCustomer(customer);? ? ? ?validatePet(customer, petId);? ? ? ?pet.setPetId(petId);? ? ? ?Pet petObject = dataManager.updatePet(customer, pet);? ? ? ?if(petObject !=null){? ? ? ? ? ?returnResponseEntity.ok(petObject);? ? ? ?}? ? ? ?returnResponseEntity.noContent().build();? ?}? ?/**? ? * 刪除某個寵物? ? *? ? * @param customer? ? * @param petId? ? * @return? ? */? ?@DeleteMapping("/{petId}")? ?publicResponseEntity<Object> removePet(@PathVariableString customer,@PathVariableString petId){? ? ? ?validateCustomer(customer);? ? ? ?validatePet(customer, petId);? ? ? ?dataManager.removePet(customer, petId);? ? ? ?returnResponseEntity.ok().build();? ?}
上述代碼中已經(jīng)實現(xiàn)了完整的增刪改查語義。 在Restful 風格的API 接口定義中,往往會引用 HTTP 狀態(tài)碼用于表示不同的結(jié)果,比如一些錯誤的狀態(tài)類型。
這里我們對Customer、Pet 進行存在性校驗,若資源不存在返回404_NotFound。
? ?/**? ? * 校驗customer是否存在? ? *? ? * @param customer? ? */? ?privatevoid validateCustomer(String customer){? ? ? ?if(dataManager.getCustomer(customer)==null){? ? ? ? ? ?thrownewObjectNotFoundException(String.format("the customer['%s'] is not found", customer));? ? ? ?}? ?}? ?/**? ? * 校驗pet是否存在? ? *? ? * @param customer? ? */? ?privatevoid validatePet(String customer,String petId){? ? ? ?if(dataManager.getPet(customer, petId)==null){? ? ? ? ? ?thrownewObjectNotFoundException(String.format("the pet['%s/%s'] is not found", customer, petId));? ? ? ?}? ?}
自定義異常攔截
? ?/**? ? * 自定義異常,及攔截邏輯? ? *? ? * @author atp? ? *? ? */? ?@SuppressWarnings("serial")? ?publicstaticclassObjectNotFoundExceptionextendsRuntimeException{? ? ? ?publicObjectNotFoundException(String msg){? ? ? ? ? ?super(msg);? ? ? ?}? ?}? ?@ResponseBody? ?@ExceptionHandler(ObjectNotFoundException.class)? ?@ResponseStatus(HttpStatus.NOT_FOUND)? ?publicString objectNotFoundExceptionHandler(ObjectNotFoundException ex){? ? ? ?return ex.getMessage();? ?}
5. 接口驗證
1. 添加寵物
URLPOST http://{{server}}/rest/pets/LiLei請求內(nèi)容
{"name":"Smart Baby","description":"very small and smart also.","type":"Dog"}
返回示例
201 createdContent-Length→0Date→Mon,09Jul201805:15:01 GMTLocation→http://localhost:8090/rest/pets/LiLei/b5400334-e7b3-42f1-b192-f5e7c3193543
2. 獲取寵物列表
URLGET http://{{server}}/rest/pets/LiLei請求內(nèi)容
返回示例
200 OKContent-Type→application/json;charset=UTF-8Date→Mon,09Jul201805:23:27 GMTTransfer-Encoding→chunked[? ?{? ? ? ?"petId":"b5400334-e7b3-42f1-b192-f5e7c3193543",? ? ? ?"name":"Smart Baby",? ? ? ?"type":"Dog",? ? ? ?"description":"very small and smart also."? ?},? ?{? ? ? ?"petId":"610780af-94f1-4011-a175-7a0f3895163d",? ? ? ?"name":"Big Cat",? ? ? ?"type":"Cat",? ? ? ?"description":"very old but I like it."? ?}]
3. 查詢寵物信息
URLGET http://{{server}}/rest/pets/LiLei/b5400334-e7b3-42f1-b192-f5e7c3193543請求內(nèi)容
返回示例
200 OKContent-Type→application/json;charset=UTF-8Date→Mon,09Jul201805:25:24 GMTTransfer-Encoding→chunked{? ?"petId":"b5400334-e7b3-42f1-b192-f5e7c3193543",? ?"name":"Smart Baby",? ?"type":"Dog",? ?"description":"very small and smart also."}
4. 更新寵物信息
URLPUT http://{{server}}/rest/pets/LiLei/b5400334-e7b3-42f1-b192-f5e7c3193543請求內(nèi)容
{"name":"Big Cat V2","description":"I don't like it any more","type":"Cat"}
返回示例
200 OKContent-Type→application/json;charset=UTF-8Date→Mon,09Jul201805:31:28 GMTTransfer-Encoding→chunked{? ?"petId":"a98e4478-e754-4969-851b-bcaccd67263e",? ?"name":"Big Cat V2",? ?"type":"Cat",? ?"description":"I don't like it any more"}
5. 刪除寵物
URLDELETE http://{{server}}/rest/pets/LiLei/b5400334-e7b3-42f1-b192-f5e7c3193543請求內(nèi)容
返回示例
200 OKContent-Length→0Date→Mon,09Jul201805:32:51 GMT
相關(guān)出錯
客戶不存在:404 the customer['test'] is not found
寵物不存在:404 the pet['LiLei/b5400334-e7b3-42f1-b192-f5e7c31935431'] is not found
四、Swagger 的使用
關(guān)于Swagger
Swagger是目前非常流行的一個API設(shè)計開發(fā)框架(基于OpenApi), 可用于API的設(shè)計、管理、代碼生成以及Mock測試等。
目前Swagger的應(yīng)用非常廣,其涵蓋的開源模塊也比較多,這里將使用swagger-ui實現(xiàn)API在線DOC的生成。
引入依賴
? ? ? ?? ? ? ? ? ?io.springfox ? ? ? ? ? ?springfox-swagger2 ? ? ? ? ? ?2.7.0 ? ? ? ?? ? ? ?? ? ? ? ? ?io.springfox ? ? ? ? ? ?springfox-swagger-ui ? ? ? ? ? ?2.7.0 ? ? ? ?
定義API配置
@EnableSwagger2@ConfigurationpublicclassSwaggerConfig{? ?publicstaticfinalString VERSION ="1.0.0";? ?@Value("${swagger.enable}")? ?privateboolean enabled;? ?ApiInfo apiInfo(){? ? ? ?returnnewApiInfoBuilder().? ? ? ? ? ? ? ?title("Pet Api Definition")? ? ? ? ? ? ? ?.description("The Petstore CRUD Example")? ? ? ? ? ? ? ?.license("Apache 2.0")? ? ? ? ? ? ? ?.licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")? ? ? ? ? ? ? ?.termsOfServiceUrl("")? ? ? ? ? ? ? ?.version(VERSION)? ? ? ? ? ? ? ?.contact(newContact("","","[email protected]"))? ? ? ? ? ? ? ?.build();? ?}? ?@Bean? ?publicDocket customImplementation(){? ? ? ?returnnewDocket(DocumentationType.SWAGGER_2).select()? ? ? ? ? ? ? ?.apis(RequestHandlerSelectors.withClassAnnotation(Api.class))? ? ? ? ? ? ? ?.build()? ? ? ? ? ? ? ?.enable(enabled)? ? ? ? ? ? ? ?.apiInfo(apiInfo());? ?}}
@EnableSwagger2聲明了Swagger的啟用,Docket的Bean定義是API配置的入口, 可以設(shè)置API名稱、版本號,掃描范圍等。
聲明API描述
在原有的Controller 方法上添加關(guān)于API的聲明,如下:
@Api(value ="Pet Restful api")@RestController@RequestMapping("/rest/pets/{customer}")publicclassRestApiController{? ?@ApiOperation("添加寵物")? ?@ApiImplicitParams({? ? ? ? ? ?@ApiImplicitParam(paramType ="path", name ="customer", dataType ="String", required =true, value ="客戶名", defaultValue =""),? ? ? ? ? ?@ApiImplicitParam(paramType ="body", name ="pet", dataType ="Pet", required =true, value ="pet 請求", defaultValue ="")})? ?@ApiResponses({? ? ? ?@ApiResponse(code =201, message ="添加成功"),? ? ? ?@ApiResponse(code =404, message ="資源不存在")? ?})? ?@PostMapping? ?publicResponseEntity<Object> addPet(@PathVariableString customer,@RequestBodyPet pet){? ? ? ?...
為了能描述返回對象的文檔說明,為Pet類做API聲明:
@ApiModel("寵物信息")publicclassPet{? ?@ApiModelProperty(name="petId", value="寵物ID")? ?privateString petId;? ?@ApiModelProperty(name="name", value="寵物名稱")? ?privateString name;? ?@ApiModelProperty(name="type", value="寵物類型")? ?privateString type;? ?@ApiModelProperty(name="description", value="寵物描述")? ?privateString description;
相關(guān)的注解:
| 注解 | 描述 |
|---|---|
| @ApiModelProperty | 用在出入?yún)?shù)對象的字段上 |
| @Api | 用于controller類 |
| @ApiOperation | 用于controller方法,描述操作 |
| @ApiResponses | 用于controller方法,描述響應(yīng) |
| @ApiResponse | 用于@ApiResponses內(nèi),描述單個響應(yīng)結(jié)果 |
| @ApiImplicitParams | 用于controller的方法,描述入?yún)?/td> |
| @ApiImplicitParam | 用于@ApiImplicitParams內(nèi),描述單個入?yún)?/td> |
| @ApiModel | 用于返回對象類 |
訪問文檔
最后,訪問 http://localhost:8000/swagger_ui.html,可看到生成的文檔界面:

兩年嘔心瀝血的文章:「面試題」「基礎(chǔ)」「進階」這里全都有!
300多篇原創(chuàng)技術(shù)文章加入交流群學習海量視頻資源精美腦圖面試題長按掃碼可關(guān)注獲取?
在看和分享對我非常重要!![]()
