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個beanApplicationHealthIndicator
和OrderedHealthAggregator
這兩個bean的作用稍後再說,現在回到RabbitHealthIndicatorAutoConfiguration
類
@AutoConfigureAfter
這個對整體邏輯沒影響,暫且不提- 類中註冊了一個bean
HealthIndicator
這個bean的建立邏輯是在父類中的
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的健康檢查實現原理全部解析完成