Spring Boot 源碼分析(二)

Spring Boot 源碼分析 (二)

sschrodinger

2019/05/30


引用


基于 Spring boot 2.1.5.RELEASE 版本


Spring 項(xiàng)目監(jiān)控


Spring Boot 使用 Spring Boot Actuator 組件監(jiān)控應(yīng)用程序的運(yùn)行狀況。

通過(guò)在 Maven 中增加 spring-boot-starter-actuator 依賴(lài),可以快速增加 actuator 功能。

actuator 使用 REST 風(fēng)格的接口暴露了多個(gè)監(jiān)控端點(diǎn)。默認(rèn)使用 http:{your ip}/acturator/{end point} 來(lái)訪問(wèn)這些端點(diǎn)。

端點(diǎn) 描述 HTTP 方法 是否敏感
autoconfig 顯示自動(dòng)配置的信息 GET
beans 顯示應(yīng)用上下文程序 GET
configgroups 顯示所有 @ConfigurationProperties 的配置屬性列表 GET
dump 顯示線程活動(dòng)的快照 GET
env 顯示應(yīng)用的環(huán)境變量 GET
health 顯示應(yīng)用的健康指標(biāo),這些值由 HealthIndicator 的實(shí)現(xiàn)提供 GET
info 顯示應(yīng)用的信息 GET
metrics 顯示應(yīng)用的度量標(biāo)準(zhǔn)信息 GET
mapping 顯示所有 @RequestMapping 的路徑列表 GET
... ... ... ...

比如,在開(kāi)啟了 actuator 的服務(wù)器上,在地址欄輸入 URL http://localhost:8080/actuator/health,會(huì)回顯 {"status":"UP"}。

Actuator 實(shí)現(xiàn)原理

主要分為兩部分,第一部分是獲得信息、第二部分是創(chuàng)建 REST 風(fēng)格的端口以供用戶訪問(wèn)(Endpoint)。

以 health 端口為例,進(jìn)行分析。

獲取 health 信息

在 Actuator 中,健康信息被封裝成了類(lèi) Health,如下:

public final class Health {

    private final Status status;

    private final Map<String, Object> details;
    
    // ...
    // constructor
    // functional method
    // build model interface
}

public final class Status {


    public static final Status UNKNOWN = new Status("UNKNOWN");
    public static final Status UP = new Status("UP");
    public static final Status DOWN = new Status("DOWN");
    public static final Status OUT_OF_SERVICE = new Status("OUT_OF_SERVICE");

    private final String code;

    private final String description;
    
    // ...
}

健康信息只包含四種狀態(tài),即 UP(正常)、DOWN(不正常)、OUT_OF_SERVICE(停止服務(wù))和 UNKNOW(未知),每一種狀態(tài)都可以有自己的信息。

所有健康監(jiān)測(cè)的基類(lèi)都是 HealthIndicator,該接口根據(jù)信息返回當(dāng)前的健康狀態(tài),定義如下:

public interface HealthIndicator {

    Health health();

}

在該類(lèi)的基礎(chǔ)上,有一個(gè)通用的 HealthIndicator 抽象實(shí)現(xiàn) AbstractHealthIndicator,該抽象類(lèi)適合用于在檢測(cè)過(guò)程中如果拋出異常,則狀態(tài)自動(dòng)修改為 DOWN 的檢測(cè)邏輯,關(guān)鍵代碼如下:

public abstract class AbstractHealthIndicator implements HealthIndicator {

    private static final String NO_MESSAGE = null;

    private static final String DEFAULT_MESSAGE = "Health check failed";

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

    /**
     * Actual health check logic.
     * @param builder the {@link Builder} to report health status and details
     * @throws Exception any {@link Exception} that should create a {@link Status#DOWN}
     * system status.
     */
    protected abstract void doHealthCheck(Health.Builder builder) throws Exception;
    
    // ...

}

在該抽象類(lèi)的基礎(chǔ)上,Actuator 實(shí)現(xiàn)了多個(gè)檢測(cè)類(lèi),包括 DiskSpaceHealthIndicator、ApplicationHealthIndicatorDataSourceHealthIndicator 等。

DiskSpaceHealthIndicator 主要用于檢測(cè)磁盤(pán)空間是否小于給定閾值,通過(guò) File 類(lèi)的 getUsableSpace() 方法實(shí)現(xiàn),如下:

public class DiskSpaceHealthIndicator extends AbstractHealthIndicator {

    private final File path;

    private final DataSize threshold;

    @Override
    protected void doHealthCheck(Health.Builder builder) throws Exception {
        long diskFreeInBytes = this.path.getUsableSpace();
        if (diskFreeInBytes >= this.threshold.toBytes()) {
            builder.up();
        }
        else {
            logger.warn(String.format(
                    "Free disk space below threshold. "
                            + "Available: %d bytes (threshold: %s)",
                    diskFreeInBytes, this.threshold));
            builder.down();
        }
        builder.withDetail("total", this.path.getTotalSpace())
                .withDetail("free", diskFreeInBytes)
                .withDetail("threshold", this.threshold.toBytes());
    }

}

DataSourceHealthIndicator 主要用于檢測(cè)數(shù)據(jù)源的健康度,主要是使用一個(gè)給定的 sql 語(yǔ)句去測(cè)試數(shù)據(jù)庫(kù),如下:

public class DataSourceHealthIndicator extends AbstractHealthIndicator
        implements InitializingBean {

    private static final String DEFAULT_QUERY = "SELECT 1";

    private DataSource dataSource;

    private String query;

    private JdbcTemplate jdbcTemplate;

    @Override
    protected void doHealthCheck(Health.Builder builder) throws Exception {
        if (this.dataSource == null) {
            builder.up().withDetail("database", "unknown");
        }
        else {
            doDataSourceHealthCheck(builder);
        }
    }

    private void doDataSourceHealthCheck(Health.Builder builder) throws Exception {
        String product = getProduct();
        builder.up().withDetail("database", product);
        String validationQuery = getValidationQuery(product);
        if (StringUtils.hasText(validationQuery)) {
            List<Object> results = this.jdbcTemplate.query(validationQuery,
                    new SingleColumnRowMapper());
            Object result = DataAccessUtils.requiredSingleResult(results);
            builder.withDetail("hello", result);
        }
    }

    private String getProduct() {
        return this.jdbcTemplate.execute((ConnectionCallback<String>) this::getProduct);
    }

    private String getProduct(Connection connection) throws SQLException {
        return connection.getMetaData().getDatabaseProductName();
    }

    protected String getValidationQuery(String product) {
        String query = this.query;
        if (!StringUtils.hasText(query)) {
            DatabaseDriver specific = DatabaseDriver.fromProductName(product);
            query = specific.getValidationQuery();
        }
        if (!StringUtils.hasText(query)) {
            query = DEFAULT_QUERY;
        }
        return query;
    }

    /**
     * Set the {@link DataSource} to use.
     * @param dataSource the data source
     */
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    private static class SingleColumnRowMapper implements RowMapper<Object> {

        @Override
        public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
            ResultSetMetaData metaData = rs.getMetaData();
            int columns = metaData.getColumnCount();
            if (columns != 1) {
                throw new IncorrectResultSetColumnCountException(1, columns);
            }
            return JdbcUtils.getResultSetValue(rs, 1);
        }

    }

}

ApplicationHealthIndicator 用于檢測(cè)應(yīng)用整體的狀態(tài),這里會(huì)直接返回 UP

public class ApplicationHealthIndicator extends AbstractHealthIndicator {

    public ApplicationHealthIndicator() {
        super("Application health check failed");
    }

    @Override
    protected void doHealthCheck(Health.Builder builder) throws Exception {
        builder.up();
    }

}

同時(shí),在基類(lèi) HealthIndicator 的基礎(chǔ)上,實(shí)現(xiàn)了 CompositeHealthIndicator 類(lèi),這是一個(gè)組合類(lèi),用于聚合多個(gè)狀態(tài)信息的結(jié)果(對(duì)所有狀態(tài)進(jìn)行聚合,并按照 Status.DOWN, Status.OUT_OF_SERVICE, Status.UP, Status.UNKNOWN 進(jìn)行排序,返回第一個(gè)結(jié)果)。

同理,beans 的檢測(cè)主要是 BeansEndpoint 持有一個(gè) ConfigurableApplicationContext 的引用實(shí)例,顯示所有加載的 bean。

Endpoint 實(shí)現(xiàn)

Endpoint 是 Actuator 提供的用于接口訪問(wèn)的包,在 org.springframework.boot.actuate.endpoint 中還有 2 個(gè)子包 -jmx (可通過(guò) jmx 協(xié)議訪問(wèn)),mvc(通過(guò) spring mvc 暴露)。

首先看 EndPoint 的基類(lèi),如下:

public interface ExposableEndpoint<O extends Operation> {

    EndpointId getEndpointId();
    boolean isEnableByDefault();
    Collection<O> getOperations();

}

public interface Operation {

    OperationType getType();
    Object invoke(InvocationContext context);

}

public enum OperationType {

    READ,
    WRITE,
    DELETE

}

在基類(lèi) ExposableEndpoint 之下,拓展了多個(gè)接口和類(lèi),如下:

// 用于標(biāo)注可否被其他 EndpointDiscoverer 發(fā)現(xiàn)
public interface DiscoveredEndpoint<O extends Operation> extends ExposableEndpoint<O> {

    boolean wasDiscoveredBy(Class<? extends EndpointDiscoverer<?, ?>> discoverer);

    Object getEndpointBean();

}

public abstract class AbstractDiscoveredEndpoint<O extends Operation>
        extends AbstractExposableEndpoint<O> implements DiscoveredEndpoint<O> {
    private final EndpointDiscoverer<?, ?> discoverer;

    private final Object endpointBean;

    @Override
    public boolean wasDiscoveredBy(Class<? extends EndpointDiscoverer<?, ?>> discoverer) {
        // 判斷該 EndPoint 是否能被 EndpointDiscoverer 發(fā)現(xiàn)
        return discoverer.isInstance(this.discoverer);
    }
    protected void appendFields(ToStringCreator creator) {
    }

}

public abstract class AbstractExposableEndpoint<O extends Operation>
        implements ExposableEndpoint<O> {

    private final EndpointId id;

    private boolean enabledByDefault;

    private List<O> operations;

    public AbstractExposableEndpoint(EndpointId id, boolean enabledByDefault,
            Collection<? extends O> operations) {
        // ...
    }
    
    // ...

}

public abstract class AbstractDiscoveredEndpoint<O extends Operation>
        extends AbstractExposableEndpoint<O> implements DiscoveredEndpoint<O> {

    private final EndpointDiscoverer<?, ?> discoverer;

    private final Object endpointBean;

    public AbstractDiscoveredEndpoint(EndpointDiscoverer<?, ?> discoverer,
            Object endpointBean, EndpointId id, boolean enabledByDefault,
            Collection<? extends O> operations) {
        // ...
    }

}

class DiscoveredWebEndpoint extends AbstractDiscoveredEndpoint<WebOperation>
        implements ExposableWebEndpoint {

    private final String rootPath;

    DiscoveredWebEndpoint(EndpointDiscoverer<?, ?> discoverer, Object endpointBean,
            EndpointId id, String rootPath, boolean enabledByDefault,
            Collection<WebOperation> operations) {
        super(discoverer, endpointBean, id, enabledByDefault, operations);
        this.rootPath = rootPath;
    }

    @Override
    public String getRootPath() {
        return this.rootPath;
    }

}

// ...

Actuator 的核心思想就是通過(guò)在 Mvc 中注冊(cè) WebMvcEndpointHandlerMapping,類(lèi)似于編寫(xiě) @Controller,每一個(gè) @Controller 調(diào)用對(duì)應(yīng) EndPointOperation 中的 invoke 方法,來(lái)執(zhí)行監(jiān)控作用。

首先介紹 EndpointMapping,該類(lèi)的作用是根據(jù)一個(gè) path 返回一個(gè)規(guī)范化的 path。如下:

public class EndpointMapping {

    private final String path;

    public EndpointMapping(String path) {
        this.path = normalizePath(path);
    }

    public String getPath() {
        return this.path;
    }

    public String createSubPath(String path) {
        return this.path + normalizePath(path);
    }

    private static String normalizePath(String path) {
        if (!StringUtils.hasText(path)) {
            return path;
        }
        String normalizedPath = path;
        if (!normalizedPath.startsWith("/")) {
            normalizedPath = "/" + normalizedPath;
        }
        if (normalizedPath.endsWith("/")) {
            normalizedPath = normalizedPath.substring(0, normalizedPath.length() - 1);
        }
        return normalizedPath;
    }

}

RequestMappingInfoHandlerMappingWebMvcEndpointHandlerMapping 的基類(lèi),主要作用就是將一個(gè)方法和一個(gè) url 組合起來(lái),如下例子:

@RequestMapping(value = "url_1", method = RequestMethod.GET)
public String method_1() {
    
}
@RequestMapping(value = "url_2", method = RequestMethod.GET)
public String method_2() {
    
}

RequestMappingInfoHandlerMapping 中,會(huì)將如上所示的使用 @RequestMapping 注解包括的信息封裝成 RequestMappingInfo,包括了 url 地址,請(qǐng)求方法等信息,最后根據(jù)這些信息選擇合適的 handler,即選擇一個(gè)合適的方法,在這里是 method_1() 或者 method_2() 對(duì)連接進(jìn)行處理(這中間包括了很多參數(shù)的封裝,略過(guò)),對(duì)應(yīng)關(guān)系如下圖:

RequestMappingInfo("url_1", get) ------->>------ method_1()
RequestMappingInfo("url_2", get) ------->>------ method_2()

RequestMappingInfoHandlerMapping 繼承自 AbstractHandlerMethodMapping<T>,實(shí)現(xiàn)了 InitializingBean 接口的 afterPropertiesSet() 方法,該方法會(huì)在設(shè)置了所有的屬性之后自動(dòng)調(diào)用,在 AbstractHandlerMethodMapping<T> 的實(shí)現(xiàn)中,只是在該方法中調(diào)用了一個(gè)抽象函數(shù),initHandlerMethods(),用于初始化 HandlerMethods。

@Override
public void afterPropertiesSet() {
    initHandlerMethods();
}

AbstractWebMvcEndpointHandlerMappingWebMvcEndpointHandlerMapping 的直接父類(lèi),先看該類(lèi)持有的變量:

// 用于轉(zhuǎn)換 url
private final EndpointMapping endpointMapping;

// 所持有的 endpoint
private final Collection<ExposableWebEndpoint> endpoints;

private final EndpointMediaTypes endpointMediaTypes;

private final CorsConfiguration corsConfiguration;

// handler method
private final Method handleMethod = ReflectionUtils.findMethod(OperationHandler.class,
        "handle", HttpServletRequest.class, Map.class);

private static final RequestMappingInfo.BuilderConfiguration builderConfig = getBuilderConfig();

最重要的為上面注釋的三個(gè)變量,其中 handleMethod 指的是在 OperationHandler 這個(gè)類(lèi)中,名字為 handle,參數(shù)為 HttpServletRequestMap 的方法,

private final class OperationHandler {

    private final ServletWebOperation operation;

    OperationHandler(ServletWebOperation operation) {this.operation = operation;}

    @ResponseBody
    public Object handle(HttpServletRequest request,
            @RequestBody(required = false) Map<String, String> body) {
        return this.operation.handle(request, body);
    }

}

由上的定義,可以在運(yùn)行時(shí),根據(jù) operation 的不同,返回不同的 @ResponseBody。根據(jù)此,我們可以猜測(cè)所有的監(jiān)控都會(huì)被封裝在 Opretion 中,監(jiān)控提供的端點(diǎn) url 都封裝在 AbstractEndpint 中。

再看 initHandlerMethods 方法,如下:

protected void initHandlerMethods() {
    for (ExposableWebEndpoint endpoint : this.endpoints) {
        for (WebOperation operation : endpoint.getOperations()) {
            registerMappingForOperation(endpoint, operation);
        }
    }
    if (StringUtils.hasText(this.endpointMapping.getPath())) {
        registerLinksMapping();
    }
}

最主要的是注冊(cè) registerMappingForOperation,該方法如下:

private void registerMappingForOperation(ExposableWebEndpoint endpoint,
        WebOperation operation) {
    ServletWebOperation servletWebOperation = wrapServletWebOperation(endpoint,
            operation, new ServletWebOperationAdapter(operation));
    registerMapping(createRequestMappingInfo(operation),
            new OperationHandler(servletWebOperation), this.handleMethod);
}

將 opretion 和 endpoint 包裝成 ServletWebOperation,然后注冊(cè)到 mapping 中。

AbstractHealthIndicatorhealth() 方法處設(shè)置斷點(diǎn),
可以得到如下的棧:

health:82, AbstractHealthIndicator (org.springframework.boot.actuate.health)
health:98, CompositeHealthIndicator (org.springframework.boot.actuate.health)
health:50, HealthEndpoint (org.springframework.boot.actuate.health)
health:54, HealthEndpointWebExtension (org.springframework.boot.actuate.health) ====>>>====== 該處正式調(diào)用 health 的方法
-------------------- 中間過(guò)程 \/--------------------------
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeMethod:282, ReflectionUtils (org.springframework.util)
invoke:76, ReflectiveOperationInvoker (org.springframework.boot.actuate.endpoint.invoke.reflect)
invoke:61, AbstractDiscoveredOperation (org.springframework.boot.actuate.endpoint.annotation)
-------------------- 中間過(guò)程 /\--------------------------
handle:294, AbstractWebMvcEndpointHandlerMapping$ServletWebOperationAdapter (org.springframework.boot.actuate.endpoint.web.servlet) ====>>>====== 該處開(kāi)始調(diào)用 health 的方法
handle:355, AbstractWebMvcEndpointHandlerMapping$OperationHandler (org.springframework.boot.actuate.endpoint.web.servlet)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
doInvoke:190, InvocableHandlerMethod (org.springframework.web.method.support)
invokeForRequest:138, InvocableHandlerMethod (org.springframework.web.method.support)
invokeAndHandle:104, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation)
invokeHandlerMethod:892, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handleInternal:797, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handle:87, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)
doDispatch:1039, DispatcherServlet (org.springframework.web.servlet)
// ...

note

  • 在 sping 中,允許多個(gè) handlerMapping 同時(shí)運(yùn)行,DispatcherServlet 根據(jù)優(yōu)先級(jí)優(yōu)先使用優(yōu)先級(jí)在前的 HandlerMapping。如果當(dāng)前的HandlerMapping能夠返回可用的 Handler,DispatcherServlet 則使用當(dāng)前返回的 Handler 進(jìn)行 Web 請(qǐng)求的處理
  • WebMvcEndpointHandlerMapping 設(shè)置優(yōu)先級(jí)為 -100,所以會(huì)有優(yōu)先運(yùn)行的權(quán)力。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容