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

    • 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自定义
    • 热部署

自定义 HealthIndicator

一句话定位:通过实现 HealthIndicator 接口,开发者可以将数据库、Redis、第三方 API 等自定义检查逻辑注册到 Actuator 健康体系,使其被 /actuator/health 自动聚合并纳入整体可用性判定。


定义与作用

Spring Boot Actuator 默认提供的 DataSourceHealthIndicator、RedisHealthIndicator、DiskSpaceHealthIndicator 等只能覆盖常见基础设施。但生产环境中,应用往往依赖更多外部服务:内部认证中心、文件存储服务、消息队列、甚至特定的业务规则(如"学生表必须有至少 1 条数据")。

HealthIndicator 接口是 Actuator 健康检查的扩展契约。任何 Spring Bean 只要实现该接口,就会被 HealthContributorRegistry 自动发现并纳入健康聚合。它解决了传统 SSM 中"手写检查接口 + 独立维护"的碎片化问题,将分散的检查逻辑统一到 Actuator 标准协议中。

对比维度传统 SSM 手写检查Spring Boot 自定义 HealthIndicator
接口规范各团队自行定义返回格式统一返回 Health 对象,Actuator 自动聚合
注册方式手动注册到某个 Controller标注 @Component 即可自动发现
状态语义自定义(success/fail/ok/error)标准(UP / DOWN / OUT_OF_SERVICE / UNKNOWN)
上下文信息随意返回通过 withDetail() 结构化附加信息
监控集成需单独配置监控抓取直接融入 /actuator/health 响应

适用位置与常用属性

自定义 HealthIndicator 是一个接口实现类,需标注为 Spring Bean(通常使用 @Component)。其暴露和控制仍通过 application.yml 完成。

接口定义

package org.springframework.boot.actuate.health;

public interface HealthIndicator {
    Health health();
}

Health 构建器常用方法

方法说明示例
Health.up()标记状态为 UPHealth.up().build()
Health.down()标记状态为 DOWNHealth.down().build()
Health.outOfService()标记状态为 OUT_OF_SERVICEHealth.outOfService().build()
Health.unknown()标记状态为 UNKNOWNHealth.unknown().build()
withDetail(String, Object)附加键值对详情withDetail("database", "MySQL")
withException(Exception)附加异常信息withException(e)

配置属性

management:
  endpoint:
    health:
      show-details: always
      show-components: always

实现要点:自定义检查器的类名通常以 HealthIndicator 结尾(如 DatabaseHealthIndicator),Spring Boot 会自动从 Bean 名称中剥离后缀,生成组件名 database。


核心原理

自定义 HealthIndicator 注册与聚合流程

流程解读:

  1. 开发者编写类实现 HealthIndicator,并标注 @Component(或 @Bean 注册)。
  2. Spring 容器启动时,HealthContributorRegistry 收集所有类型为 HealthIndicator 的 Bean。
  3. Bean 名称的后缀 HealthIndicator 被自动移除,作为端点响应中的组件名。例如 StudentDatabaseHealthIndicator → 组件名 studentDatabase。
  4. 访问 /actuator/health 时,Actuator 并行调用每个检查器的 health() 方法。
  5. 各检查器返回的 Health 对象被聚合,整体状态遵循"一票否决"规则。

Health 对象构造层次


完整示例

场景说明

飞翔科技的学生成绩管理系统依赖 MySQL 数据库 student_db。架构师白歌要求:即使 Spring Boot 默认的 DataSourceHealthIndicator 能通过简单连接验证,也需要一个自定义检查器,确保不仅能连通,还能执行 SELECT 1 并检查 student 表是否存在。小崔需要实现这个自定义检查器并验证其集成效果。

操作前:仅依赖默认健康检查

# 操作前:application.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/student_db
    username: root
    password: root
server:
  port: 8080
management:
  server:
    port: 8081
  endpoints:
    web:
      exposure:
        include: "health"

此时访问 /actuator/health 仅包含默认的 db(验证连接是否可用)和 diskSpace、ping。但白歌的业务需求——确认 student 表存在——无法被默认检查覆盖。

使用该特性的完整代码

步骤一:实现自定义 HealthIndicator

package com.feixiang.student.health;

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;

/**
 * 学生数据库自定义健康检查器
 * 检查:1)数据库可连接;2)student 表存在
 * 
 * @author 小崔
 * @since 2024
 */
@Component
public class StudentDatabaseHealthIndicator implements HealthIndicator {

    private final DataSource dataSource;

    public StudentDatabaseHealthIndicator(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public Health health() {
        try (Connection connection = dataSource.getConnection();
             Statement statement = connection.createStatement()) {

            // 检查 1:基础连通性(执行 SELECT 1)
            statement.execute("SELECT 1");

            // 检查 2:确认 student 表存在
            ResultSet rs = statement.executeQuery(
                "SELECT COUNT(*) FROM information_schema.tables " +
                "WHERE table_schema = 'student_db' AND table_name = 'student'"
            );
            rs.next();
            int tableCount = rs.getInt(1);

            if (tableCount == 0) {
                return Health.down()
                        .withDetail("database", "student_db")
                        .withDetail("check", "student 表缺失")
                        .withDetail("student_table_exists", false)
                        .build();
            }

            return Health.up()
                    .withDetail("database", "student_db")
                    .withDetail("connection", "valid")
                    .withDetail("student_table_exists", true)
                    .withDetail("driver", connection.getMetaData().getDriverName())
                    .build();

        } catch (Exception e) {
            return Health.down()
                    .withDetail("database", "student_db")
                    .withDetail("error", e.getMessage())
                    .withException(e)
                    .build();
        }
    }
}

步骤二:确保 application.yml 暴露 health 详情(用于演示)

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/student_db
    username: root
    password: root

server:
  port: 8080

management:
  server:
    port: 8081
  endpoints:
    web:
      exposure:
        include: "health"
  endpoint:
    health:
      show-details: always
      show-components: always

注意:此实现类无需任何额外配置,仅通过 @Component 即可被 Spring Boot 自动扫描并注册到 Actuator。

操作后运行结果及分析

应用正常启动后,访问健康端点:

$ curl http://localhost:8081/actuator/health

返回结果(数据库正常时):

{
    "status": "UP",
    "components": {
        "db": {
            "status": "UP",
            "details": {
                "database": "MySQL",
                "validationQuery": "SELECT 1"
            }
        },
        "diskSpace": {
            "status": "UP"
        },
        "ping": {
            "status": "UP"
        },
        "studentDatabase": {
            "status": "UP",
            "details": {
                "database": "student_db",
                "connection": "valid",
                "student_table_exists": true,
                "driver": "MySQL Connector/J"
            }
        }
    }
}

模拟 student 表被误删后再次访问:

{
    "status": "DOWN",
    "components": {
        "db": {
            "status": "UP"
        },
        "studentDatabase": {
            "status": "DOWN",
            "details": {
                "database": "student_db",
                "check": "student 表缺失",
                "student_table_exists": false
            }
        }
    }
}

分析:

  1. @Component 自动注册生效:StudentDatabaseHealthIndicator 被 Spring 扫描后,Actuator 自动将其纳入 HealthContributorRegistry。
  2. 组件名自动推断:StudentDatabaseHealthIndicator 去掉 HealthIndicator 后缀后,端点中显示为 studentDatabase。
  3. 自定义检查逻辑生效:不仅验证了连接,还验证了 student 表的存在性,满足白歌的业务要求。
  4. 状态聚合正确:studentDatabase 为 DOWN 时,整体状态变为 DOWN,负载均衡器可自动摘除该实例。

易错场景与面试考点

易错场景一:忘记标注 @Component 导致检查器未注册

// 错误示范:未标注 @Component
public class StudentDatabaseHealthIndicator implements HealthIndicator {
    ...
}

后果:该类虽实现了接口,但 Spring 容器未将其识别为 Bean,HealthContributorRegistry 扫描不到它,端点响应中始终缺少该组件。小崔在本地测试检查逻辑明明正确,但访问端点就是不显示,最后发现是启动类的包扫描范围未覆盖到 health 包,或者根本忘了加 @Component。

正确做法:确保类标注 @Component(或在其配置类中用 @Bean 注册),且该类位于启动类包扫描路径内。

@Component
public class StudentDatabaseHealthIndicator implements HealthIndicator {
    ...
}

易错场景二:健康检查逻辑阻塞或超时未处理

// 错误示范:检查逻辑可能长时间阻塞,未设置超时
@Override
public Health health() {
    try {
        // 假设某第三方 API 响应极慢
        Response response = thirdPartyApi.ping();  // 可能阻塞 60 秒
        return Health.up().build();
    } catch (Exception e) {
        return Health.down().build();
    }
}

后果:当第三方服务不可用时,health() 方法长时间阻塞,导致 /actuator/health 请求超时。如果负载均衡器或 K8s 探针频繁访问该端点,会积压大量线程,拖垮应用自身。

正确做法:健康检查逻辑必须轻量、快速,对外部调用设置严格的超时(如 2 秒),或使用异步/熔断机制。检查失败应快速返回 DOWN,而不是挂起。

@Override
public Health health() {
    try {
        // 使用带超时的 HTTP 客户端或独立线程池
        boolean ok = pingWithTimeout(2000);
        return ok ? Health.up().build() : Health.down().withDetail("timeout", "2s").build();
    } catch (Exception e) {
        return Health.down().withException(e).build();
    }
}

面试考点

Q:自定义 HealthIndicator 时,Bean 名称如何映射到端点中的组件名?

Spring Boot 会自动移除 Bean 名称尾部的 HealthIndicator 后缀作为组件标识。例如 RedisHealthIndicator → redis,StudentDatabaseHealthIndicator → studentDatabase。如果显式指定了 @Component("myCheck"),则组件名为 myCheck。

Q:Health.up() 和 Health.down() 除了状态还有什么作用?

它们返回 Health.Builder,可通过链式调用 withDetail() 附加任意键值对信息。这些信息在 show-details=always 时会被完整返回,帮助运维快速定位问题根因。withException() 可将异常栈转为详情字符串。

Q:如果应用依赖 5 个外部服务,是否要写 5 个 HealthIndicator?

是的,每个外部服务建议独立一个 HealthIndicator。这样做的好处是:端点响应中每个组件独立显示状态,故障时一眼看出哪个服务出问题。如果全部写在一个检查器里,只能得到整体成功或失败,无法细分。

Q:健康检查逻辑抛出异常会怎样?

Actuator 框架会捕获 health() 方法抛出的异常,自动将该组件状态标记为 DOWN,并将异常信息附加到详情中。但建议开发者在实现内部自行 try-catch,使用 withException() 构造更清晰的详情,避免异常类型信息泄露到生产端点。

上一页
自定义Endpoint