原文:Building a RESTful Web Service with Spring Boot Actuator

译者:xyq000

校对:strongant

Spring Boot Actuator 是 Spring Boot 的一个子项目。开发者只需少量工作,就可为应用添加若干种生产级服务。在这篇指南中,你将构建一个应用并学会如何添加这些服务。

你将构建什么

这篇指南将带领你利用 Spring Boot Actuator 构建一个“hello world”的 RESTful web 服务。你将构建一个可接收 HTTP GET 请求的服务:

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

它会以下面的 JSON 响应:

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

为了在一个生产(或其他)环境中管理服务,也会有许多其他开箱即用的特性被添加到你的应用中。这项服务的业务功能类似于构建 RESTful Web 服务。你在使用本指南时,无需参考该指南,尽管比较二者的结果可能会很有趣。

你需要什么

如何完成指南

像大多数 Spring 入门指南 一样,你可以从头开始完成每一步, 或者绕过你熟悉的基本步骤。不管通过哪种方式,你最后都会得到一份可用的代码。

若从基础开始,查看使用 Gradle 构建

若跳过基础部分,按如下流程操作:

  • 下载 并解压本指南的源码存档,或使用 Git 克隆:git clone https://github.com/spring-guides/gs-actuator-service.git
  • 进入 gs-actuator-service/initial 目录
  • 跳转到创建表示类.

当你完成之后,你可以在 gs-actuator-service/complete 根据代码检查结果。

<h2 id=”scratch”> 使用 Gradle 构建 </h2>

首先你得编写一个基础构建脚本。在使用 Spring 构建应用的时候,你可以使用任何你喜欢的构建系统,这里提供一份你可能会用到的用 GradleMaven 构建的代码。 如果你两者都不是很熟悉, 你可以先去参考如何使用 Gradle 构建 Java 项目或者如何使用 Maven 构建 Java 项目

创建目录结构

在你的项目根目录,创建如下的子目录结构;例如,在 *nix 系统上使用 mkdir -p src/main/java/hello:

└── src
    └── main
        └── java
            └── hello

创建Gradle构建文件

下面是一份 Gradle 初始构建文件

build.gradle

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.7.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'

jar {
    baseName = 'gs-accessing-data-rest'
    version = '0.1.0'
}

repositories {
    mavenCentral()
}

sourceCompatibility = 1.8
targetCompatibility = 1.8

dependencies {
    compile("org.springframework.boot:spring-boot-starter-data-rest")
    compile("org.springframework.boot:spring-boot-starter-data-jpa")
    compile("com.h2database:h2")
    testCompile("org.springframework.boot:spring-boot-starter-test")
}

Spring Boot gradle 插件 提供了很多非常方便的功能:

  • 将 classpath 里面所有用到的 jar 包构建成一个可执行的 JAR 文件,这使得执行和分发你的服务变得更加方便。
  • 搜索 public static void main() 方法并且将它标记为可执行类。
  • 提供了内置的依赖解析器,可以将 Spring Boot 依赖的版本号设置为最新。你可以用你想要的任意版本进行改写,不过这会是 Spring Boot 的默认版本设置。

使用 Maven 构建

首先你得编写一个基础构建脚本。在使用 Spring 构建应用的时候,你可以使用任何你喜欢的构建系统,这里提供一份你可能会用到的用 Maven 构建的代码。 如果你对 Maven 不熟悉, 你可以先去参考如何使用 Maven 构建 Java 项目

创建目录结构

在你的项目根目录,创建如下的子目录结构;例如,在 *nix 系统上使用 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 http://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>1.5.8.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 包构建成一个可执行的 JAR 文件,这使得执行和分发你的服务变得更加方便。
  • 搜索 public static void main() 方法并且将它标记为可执行类。
  • 提供了内置的依赖解析器,可以将 Spring Boot 依赖的版本号设置为最新。你可以用你想要的任意版本进行改写,不过这会是 Spring Boot 的默认版本设置。

使用你的 IDE 构建

运行空服务

为了照顾初学者,这里有一个空的 Spring MVC 应用。

src/main/java/hello/HelloWorldConfiguration.java

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class HelloWorldConfiguration {

    public static void main(String[] args) {
        SpringApplication.run(HelloWorldConfiguration.class, args);
    }

}

@SpringBootApplication 注解根据类路径上的内容,提供了一系列默认加载(比如嵌入式servlet容器)及其他事物。它也开启了Spring MVC 的 @EnableWebMvc注解来激活 web 端点。

本应用中不会定义任何端点,但已足够启动服务并体会 Actuator的部分特性。SpringApplication.run() 命令知道如何启动这个web 应用。你所需做的全部工作仅是运行这个命令。

$ ./gradlew clean build && java -jar build/libs/gs-actuator-service-0.1.0.jar

你还几乎没有写任何代码,所以会发生什么呢?等待server启动,并打开另一个终端试试看吧:

$ curl localhost:8080
{"timestamp":1384788106983,"error":"Not Found","status":404,"message":""}

可见 server 正在运行,但你还没有定义任何业务端点。你能看见一条来自 Actuator /error 端点的通用 JSON 响应,而不是由容器产生的默认的 HTML 报错响应。你能从 server 启动的控制台日志看见有哪些开箱即用的端点被提供了。试点别的吧,例如

$ curl localhost:8080/health
{"status":"UP"}

很好,你“上线”了。

更多细节,请参见 Spring Boot 的 Actuator 项目

<h2 id=”representtationClass”> 创建表示类 </h2>

首先,想想你的 API 将是什么样子。

你希望能处理对 /hello-world 的 GET 请求,它可以选择性地附带一个姓名查询参数。你会回送一条表示欢迎的 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;
    }

}

现在你将创建一个服务于该表示类的端点控制器。

创建资源控制器

在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.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/hello-world")
public class HelloWorldController {

    private static final String template = "Hello, %s!";
    private final AtomicLong counter = new AtomicLong();

    @RequestMapping(method=RequestMethod.GET)
    public @ResponseBody Greeting sayHello(@RequestParam(value="name", required=false, defaultValue="Stranger") String name) {
        return new Greeting(counter.incrementAndGet(), String.format(template, name));
    }

}

在面向人的控制器和 REST 端点控制器之间的关键差别在于响应是如何创建的。不同于在 HTML 中依靠视图(如JSP)渲染模型数据,一个端点控制器简单地返回直接写在响应实体中的数据。

@ResponseBody 注解告诉Spring MVC 不要在视图中渲染模型,而是把要返回的对象写入响应实体。它利用 Spring 的一种消息转换器来做到这一点。由于 Jackson 2 在类路径下,这意味着如果请求的 Accept 首部指定了应返回 JSON,那么MappingJackson2HttpMessageConverter 将会处理从 Greeting 对象到 JSON 的转换。

你是怎么知道 Jackson 2 在类路径下呢?运行 mvn dependency:tree./gradlew dependencies ,你会得到一个包含 Jackson 2.x 的详细的依赖树。如你所见它来自于 spring-boot-starter-web

创建可执行主类

你可以从一个自定义的主类启动应用,或者我们可以直接用一个配置类做到这一点。最简单的办法是试用 SpringApplication 辅助类:

src/main/java/hello/HelloWorldConfiguration.java

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class HelloWorldConfiguration {

    public static void main(String[] args) {
        SpringApplication.run(HelloWorldConfiguration.class, args);
    }

}

在一个传统的 Spring MVC 应用中,你可以通过添加 @EnableWebMvc 注解来开启包括配置 DispatcherServlet 在内的关键行为。在 Spring Boot 中,当它检测到 spring-webmvc 在类路径下时,它会自动启用该注解。这使得你可以在后续步骤中构建控制器。

@SpringBootApplication 同时引入了 @ComponentScan 注解,它告诉 Spring 扫描 hello 包来寻找控制器(以及其他被注解了的组件类)。

构建可执行 JAR 包

利用 Gradle 或 Maven,你可以从命令行启动应用。或者你可以构建一个单一的可执行 JAR 文件并运行它,该 JAR 文件中包含了所需的所有依赖,class 文件和资源文件。这使得在整个开发的生命周期中,在不同环境间以应用的形式迁移、改版和部署服务等等都变得更容易。

如果你使用的是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!"}

切换到其他 server 端口

Spring Boot Actuator 默认运行在 8080 端口上。通过添加一个application.properties 文件,你可以重写该设置。

src/main/resources/application.properties

server.port: 9000
management.port: 9001
management.address: 127.0.0.1

重启 server:

$ ./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/health
{"status":"UP"}

测试你的应用

你应当编写单元/集成测试来检查你的应用是否起作用。你可以在下面找到这样一个测试的例子,它用于检查:

  • 你的控制器是否有响应
  • 你的管理端点是否有响应

正如你所见,我们在一个随机端口上启动应用来做测试。

src/test/java/hello/HelloWorldConfigurationTests.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
 *
 *      http://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.context.embedded.LocalServerPort;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
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(classes = HelloWorldConfiguration.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(properties = {"management.port=0"})
public class HelloWorldConfigurationTests {

    @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 + "/info", Map.class);

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

}

总结

恭喜!你刚用Spring开发了一个简单的 RESTful 服务。你还为它添加了一些有用的内置服务,这多亏了Spring Boot Actutor。

参见

以下指南可能也有帮助:

本文由spring4all.com翻译小分队创作,采用知识共享-署名-非商业性使用-相同方式共享 4.0 国际 许可 协议进行许可。

评论 抢沙发

请登录后发表评论

    暂无评论内容