SpringBoot健康檢查實現原理

相信看完以前文章的同窗都知道了SpringBoot自動裝配的套路了,直接看spring.factories文件,當咱們使用的時候只須要引入以下依賴react

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

而後在org.springframework.boot.spring-boot-actuator-autoconfigure包下去就能夠找到這個文件spring

自動裝配

查看這個文件發現引入了不少的配置類,這裏先關注一下XXXHealthIndicatorAutoConfiguration系列的類,這裏我們拿第一個RabbitHealthIndicatorAutoConfiguration爲例來解析一下。看名字就知道這個是RabbitMQ的健康檢查的自動配置類app

@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);
	}
}

按照以往的慣例,先解析註解ide

  1. @ConditionalOnXXX系列又出現了,前兩個就是說若是當前存在RabbitTemplate這個bean也就是說咱們的項目中使用到了RabbitMQ才能進行下去
  2. @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屬性,若是這個屬性尚未的話則設置默認值爲truespring-boot

當這裏返回true時整個RabbitHealthIndicatorAutoConfiguration類的自動配置才能繼續下去學習

  1. @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這個配置類是系統狀態相關的配置ui

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

	private List<String> order = null;

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

接着就是註冊了2個beanApplicationHealthIndicatorOrderedHealthAggregator 這兩個bean的做用稍後再說,如今回到RabbitHealthIndicatorAutoConfigurationthis

  1. @AutoConfigureAfter這個對總體邏輯沒影響,暫且不提
  2. 類中註冊了一個beanHealthIndicator這個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);
		}
	}

}
  1. 首先這裏注入了一個對象HealthAggregator,這個對象就是剛纔註冊的OrderedHealthAggregator
  2. 第一個createHealthIndicator方法執行邏輯爲:若是傳入的beans的size 爲1,則調用createHealthIndicator建立HealthIndicator 不然建立CompositeHealthIndicator,遍歷傳入的beans,依次建立HealthIndicator,加入到CompositeHealthIndicator
  3. 第二個createHealthIndicator的執行邏輯爲:得到CompositeHealthIndicatorConfiguration中的泛型參數根據泛型參數H對應的class和S對應的class,在H對應的class中找到聲明瞭參數爲S類型的構造器進行實例化
  4. 最後這裏建立出來的bean爲RabbitHealthIndicator
  5. 回憶起以前學習健康檢查的使用時,若是咱們須要自定義健康檢查項時通常的操做都是實現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等狀況應該也是這麼個操做lua

那麼接下來無非就是當有調用方訪問以下地址時,分別調用整個系統的全部的HealthIndicator的實現類的health方法便可了code

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的 1 而後都放入了其中一個聚合的實現類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的健康檢查實現原理所有解析完成

1

相關文章
相關標籤/搜索