使用 Spring Boot Actuator 構建 RESTful Web 應用
Spring Boot Actuator 是 Spring Boot 的一個子專案。通過它,可以很輕易地為應用提供多種生產級服務。本教程中,你將通過構建一個應用來學習如何新增這些服務。
1. 你需要構建什麼
本教程將帶你使用 Spring Boot Actuator 建立一個 “hello world” RESTful Web 服務。你需要構建一個 HTTP GET 請求服務:
$ curl localhost:9000/hello-world
返回以下 JSON:
{"id":1,"content":"Hello, World!"}
它們也向你的應用中增加了很多開箱即用的、可在生產(或其他)環境管理服務的功能。你所構建的服務,其業務功能與 構建 RESTful Web 應用 教程結果相一致。儘管比較結果可能很有趣,但也無需為了學習而學習此教程。
1.1. 你需要準備什麼
大約 15 分鐘時間一個喜歡的 IDE 或文字編輯器JDK 1.8 或更高版本Gradle 4+ 或 Maven 3.2+還可以直接將程式碼匯入到 IDE 中:Spring Tool Suite (STS)IntelliJ IDEA
2. 如何完成本教程
像大多數 Spring 入門指南 一樣,你可以從頭開始並完成每一步,也可以跳過已經熟悉的基礎配置環節。無論如何,最終都會得到可正常工作的程式碼。
從頭開始,請移步 使用 Gradle 構建 章節
跳過基礎環節,請執行以下步驟:
下載 並解壓本教程的原始碼,或使用 Git 進行 clone:git clone github/spring-guides/gs-actuator-service.git進入 gs-actuator-service/initial 目錄向前跳轉至 建立表現類 章節
結束後,可以根據
gs-actuator-service/complete 目錄下的程式碼來檢查結果。
3. 使用 Gradle 構建
首先,設定一個基本的構建指令碼。在使用 Spring 構建應用時,可以使用任何你喜歡的構建程式。此處包含的程式碼需要通過 Gradle 或 Maven 來執行。如果還不熟悉它們,請參閱 使用 Gradle 構建 Java 專案 或 使用 Maven 構建 Java 專案。
3.1. 建立目錄結構
在工作目錄中,建立如下所示的子目錄結構;例如,在類 UNIX 系統中,可使用 mkdir -p src/main/java/hello 命令建立。
└── src
└── main
└── java
└── hello
3.2. 建立 Gradle 構建指令碼
下面是 Gradle 初始化構建指令碼:
build.gradle
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:2.1.6.RELEASE")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
bootJar {
baseName='gs-actuator-service'
version='0.1.0'
}
sourceCompatibility=1.8
targetCompatibility=1.8
repositories {
mavenCentral()
}
dependencies {
compile("org.springframework.boot:spring-boot-starter-web")
compile("org.springframework.boot:spring-boot-starter-actuator")
testCompile("org.springframework.boot:spring-boot-starter-test")
testCompile("junit:junit")
}
Spring Boot Gradle 外掛 提供了很多方便的功能:
彙集 classpath 下的所有 jar 包依賴,並構建一個可執行的單體 “über-jar”,這將使執行和傳輸你的服務變得更加方便。搜尋 public static void main() 方法所在的類,並將其標記為可執行類。提供一個內建有 Spring Boot 依賴 匹配版本號集合的依賴解析器。你也可以重寫為任意版本,但它預設為 Spring Boot 所選的版本號集合。
4. 使用 Maven 構建
首先,設定一個基本的構建指令碼。在使用 Spring 構建應用時,可以使用任何你喜歡的構建程式。此處包含的程式碼需要通過 Maven 來執行。如果還不熟悉它,請參閱 使用 Maven 構建 Java 專案。
4.1. 建立目錄結構
在工作目錄中,建立如下所示的子目錄結構;例如,在類 UNIX 系統中,可使用 mkdir -p src/main/java/hello 命令建立。
└── src
└── main
└── java
└── hello
pom.xml
xsi:schemaLocation="maven.apache/POM/4.0.0 maven.apache/xsd/maven-4.0.0.xsd">
4.0.0
org.springframework
gs-actuator-service
0.1.0
org.springframework.boot
spring-boot-starter-parent
2.1.6.RELEASE
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-actuator
org.springframework.boot
spring-boot-starter-test
test
1.8
org.springframework.boot
spring-boot-maven-plugin
Spring Boot Maven 外掛 提供了很多方便的功能:
彙集 classpath 下的所有 jar 包依賴,並構建一個可執行的單體 “über-jar”,這使得執行和傳輸你的服務變得更加方便。搜尋 public static void main() 方法所在的類,並將其標記為可執行類。提供一個內建有 Spring Boot 依賴 匹配版本號集合的依賴解析器。你也可以重寫為任意版本,但它預設為 Spring Boot 所選的版本號集合。
5. 使用 IDE 構建
閱讀如何將本教程程式碼直接匯入到 Spring Tool Suite閱讀如何在 IntelliJ IDEA 中使用本教程程式碼
6. 執行空服務
對初學者來說,這兒有一個空白的 Spring MVC 應用。
src/main/java/hello/HelloWorldApplication.java
package hello;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class HelloWorldApplication {
public static void main(String[] args) {
SpringApplication(HelloWorldApplication.class, args);
}
}
@SpringBootApplication 註解提供了一些預設值(如嵌入式 Servlet 容器),當然,這取決於你 classpath 下的內容和其他內容。同時,還開啟了 Spring MVC 的@EnableWebMvc 註解,以啟用 Web 端點。
程式中沒有定義任何端點,但它已足夠啟動並觀察 Actuator 的一些功能。SpringApplication() 命令知道如何啟動 Web 應用。你只需要執行此命令即可。
$ ./gradlew clean build && java -jar build/libs/gs-actuator-service-0.1.0.jar
你幾乎沒有寫任何程式碼,結果會發生什麼?等服務啟動好之後,開啟另一個 Terminal 終端來進行測試:
$ curl localhost:8080
{"timestamp":1384788106983,"error":"Not Found","status":404,"message":""}
伺服器正在執行,而你並未定義任何業務端點。你可以看到來自 Actuator/error 端點的通用 JSON 響應,而不是容器預設生成的 HTML 錯誤響應 。你可在服務啟動的控制檯日誌中看到暴露出來了哪些開箱即用的端點。例如:
$ curl localhost:8080/actuator/health
{"status":"UP"}
很好,服務已然 “UP”。
檢視 Spring Boot Actuator 工程 以瞭解更多詳情。
7. 建立表現類
首先,考慮一下你的 API 會是什麼樣子。
你希望處理 /hello-world 的 GET 請求時,可以使用 name 查詢引數。為了響應這樣的請求,你將返回如下所示的 JSON 來代表一個問候語。
{
"id": 1,
"content": "Hello, World!"
}
id 欄位是問候語的唯一標識,content 欄位則是問候語的文字表示。
建立一個表示類來對問候語表示進行建模:
src/main/java/hello/Greeting.java
package hello;
public class Greeting {
private final long id;
private final String content;
public Greeting(long id, String content) {
this.id=id;
this.content=content;
}
public long getId() {
return id;
}
public String getContent() {
return content;
}
}
現在,你可以建立一個為表現類服務的控制器端點。
8. 建立資源控制器
在 Spring 中,REST 端點就是 Spring MVC 控制器。下面的 Spring MVC 控制器處理了 /hello-world 的 GET 請求,並返回 Greeting 資源:
src/main/java/hello/HelloWorldController.java
package hello;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HelloWorldController {
private static final String template="Hello, %s!";
private final AtomicLong counter=new AtomicLong();
@GetMapping("/hello-world")
@ResponseBody
public Greeting sayHello(@RequestParam(name="name", required=false, defaultValue="Stranger") String name) {
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
}
面向使用者的控制器和 REST 端點控制器的關鍵區別在於如何建立響應。端點控制器不依賴檢視(例如JSP)來渲染 HTML 中的模型資料,而是簡單地將要寫入的資料直接返回到響應體中。
@ResponseBody 註解告訴 Spring MVC 不要將模型渲染到檢視中,而是將要返回的物件寫入響應體。渲染這一步驟將通過 Spring 訊息轉換器來實現。Jackson 2 已在 classpath 中,這意味著,如果 Accept 請求頭指定應該返回 JSON,
MappingJackson2HttpMessageConverter 將處理 Greeting 到 JSON 之間的轉換。
如何知道 Jackson 2 在 classpath 中呢?執行 mvn dependency:tree 或 ./gradlew dependencues 命令,將得到詳細的依賴樹,並將顯示 Jackson 2.x。你還可以看到它來自於 spring-boot-starter-json,其則是由 spring-boot-starter-web 依賴匯入。
9. 建立可執行的 main 類
你可以從自定義主類啟動應用,或者也可以直接從其中一個配置類執行此操作。最簡單的辦法就是使用 SpringApplication 輔助類:
src/main/java/hello/HelloWorldApplication.java
package hello;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class HelloWorldApplication {
public static void main(String[] args) {
SpringApplication(HelloWorldApplication.class, args);
}
}
在傳統 Spring MVC 應用中,你需要通過新增 @EnableWebMvc 註解來開啟包括 DispatcherServlet 在內的關鍵特性。當 Spring Boot 在 classpath 中檢測到 spring-webmvc 時,會自動開啟此註解。這將使你在接下來的步驟中可以更方便地構建控制器。
@SpringBootApplication 還引入了 @ComponentSacn 註解,來告訴 Spring 掃描 hello 包,並載入那些控制器(以及其他被標註了註解的元件類)。
10. 構建可執行 JAR
你可以在命令列中通過 Gradle 或 Maven 來執行應用,也可以構建並執行一個包含了必要依賴、類和資原始檔的可執行 JAR 包。這將使在整個開發生命週期中,跨不同環境應用程式釋出、版本和部署更為容易。
如果你使用的是 Gradle,可以通過 ./gradlew bootRun 來啟動應用;也可通過./gradlew build 來構建 JAR 包,並通過下述命令執行之:
java -jar build/libs/gs-actuator-service-0.1.0.jar
如果你使用的是 Maven,可以通過 ./mvnw spring-boot:run 來啟動應用;也可通過 ./mvnw clean package 來構建 JAR 包,並通過下述命令執行之:
java -jar target/gs-actuator-service-0.1.0.jar
上述兩種方式將建立一個可執行 JAR 包,你也可以 構建一個經典 WAR 包。
... service comes up ...
測試一下:
$ curl localhost:8080/hello-world
{"id":1,"content":"Hello, Stranger!"}
11. 切換到其他埠
Spring Boot Actuator 預設執行在 8080 埠,通過新增 applicationperties檔案可以覆蓋該配置。
src/main/resources/applicationperties
server.port: 9000
management.server.port: 9001
management.server.address: 127.0.0.1
重啟應用:
$ ./gradlew clean build && java -jar build/libs/gs-actuator-service-0.1.0.jar
... service comes up on port 9000 ...
測試一下:
$ curl localhost:8080/hello-world
curl: (52) Empty reply from server
$ curl localhost:9000/hello-world
{"id":1,"content":"Hello, Stranger!"}
$ curl localhost:9001/actuator/health
{"status":"UP"}
12. 測試應用
為了檢查應用程式是否可以正常執行,你應該編寫應用程式的單元/整合測試類。可參照下面測試案例:
控制器是否正常管理端點是否正常
正如在測試類中所看到的那樣,我們在隨機埠啟動應用。
src/test/java/hello/HelloWorldApplicationTests.java
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* apache/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package hello;
import java.util.Map;
import org.junit.Test;
import org.junitnerWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.BDDAssertions.then;
/**
* Basic integration tests for service demo application.
*
* @author Dave Syer
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment=SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(properties={"management.port=0"})
public class HelloWorldApplicationTests {
@LocalServerPort
private int port;
@Value("${local.management.port}")
private int mgt;
@Autowired
private TestRestTemplate testRestTemplate;
@Test
public void shouldReturn200WhenSendingRequestToController() throws Exception {
@SuppressWarnings("rawtypes")
ResponseEntity
"localhost:" + this.port + "/hello-world", Map.class);
then(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@Test
public void shouldReturn200WhenSendingRequestToManagementEndpoint() throws Exception {
@SuppressWarnings("rawtypes")
ResponseEntity
"localhost:" + this.mgt + "/actuator/info", Map.class);
then(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
}