Actuator 端点注册
概述
Spring Boot Actuator 通过 @Endpoint 注解机制定义了操作端点,并通过 EndpointDiscoverer 扫描发现,最终通过 WebEndpointServletHandlerMapping 映射为 HTTP 端点暴露给外部访问。
本文深入拆解 Actuator 端点从注册、发现、适配到暴露的完整链路,涵盖 12 个细节点。
本文基于 Spring Boot 3.x Actuator 源码分析。
1. @Endpoint(id = "health") 注解扫描
1.1 关键类和整体流程
端点发现的整体流程:
EndpointAutoConfiguration
│
└─ EndpointDiscoverer.discoverEndpoints()
│
├─ 1. 从 BeanFactory 获取所有 @Endpoint Bean
│ └─ beanFactory.getBeansWithAnnotation(Endpoint.class)
│
├─ 2. 过滤出有效的端点
│ ├─ 检查 @Endpoint.id 非空
│ └─ 检查 enableByDefault(默认启用)
│
├─ 3. 解析操作
│ ├─ @ReadOperation → GET
│ ├─ @WriteOperation → POST
│ └─ @DeleteOperation → DELETE
│
└─ 4. 返回 ExposableEndpoint 列表1.2 @Endpoint 注解
java
// @Endpoint.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Endpoint {
/**
* 端点唯一标识。
* 例如: "health"、"metrics"、"loggers"
* 对应 URL: /actuator/health、/actuator/metrics、/actuator/loggers
*/
String id();
/**
* 是否默认启用。
* 如果为 false,需要显式通过配置启用
* management.endpoint.<id>.enabled=true
*/
boolean enableByDefault() default true;
}1.3 EndpointDiscoverer.discoverEndpoints() 源码
java
// EndpointDiscoverer.java
public class EndpointDiscoverer<C extends OperationInvoker> {
private final ApplicationContext applicationContext;
// 从 ApplicationContext 发现所有端点
public Collection<ExposableEndpoint<C>> discoverEndpoints() {
// 1. 获取所有标注 @Endpoint 的 Bean
Map<String, Object> endpointBeans = this.applicationContext
.getBeansWithAnnotation(Endpoint.class);
// 2. 转换为 ExposableEndpoint
List<ExposableEndpoint<C>> endpoints = new ArrayList<>();
for (Map.Entry<String, Object> entry : endpointBeans.entrySet()) {
Object bean = entry.getValue();
// 获取 @Endpoint 注解
Endpoint endpoint = bean.getClass()
.getAnnotation(Endpoint.class);
// 3. 过滤:检查端点是否启用
if (!isExposed(endpoint)) {
continue; // 未启用 → 跳过
}
// 4. 解析操作
List<Operation> operations = discoverOperations(bean);
// 5. 创建底层端点
ExposableEndpoint<C> exposableEndpoint =
createEndpoint(bean, endpoint, operations);
endpoints.add(exposableEndpoint);
}
return endpoints;
}
// 过滤未启用的端点
private boolean isExposed(Endpoint endpoint) {
// 从 Environment 读取 management.endpoint.<id>.enabled
String property = "management.endpoint."
+ endpoint.id() + ".enabled";
// 如果配置了 → 按配置为准
if (this.environment.containsProperty(property)) {
return this.environment.getProperty(
property, boolean.class, true);
}
// 未配置 → 以 enableByDefault 为准
return endpoint.enableByDefault();
}
}1.4 内置端点扫描结果
java
// Spring Boot Actuator 自动配置中注册的内置 @Endpoint Bean
// HealthEndpoint
@Endpoint(id = "health")
public class HealthEndpoint {
// ...
}
// MetricsEndpoint
@Endpoint(id = "metrics")
public class MetricsEndpoint {
// ...
}
// LoggersEndpoint
@Endpoint(id = "loggers")
public class LoggersEndpoint {
// ...
}
// BeansEndpoint
@Endpoint(id = "beans")
public class BeansEndpoint {
// ...
}
// EnvironmentEndpoint
@Endpoint(id = "env")
public class EnvironmentEndpoint {
// ...
}
// InfoEndpoint
@Endpoint(id = "info")
public class InfoEndpoint {
// ...
}
// 更多内置端点: conditions, configprops, scheduledtasks, mappings, shutdown, threaddump, heapdump, auditevents, caches, httptrace, integrationgraph, flyway, liquibase, quartz...2. @ReadOperation / @WriteOperation / @DeleteOperation 的 HTTP 映射
2.1 注解定义
java
// @ReadOperation.java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOperation {
String[] produces() default {};
}
// @WriteOperation.java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface WriteOperation {
String[] produces() default {};
}
// @DeleteOperation.java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DeleteOperation {
String[] produces() default {};
}2.2 OperationMethodResolver 的 HTTP 映射判断
java
// OperationMethodResolver.java (简化)
public class OperationMethodResolver {
// 根据方法上的注解判断 HTTP 方法
public static String getHttpMethod(Method method) {
if (method.isAnnotationPresent(ReadOperation.class)) {
return "GET";
}
if (method.isAnnotationPresent(WriteOperation.class)) {
return "POST";
}
if (method.isAnnotationPresent(DeleteOperation.class)) {
return "DELETE";
}
throw new IllegalStateException(
"No operation annotation found on " + method);
}
}2.3 具体端点的操作映射
java
// HealthEndpoint —— GET
@Endpoint(id = "health")
public class HealthEndpoint {
@ReadOperation // → GET /actuator/health
public HealthComponent health() {
// 返回健康检查结果
}
}
// LoggersEndpoint —— GET + POST
@Endpoint(id = "loggers")
public class LoggersEndpoint {
@ReadOperation // → GET /actuator/loggers
public Map<String, Object> loggers() {
// 返回所有日志级别
}
@ReadOperation // → GET /actuator/loggers/{name}
public LoggerLevels loggerLevel(@Selector String name) {
// 返回指定 Logger 的级别
}
@WriteOperation // → POST /actuator/loggers/{name} (Body: {"configuredLevel": "DEBUG"})
public void configureLogLevel(@Selector String name,
@Nullable LogLevel configuredLevel) {
// 修改日志级别
}
}
// BeansEndpoint —— GET
@Endpoint(id = "beans")
public class BeansEndpoint {
@ReadOperation // → GET /actuator/beans
public ApplicationBeans beans() {
// 返回所有 Bean
}
}
// EnvironmentEndpoint —— GET + DELETE
@Endpoint(id = "env")
public class EnvironmentEndpoint {
@ReadOperation // → GET /actuator/env
public EnvironmentEntryDescriptor environment(@Selector String property) {
// 返回环境属性
}
@DeleteOperation // → DELETE /actuator/env/{property}
public void resetPropertyValue(@Selector String property) {
// 重置环境属性(只对动态属性如 Spring Cloud Config 有效)
}
}
// InfoEndpoint —— GET
@Endpoint(id = "info")
public class InfoEndpoint {
@ReadOperation // → GET /actuator/info
public Map<String, Object> info() {
// 返回应用信息
}
}2.4 注解映射汇总
| 操作注解 | HTTP 方法 | 语义 | 示例端点 |
|---|---|---|---|
@ReadOperation | GET | 读取数据 | health、metrics、beans、env、info |
@WriteOperation | POST | 写入/修改数据 | loggers(更改级别)、shutdown(关闭) |
@DeleteOperation | DELETE | 删除/重置数据 | env(重命名属性) |
3. HealthIndicator.getHealth() 聚合
3.1 健康检查的聚合流程
/actuator/health 请求到达
│
├─ 1. HealthEndpoint.health()
│ └─ health(HealthEndpointHealthInvoker)
│
├─ 2. HealthEndpointHealthInvoker.invoke()
│ └─ HealthContributorRegistry.getAllContributors()
│
├─ 3. 遍历所有 HealthIndicator 和 HealthContributor
│ ├─ DataSourceHealthIndicator → UP
│ ├─ RedisHealthIndicator → UP
│ ├─ DiskSpaceHealthIndicator → UP
│ └─ ElasticsearchHealthIndicator → UP
│
├─ 4. HealthAggregator.aggregate()
│ └─ StatusAggregator.getAggregateStatus()
│ ├─ DOWN > OUT_OF_SERVICE > UP > UNKNOWN
│ └─ 最终状态: UP
│
└─ 5. 返回 HealthComponent (含组件详情)
{ "status": "UP", "components": { "db": {...}, "redis": {...} } }3.2 HealthContributorRegistry 的贡献者收集
java
// HealthEndpoint.java
@Endpoint(id = "health")
public class HealthEndpoint {
private final HealthContributorRegistry registry;
@ReadOperation
public HealthComponent health() {
// 获取所有贡献者的健康状态
return health(HealthEndpointHealthInvoker.DEFAULT);
}
public HealthComponent health(HealthEndpointHealthInvoker invoker) {
if (invoker == HealthEndpointHealthInvoker.DEFAULT) {
// 收集所有 HealthContributor
Map<String, HealthContributor> contributors =
this.registry.getAllContributors();
// 逐一获取健康信息
Map<String, HealthComponent> components = new LinkedHashMap<>();
for (Map.Entry<String, HealthContributor> entry :
contributors.entrySet()) {
HealthComponent component =
((HealthIndicator) entry.getValue()).getHealth(true);
components.put(entry.getKey(), component);
}
// 聚合状态
Status aggregateStatus =
this.statusAggregator.getAggregateStatus(
components.values().stream()
.map(HealthComponent::getStatus)
.collect(Collectors.toList()));
return new HealthComponent(aggregateStatus, components);
}
return invoker.invoke();
}
}3.3 HealthIndicator.getHealth() 的调用
java
// 一个典型的 HealthIndicator 实现
public class RedisHealthIndicator extends AbstractHealthIndicator {
private final RedisConnectionFactory connectionFactory;
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
// 1. 获取 Redis 连接
RedisConnection connection =
RedisConnectionUtils.getConnection(connectionFactory);
try {
// 2. 执行 ping 命令
String response = connection.ping();
// 3. 根据结果设置状态
if ("PONG".equals(response)) {
builder.up(); // → 状态 UP
builder.withDetail("version", connection.info("server"));
} else {
builder.down(); // → 状态 DOWN
builder.withDetail("response", response);
}
} catch (Exception ex) {
builder.down(ex); // → 状态 DOWN + 异常信息
} finally {
RedisConnectionUtils.releaseConnection(connection, connectionFactory);
}
}
}
// 内置的 HealthIndicator (按 classpath 自动注册)
// DataSourceHealthIndicator → 检查数据库连接(需要 spring-jdbc)
// RedisHealthIndicator → 检查 Redis(需要 spring-data-redis)
// DiskSpaceHealthIndicator → 检查磁盘空间(默认)
// ElasticsearchHealthIndicator → 检查 ES(需要 spring-data-elasticsearch)
// MongoHealthIndicator → 检查 MongoDB(需要 spring-data-mongodb)
// CassandraHealthIndicator → 检查 Cassandra
// RabbitHealthIndicator → 检查 RabbitMQ
// KafkaHealthIndicator → 检查 Kafka
// LdapHealthIndicator → 检查 LDAP
// MailHealthIndicator → 检查邮件服务器
// JmsHealthIndicator → 检查 JMS4. StatusAggregator 默认实现 SimpleStatusAggregator
4.1 源码
java
// SimpleStatusAggregator.java
public class SimpleStatusAggregator implements StatusAggregator {
// 状态优先级顺序(从高到低)
// 优先级最高的状态将作为聚合结果
private static final Map<String, Integer> STATUS_ORDER;
static {
// 从 spring.factories 加载状态优先级
// 默认顺序:
// DOWN = 0
// OUT_OF_SERVICE = 1
// UP = 2
// UNKNOWN = 3
STATUS_ORDER = new LinkedHashMap<>();
STATUS_ORDER.put(Status.DOWN.getCode(), 0);
STATUS_ORDER.put(
Status.OUT_OF_SERVICE.getCode(), 1);
STATUS_ORDER.put(Status.UP.getCode(), 2);
STATUS_ORDER.put(Status.UNKNOWN.getCode(), 3);
}
@Override
public Status getAggregateStatus(List<Status> statuses) {
// 遍历状态列表,返回优先级最高的
Status result = Status.UNKNOWN;
for (Status status : statuses) {
// 比较优先级
if (getStatusOrder(status) < getStatusOrder(result)) {
// 如果当前状态优先级更高 → 替换
result = status;
}
}
return result;
}
private int getStatusOrder(Status status) {
// 从 STATUS_ORDER 中获取优先级
return STATUS_ORDER.getOrDefault(
status.getCode(), STATUS_ORDER.size());
}
}4.2 状态聚合示例
java
// 场景: 应用依赖数据库正常,Redis 异常
// 各个 HealthIndicator 返回:
// DataSourceHealthIndicator → UP (order=2)
// RedisHealthIndicator → DOWN (order=0)
// DiskSpaceHealthIndicator → UP (order=2)
// SimpleStatusAggregator 聚合:
// 遍历所有状态: [UP, DOWN, UP]
// 取优先级最高的: DOWN (order=0,最低数字)
// → 最终状态: DOWN
// → HTTP 503 Service Unavailable4.3 自定义状态优先级
java
// 自定义 StatusAggregator
@Component
public class CustomStatusAggregator implements StatusAggregator {
@Override
public Status getAggregateStatus(List<Status> statuses) {
// 自定义策略: 只要有一个 WARNING 就返回 WARNING
// 全部 UP 才返回 UP
boolean hasWarning = false;
for (Status status : statuses) {
if (Status.DOWN.getCode().equals(status.getCode())) {
return Status.DOWN;
}
if ("WARNING".equals(status.getCode())) {
hasWarning = true;
}
}
if (hasWarning) {
return new Status("WARNING");
}
return Status.UP;
}
}
// 或者通过配置修改状态顺序
management:
endpoint:
health:
status:
order: DOWN, OUT_OF_SERVICE, UP, UNKNOWN, WARNING5. HealthEndpointWebExtension 的 Web 适配
5.1 架构
HealthEndpoint 端点
│
├─ HealthEndpoint (核心端点)
│ └─ @ReadOperation → health()
│ └─ 返回 HealthComponent
│
├─ HealthEndpointWebExtension (Web 适配器)
│ └─@ReadOperation → health(ServerWebExchange)
│ └─ 返回 HealthComponent HTTP 响应
│ └─ 控制详情显示(show-details, show-components)
│ └─ 设置响应状态码(UP→200, DOWN→503)
│
└─ HealthEndpointReactiveExtension (Reactive Web 适配器,可选)
└─ 同上,适用于 WebFlux5.2 源码
java
// HealthEndpointWebExtension.java
@EndpointExtension(endpoint = HealthEndpoint.class, extension = "web")
public class HealthEndpointWebExtension
implements HealthEndpointHealthInvoker {
private final HealthEndpoint delegate;
private final HealthEndpointProperties properties;
@ReadOperation // → GET /actuator/health
public HealthComponent health(
@Nullable ServerWebExchange exchange) {
// 1. 委托给核心 HealthEndpoint
HealthComponent health = delegate.health();
// 2. 根据 show-details / show-components 控制详情
boolean showDetails = isShowDetails(exchange);
boolean showComponents = isShowComponents(exchange);
if (!showDetails && !showComponents) {
// 只返回状态
return new HealthComponent(health.getStatus());
}
// 3. 根据 show-details 决定是否显示详情
return createHealthResponse(health, showDetails);
}
// 控制响应状态码
@Override
public ResponseEntity<HealthComponent> invoke() {
HealthComponent health = delegate.health();
// 根据状态码决定 HTTP 响应码
HttpStatus httpStatus;
if (health.getStatus() == Status.UP) {
httpStatus = HttpStatus.OK; // 200
} else if (health.getStatus() == Status.DOWN) {
httpStatus = HttpStatus.SERVICE_UNAVAILABLE; // 503
} else if (health.getStatus() == Status.OUT_OF_SERVICE) {
httpStatus = HttpStatus.SERVICE_UNAVAILABLE; // 503
} else {
httpStatus = HttpStatus.OK; // 200
}
return new ResponseEntity<>(health, httpStatus);
}
}5.3 配置控制
yaml
management:
endpoint:
health:
show-details: always # always / when-authorized / never
show-components: always # always / when-authorized / never
roles: ADMIN # show-details/when-authorized 时的角色
status:
http-mapping:
DOWN: 503 # 自定义状态码映射
OUT_OF_SERVICE: 503
UP: 200
UNKNOWN: 2006. MetricsEndpoint 的 MeterRegistry 遍历
6.1 源码
java
// MetricsEndpoint.java
@Endpoint(id = "metrics")
public class MetricsEndpoint {
private final MeterRegistry meterRegistry;
@ReadOperation // → GET /actuator/metrics
public Map<String, Object> metrics() {
// 返回所有可用的 Meter 名称列表
List<String> names = new ArrayList<>();
for (Meter meter : this.meterRegistry.getMeters()) {
String name = meter.getId().getName();
if (!names.contains(name)) {
names.add(name);
}
}
return Map.of("names", names);
}
@ReadOperation // → GET /actuator/metrics/{requiredMetricName}
@Selector // requiredMetricName 从 URL 路径获取
public MetricResponse metric(
@Selector String requiredMetricName,
@Nullable String tag) {
// 1. 查找指定名称的 Meter
// tag 格式: "key:value" 例如 "exception:None"
Meter meter = this.meterRegistry.find(requiredMetricName)
.tags(parseTags(tag))
.meter();
if (meter == null) {
return null; // 404
}
// 2. 收集所有 Measurements
List<MeasurementResponse> measurements = new ArrayList<>();
for (Measurement measurement : meter.measure()) {
measurements.add(new MeasurementResponse(
measurement.getStatistic().getTagValueRepresentation(),
measurement.getValue()));
}
// 3. 收集可用 Tags
Map<String, List<String>> availableTags = new LinkedHashMap<>();
for (Tag t : meter.getId().getTags()) {
availableTags.computeIfAbsent(
t.getKey(), k -> new ArrayList<>()).add(t.getValue());
}
return new MetricResponse(requiredMetricName,
measurements, availableTags);
}
}6.2 使用示例
bash
# 获取所有 Metrics 名称
GET /actuator/metrics
# → { "names": ["jvm.memory.used", "jvm.memory.max", "jvm.gc.pause", "http.server.requests", ...] }
# 获取指定 Metric
GET /actuator/metrics/jvm.memory.used
# → {
# "name": "jvm.memory.used",
# "measurements": [{"statistic": "VALUE", "value": 104857600}],
# "availableTags": [
# {"tag": "area", "values": ["heap", "nonheap"]},
# {"tag": "id", "values": ["G1 Eden Space", "G1 Old Gen", ...]}
# ]
# }
# 过滤 tag
GET /actuator/metrics/jvm.memory.used?tag=area:heap
# → 只返回堆内存使用情况6.3 三种 MeterRegistry.find() 的过滤
java
// FilterMeterRegistry (MeterRegistry.find() 返回的代理类)
meterRegistry.find("http.server.requests")
.tag("uri", "/api/order") // 精确匹配 tag
.tag("status", "200")
.tags(Map.of("method", "GET")) // 批量设置 tag
.meter(); // 获取唯一匹配的 Meter
// 匹配规则:
// 1. name 精确匹配
// 2. tags 全部匹配 - 必须完全匹配
// 3. 如果有多个匹配 → 返回第一个
// 4. 如果没有匹配 → 返回 null7. LoggersEndpoint 的运行时日志级别修改
7.1 源码
java
// LoggersEndpoint.java
@Endpoint(id = "loggers")
public class LoggersEndpoint {
private final LoggingSystem loggingSystem;
@WriteOperation // → POST /actuator/loggers/{name}
public void configureLogLevel(
@Selector String name,
@Nullable LogLevel configuredLevel) {
// 获取日志系统(自动检测实现)
// LoggingSystem 会根据 classpath 选择实现:
// LogbackLoggingSystem (logback-classic)
// Log4J2LoggingSystem (log4j2)
// JavaLoggingSystem (JUL)
// Slf4JLoggingSystem (SLF4J + 其他实现)
// 设置日志级别
if (configuredLevel != null) {
this.loggingSystem.setLogLevel(
name, // "com.example.service" ← Logger 名称
configuredLevel // LogLevel.DEBUG ← 要设置的级别
);
} else {
// 如果 configuredLevel 为 null → 重置为默认级别
this.loggingSystem.setLogLevel(name, null);
}
}
}7.2 请求示例
bash
# 查看所有 Logger 的当前级别
GET /actuator/loggers
# → {
# "levels": ["OFF", "FATAL", "ERROR", "WARN", "INFO", "DEBUG", "TRACE"],
# "loggers": {
# "ROOT": {"configuredLevel": "INFO", "effectiveLevel": "INFO"},
# "com.example": {"configuredLevel": null, "effectiveLevel": "INFO"},
# "com.example.service.OrderService": {"configuredLevel": "DEBUG", "effectiveLevel": "DEBUG"},
# "org.springframework": {"configuredLevel": null, "effectiveLevel": "INFO"}
# }
# }
# 实时修改指定 Logger 的级别
POST /actuator/loggers/com.example.service.OrderService
Content-Type: application/json
{
"configuredLevel": "DEBUG" // ← 实时生效,无需重启
}
# 重置指定 Logger 的级别(使用继承的级别)
POST /actuator/loggers/com.example.service.OrderService
Content-Type: application/json
{
"configuredLevel": null // ← 将恢复为 ROOT 级别
}7.3 LoggingSystem.setLogLevel() 的实现差异
java
// LogbackLoggingSystem.java
@Override
public void setLogLevel(String loggerName, LogLevel level) {
// 1. 获取 LoggerContext
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
// 2. 获取或创建 Logger
Logger logger = context.getLogger(loggerName);
// 3. 设置级别
if (level != null) {
// 将 Spring Boot 的 LogLevel 转换为 Logback 的 Level
logger.setLevel(LogbackLogLevels.toLevel(level));
} else {
// 重置为 null(继承父 Logger 的级别)
logger.setLevel(null);
}
}
// Log4J2LoggingSystem.java
@Override
public void setLogLevel(String loggerName, LogLevel level) {
// 1. 获取 LoggerContext
LoggerContext context = (LoggerContext) org.apache.logging.log4j.LogManager.getContext(false);
// 2. 获取配置
Configuration config = context.getConfiguration();
// 3. 创建 LoggerConfig
LoggerConfig loggerConfig = new LoggerConfig(loggerName,
Log4J2LogLevels.toLevel(level), true);
// 4. 添加并更新
config.addLogger(loggerName, loggerConfig);
context.updateLoggers();
}8. BeansEndpoint 的 Bean 列表
8.1 源码
java
// BeansEndpoint.java
@Endpoint(id = "beans")
public class BeansEndpoint {
private final ConfigurableApplicationContext applicationContext;
@ReadOperation // → GET /actuator/beans
public ApplicationBeans beans() {
// 1. 获取 ApplicationContext 层次中的所有 Context
Map<String, ContextBeans> contexts = new LinkedHashMap<>();
// 2. 遍历所有 Context(包括父 Context)
for (ConfigurableApplicationContext context :
getAllContexts(this.applicationContext)) {
// 3. 获取该 Context 的所有 Bean
String[] beanNames = context.getBeanDefinitionNames();
Map<String, BeanDescriptor> beans = new LinkedHashMap<>();
for (String beanName : beanNames) {
BeanDefinition bd = context.getBeanFactory()
.getBeanDefinition(beanName);
// 4. 收集 Bean 元数据
beans.put(beanName, new BeanDescriptor(
bd.getClassName(), // Bean 类名
bd.getScope(), // 作用域 (singleton/prototype)
bd.getResourceDescription(), // 资源位置
bd.getRole(), // 角色 (APPLICATION/INFRASTRUCTURE/SUPPORT)
context.getType(beanName), // 实际类型
// Bean 上标注的所有注解
new ArrayList<>(getAnnotationNames(beanName, context.getBeanFactory()))
));
}
// 5. 按 Context ID 组织
contexts.put(context.getId(),
new ContextBeans(context.getDisplayName(), beans));
}
return new ApplicationBeans(contexts);
}
}8.2 响应示例
json
// GET /actuator/beans
// → {
// "contexts": {
// "application": {
// "beans": {
// "orderService": {
// "aliases": [],
// "scope": "singleton",
// "type": "com.example.service.OrderService",
// "resource": "class path resource [com/example/service/OrderService.class]",
// "dependencies": ["orderRepository", "paymentService"],
// "annotations": [
// "org.springframework.stereotype.Service",
// "org.springframework.transaction.annotation.Transactional"
// ]
// },
// "dataSource": {
// "aliases": [],
// "scope": "singleton",
// "type": "com.zaxxer.hikari.HikariDataSource",
// "resource": "class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]",
// "dependencies": ["spring.datasource-org.springframework.boot.autoconfigure.jdbc.DataSourceProperties"],
// "annotations": []
// }
// }
// },
// "parent": {
// "beans": { ... }
// }
// }
// }9. EnvironmentEndpoint 的属性暴露控制
9.1 源码
java
// EnvironmentEndpoint.java
@Endpoint(id = "env")
public class EnvironmentEndpoint {
private final Environment environment;
private final Sanitizer sanitizer;
@ReadOperation // → GET /actuator/env
public EnvironmentDescriptor environment(
@Nullable String pattern) {
// 1. 收集所有 PropertySource
List<PropertySourceDescriptor> propertySources = new ArrayList<>();
if (this.environment instanceof ConfigurableEnvironment) {
for (org.springframework.core.env.PropertySource<?> source :
((ConfigurableEnvironment) this.environment)
.getPropertySources()) {
// 2. 获取该 PropertySource 中的所有属性
Map<String, PropertyValueDescriptor> properties =
getProperties(source, pattern);
if (!properties.isEmpty()) {
propertySources.add(new PropertySourceDescriptor(
source.getName(), properties));
}
}
}
return new EnvironmentDescriptor(
getActiveProfiles(), propertySources);
}
// 获取属性时的消毒处理
private Map<String, PropertyValueDescriptor> getProperties(
PropertySource<?> source, @Nullable String pattern) {
Map<String, PropertyValueDescriptor> result = new LinkedHashMap<>();
// 遍历 PropertySource 中的属性
for (String name : getPropertyNames(source)) {
// 1. 匹配 pattern(可选过滤)
if (pattern != null && !name.contains(pattern)) {
continue;
}
Object value = source.getProperty(name);
// 2. 关键: 使用 Sanitizer 消毒敏感属性
// 检查 name 是否匹配 keys-to-sanitize 的正则
Object sanitized = this.sanitizer.sanitize(name, value);
// 如果匹配: value → "******"
// 如果不匹配: 保留原始值
result.put(name,
new PropertyValueDescriptor(
sanitized, // 消毒后的值
getOrigin(source, name))); // 属性来源
}
return result;
}
}9.2 消毒配置
yaml
# 默认消毒模式: 自动匹配常见敏感属性名
# 匹配模式包含: password, secret, key, token, .*credentials.*,
# vcap_services, sun.java.command 等
# 自定义消毒模式
management:
endpoints:
web:
exposure:
include: env
endpoint:
env:
keys-to-sanitize:
- password # 匹配所有包含 password 的属性
- secret
- token
- "myapp.*.apikey.*" # 自定义正则9.3 消毒效果
bash
# 消毒前
GET /actuator/env
# → {
# "propertySources": [{
# "name": "systemProperties",
# "properties": {
# "db.password": { "value": "mypassword123", "origin": "file:application.yml:12" },
# "api.key": { "value": "sk-abc123", "origin": null }
# }
# }]
# }
# 消毒后
# → {
# "propertySources": [{
# "name": "systemProperties",
# "properties": {
# "db.password": { "value": "******", "origin": "file:application.yml:12" },
# "api.key": { "value": "******", "origin": null }
# }
# }]
# }10. InfoEndpoint 的 InfoContributor
10.1 源码
java
// InfoEndpoint.java
@Endpoint(id = "info")
public class InfoEndpoint {
private final List<InfoContributor> infoContributors;
@ReadOperation // → GET /actuator/info
public Map<String, Object> info() {
// 构建 Info 构建器
Info.Builder builder = new Info.Builder();
// 逐一调用所有 InfoContributor
for (InfoContributor contributor : this.infoContributors) {
contributor.contribute(builder);
}
// 构建并返回
Info info = builder.build();
return info.getContents();
}
}10.2 内置 InfoContributor
java
// 1. EnvironmentInfoContributor —— 默认启用
public class EnvironmentInfoContributor implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
// 读取以 "info." 开头的 Environment 属性
// 例如: info.app.name=MyApp, info.app.version=1.0.0
// 自动组织为 Map:
// { "app": { "name": "MyApp", "version": "1.0.0" } }
builder.withInfoFrom(this.environment);
}
}
// 2. GitInfoContributor —— 需要 git.properties
// 由 spring-boot-maven-plugin 或 git-commit-id-plugin 生成
public class GitInfoContributor implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
// 读取 classpath:git.properties
// 包含:
// git.branch, git.commit.id, git.commit.time, git.commit.message
// 自动组织:
// { "git": { "branch": "main", "commit": { "id": "abc123", "time": "..." } } }
builder.withDetail("git", this.gitInfo);
}
}
// 3. BuildInfoContributor —— 需要 build-info.properties
// 由 spring-boot-maven-plugin 生成
public class BuildInfoContributor implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
// 读取 classpath:META-INF/build-info.properties
// 包含:
// build.artifact, build.group, build.name, build.time, build.version
// 自动组织:
// { "build": { "artifact": "my-app", "version": "1.0.0", "time": "..." } }
builder.withDetail("build", this.buildInfo);
}
}10.3 配置和自定义
yaml
# 自定义 info 信息(通过 EnvironmentInfoContributor 自动读取)
info:
app:
name: My Application
version: "@project.version@" # Maven 属性会被自动替换
description: Spring Boot Demo
contact:
email: admin@example.com
phone: "123-456-7890"bash
# GET /actuator/info
# → {
# "app": {
# "name": "My Application",
# "version": "1.0.0",
# "description": "Spring Boot Demo"
# },
# "contact": {
# "email": "admin@example.com",
# "phone": "123-456-7890"
# }
# }java
// 自定义 InfoContributor
@Component
public class CustomInfoContributor implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
builder.withDetail("custom",
Map.of("key1", "value1", "timestamp", System.currentTimeMillis()));
}
}11. WebEndpointServletHandlerMapping 的 URL 映射
11.1 源码
java
// WebEndpointServletHandlerMapping.java
public class WebEndpointServletHandlerMapping
extends AbstractHandlerMapping {
private final EndpointHandlerMapping mapping;
public WebEndpointServletHandlerMapping(
EndpointHandlerMapping handlerMapping) {
this.mapping = handlerMapping;
// 设置 URL 路径前缀
// 默认: /actuator/{endpointId}
setOrder(-100); // 高优先级,在 MVC HandlerMapping 之前
}
@Override
protected Object getHandlerInternal(HttpServletRequest request)
throws Exception {
// 1. 获取请求路径(去除 contextPath)
String path = getPath(request);
// 2. 匹配端点
// 路径格式: /actuator/{endpointId}[/{operation}]
// 3. 查找对应的 EndpointHandler
EndpointHandler handler = this.mapping.getHandler(path);
if (handler == null) {
return null; // 未匹配 → 交给下一个 HandlerMapping
}
// 4. 创建 Invocation 包装给 EndpointHandler
return new EndpointInvocation(handler, request);
}
}
// EndpointHandlerMapping —— 端点到处理器的映射
public class EndpointHandlerMapping {
// 所有端点的 URL → Handler 映射
private final Map<String, EndpointHandler> handlerMap;
public EndpointHandler getHandler(String path) {
// 从映射表中查找匹配的 Handler
return this.handlerMap.get(path);
}
}11.2 URL 映射汇总
| 端点 | 路径 | HTTP 方法 | 操作 |
|---|---|---|---|
health | /actuator/health | GET | 健康检查 |
metrics | /actuator/metrics | GET | 所有指标名称 |
metrics/{name} | /actuator/metrics/jvm.memory.used | GET | 指定指标详情 |
loggers | /actuator/loggers | GET | 所有 Logger 级别 |
loggers/{name} | /actuator/loggers/com.example.MyService | GET | 指定 Logger 级别 |
loggers/{name} | /actuator/loggers/com.example.MyService | POST | 修改日志级别 |
beans | /actuator/beans | GET | 所有 Bean |
env | /actuator/env | GET | 所有环境属性 |
env/{property} | /actuator/env/java.version | GET | 指定环境属性 |
info | /actuator/info | GET | 应用信息 |
conditions | /actuator/conditions | GET | 条件评估报告 |
configprops | /actuator/configprops | GET | 配置属性绑定 |
11.3 AbstractHandlerMapping 的 URL 生成
java
// AbstractHandlerMapping.java
@Override
protected void initHandlerMethods() {
// 遍历所有端点,生成 URL 映射
for (ExposableEndpoint<?> endpoint : this.endpoints) {
String path = "/actuator/" + endpoint.getId();
// 例如:
// endpoint = HealthEndpoint(id="health") → path = "/actuator/health"
// endpoint = MetricsEndpoint(id="metrics") → path = "/actuator/metrics"
// 操作路径(含 Selector 参数)
for (Operation operation : endpoint.getOperations()) {
// 如果是 @Selector 参数 → 路径为 /actuator/metrics/{name}
// 如果没有 Selector → 路径为 /actuator/metrics
String operationPath = buildOperationPath(path, operation);
// 注册 Handler
registerHandler(operationPath, operation);
}
}
}12. 端点暴露策略 management.endpoints.web.exposure.include
12.1 源码
java
// WebEndpointProperties.java
@ConfigurationProperties(prefix = "management.endpoints.web")
public class WebEndpointProperties {
private final Exposure exposure = new Exposure();
public static class Exposure {
// 要暴露的端点 ID 列表
private Set<String> include = new LinkedHashSet<>();
// 要排除的端点 ID 列表(优先级高于 include)
private Set<String> exclude = new LinkedHashSet<>();
// getter / setter
}
// 判断端点 ID 是否在暴露列表中
public boolean isExposed(String endpointId) {
// 1. 检查 exclude 中是否包含
if (this.exposure.getExclude().contains(endpointId)) {
return false;
}
// 2. 如果 include 包含 "*" → 全部暴露
if (this.exposure.getInclude().contains("*")) {
return true;
}
// 3. 检查 include 中是否包含
return this.exposure.getInclude().contains(endpointId);
}
}12.2 配置策略
yaml
# 策略 1: 暴露所有端点(生产环境建议谨慎)
management:
endpoints:
web:
exposure:
include: "*" # 暴露所有端点
# 策略 2: 只暴露健康检查和 info
management:
endpoints:
web:
exposure:
include: health,info # 只暴露 health 和 info
# 策略 3: 暴露大部分,排除敏感端点
management:
endpoints:
web:
exposure:
include: "*"
exclude: shutdown,env,beans,loggers # 排除敏感端点
# 策略 4: 基于端点的独立启用控制
management:
endpoint:
health:
enabled: true
info:
enabled: true
shutdown:
enabled: false
loggers:
enabled: false12.3 安全考虑
yaml
# 生产环境推荐的最小暴露方案
management:
endpoints:
web:
exposure:
include: health,info,prometheus # 仅健康、信息、指标
base-path: /actuator # 可自定义基础路径
endpoint:
health:
show-details: when-authorized # 仅授权用户可查看详情
roles: ADMIN
info:
enabled: true
prometheus:
enabled: true # 暴露 Prometheus 指标
# 结合 Spring Security 保护
spring:
security:
user:
roles: ACTUATOR, ADMIN
---
management:
endpoints:
web:
exposure:
include: health,info总结
| # | 细节点 | 核心要点 |
|---|---|---|
| ① | @Endpoint 注解扫描 | EndpointDiscoverer.discoverEndpoints() 从 BeanFactory 获取所有 @Endpoint Bean |
| ② | 操作 HTTP 映射 | @ReadOperation→GET、@WriteOperation→POST、@DeleteOperation→DELETE |
| ③ | HealthIndicator 聚合 | HealthContributorRegistry.getAllContributors() 逐一个获取状态 |
| ④ | SimpleStatusAggregator | DOWN > OUT_OF_SERVICE > UP > UNKNOWN 优先级聚合 |
| ⑤ | Web 适配 | HealthEndpointWebExtension 控制详情显示和响应状态码映射 |
| ⑥ | MetricsEndpoint | MeterRegistry.find(name).tag(key,val).meter() 三层过滤 |
| ⑦ | LoggersEndpoint 运行时修改 | LoggingSystem.setLogLevel() 实时生效,支持 Logback/Log4J2/JUL |
| ⑧ | BeansEndpoint | ApplicationContext.getBeanDefinitionNames() → BeanDefinition 元数据 |
| ⑨ | 属性暴露控制 | keys-to-sanitize 正则匹配,password/secret/key/token 自动消毒为 ****** |
| ⑩ | InfoContributor | EnvironmentInfoContributor 读取 info.* 属性,GitInfoContributor 读取 git.properties,BuildInfoContributor 读取 build-info.properties |
| ⑪ | URL 映射 | WebEndpointServletHandlerMapping 将 /actuator/{endpointId} 映射到 EndpointHandler |
| ⑫ | 暴露策略 | include 支持 "*" 通配符,exclude 优先级高于 include |