相信看完之前文章的同學(xué)都知道了SpringBoot自動裝配的套路了,直接看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è)文件
自動裝配
查看這個(gè)文件發(fā)現(xiàn)引入了很多的配置類率碾,這里先關(guān)注一下XXXHealthIndicatorAutoConfiguration
系列的類谴供,這里咱們拿第一個(gè)RabbitHealthIndicatorAutoConfiguration
為例來解析一下框喳〈蕉遥看名字就知道這個(gè)是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
系列又出現(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án)境變量中查找是否存在management.health.rabbit.enabled
屬性,如果沒有的話則去查找management.health.defaults.enabled
屬性崎脉,如果這個(gè)屬性還沒有的話則設(shè)置默認(rèn)值為true
當(dāng)這里返回true時(shí)整個(gè)RabbitHealthIndicatorAutoConfiguration
類的自動配置才能繼續(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<>();
}
接著就是注冊了2個(gè)beanApplicationHealthIndicator
和OrderedHealthAggregator
這兩個(gè)bean的作用稍后再說拧咳,現(xiàn)在回到RabbitHealthIndicatorAutoConfiguration
類
-
@AutoConfigureAfter
這個(gè)對整體邏輯沒影響,暫且不提 - 類中注冊了一個(gè)bean
HealthIndicator
這個(gè)bean的創(chuàng)建邏輯是在父類中的
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è)對象
HealthAggregator
囚灼,這個(gè)對象就是剛才注冊的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對應(yīng)的class和S對應(yīng)的class,在H對應(yīng)的class中找到聲明了參數(shù)為S類型的構(gòu)造器進(jìn)行實(shí)例化 - 最后這里創(chuàng)建出來的bean為
RabbitHealthIndicator
- 回憶起之前學(xué)習(xí)健康檢查的使用時(shí)骆膝,如果我們需要自定義健康檢查項(xiàng)時(shí)一般的操作都是實(shí)現(xiàn)
HealthIndicator
接口,由此可以猜測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碎连,也就是處理如下請求的
http://ip:port/actuator/health
那么首先看一下它的構(gòu)造方法傳入的是個(gè)啥對象吧
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)建好了破花,那么只剩下最后一步處理請求了
@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());
}
//對結(jié)果集排序
return this.healthAggregator.aggregate(healths);
}
至此SpringBoot的健康檢查實(shí)現(xiàn)原理全部解析完成