本文為實戰SpringCloud響應式微服務系列教程第八章,講解構建響應式RESTful服務。建議沒有之前基礎的童鞋,先看之前的章節,章節目錄放在文末。
1.使用springboot2.1.4構建RESTful風格服務
Springboot的設計是用來簡化Spring應用程序的初始搭建和開發過程,為了實現這種簡化效果,Springboot繼承了眾多第三方庫,並大量使用約定優於配置的設計理念,通過特定的方式使得開發人員不再需要定義繁雜而且多余的配置內容。
1.1基於Springboot的第一個RESTful服務
微服務架構推崇采用RESTful風格實現服務之間的交互。關於RESTful,很多開發人員在知識體系上的一些誤解和不足。我們先對RESTful風格做一個簡單的介紹,然后詳細闡述使用SpringBoot構建單個RESTful服務的過程。
(1)RESTful風格簡介
REST提出了一組建構約束條件和原則,滿足這些約束條件和原則的設計風格就是RESTful。
現實世界中的事物都可以被認為一種資源,我們可以根據這些約束條件和原則設計以資源為中心的服務。REST中最重要的一條原則就是客戶端和服務器之間的交互無狀態性。
從客戶端到服務器的每個請求都必須包含理解該請求所必需的信息,無狀態請求可以由任何可用服務實現響應,十分適合微服務架構的運行環境。所以RESTful代表的實際上是一種風格,而不是一種設計和架構模式,更不是一種具體的技術體系。
關於RESTful另一個比較容易忽視的核心概念是HATEOAS(Hypermedia as the Engine of Application State,基於超媒體的應用狀態引擎)。要解釋HATEOAS的概念,先要解釋什么是超媒體。
我們已經知道什么是超鏈接以及什么是超文本,其中超文本的特有優勢是擁有超鏈接。如果把超鏈接映入到多媒體中,就得到了超媒體。因此關鍵要素還是超鏈接。使用過超媒體作為應用引擎狀態,意思就是應用引擎的狀態變更由客戶端訪問不同的超媒體資源來驅動。
使用HATEOAS表現服務請求響應的風格如下,可以看到這里多了_links屬性,其中有一個self.href鏈接指向當前user資源。
GET http://api.example.com/users/tianyalan Content-type:application/json { _links:{ self:{ href:"/users/tianyalan" } } "id":"tianyalan", "name":"tianyalan", "email":"tianyalan@email.com" }
HATEOAS在spring boot和spring cloud中應用也非常廣泛,例如springboot提供了應用監控組件Actuator,通過Actuator可以獲取springboot應用程序當前的運行狀態,我們將在后續章節中詳細介紹Actuator組件。
Actuator組件對外暴露的也是一些http端點,訪問這些端點返回的數據跟常見的RESTful風格有所不同,這就是HATEOAS風格,所以可以參考相關資料進一步了解。
(2)引入spring-boot-starter-web工程
spring boot提供了一系列starter工程來簡化各組件之間的依賴關系。
例如在springboot中開發基於RESTful風格的端點時,通常會引入spring-boot-starter-web這個工程,而打開這個工程會發現里面實際上只定義了如下所示的一些pom依賴,其中包括所有我們能夠預見到的組件,例如非常經典的spring-web,spring-webmvc組件,可以看到spring-boot-starter-web還是基於這兩個組件構建web請求響應流程的。
另外還包含了基於JSON序列化和反序列化的jackson-databind組件,以及啟動內置tomcat服務器的spring-boot-starter-tomcat組件。
- org.springframework.boot:spring-boot-starter
- org.springframework.boot:spring-boot-starter-tomcat
- org.springframework.boot:spring-boot-starter-validation
- org.springframework.boot:spring-web
- org.springframework.boot:spring-webmvc
- org.fasterxml.jackson.core:jackson-databind
引入spring-boot-starter-web就像引入一個普通的maven依賴一樣,代碼如下:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
一旦spring-boot-starter-web組件引入完畢,我們就可以充分利用springboot的自動裝配機制開發單個服務。
(3)Application類
使用springboot的首要條件是構建一個Application啟動類。代碼如下:
@SpringBootApplication public class SoulApplication { public static void main(String[] args) { SpringApplication.run(SoulApplication.class, args); } }
結構比較固化。
顯然以上代碼關鍵的是@SpringBootApplication
注解。springboot使用@SpringBootApplication
注解來告訴spring容器具備該注解的類是整個spring容器中所有javaBean對象的入口,而具備該注解的類在springboot中就是Application類。
在上面代碼中,SoulApplication類就是整個容器的Application類。
@SpringBootApplication注解在指定Application類的同時,還會自動掃描與當前類同級以及子包下的@Component
、@Service
、@Repository
、@Controller
、@Entity
等注解,並把這些注解對應的類轉換為Bean對象全部加載到Spring容器中管理起來。
@SpringBootApplication
注解的定義如下,我們可以看到該注解實際上由三個注解組合而成,分別是@Configuration
、@EnableAutoConfigguration
和@ComponentScan
。
@Target(ElementType.TYPE) @Retenation(RetentionPolicy.PUNTIME) @Documented @Inherited @Configuration @EnableAutoConfigguration @ComponentScan public @interface SpringBootApplication
在Spring 中@Configuration
比較常見,提供javaConfig配置類實現。而@ComponentScan
則掃描@Component
等注解,幫相關的javaBean定義批量加載到IOC容器中。@EnableAutoConfigguration
最終會使用JDK提供的SPI機制來實現類的動態加載。
關於@EnableAutoConfigguration
注解更多的可以參考相關的資料。
我們還注意到在上面的代碼示例中包含一個main函數並執行了ApplicationContext對象,我們可以根據需求對該ApplicationContext對象做響應處理。
(4)Controller類
Application類提供了Springboot程序的入口,相當於應用程序擁有了最基本的骨架。接下來我們就可以添加各種業務相關的訪問入口,表現在RESTful風格上也就是一系列的Controller類所代表的的HTTP端點。
這里的Controller和springMvc的Controller在概念上是一致的。最簡單的Controller如下:
@RestController public class HelloController{ @GetMapping("/") public String index(){ return "Hello Spring Boot"; } }
以上代碼包含了@RestController
和@GetMapping("/")
兩個注解。我們知道在springMvc中包含了@Controller
注解用來表示當前類是一個servlet。
而@RestController
繼承了@Controller
,它告訴Springboot這是一個基於RESTful風格的HTTP端點,並會自動使用JSON實現HTTP請求和響應的序列化和反序列化操作。至於@GetMapper
類似@RequestMapping
,指定請求方式為GET。這里不做多余介紹。
以下代碼展示了一個典型的Controller,在Controller中通過靜態的業務代碼完成了根據商品編號,獲取商品信息的業務流程。
這里用到了兩層Mapping注解,在服務層級定義了服務的版本和路徑,分別為v1和products;而在操作級別有定義了HTTP請求方法的具體路徑及參數信息。
@RestController @RequestMapping("/v1/products") public class Productontroller{ @GetMapping("/{productCode}") public Product getProduct(@PathVariable String productCode){ Product product = new Product(); product .setId(1L); product .setPrice(100F); product .setProductCode("product001"); product .setProductName("product"); return product; } }