乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Spring Boot 概述

    • 章节导读:Spring Boot概述与核心理念
    • Spring Boot是什么
    • Spring Boot与Spring Framework的关系
    • 约定优于配置
  • 第2章 快速入门与第一个应用

    • 章节导读:快速入门与第一个应用
    • SpringApplication
    • 第一个Spring Boot应用
  • 第3章 起步依赖与版本管理

    • 章节导读:起步依赖与版本管理
    • 起步依赖
    • BOM版本管理
  • 第4章 自动配置原理

    • 章节导读:自动配置原理
    • 自动配置原理
    • AutoConfigurationImportSelector
    • 自动配置报告
  • 第5章 核心注解

    • 章节导读:核心注解
    • @SpringBootApplication
    • @EnableAutoConfiguration
    • @ConditionalOnClass
    • @ConditionalOnMissingBean
    • @ConditionalOnBean
    • @ConditionalOnProperty
  • 第6章 外部化配置与属性绑定

    • 章节导读:外部化配置与属性绑定
    • 外部化配置
    • @ConfigurationProperties
    • @Value
    • 配置属性优先级
  • 第7章 Profile 与环境切换

    • 章节导读:Profile与环境切换
    • @Profile
    • 多环境配置文件
  • 第8章 内嵌服务器与部署

    • 章节导读:内嵌服务器与部署
    • 内嵌服务器
    • Fat Jar
  • 第9章 Actuator 与监控

    • 章节导读:Actuator与监控
    • Actuator Health
    • Actuator Info
    • Actuator Metrics
    • 自定义Endpoint
    • 自定义HealthIndicator
  • 第10章 开发工具与最佳实践

    • 章节导读:开发工具与最佳实践
    • Banner自定义
    • 热部署

自定义 Endpoint

一句话定位:Spring Boot Actuator 的 @Endpoint 注解允许开发者以声明式方式创建自定义运维端点,通过 @ReadOperation、@WriteOperation 和 @Selector 定义 REST 风格的读写操作,将业务运维能力(如开关配置、数据清理)直接暴露到 Actuator 管理框架中。


定义与作用

Actuator 内置的 health、metrics、info 等端点覆盖的是通用基础设施层面。但生产环境中,运维团队经常需要执行一些业务级操作:查看当前功能开关状态、手动刷新缓存、触发对账任务、清理过期会话等。

传统做法是额外编写一个 AdminController,再单独配置访问控制。这种方式的问题是:管理接口和业务 Controller 混在一起,URL 风格不统一(有的 /admin/*,有的 /api/ops/*),安全策略需要重复配置,监控难以区分管理流量和业务流量。

Spring Boot 的自定义 Endpoint 机制允许开发者创建与 Actuator 原生端点风格完全一致的自定义接口,自动继承管理端口隔离、端点暴露控制、安全框架集成等能力。

对比维度传统手写 AdminControllerSpring Boot 自定义 @Endpoint
URL 规范各团队自行定义,风格混乱统一在 /actuator/{id} 下
端口隔离需手动实现或共用业务端口自动跟随 management.server.port
暴露控制需手写拦截器或 Security 配置通过 management.endpoints.web.exposure.include 统一控制
操作语义仅支持 HTTP 方法映射原生支持 @ReadOperation(GET)、@WriteOperation(POST)、@DeleteOperation(DELETE)
路径变量使用 Spring MVC @PathVariable使用 @Selector,语义更清晰

适用位置与常用属性

自定义端点是一个标注了 @Endpoint 的 Spring Bean,其操作通过方法级别的注解定义。

注解说明

注解适用位置作用映射 HTTP 方法
@Endpoint(id = "xxx")类声明端点,id 为 URL 路径片段—
@ReadOperation方法读取操作,无副作用GET
@WriteOperation方法写入操作,可能改变状态POST
@DeleteOperation方法删除操作DELETE
@Selector参数路径变量,从 URL 中提取如 /actuator/xxx/{key}

端点 ID 约束:只能包含字母、数字和连字符,且不能是 Actuator 保留字(如 health、metrics、info、env 等)。Spring Boot 2.7.x 对非法 ID 会在启动时校验失败。

配置属性

management:
  server:
    port: 8081
  endpoints:
    web:
      exposure:
        include: "health,info,feature-flags"
  endpoint:
    feature-flags:
      enabled: true

核心原理

自定义端点注册与请求映射流程

流程解读:

  1. 开发者编写类并标注 @Endpoint,内部方法标注 @ReadOperation 或 @WriteOperation。
  2. Spring Boot 启动时,EndpointDiscoverer 扫描所有 @Endpoint Bean,提取端点 ID 和操作元数据。
  3. WebEndpointExtension 将每个操作映射为 HTTP 路由:@ReadOperation → GET,@WriteOperation → POST,@DeleteOperation → DELETE。
  4. 请求到达时,Web 适配器将 HTTP 参数和路径变量(@Selector)绑定到方法参数,调用端点方法,并将返回值序列化为 JSON。

端点架构层次


完整示例

场景说明

飞翔科技的学生成绩管理系统需要一套**功能开关(Feature Flags)**机制,用于灰度发布。架构师白歌要求:运维人员能通过 Actuator 端点查看当前开关状态,并能远程修改某个开关的开启/关闭状态。小崔需要实现这个自定义端点,并验证其读写操作。

操作前:手写 AdminController

package com.feixiang.student.controller;

import org.springframework.web.bind.annotation.*;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@RestController
@RequestMapping("/admin/features")
public class FeatureAdminController {

    private final Map<String, Boolean> features = new ConcurrentHashMap<>();

    @GetMapping
    public Map<String, Boolean> list() {
        return features;
    }

    @PostMapping("/{key}")
    public void set(@PathVariable String key, @RequestParam boolean enabled) {
        features.put(key, enabled);
    }
}

问题:

  1. URL /admin/features 与业务 Controller 混在一起,需额外配置 Spring Security 保护。
  2. 管理流量占用业务端口 8080,缺乏隔离。
  3. 接口风格与 Actuator 不一致,监控系统需要单独适配。

使用该特性的完整代码

小崔改用 Actuator 自定义端点实现:

package com.feixiang.student.endpoint;

import org.springframework.boot.actuate.endpoint.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 功能开关自定义端点
 * 提供当前系统灰度功能开关的查询和修改能力
 * 
 * @author 小崔
 * @since 2024
 */
@Component
@Endpoint(id = "feature-flags")
public class FeatureFlagsEndpoint {

    private final Map<String, Boolean> features = new ConcurrentHashMap<>();

    public FeatureFlagsEndpoint() {
        // 初始化默认开关状态
        features.put("new-score-ui", false);
        features.put("batch-import", true);
        features.put("sms-notification", false);
    }

    /**
     * 读取操作:获取所有功能开关当前状态
     * HTTP: GET /actuator/feature-flags
     */
    @ReadOperation
    public Map<String, Boolean> featureFlags() {
        return Map.copyOf(features);
    }

    /**
     * 读取操作:获取单个功能开关状态
     * HTTP: GET /actuator/feature-flags/{featureName}
     */
    @ReadOperation
    public Boolean featureFlag(@Selector String featureName) {
        return features.get(featureName);
    }

    /**
     * 写入操作:修改功能开关状态
     * HTTP: POST /actuator/feature-flags/{featureName}
     * 请求体:{ "enabled": true }
     */
    @WriteOperation
    public void setFeatureFlag(@Selector String featureName, boolean enabled) {
        features.put(featureName, enabled);
    }
}

application.yml 配置:

spring:
  application:
    name: student-app

server:
  port: 8080

management:
  server:
    port: 8081
  endpoints:
    web:
      exposure:
        include: "health,info,feature-flags"
  endpoint:
    feature-flags:
      enabled: true

注意:自定义端点类无需继承任何父类或实现任何接口,仅通过 @Endpoint 和操作方法注解即可完整定义。

操作后运行结果及分析

查询所有开关:

$ curl http://localhost:8081/actuator/feature-flags

返回结果:

{
    "new-score-ui": false,
    "batch-import": true,
    "sms-notification": false
}

查询单个开关:

$ curl http://localhost:8081/actuator/feature-flags/batch-import

返回结果:

true

修改开关状态:

$ curl -X POST http://localhost:8081/actuator/feature-flags/new-score-ui \
  -H "Content-Type: application/json" \
  -d '{"enabled": true}'

再次查询:

$ curl http://localhost:8081/actuator/feature-flags/new-score-ui

返回结果:

true

分析:

  1. @Endpoint(id = "feature-flags") 生效:端点自动挂载到 /actuator/feature-flags,与原生端点风格统一。
  2. @ReadOperation 生效:支持无参查询全部,也支持 @Selector 查询单个,返回类型自动序列化为 JSON。
  3. @WriteOperation 生效:HTTP POST 请求自动映射到写入方法,请求体中的 JSON 字段按名称绑定到方法参数。
  4. 管理端口隔离生效:所有操作都在 8081 端口完成,与业务端口 8080 完全分离。
  5. 暴露控制生效:仅因 include 列表中显式包含 feature-flags,该端点才对外可见。生产环境可通过调整 include 快速关闭。

易错场景与面试考点

易错场景一:端点 ID 与保留字冲突

// 错误示范:使用了 Actuator 保留字
@Component
@Endpoint(id = "health")
public class CustomHealthEndpoint {
    ...
}

后果:应用启动时抛出 IllegalArgumentException: Endpoint ID 'health' is already in use or is reserved。端点注册失败,应用启动中断。

正确做法:自定义端点 ID 避免使用 health、metrics、info、env、beans、conditions、configprops、mappings、loggers、threaddump、heapdump、shutdown、scheduledtasks 等保留字。建议采用带业务前缀的命名,如 feature-flags、cache-stats、job-status。

易错场景二:端点 ID 包含非法字符

// 错误示范:端点 ID 包含下划线
@Component
@Endpoint(id = "feature_flags")
public class FeatureFlagsEndpoint {
    ...
}

后果:Spring Boot 2.7.x 对端点 ID 的合法性校验更严格,包含下划线会触发警告或启动失败(取决于配置)。

正确做法:端点 ID 只能使用小写字母、数字和连字符。正确写法为 feature-flags。

易错场景三:未暴露端点导致 404

# 错误示范:include 列表中遗漏了自定义端点
management:
  endpoints:
    web:
      exposure:
        include: "health,info"

后果:即使端点 Bean 正确定义,访问 /actuator/feature-flags 仍返回 404。小崔误以为端点代码有问题。

正确做法:显式将自定义端点 ID 加入暴露列表:

management:
  endpoints:
    web:
      exposure:
        include: "health,info,feature-flags"

面试考点

Q:@Endpoint 和 @Controller 有什么区别?

@Endpoint 是 Actuator 框架的专用注解,创建的端点自动挂载到 /actuator/{id} 路径下,继承管理端口隔离、暴露控制、安全策略等原生能力。@Controller 是 Spring MVC 的 Web 层注解,需要手动配置 URL 前缀、端口隔离和安全策略,且风格与 Actuator 不一致。

Q:@ReadOperation 和 @WriteOperation 分别对应什么 HTTP 方法?

@ReadOperation 映射 HTTP GET,用于无副作用的查询;@WriteOperation 映射 HTTP POST,用于改变状态或触发操作;@DeleteOperation 映射 HTTP DELETE。这种语义映射是固定的,不能通过额外配置修改。

Q:@Selector 和 Spring MVC 的 @PathVariable 有什么区别?

两者在用法上类似,都是提取 URL 路径变量。但 @Selector 是 Actuator 端点框架的专属注解,语义上表示"从端点的资源集合中选择一项",与 @Endpoint 配套使用。在自定义端点中必须使用 @Selector,不能使用 @PathVariable。

Q:自定义端点的方法返回值可以是什么类型?

可以是任意 POJO、Map、String、Boolean 等。Actuator 会自动将其序列化为 JSON(或 XML,取决于请求 Accept 头)。返回 void 时,HTTP 响应体为空。若返回 null,@ReadOperation 会返回 404 Not Found(表示资源不存在),这是 Actuator 的默认语义约定。

上一页
Actuator Metrics
下一页
自定义HealthIndicator