> For the complete documentation index, see [llms.txt](https://potoyang.gitbook.io/spring-in-action-v5/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](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).

# 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 注解。
