详解SpringBoot健康检查的实现原理

 更新时间:2021年03月12日 09:45:42   作者:筱进客  
这篇文章主要介绍了详解SpringBoot健康检查的实现原理,帮助大家更好的理解和学习使用SpringBoot框架,感兴趣的朋友可以了解下

SpringBoot自动装配的套路,直接看 spring.factories 文件,当我们使用的时候只需要引入如下依赖

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

然后在 org.springframework.boot.spring-boot-actuator-autoconfigure 包下去就可以找到这个文件

自动装配

查看这个文件发现引入了很多的配置类,这里先关注一下 XXXHealthIndicatorAutoConfiguration 系列的类,这里咱们拿第一个 RabbitHealthIndicatorAutoConfiguration 为例来解析一下。看名字就知道这个是RabbitMQ的健康检查的自动配置类

@Configuration
@ConditionalOnClass(RabbitTemplate.class)
@ConditionalOnBean(RabbitTemplate.class)
@ConditionalOnEnabledHealthIndicator("rabbit")
@AutoConfigureBefore(HealthIndicatorAutoConfiguration.class)
@AutoConfigureAfter(RabbitAutoConfiguration.class)
public class RabbitHealthIndicatorAutoConfiguration extends
  CompositeHealthIndicatorConfiguration<RabbitHealthIndicator, RabbitTemplate> {

 private final Map<String, RabbitTemplate> rabbitTemplates;

 public RabbitHealthIndicatorAutoConfiguration(
   Map<String, RabbitTemplate> rabbitTemplates) {
  this.rabbitTemplates = rabbitTemplates;
 }

 @Bean
 @ConditionalOnMissingBean(name = "rabbitHealthIndicator")
 public HealthIndicator rabbitHealthIndicator() {
  return createHealthIndicator(this.rabbitTemplates);
 }
}

按照以往的惯例,先解析注解

  • @ConditionalOnXXX 系列又出现了,前两个就是说如果当前存在 RabbitTemplate 这个bean也就是说我们的项目中使用到了RabbitMQ才能进行下去
  • @ConditionalOnEnabledHealthIndicator 这个注解很明显是SpringBoot actuator自定义的注解,看一下吧
@Conditional(OnEnabledHealthIndicatorCondition.class)
public @interface ConditionalOnEnabledHealthIndicator {
 String value();
}
class OnEnabledHealthIndicatorCondition extends OnEndpointElementCondition {

 OnEnabledHealthIndicatorCondition() {
  super("management.health.", ConditionalOnEnabledHealthIndicator.class);
 }

}
public abstract class OnEndpointElementCondition extends SpringBootCondition {

 private final String prefix;

 private final Class<? extends Annotation> annotationType;

 protected OnEndpointElementCondition(String prefix,
   Class<? extends Annotation> annotationType) {
  this.prefix = prefix;
  this.annotationType = annotationType;
 }

 @Override
 public ConditionOutcome getMatchOutcome(ConditionContext context,
   AnnotatedTypeMetadata metadata) {
  AnnotationAttributes annotationAttributes = AnnotationAttributes
    .fromMap(metadata.getAnnotationAttributes(this.annotationType.getName()));
  String endpointName = annotationAttributes.getString("value");
  ConditionOutcome outcome = getEndpointOutcome(context, endpointName);
  if (outcome != null) {
   return outcome;
  }
  return getDefaultEndpointsOutcome(context);
 }

 protected ConditionOutcome getEndpointOutcome(ConditionContext context,
   String endpointName) {
  Environment environment = context.getEnvironment();
  String enabledProperty = this.prefix + endpointName + ".enabled";
  if (environment.containsProperty(enabledProperty)) {
   boolean match = environment.getProperty(enabledProperty, Boolean.class, true);
   return new ConditionOutcome(match,
     ConditionMessage.forCondition(this.annotationType).because(
       this.prefix + endpointName + ".enabled is " + match));
  }
  return null;
 }

 protected ConditionOutcome getDefaultEndpointsOutcome(ConditionContext context) {
  boolean match = Boolean.valueOf(context.getEnvironment()
    .getProperty(this.prefix + "defaults.enabled", "true"));
  return new ConditionOutcome(match,
    ConditionMessage.forCondition(this.annotationType).because(
      this.prefix + "defaults.enabled is considered " + match));
 }

}
public abstract class SpringBootCondition implements Condition {

 private final Log logger = LogFactory.getLog(getClass());

 @Override
 public final boolean matches(ConditionContext context,
   AnnotatedTypeMetadata metadata) {
  String classOrMethodName = getClassOrMethodName(metadata);
  try {
   ConditionOutcome outcome = getMatchOutcome(context, metadata);
   logOutcome(classOrMethodName, outcome);
   recordEvaluation(context, classOrMethodName, outcome);
   return outcome.isMatch();
  }
  catch (NoClassDefFoundError ex) {
   throw new IllegalStateException(
     "Could not evaluate condition on " + classOrMethodName + " due to "
       + ex.getMessage() + " not "
       + "found. Make sure your own configuration does not rely on "
       + "that class. This can also happen if you are "
       + "@ComponentScanning a springframework package (e.g. if you "
       + "put a @ComponentScan in the default package by mistake)",
     ex);
  }
  catch (RuntimeException ex) {
   throw new IllegalStateException(
     "Error processing condition on " + getName(metadata), ex);
  }
 }
 private void recordEvaluation(ConditionContext context, String classOrMethodName,
   ConditionOutcome outcome) {
  if (context.getBeanFactory() != null) {
   ConditionEvaluationReport.get(context.getBeanFactory())
     .recordConditionEvaluation(classOrMethodName, this, outcome);
  }
 }
}

上方的入口方法是 SpringBootCondition 类的 matches 方法, getMatchOutcome 这个方法则是子类 OnEndpointElementCondition 的,这个方法首先会去环境变量中查找是否存在 management.health.rabbit.enabled 属性,如果没有的话则去查找 management.health.defaults.enabled 属性,如果这个属性还没有的话则设置默认值为true

当这里返回true时整个 RabbitHealthIndicatorAutoConfiguration 类的自动配置才能继续下去

  • @AutoConfigureBefore 既然这样那就先看看类 HealthIndicatorAutoConfiguration 都是干了啥再回来吧
@Configuration
@EnableConfigurationProperties({ HealthIndicatorProperties.class })
public class HealthIndicatorAutoConfiguration {

 private final HealthIndicatorProperties properties;

 public HealthIndicatorAutoConfiguration(HealthIndicatorProperties properties) {
  this.properties = properties;
 }

 @Bean
 @ConditionalOnMissingBean({ HealthIndicator.class, ReactiveHealthIndicator.class })
 public ApplicationHealthIndicator applicationHealthIndicator() {
  return new ApplicationHealthIndicator();
 }

 @Bean
 @ConditionalOnMissingBean(HealthAggregator.class)
 public OrderedHealthAggregator healthAggregator() {
  OrderedHealthAggregator healthAggregator = new OrderedHealthAggregator();
  if (this.properties.getOrder() != null) {
   healthAggregator.setStatusOrder(this.properties.getOrder());
  }
  return healthAggregator;
 }

}

首先这个类引入了配置文件 HealthIndicatorProperties 这个配置类是系统状态相关的配置

@ConfigurationProperties(prefix = "management.health.status")
public class HealthIndicatorProperties {

 private List<String> order = null;

 private final Map<String, Integer> httpMapping = new HashMap<>();
}

接着就是注册了2个bean ApplicationHealthIndicatorOrderedHealthAggregator 这两个bean的作用稍后再说,现在回到 RabbitHealthIndicatorAutoConfiguration 类

@AutoConfigureAfter
HealthIndicator
public abstract class CompositeHealthIndicatorConfiguration<H extends HealthIndicator, S> {

 @Autowired
 private HealthAggregator healthAggregator;

 protected HealthIndicator createHealthIndicator(Map<String, S> beans) {
  if (beans.size() == 1) {
   return createHealthIndicator(beans.values().iterator().next());
  }
  CompositeHealthIndicator composite = new CompositeHealthIndicator(
    this.healthAggregator);
  for (Map.Entry<String, S> entry : beans.entrySet()) {
   composite.addHealthIndicator(entry.getKey(),
     createHealthIndicator(entry.getValue()));
  }
  return composite;
 }

 @SuppressWarnings("unchecked")
 protected H createHealthIndicator(S source) {
  Class<?>[] generics = ResolvableType
    .forClass(CompositeHealthIndicatorConfiguration.class, getClass())
    .resolveGenerics();
  Class<H> indicatorClass = (Class<H>) generics[0];
  Class<S> sourceClass = (Class<S>) generics[1];
  try {
   return indicatorClass.getConstructor(sourceClass).newInstance(source);
  }
  catch (Exception ex) {
   throw new IllegalStateException("Unable to create indicator " + indicatorClass
     + " for source " + sourceClass, ex);
  }
 }

}
  • 首先这里注入了一个对象 HealthAggregator ,这个对象就是刚才注册的 OrderedHealthAggregator
  • 第一个 createHealthIndicator 方法执行逻辑为:如果传入的beans的size 为1,则调用 createHealthIndicator 创建 HealthIndicator 否则创建 CompositeHealthIndicator ,遍历传入的beans,依次创建 HealthIndicator ,加入到 CompositeHealthIndicator
  • 第二个 createHealthIndicator 的执行逻辑为:获得 CompositeHealthIndicatorConfiguration 中的泛型参数根据泛型参数H对应的class和S对应的class,在H对应的class中找到声明了参数为S类型的构造器进行实例化
  • 最后这里创建出来的bean为 RabbitHealthIndicator
  • 回忆起之前学习健康检查的使用时,如果我们需要自定义健康检查项时一般的操作都是实现 HealthIndicator 接口,由此可以猜测 RabbitHealthIndicator 应该也是这样做的。观察这个类的继承关系可以发现这个类继承了一个实现实现此接口的类 AbstractHealthIndicator ,而RabbitMQ的监控检查流程则如下代码所示
 //这个方法是AbstractHealthIndicator的
public final Health health() {
  Health.Builder builder = new Health.Builder();
  try {
   doHealthCheck(builder);
  }
  catch (Exception ex) {
   if (this.logger.isWarnEnabled()) {
    String message = this.healthCheckFailedMessage.apply(ex);
    this.logger.warn(StringUtils.hasText(message) ? message : DEFAULT_MESSAGE,
      ex);
   }
   builder.down(ex);
  }
  return builder.build();
 }
//下方两个方法是由类RabbitHealthIndicator实现的
protected void doHealthCheck(Health.Builder builder) throws Exception {
  builder.up().withDetail("version", getVersion());
 }

 private String getVersion() {
  return this.rabbitTemplate.execute((channel) -> channel.getConnection()
    .getServerProperties().get("version").toString());
 }

健康检查

上方一系列的操作之后,其实就是搞出了一个RabbitMQ的 HealthIndicator 实现类,而负责检查RabbitMQ健康不健康也是这个类来负责的。由此我们可以想象到如果当前环境存在MySQL、Redis、ES等情况应该也是这么个操作

那么接下来无非就是当有调用方访问如下地址时,分别调用整个系统的所有的 HealthIndicator 的实现类的 health 方法即可了

http://ip:port/actuator/health

HealthEndpointAutoConfiguration

上边说的这个操作过程就在类 HealthEndpointAutoConfiguration 中,这个配置类同样也是在 spring.factories 文件中引入的

@Configuration
@EnableConfigurationProperties({HealthEndpointProperties.class, HealthIndicatorProperties.class})
@AutoConfigureAfter({HealthIndicatorAutoConfiguration.class})
@Import({HealthEndpointConfiguration.class, HealthEndpointWebExtensionConfiguration.class})
public class HealthEndpointAutoConfiguration {
 public HealthEndpointAutoConfiguration() {
 }
}

这里重点的地方在于引入的 HealthEndpointConfiguration 这个类

@Configuration
class HealthEndpointConfiguration {

 @Bean
 @ConditionalOnMissingBean
 @ConditionalOnEnabledEndpoint
 public HealthEndpoint healthEndpoint(ApplicationContext applicationContext) {
  return new HealthEndpoint(HealthIndicatorBeansComposite.get(applicationContext));
 }

}

这个类只是构建了一个类 HealthEndpoint ,这个类我们可以理解为一个SpringMVC的Controller,也就是处理如下请求的

http://ip:port/actuator/health

那么首先看一下它的构造方法传入的是个啥对象吧

public static HealthIndicator get(ApplicationContext applicationContext) {
  HealthAggregator healthAggregator = getHealthAggregator(applicationContext);
  Map<String, HealthIndicator> indicators = new LinkedHashMap<>();
  indicators.putAll(applicationContext.getBeansOfType(HealthIndicator.class));
  if (ClassUtils.isPresent("reactor.core.publisher.Flux", null)) {
   new ReactiveHealthIndicators().get(applicationContext)
     .forEach(indicators::putIfAbsent);
  }
  CompositeHealthIndicatorFactory factory = new CompositeHealthIndicatorFactory();
  return factory.createHealthIndicator(healthAggregator, indicators);
 }

跟我们想象中的一样,就是通过Spring容器获取所有的 HealthIndicator 接口的实现类,我这里只有几个默认的和RabbitMQ

然后都放入了其中一个聚合的实现类 CompositeHealthIndicator

既然 HealthEndpoint构建好了,那么只剩下最后一步处理请求了

@Endpoint(id = "health")
public class HealthEndpoint {

 private final HealthIndicator healthIndicator;

 @ReadOperation
 public Health health() {
  return this.healthIndicator.health();
 }

}

刚刚我们知道,这个类是通过 CompositeHealthIndicator 构建的,所以 health 方法的实现就在这个类中

public Health health() {
  Map<String, Health> healths = new LinkedHashMap<>();
  for (Map.Entry<String, HealthIndicator> entry : this.indicators.entrySet()) {
   //循环调用
   healths.put(entry.getKey(), entry.getValue().health());
  }
  //对结果集排序
  return this.healthAggregator.aggregate(healths);
 }

至此SpringBoot的健康检查实现原理全部解析完成

以上就是详解SpringBoot健康检查的实现原理的详细内容,更多关于SpringBoot健康检查实现原理的资料请关注脚本之家其它相关文章!

相关文章

  • Java雪花算法的实现详解

    Java雪花算法的实现详解

    雪花算法(Snowflake)是一种分布式唯一ID生成算法,用于生成全局唯一的ID,使用雪花算法生成的ID通常是一个64位的整数,可以根据需要进行转换和展示,在Java等编程语言中,可以使用相应的库或工具来生成雪花算法的ID,本文给大家介绍了Java雪花算法的实现
    2023-11-11
  • 使用redis的increment()方法实现计数器功能案例

    使用redis的increment()方法实现计数器功能案例

    这篇文章主要介绍了使用redis的increment()方法实现计数器功能案例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-11-11
  • springboot 自定义配置Boolean属性不生效的解决

    springboot 自定义配置Boolean属性不生效的解决

    这篇文章主要介绍了springboot 自定义配置Boolean属性不生效的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • Jmeter JDBC请求常见问题解决方案

    Jmeter JDBC请求常见问题解决方案

    这篇文章主要介绍了Jmeter JDBC请求常见问题解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • Mybatis-plus通过添加拦截器实现简单数据权限

    Mybatis-plus通过添加拦截器实现简单数据权限

    系统需要根据用户所属的公司,来做一下数据权限控制,具体一点,就是通过表中的company_id进行权限控制,项目使用的是mybatis-plus,所以通过添加拦截器的方式,修改查询sql,实现数据权限,本文就通过代码给大家详细的讲解一下,需要的朋友可以参考下
    2023-08-08
  • Springboot配置全局跨域未生效,访问接口报错问题及解决

    Springboot配置全局跨域未生效,访问接口报错问题及解决

    这篇文章主要介绍了Springboot配置全局跨域未生效,访问接口报错问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12
  • Java实战之图书管理系统的实现

    Java实战之图书管理系统的实现

    这篇文章主要介绍了如何利用Java语言编写一个图书管理系统,文中采用的技术有Springboot、SpringMVC、MyBatis、ThymeLeaf 等,需要的可以参考一下
    2022-03-03
  • java汉字转拼音工具类分享

    java汉字转拼音工具类分享

    这篇文章主要为大家详细介绍了java汉字转拼音工具类,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-01-01
  • 详解SpringMVC学习系列之国际化

    详解SpringMVC学习系列之国际化

    这篇文章主要介绍了详解SpringMVC学习系列之国际化,详细的介绍了关于浏览器,Session,Cookie,URL请求的国际化的实现,有兴趣的可以了解一下
    2017-07-07
  • maven的安装配置以及在IDEA中的配置图文教程

    maven的安装配置以及在IDEA中的配置图文教程

    下面小编就为大家分享一篇maven的安装配置以及在IDEA中的配置图文教程,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2017-12-12

最新评论