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。

程序清单 16.7 记录便笺的自定义端点
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;
  }
}

该端点是一个简单的便笺端点。在该端点中,用户可以使用写入操作增加一个便笺,使用读取操作读取便笺列表,然后使用删除操作删除便笺。诚然,这个端点对于 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 请求:

$ 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"}]

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

$ 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 请求:

$ 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 注解端点类:

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

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

最后更新于