原创

精通Spring Boot教程(16):Actuator 构建 RESTful Web 应用

Spring Boot Actuator 是 Spring Boot 的一个子项目。通过它,可以很轻易地为应用提供多种生产级服务。本教程中,你将通过构建一个应用来学习如何添加这些服务。

1. 你需要构建什么

本教程将带你使用 Spring Boot Actuator 创建一个 “hello world” RESTful Web 服务。你需要构建一个 HTTP GET 请求服务:

$ curl http://localhost:9000/hello-world

返回以下 JSON

{"id":1,"content":"Hello, World!"}

它们也向你的应用中增加了很多开箱即用的、可在生产(或其他)环境管理服务的功能。你所构建的服务,其业务功能与 构建 RESTful Web 应用 教程结果相一致。尽管比较结果可能很有趣,但也无需为了学习而学习此教程。

1.1. 你需要准备什么

2. 如何完成本教程

像大多数 Spring 入门指南 一样,你可以从头开始并完成每一步,也可以跳过已经熟悉的基础配置环节。无论如何,最终都会得到可正常工作的代码。

从头开始,请移步 使用 Gradle 构建 章节

跳过基础环节,请执行以下步骤:

  • 下载 并解压本教程的源代码,或使用 Git 进行 clone:
    git clone https://github.com/spring-guides/gs-actuator-service.git
  • 进入 gs-actuator-service/initial 目录
  • 向前跳转至 创建表现类 章节

结束后,可以根据 gs-actuator-service/complete 目录下的代码来检查结果。

3. 使用 Gradle 构建

首先,设置一个基本的构建脚本。在使用 Spring 构建应用时,可以使用任何你喜欢的构建程序。此处包含的代码需要通过 GradleMaven 来运行。如果还不熟悉它们,请参阅 使用 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
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.springframework</groupId>
    <artifactId>gs-actuator-service</artifactId>
    <version>0.1.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Spring Boot Maven 插件 提供了很多方便的功能:

  • 汇集 classpath 下的所有 jar 包依赖,并构建一个可执行的单体 “über-jar”,这使得执行和传输你的服务变得更加方便。
  • 搜索 public static void main() 方法所在的类,并将其标记为可执行类。
  • 提供一个内置有 Spring Boot 依赖 匹配版本号集合的依赖解析器。你也可以重写为任意版本,但它默认为 Spring Boot 所选的版本号集合。

5. 使用 IDE 构建

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.run(HelloWorldApplication.class, args);
    }

}

@SpringBootApplication 注解提供了一些默认值(如嵌入式 Servlet 容器),当然,这取决于你 classpath 下的内容和其他内容。同时,还开启了 Spring MVC 的 @EnableWebMvc 注解,以激活 Web 端点。

程序中没有定义任何端点,但它已足够启动并观察 Actuator 的一些功能。 SpringApplication.run() 命令知道如何启动 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.run(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 端口,通过添加 application.properties 文件可以覆盖该配置。

src/main/resources/application.properties
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
 *
 *      https://www.apache.org/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.junit.runner.RunWith;
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<Map> entity = this.testRestTemplate.getForEntity(
                "http://localhost:" + this.port + "/hello-world", Map.class);

        then(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
    }

    @Test
    public void shouldReturn200WhenSendingRequestToManagementEndpoint() throws Exception {
        @SuppressWarnings("rawtypes")
        ResponseEntity<Map> entity = this.testRestTemplate.getForEntity(
                "http://localhost:" + this.mgt + "/actuator/info", Map.class);

        then(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
    }

}

13. 总结

恭喜你,你已用 Spring 开发了一个简单的 RESTful 服务。正因为 Spring Boot Actuator,你添加了一些有用的内置服务。

正文到此结束