15.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。
程序清单 15.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":"2020-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":"2021-07-03T12:39:13.058+0000","text":"Bring home milk"},
{"time":"2021-07-03T12:39:16.012+0000","text":"Take dry cleaning"}]
如您所见,每次发布新便笺时,端点都会返回最新便笺列表。如果以后要查看便笺列表,可以执行以下操作:
$ curl localhost:8080/actuator/notes
[{"time":"2021-07-03T12:39:13.058+0000","text":"Bring home milk"},
{"time":"2021-07-03T12:39:16.012+0000","text":"Take dry cleaning"}]
`
如果您决定删除其中一个便笺,则发送带有索引参数的 DELETE 请求:
$ curl localhost:8080/actuator/notes?index=1 -X DELETE
[{"time":"2021-07-03T12:39:13.058+0000","text":"Bring home milk"}]
需要注意的是,尽管这里只演示了使用 HTTP 如何与端点交互,但它还作为 MBean 公开了,可以使用任何您熟悉的 JMX 客户端进行访问。如果您想将其限制为仅公开 HTTP 端点,您可以使用 @WebEndpoint 而不是 @endpoint 注解端点类:
@Component
@WebEndpoint(id="notes", enableByDefault=true)
public class NotesEndpoint {
...
}
同样,如果您喜欢只使用 MBean 的端点,请使用 @JmxEndpoint 注解。