天天看點

初試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項目。在彈出的建立項目頁面中,輸入相應的項目資訊,如下圖。

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

建立的Maven項目,其結構如下。

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。

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