SpringBoot健康檢查實(shí)現(xiàn)原理

相信看完之前文章的同學(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);
    }
}

按照以往的慣例坎弯,先解析注解

  1. @ConditionalOnXXX系列又出現(xiàn)了纺涤,前兩個(gè)就是說如果當(dāng)前存在RabbitTemplate這個(gè)bean也就是說我們的項(xiàng)目中使用到了RabbitMQ才能進(jìn)行下去
  2. @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ù)下去

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

}

首先這個(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è)beanApplicationHealthIndicatorOrderedHealthAggregator
這兩個(gè)bean的作用稍后再說拧咳,現(xiàn)在回到RabbitHealthIndicatorAutoConfiguration

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

}
  1. 首先這里注入了一個(gè)對象HealthAggregator囚灼,這個(gè)對象就是剛才注冊的OrderedHealthAggregator
  2. 第一個(gè)createHealthIndicator方法執(zhí)行邏輯為:如果傳入的beans的size 為1,則調(diào)用createHealthIndicator創(chuàng)建HealthIndicator
    否則創(chuàng)建CompositeHealthIndicator,遍歷傳入的beans,依次創(chuàng)建HealthIndicator,加入到CompositeHealthIndicator
  3. 第二個(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í)例化
  4. 最后這里創(chuàng)建出來的bean為RabbitHealthIndicator
  5. 回憶起之前學(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)原理全部解析完成

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末座每,一起剝皮案震驚了整個(gè)濱河市前鹅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌峭梳,老刑警劉巖舰绘,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異葱椭,居然都是意外死亡捂寿,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門孵运,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秦陋,“玉大人,你說我怎么就攤上這事治笨〔蹈牛” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵旷赖,是天一觀的道長顺又。 經(jīng)常有香客問我,道長等孵,這世上最難降的妖魔是什么稚照? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮俯萌,結(jié)果婚禮上果录,老公的妹妹穿的比我還像新娘。我一直安慰自己咐熙,他們只是感情好雕憔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著糖声,像睡著了一般斤彼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蘸泻,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天琉苇,我揣著相機(jī)與錄音,去河邊找鬼悦施。 笑死并扇,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的抡诞。 我是一名探鬼主播穷蛹,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼土陪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了肴熏?” 一聲冷哼從身側(cè)響起鬼雀,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蛙吏,沒想到半個(gè)月后源哩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鸦做,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年励烦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泼诱。...
    茶點(diǎn)故事閱讀 39,919評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡坛掠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出治筒,到底是詐尸還是另有隱情却音,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布矢炼,位于F島的核電站,受9級特大地震影響阿纤,放射性物質(zhì)發(fā)生泄漏句灌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一欠拾、第九天 我趴在偏房一處隱蔽的房頂上張望胰锌。 院中可真熱鬧,春花似錦藐窄、人聲如沸资昧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽格带。三九已至,卻和暖如春刹枉,著一層夾襖步出監(jiān)牢的瞬間叽唱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工微宝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留棺亭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓蟋软,卻偏偏與公主長得像镶摘,于是被迫代替她去往敵國和親嗽桩。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容