# 16.3.4 创建自定义端点

乍一看，您可能认为 Actuator 的端点实现，和 Spring MVC 的 Controller 一样。正如您将在第 18 章中看到的，端点可以作为 JMX MBean，还可以通过 HTTP 访问。因此,对于这些端点来说，并不仅仅是 Controller 类。

事实上，Actuator 端点的定义与 Controller 完全不同。不同于用 @Controller 或 @RestController 注解的类，Actuator 端点使用带有 @Endpoint 注解的类定义。

更重要的是，不再使用 HTTP 命名注解，诸如 @GetMapping、@PostMapping 或 @DeleteMapping。Actuator 端点用 @ReadOperation、@WriteOperation 和 @DeleteOperation 注解。这些注解并不意味着任何特定的通信机制，事实上，允许 Actuator 通过各种通信机制进行通信，HTTP 和 JMX 开箱即用。

为了演示如何编写自定义 Actuator 端点，请参考如下的 NotesEndpoint。

{% code title="程序清单 16.7 记录便笺的自定义端点" %}

```java
package tacos.ingredients;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.stereotype.Component;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Component
@Endpoint(id="notes", enableByDefault=true)
public class NotesEndpoint {

  private List<Note> notes = new ArrayList<>();

  @ReadOperation
    public List<Note> notes() {
  return notes;
  }

  @WriteOperation
  public List<Note> addNote(String text) {
    notes.add(new Note(text));
    return notes;
  }

  @DeleteOperation
  public List<Note> deleteNote(int index) {
    if (index < notes.size()) {
      notes.remove(index);
    }
  return notes;
  }

  @RequiredArgsConstructor
  private class Note {
    @Getter
    private Date time = new Date();
    @Getter
    private final String text;
  }
}
```

{% endcode %}

该端点是一个简单的便笺端点。在该端点中，用户可以使用写入操作增加一个便笺，使用读取操作读取便笺列表，然后使用删除操作删除便笺。诚然，这个端点对于 Actuator 来说不是很有用，但是当您考虑开箱即用的 Actuator 端点会涉及太多领域时，很难想出一个实用的自定义示例。

可以看到，NotesEndpoint 类合使用 @Component 注解，以便通过 Spring 的组件扫描获取，并在 Spring 中实例化为 bean。它还添加了注解 @Endpoint，使其成为 ID 为 notes 的 Actuator 端点。并且它是默认启用的，这样您就不需要通过将其包含在 `management.web.endpoints.web.exposure.include` 中来显式启用它。

如您所见，NotesEndpoint 提供了如下操作：

* `notes()` 方法用 @ReadOperation 注解。调用时，它将返回可用的便笺列表。使用 HTTP 时，这意味着它将处理对 `/exactor/notes` 的 HTTP GET 请求，并响应 JSON 形式的便笺列表。
* `addNote()` 方法用 @WriteOperation 注解。当被调用时，它将根据给定文本创建新便笺，并将其添加到列表中。使用 HTTP 时，

  它处理 POST 请求，其中请求体是包含文本属性的 JSON 对象。处理完成后返回列表的当前状态。
* `deleteNote()` 方法用 @DeleteOperation 注解。调用时，它将删除给定索引的便笺。使用 HTTP 时，该端点处理 DELETE 请求，在请求参数中给出便笺索引。

要查看实际运行效果，可以使用 curl 测试新端点。首先，添加几个便笺，使用两个单独的 POST 请求：

```bash
$ curl localhost:8080/actuator/notes \
          -d'{"text":"Bring home milk"}' \
          -H"Content-type: application/json"
[{"time":"2018-06-08T13:50:45.085+0000","text":"Bring home milk"}]

$ curl localhost:8080/actuator/notes \
          -d'{"text":"Take dry cleaning"}' \
          -H"Content-type: application/json"
[{"time":"2018-06-08T13:50:45.085+0000","text":"Bring home milk"},
{"time":"2018-06-08T13:50:48.021+0000","text":"Take dry cleaning"}]
```

如您所见，每次发布新便笺时，端点都会返回最新便笺列表。如果以后要查看便笺列表，可以执行以下操作：

```bash
$ curl localhost:8080/actuator/notes
[{"time":"2018-06-08T13:50:45.085+0000","text":"Bring home milk"},
{"time":"2018-06-08T13:50:48.021+0000","text":"Take dry cleaning"}]
`
```

如果您决定删除其中一个便笺，则发送带有索引参数的 DELETE 请求：

```bash
$ curl localhost:8080/actuator/notes?index=1 -X DELETE
[{"time":"2018-06-08T13:50:45.085+0000","text":"Bring home milk"}]
```

需要注意的是，尽管这里只演示了使用 HTTP 如何与端点交互，但它还作为 MBean 公开了，可以使用任何您熟悉的 JMX 客户端进行访问。如果您想将其限制为仅公开 HTTP 端点，您可以使用 @WebEndpoint 而不是 @endpoint 注解端点类：

```java
@Component
@WebEndpoint(id="notes", enableByDefault=true)
public class NotesEndpoint {
  ...
}
```

同样，如果您喜欢只使用 MBean 的端点，请使用 @JmxEndpoint 注解。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://potoyang.gitbook.io/spring-in-action-v5/di-16-zhang-shi-yong-springboot-actuator/16.3-zi-ding-yi-actuator/16.3.4-chuang-jian-zi-ding-yi-duan-dian.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
