1. 程式人生 > >初試Spring Boot:構建第一個Web程式

初試Spring Boot:構建第一個Web程式

Spring Boot主要提供快速構建專案的功能。本文中我們會使用Spring Boot構建第一個Web程式,同時介紹Spring Boot最簡單的功能,例如執行單元測試,釋出與呼叫REST服務等。

本文作者楊恩雄,選自新書《Spring Boot 2+Thymeleaf企業應用實戰》。

1 Spring Boot介紹

我們先來了解一下Spring Boot專案。

1.1 Spring Boot簡介

開發一個全新的專案,需要先搭建開發環境,例如確定要使用的技術框架及版本,還要考慮各個框架之間的版本相容問題。完成這些煩瑣的工作後,還要對新專案進行配置,測試其能否正常執行,最後才將搭建好的環境提交給專案組的其他成員使用。經常會出現的情形是,專案表面上已經成功執行,但部分專案組成員仍然無法執行專案。對於每一個專案,在初期都會浪費大量的時間做這些固定的事情。

受Ruby On Rails、Node.js等技術的影響,Java EE領域需要一種更為簡便的開發方式,來取代這些煩瑣的專案搭建工作。在此背景下,Spring推出了Spring Boot專案,該專案可以讓使用者更快速地搭建專案,從而使用者可以更專注於業務系統的開發。系統配置、基礎程式碼、專案依賴的jar包,甚至開發時所用到的應用伺服器等,Spring Boot都可以幫我們準備好。只要在建立專案時,使用構建工具加入相應的Spring Boot依賴包,專案即可執行,使用者無須關心版本相容等問題。

Spring Boot支援Maven和Gradle這兩款構建工具。Gradle使用Groovy語言編寫構建指令碼,與Maven、Ant等構建工具有良好的相容性。鑑於筆者使用Maven較多,因此本文使用Maven作為專案構建工具。本文寫作時,Spring Boot最新的正式版本為2.0.1,其要求Maven版本為3.2或以上。

1.2 starter模組

Spring Boot提供了許多starter模組,starter為我們提供了“一站式”服務,在專案中另加入對應框架的starter的依賴,可以免去我們到處尋找依賴包的煩惱。只需要加入一個依賴,專案就可以執行,這就是starter的作用。例如,如果你想讓你的專案擁有訪問關係型資料庫的能力,則只需要在你的專案中加入spring-boot-starter-data-jpa依賴就可以實現:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
  <version>2.0.1.RELEASE</version>
</dependency>

Spring Boot官方提供的starter模組,命名規則為“spring-boot-starter-*”,其中“*”代表對應的應用型別,這些starter的名稱,可以幫助我們快速地找到相應的依賴。如果想構建自己的starter模組,官方建議命名規則為“project-spring-boot-starter”。

下面介紹一些比較常用的starter模組。

  • spring-boot-starter-web:用於構建Web應用的starter模組,加入該依賴後,會包含Spring MVC框架,預設內嵌Tomcat容器。
  • spring-boot-starter-jpa:用於構建Spring Data JPA應用,使用Hibernate作為ORM框架。
  • spring-boot-starter-mongodb:用於構建Spring Data MongoDB應用,底層使用MongoDB驅動操作MongoDB資料庫。
  • spring-boot-starter-redis:用於構建Spring Data Redis應用,使用Jedis框架操作Redis資料庫。
  • spring-boot-starter-thymeleaf:構建一個使用Thymeleaf作為檢視的Web應用。
  • spring-boot-starter-test:顧名思義,這個starter模組主要用於進行單元測試。

2 構建第一個Spring Boot程式

這一節,我們使用Spring Boot構建一個最簡單的Web應用。

2.1 新建Maven專案

Spring Boot使用3.2以上版本的Maven,這裡我們使用的版本為3.5。在Eclipse的選單中選擇“File→New→Other”命令,選中“Maven”下的“Maven Project”,單擊“Next”按鈕,在“New Maven Project”頁面中,選擇“Create a simple project”項,建立一個簡單的Maven專案。在彈出的建立專案頁面中,輸入相應的專案資訊,如下圖。

v2-efee1ba630cb6488f1baac4811b48fe6_hd.j 新建的Maven專案,其結構如下。

初試Spring Boot:構建第一個Web程式

src/main/java用於存放主應用程式的類,src/main/resources用於存放主應用程式的資原始檔,src/test用於存放測試相關的Java類和資源,pom.xml則是Maven的構建指令碼。

一般情況下,Maven指令碼檔案需要繼承“spring-boot-starter-parent”專案,並在指令碼中根據需要宣告一個或多個starter。修改專案的pom.xml檔案,如程式碼清單2-1所示。

程式碼清單2-1:codesirst-bootpom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
  http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.crazyit.boot.c2</groupId>
  <artifactId>first-boot</artifactId>
  <version>0.0.1-SNAPSHOT</version>
 
  <!-- 繼承spring boot的starter -->
  <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.0.1.RELEASE</version>
  </parent>
 
  <!-- 新增web starter的依賴 -->
  <dependencies>
  <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  </dependencies>

</project>
注意:在加入依賴後,如果Eclipse中的Maven專案存在錯誤,則可以選中專案,滑鼠右擊,在彈出的選單中選擇“Maven→Update Project”命令來解決問題。

加入依賴後,由於starter的作用,Maven會自動幫我們引用Spring框架的各個模組,例如spring-context、spring-web等,還會引入嵌入式的Tomcat。具體會幫我們的專案加入哪些依賴包,我們在Eclipse下面看一下,有個大概印象即可。

2.2 編寫啟動類

編寫一個簡單的啟動類,就可以直接啟動Web服務,啟動類如程式碼清單2-2所示。

程式碼清單2-2:codesirst-bootsrcmainjavaorgcrazyitootc2FirstApp.java

 

FirstApp類使用了@SpringBootApplication註解,該註解宣告該類是一個Spring Boot應用,該註解具有@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan等註解的功能。直接執行MyApp的main方法,看到以下輸出資訊後,證明啟動成功:

  2017-10-30 11:40:49.968 INFO 5916 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
  2017-10-30 11:40:50.199 INFO 5916 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
  2017-10-30 11:40:50.208 INFO 5916 --- [ main] org.crazyit.boot.c2.FirstApp : Started FirstApp in 6.226 seconds (JVM running for 6.888)

根據輸出資訊可知,啟動的Tomcat埠為8080。開啟瀏覽器訪問http://localhost:8080,可以看到錯誤頁面,表示應用已經成功啟動。

注意:在預設情況下,啟動埠為8080,如果需要修改Tomcat埠,則可以在src/main/ resources目錄下面,新建application.properties檔案,通過設定server.port屬性來配置Tomcat埠。

2.3 編寫控制器

前面我們加入了spring-boot-starter-web模組,預設集成了Spring MVC,因此只需要編寫一個Controller,即可實現一個最簡單的HelloWorld程式。程式碼清單2-3為該控制器的程式碼。

程式碼清單2-3:codes\02\first-boot\src\main\java\org\crazyit\boot\c2\MyController.java

@Controller
public class MyController {
 
  @GetMapping("/hello")
  @ResponseBody
  public String hello() {
  return "Hello World";
  }
}

在程式碼清單2-3中,使用了@Controller註解來修飾MyController,由於啟動類中使用了@SpringBootApplication註解,該註解具有@ComponentScan的功能,因此@Controller會被掃描並註冊。在hello方法中使用了@GetMapping與@ResponseBody註解,以宣告hello方法的訪問地址及返回內容。重新執行啟動類,開啟瀏覽器並訪問地址http://localhost:8080/hello,則可以看到控制器的返回內容。

至此,我們的第一個Spring Boot Web程式已經完成了,整個過程非常簡單。大家可以看到,使用Spring Boot後,使我們節省了很多搭建專案框架的時間,Spring Boot的starter提供了這種“一站式”的服務,幫助我們開發Web應用。

2.4 開發環境的熱部署

每次修改Java後,都需要重新執行main方法才能生效,這樣會降低開發效率。我們可以使用Spring Boot提供的開發工具來實現熱部署,為專案加上以下依賴:

 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-devtools</artifactId>
 </dependency>

修改Java檔案後,容器會重新載入本專案的Java類。

3 執行單元測試

單元測試對於程式來說非常重要,它不僅能增強程式的健壯性,而且也為程式的重構提供了依據,目前很多開源專案的測試覆蓋率高達90%以上,可見大家對單元測試的重視。Spring Boot執行Web應用,只需要執行main方法即可,那麼如何測試這個Web程式?如何測試Spring Boot中的元件呢?這一節,將簡單介紹Spring Boot的單元測試。

3.1 測試Web服務

Spring Boot提供了@SpringBootTest註解,可以讓我們在單元測試中測試Spring Boot的程式。先為我們的專案加入“spring-boot-starter-test”依賴:

 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-test</artifactId>
 </dependency>

新建測試類,用於測試第一個Spring Boot程式的“/hello”服務,測試類請見程式碼清單3-1。

程式碼清單3-1:codes\02\first-boot\src\test\java\org\crazyit\boot\c2\RandomPortTest.java


@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class RandomPortTest {
 
  @Autowired
  private TestRestTemplate restTemplate;
 
  @Test
  public void testHello() {
    // 測試hello方法
    String result = restTemplate.getForObject("/hello", String.class);
    assertEquals("Hello World", result);
  }
}


在程式碼清單3-1中,為測試類加入了@RunWith、@SpringBootTest註解,其中為SpringBootTest配置了webEnvironment屬性,表示在執行測試時,會為Web容器隨機分配埠。在測試方法中,使用@Test註解修飾,使用TestRestTemplate呼叫“/hello”服務。

這個TestRestTemplate物件,實際上是對RestTemplate進行了封裝,可以讓我們在測試環境更方便使用RestTemplate的功能,例如程式碼清單3-1,我們不需要知道Web容器的埠是多少,就可以直接進行測試。

在程式碼清單3-1中配置了隨機埠,如果想使用固定的埠,則可以將webEnvironment配置為WebEnvironment.DEFINED_PORT。使用該屬性,會讀取專案配置檔案(例如application.properties)中的埠(server.port屬性)來啟動Web容器,如果沒有配置,則使用預設埠8080。

3.2 模擬Web測試

在設定@SpringBootTest的webEnvironment屬性時,不管設定為RANDOM_PORT還是設定為DEFINED_PORT,在執行單元測試時,都會啟動一個真實的Web容器。如果不想啟動真實的Web容器,則可以將webEnvironment屬性設定為WebEnvironment.MOCK,來啟動一個模擬的Web容器,如程式碼清單3-2所示。

程式碼清單3-2:codesirst-bootsrc estjavaorgcrazyitootc2MockEnvTest.java


 

為測試類加入@AutoConfigureMockMvc註解,讓其進行mock相關的自動配置。在測試方法中,使用Spring的MockMvc進行模擬測試,向“/hello”傳送請求並得到迴應。

注意:webEnvironment屬性的預設值是WebEnvironment.MOCK,只所以在程式碼清單3-2中“多此一舉”,是為了展示該配置。

3.3 測試業務元件

前面都是針對Web容器進行測試,如果不想測試Web容器,只是想測試容器中的bean,則可以只啟動Spring的容器,請見程式碼清單3-3。

程式碼清單3-3:codesirst-bootsrcmainjavaorgcrazyitootc2MyService.java

codesirst-bootsrc estjavaorgcrazyitootc2MyServiceTest.java


 

在程式碼清單3-3中,新建了一個MyService的服務類,MyServiceTest會對該類進行測試,直接在測試類中注入MyService的例項。注意,SpringBootTest的webEnvironment屬性被設定為NONE,因而Web容器將不會被啟動。

3.4 模擬業務元件

在實際應用中,我們的程式可能會操作資料庫,也有可能呼叫第三方介面,為了不讓這些外部的不穩定因素影響單元測試的執行結果,可以使用mock來模擬某些元件的返回結果,確保被測試元件程式碼的健壯性。程式碼清單3-4顯示了兩個業務元件。

程式碼清單3-4:codesirst-bootsrcmainjavaorgcrazyitootc2MainService.java

codesirst-bootsrcmainjavaorgcrazyitootc2RemoteService.java

@Service
public class RemoteService {
 
  public String call() {
    return "hello";
  }
}
 
@Service
public class MainService {
 
  @Autowired
  private RemoteService remoteService;
 
  public void mainService() {
    System.out.println("這是需要測試的業務方法");
    String result = remoteService.call();
    System.out.println("呼叫結果:" + result);
  }
}

RemoteService的call方法在正常情況下會返回hello字串,MainService中的mainService方法會呼叫call方法。假設call方法無法正常執行,為了能測試MainService,我們需要模擬call方法的返回結果。程式碼清單3-5為MainService的測試方法。

程式碼清單3-5:codesirst-bootsrc estjavaorgcrazyitootc2MockTest.java


@RunWith(SpringRunner.class)
@SpringBootTest
public class MockTest {
 
  @MockBean
  private RemoteService remoteService;
 
  @Autowired
  private MainService mainService;
 
  @Test
  public void testMainService() {
    // 模擬remoteService 的 call 方法返回 angus
    BDDMockito.given(this.remoteService.call()).willReturn("angus");
    mainService.mainService();
  }
}

在測試類中,使用MockBean來修飾需要模擬的元件,在測試方法中使用了Mockito的API來模擬remoteService的call方法返回。在模擬中這個方法被呼叫後,將會返回“angus”字串,執行程式碼清單3-5,輸出結果如下:

這是需要測試的業務方法
呼叫結果:angus

根據結果可知,RemoteService的call方法被成功模擬。

這一節,簡單介紹瞭如何在Spring Boot中進行單元測試,本節的知識基本上能滿足大部分的需求,由於篇幅所限,在此不展開討論。我們下面介紹如何使用Spring Boot來發布和呼叫REST服務。

4 釋出與呼叫REST服務

在系統間進行通訊,很多系統都會選擇SOAP協議,隨著REST的興起,現在很多系統在釋出與呼叫Web Service時,都首選REST。這一節,我們介紹如何在Spring Boot中釋出和呼叫REST服務。

4.1 REST

REST是英文Representational State Transfer的縮寫,一般翻譯為“表述性狀態轉移”,是Roy Thomas Fielding博士在他的論文“Architectural Styles and the Design of Network-based Software Architectures”中提出的一個術語。REST本身只是分散式系統設計中的一種架構風格,並不是某種標準或者規範,而是一種輕量級的基於HTTP協議的Web Service風格。從另外一個角度看,REST更像是一種設計原則,更可以將其理解為一種思想。

4.2 釋出REST服務

在Spring Boot中釋出REST服務非常簡單,只需要在控制器中使用@RestController即可。下面我們來看一個示例。新建一個rest-server的Maven專案,加入“spring-boot-starter-web”依賴,將啟動類和控制器寫入同一個類中,請見程式碼清單4-1。

程式碼清單4-1:codes est-serversrcmainjavaorgcrazyitootc2RestApp.java


 

在程式碼清單4-1中,釋出了一個“/person/name”的服務,呼叫該服務後,會返回一個Person例項的JSON字串,該服務對應的方法使用了組合註解@GetMapping,該註解的作用相當於@RequestMapping(method = RequestMethod.GET)。執行程式碼清單4-1,在瀏覽器中訪問:http://localhost:8080/person/angus,則返回以下資訊:{"name":"angus","age":33}。

很簡單的一個註解就幫我們完成了釋出REST服務的工作,這再一次展示了Spring Boot的便捷。如果不使用Spring Boot,估計你還要為尋找依賴包而疲於奔命。

4.3 使用RestTemplate呼叫服務

下面,我們使用Spring的RestTemplate來呼叫服務。RestTemplate是Spring Framework的一個類,其主要用來呼叫REST服務,它提供了攔截器機制,我們可以對它進行個性化定製。另外,在Spring Cloud中也可以使用RestTemplate來呼叫服務,而且還可以實現負載均衡的功能,有興趣的朋友可參考筆者的另外一本書《瘋狂Spring Cloud微服務架構實戰》。

我們來看一個例子。

新建一個rest-client的Maven專案,加入“spring-boot-starter-web”與“spring-boot-starter-test”的依賴,新建一個最普通的main方法,直接呼叫前面的服務,請見程式碼清單4-2。

程式碼清單4-2:codes est-clientsrcmainjavaorgcrazyitootc2RestTemplateMain.java


 

在main方法中,直接建立RestTemplate的例項並呼叫服務,操作非常簡單。如果想在Spring的bean裡面使用RestTemplate,則可以使用RestTemplateBuilder,請見程式碼清單4-3。

程式碼清單4-3:codes est-clientsrcmainjavaorgcrazyitootc2MyService.java


 

在我們自已的bean裡面注入RestTemplateBuilder,建立一個RestTemplate的bean。在建立RestTemplate例項時,使用RestTemplateBuilder的rootUri方法設定訪問的URI。除了rootUri方法外,RestTemplateBuilder還提供了很多方法用於設定RestTemplate,在此不再贅述。接下來,編寫一個單元測試類,來測試我們這個MyService的bean,請見程式碼清單4-4。

程式碼清單4-4:codes est-clientsrc estjavaorgcrazyitootc2MyServiceTest.java


 

與前面的單元測試類似,直接注入MyService即可。

注意:在執行單元測試時,專案中一定要有Spring Boot的啟動類,否則會得到以下異常:java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test
Spring的RestTemplate只是眾多REST客戶端中的一個。接下來,我們介紹另外一個REST客戶端Feign。

4.4 使用Feign呼叫服務

Feign是Github上的一個開源專案,其目的是簡化Web Service客戶端的開發。Spring Cloud專案將Feign整合進來,讓其作為REST客戶端。這一節,我們來了解如何使用Feign框架呼叫REST服務。在rest-client專案中加入以下依賴:


<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-core</artifactId>
    <version>9.5.0</version>
  </dependency>
  <dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-gson</artifactId>
    <version>9.5.0</version>
  </dependency>

新建PersonClient介面,請見程式碼清單4-5。

程式碼清單4-5:codes est-clientsrcmainjavaorgcrazyitootc2eignPersonClient.java


 

在介面中,使用了@RequestLine和@Param註解,這兩個註解是Feign的註解。使用註解修飾後,getPerson方法被呼叫,然後使用HTTP的GET方法向“/person/name”服務傳送請求。接下來編寫客戶端執行類,請見程式碼清單4-6。

程式碼清單4-6:codes est-clientsrcmainjavaorgcrazyitootc2eignFeignMain.java


 

在程式碼清單4-6中,使用Feign來建立PersonClient介面的例項,最後通過呼叫介面方法來訪問服務。熟悉AOP的朋友可能已經猜到,Feign實際上幫助我們動態生成了代理類,Feign使用的是JDK的動態代理,代理類會將請求的資訊封裝,最終使用java.netHttpURLConnection來發送HTTP請求。如果想將這裡的PersonClient作為bean放到Spring容器中,則可以新增一個建立該例項的方法:


 @Bean
  public PersonClient personClient() {
    return Feign.builder()
      .decoder(new GsonDecoder())
      .target(PersonClient.class, "http://localhost:8080/");
  }

5 本文小結

本文主要運行了第一個Spring Boot程式,通過這個程式,大家可以瞭解什麼是Spring Boot,在此過程中,大家應該也能感受到Spring Boot的便捷。除了這個簡單的Spring Boot程式外,還介紹瞭如何在Spring Boot環境中執行單元測試,包括對Web應用的測試、對Spring元件的模擬測試。最後,介紹瞭如何在Spring Boot中釋出和呼叫REST服務,其中重點介紹了RestTemplate和Feign框架。本文作為Spring Boot入門的文章,涉及的知識較為簡單,在《Spring Boot 2+Thymeleaf企業應用實戰》一書中我們會繼續學習Spring Boot。

f8c287ecb3bee7a9e0c994a0a0dc23566299e5c1