詳解SpringBoot健康檢查的實(shí)現(xiàn)原理
SpringBoot自動(dòng)裝配的套路,直接看 spring.factories
文件,當(dāng)我們使用的時(shí)候只需要引入如下依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
然后在 org.springframework.boot.spring-boot-actuator-autoconfigure
包下去就可以找到這個(gè)文件
自動(dòng)裝配
查看這個(gè)文件發(fā)現(xiàn)引入了很多的配置類,這里先關(guān)注一下 XXXHealthIndicatorAutoConfiguration
系列的類,這里咱們拿第一個(gè) RabbitHealthIndicatorAutoConfiguration
為例來解析一下??疵志椭肋@個(gè)是RabbitMQ的健康檢查的自動(dòng)配置類
@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
系列又出現(xiàn)了,前兩個(gè)就是說如果當(dāng)前存在RabbitTemplate
這個(gè)bean也就是說我們的項(xiàng)目中使用到了RabbitMQ才能進(jìn)行下去@ConditionalOnEnabledHealthIndicator
這個(gè)注解很明顯是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
這個(gè)方法則是子類 OnEndpointElementCondition
的,這個(gè)方法首先會(huì)去環(huán)境變量中查找是否存在 management.health.rabbit.enabled
屬性,如果沒有的話則去查找 management.health.defaults.enabled
屬性,如果這個(gè)屬性還沒有的話則設(shè)置默認(rèn)值為true
當(dāng)這里返回true時(shí)整個(gè) RabbitHealthIndicatorAutoConfiguration
類的自動(dòng)配置才能繼續(xù)下去
@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; } }
首先這個(gè)類引入了配置文件 HealthIndicatorProperties
這個(gè)配置類是系統(tǒng)狀態(tài)相關(guān)的配置
@ConfigurationProperties(prefix = "management.health.status") public class HealthIndicatorProperties { private List<String> order = null; private final Map<String, Integer> httpMapping = new HashMap<>(); }
接著就是注冊(cè)了2個(gè)bean ApplicationHealthIndicator
和 OrderedHealthAggregator
這兩個(gè)bean的作用稍后再說,現(xiàn)在回到 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); } } }
- 首先這里注入了一個(gè)對(duì)象
HealthAggregator
,這個(gè)對(duì)象就是剛才注冊(cè)的OrderedHealthAggregator
- 第一個(gè)
createHealthIndicator
方法執(zhí)行邏輯為:如果傳入的beans的size 為1,則調(diào)用createHealthIndicator
創(chuàng)建HealthIndicator
否則創(chuàng)建CompositeHealthIndicator
,遍歷傳入的beans,依次創(chuàng)建HealthIndicator
,加入到CompositeHealthIndicator
中 - 第二個(gè)
createHealthIndicator
的執(zhí)行邏輯為:獲得CompositeHealthIndicatorConfiguration
中的泛型參數(shù)根據(jù)泛型參數(shù)H對(duì)應(yīng)的class和S對(duì)應(yīng)的class,在H對(duì)應(yīng)的class中找到聲明了參數(shù)為S類型的構(gòu)造器進(jìn)行實(shí)例化 - 最后這里創(chuàng)建出來的bean為
RabbitHealthIndicator
- 回憶起之前學(xué)習(xí)健康檢查的使用時(shí),如果我們需要自定義健康檢查項(xiàng)時(shí)一般的操作都是實(shí)現(xiàn)
HealthIndicator
接口,由此可以猜測(cè)RabbitHealthIndicator
應(yīng)該也是這樣做的。觀察這個(gè)類的繼承關(guān)系可以發(fā)現(xiàn)這個(gè)類繼承了一個(gè)實(shí)現(xiàn)實(shí)現(xiàn)此接口的類AbstractHealthIndicator
,而RabbitMQ的監(jiān)控檢查流程則如下代碼所示
//這個(gè)方法是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(); } //下方兩個(gè)方法是由類RabbitHealthIndicator實(shí)現(xiàn)的 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()); }
健康檢查
上方一系列的操作之后,其實(shí)就是搞出了一個(gè)RabbitMQ的 HealthIndicator
實(shí)現(xiàn)類,而負(fù)責(zé)檢查RabbitMQ健康不健康也是這個(gè)類來負(fù)責(zé)的。由此我們可以想象到如果當(dāng)前環(huán)境存在MySQL、Redis、ES等情況應(yīng)該也是這么個(gè)操作
那么接下來無非就是當(dāng)有調(diào)用方訪問如下地址時(shí),分別調(diào)用整個(gè)系統(tǒng)的所有的 HealthIndicator
的實(shí)現(xiàn)類的 health
方法即可了
http://ip:port/actuator/health
HealthEndpointAutoConfiguration
上邊說的這個(gè)操作過程就在類 HealthEndpointAutoConfiguration
中,這個(gè)配置類同樣也是在 spring.factories
文件中引入的
@Configuration @EnableConfigurationProperties({HealthEndpointProperties.class, HealthIndicatorProperties.class}) @AutoConfigureAfter({HealthIndicatorAutoConfiguration.class}) @Import({HealthEndpointConfiguration.class, HealthEndpointWebExtensionConfiguration.class}) public class HealthEndpointAutoConfiguration { public HealthEndpointAutoConfiguration() { } }
這里重點(diǎn)的地方在于引入的 HealthEndpointConfiguration
這個(gè)類
@Configuration class HealthEndpointConfiguration { @Bean @ConditionalOnMissingBean @ConditionalOnEnabledEndpoint public HealthEndpoint healthEndpoint(ApplicationContext applicationContext) { return new HealthEndpoint(HealthIndicatorBeansComposite.get(applicationContext)); } }
這個(gè)類只是構(gòu)建了一個(gè)類 HealthEndpoint
,這個(gè)類我們可以理解為一個(gè)SpringMVC的Controller,也就是處理如下請(qǐng)求的
http://ip:port/actuator/health
那么首先看一下它的構(gòu)造方法傳入的是個(gè)啥對(duì)象吧
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
接口的實(shí)現(xiàn)類,我這里只有幾個(gè)默認(rèn)的和RabbitMQ
然后都放入了其中一個(gè)聚合的實(shí)現(xiàn)類 CompositeHealthIndicator
中
既然 HealthEndpoint
構(gòu)建好了,那么只剩下最后一步處理請(qǐng)求了
@Endpoint(id = "health") public class HealthEndpoint { private final HealthIndicator healthIndicator; @ReadOperation public Health health() { return this.healthIndicator.health(); } }
剛剛我們知道,這個(gè)類是通過 CompositeHealthIndicator
構(gòu)建的,所以 health
方法的實(shí)現(xiàn)就在這個(gè)類中
public Health health() { Map<String, Health> healths = new LinkedHashMap<>(); for (Map.Entry<String, HealthIndicator> entry : this.indicators.entrySet()) { //循環(huán)調(diào)用 healths.put(entry.getKey(), entry.getValue().health()); } //對(duì)結(jié)果集排序 return this.healthAggregator.aggregate(healths); }
至此SpringBoot的健康檢查實(shí)現(xiàn)原理全部解析完成
以上就是詳解SpringBoot健康檢查的實(shí)現(xiàn)原理的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot健康檢查實(shí)現(xiàn)原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java字符串遍歷以及統(tǒng)計(jì)字符串中各類字符
這篇文章主要為大家詳細(xì)介紹了java字符串遍歷以及字符串中各類字符統(tǒng)計(jì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03Springboot整個(gè)Quartz實(shí)現(xiàn)動(dòng)態(tài)定時(shí)任務(wù)的示例代碼
這篇文章主要介紹了Springboot整個(gè)Quartz實(shí)現(xiàn)動(dòng)態(tài)定時(shí)任務(wù)的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-09-09SpringBoot居然有44種應(yīng)用啟動(dòng)器,你都知道嗎
很多人都不知道SpringBoot應(yīng)用啟動(dòng)器竟然有44個(gè),本文就一起來介紹一下,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2021-01-01Java中實(shí)現(xiàn)線程的三種方式及對(duì)比_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
本文給大家分享了java實(shí)現(xiàn)線程的三種方式,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2017-05-05Spring?Boot應(yīng)用中如何動(dòng)態(tài)指定數(shù)據(jù)庫(kù)實(shí)現(xiàn)不同用戶不同數(shù)據(jù)庫(kù)的問題
讓我們創(chuàng)建一個(gè) Spring Boot 項(xiàng)目首先設(shè)置一個(gè)具有必要依賴項(xiàng)的新 Spring Boot項(xiàng)目,在項(xiàng)目配置中包括 Spring Web、Spring Data JPA 和關(guān)于數(shù)據(jù)庫(kù)的依賴項(xiàng),接下來介紹Spring?Boot應(yīng)用中如何動(dòng)態(tài)指定數(shù)據(jù)庫(kù),實(shí)現(xiàn)不同用戶不同數(shù)據(jù)庫(kù)的場(chǎng)景?,需要的朋友可以參考下2024-04-04Spring、Spring?Boot、Spring?Cloud?的區(qū)別與聯(lián)系分析
Spring、SpringBoot和SpringCloud是Java開發(fā)中常用的框架,分別針對(duì)企業(yè)級(jí)應(yīng)用開發(fā)、快速開發(fā)和分布式系統(tǒng),本文介紹Spring、Spring?Boot、Spring?Cloud?的區(qū)別與聯(lián)系,感興趣的朋友一起看看吧2025-03-03