美文网首页
Spring Boot 源码分析(二)

Spring Boot 源码分析(二)

作者: sschrodinger | 来源:发表于2019-08-21 11:04 被阅读0次

Spring Boot 源码分析 (二)

sschrodinger

2019/05/30


引用


基于 Spring boot 2.1.5.RELEASE 版本


Spring 项目监控


Spring Boot 使用 Spring Boot Actuator 组件监控应用程序的运行状况。

通过在 Maven 中增加 spring-boot-starter-actuator 依赖,可以快速增加 actuator 功能。

actuator 使用 REST 风格的接口暴露了多个监控端点。默认使用 http:{your ip}/acturator/{end point} 来访问这些端点。

端点 描述 HTTP 方法 是否敏感
autoconfig 显示自动配置的信息 GET
beans 显示应用上下文程序 GET
configgroups 显示所有 @ConfigurationProperties 的配置属性列表 GET
dump 显示线程活动的快照 GET
env 显示应用的环境变量 GET
health 显示应用的健康指标,这些值由 HealthIndicator 的实现提供 GET
info 显示应用的信息 GET
metrics 显示应用的度量标准信息 GET
mapping 显示所有 @RequestMapping 的路径列表 GET
... ... ... ...

比如,在开启了 actuator 的服务器上,在地址栏输入 URL http://localhost:8080/actuator/health,会回显 {"status":"UP"}

Actuator 实现原理

主要分为两部分,第一部分是获得信息、第二部分是创建 REST 风格的端口以供用户访问(Endpoint)。

以 health 端口为例,进行分析。

获取 health 信息

在 Actuator 中,健康信息被封装成了类 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;
    
    // ...
}

健康信息只包含四种状态,即 UP(正常)、DOWN(不正常)、OUT_OF_SERVICE(停止服务)和 UNKNOW(未知),每一种状态都可以有自己的信息。

所有健康监测的基类都是 HealthIndicator,该接口根据信息返回当前的健康状态,定义如下:

public interface HealthIndicator {

    Health health();

}

在该类的基础上,有一个通用的 HealthIndicator 抽象实现 AbstractHealthIndicator,该抽象类适合用于在检测过程中如果抛出异常,则状态自动修改为 DOWN 的检测逻辑,关键代码如下:

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;
    
    // ...

}

在该抽象类的基础上,Actuator 实现了多个检测类,包括 DiskSpaceHealthIndicatorApplicationHealthIndicatorDataSourceHealthIndicator 等。

DiskSpaceHealthIndicator 主要用于检测磁盘空间是否小于给定阈值,通过 File 类的 getUsableSpace() 方法实现,如下:

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 主要用于检测数据源的健康度,主要是使用一个给定的 sql 语句去测试数据库,如下:

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 用于检测应用整体的状态,这里会直接返回 UP

public class ApplicationHealthIndicator extends AbstractHealthIndicator {

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

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

}

同时,在基类 HealthIndicator 的基础上,实现了 CompositeHealthIndicator 类,这是一个组合类,用于聚合多个状态信息的结果(对所有状态进行聚合,并按照 Status.DOWN, Status.OUT_OF_SERVICE, Status.UP, Status.UNKNOWN 进行排序,返回第一个结果)。

同理,beans 的检测主要是 BeansEndpoint 持有一个 ConfigurableApplicationContext 的引用实例,显示所有加载的 bean。

Endpoint 实现

Endpoint 是 Actuator 提供的用于接口访问的包,在 org.springframework.boot.actuate.endpoint 中还有 2 个子包 -jmx (可通过 jmx 协议访问),mvc(通过 spring mvc 暴露)。

首先看 EndPoint 的基类,如下:

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

}

在基类 ExposableEndpoint 之下,拓展了多个接口和类,如下:

// 用于标注可否被其他 EndpointDiscoverer 发现
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 发现
        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 的核心思想就是通过在 Mvc 中注册 WebMvcEndpointHandlerMapping,类似于编写 @Controller,每一个 @Controller 调用对应 EndPointOperation 中的 invoke 方法,来执行监控作用。

首先介绍 EndpointMapping,该类的作用是根据一个 path 返回一个规范化的 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 的基类,主要作用就是将一个方法和一个 url 组合起来,如下例子:

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

RequestMappingInfoHandlerMapping 中,会将如上所示的使用 @RequestMapping 注解包括的信息封装成 RequestMappingInfo,包括了 url 地址,请求方法等信息,最后根据这些信息选择合适的 handler,即选择一个合适的方法,在这里是 method_1() 或者 method_2() 对连接进行处理(这中间包括了很多参数的封装,略过),对应关系如下图:

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

RequestMappingInfoHandlerMapping 继承自 AbstractHandlerMethodMapping<T>,实现了 InitializingBean 接口的 afterPropertiesSet() 方法,该方法会在设置了所有的属性之后自动调用,在 AbstractHandlerMethodMapping<T> 的实现中,只是在该方法中调用了一个抽象函数,initHandlerMethods(),用于初始化 HandlerMethods。

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

AbstractWebMvcEndpointHandlerMappingWebMvcEndpointHandlerMapping 的直接父类,先看该类持有的变量:

// 用于转换 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();

最重要的为上面注释的三个变量,其中 handleMethod 指的是在 OperationHandler 这个类中,名字为 handle,参数为 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);
    }

}

由上的定义,可以在运行时,根据 operation 的不同,返回不同的 @ResponseBody。根据此,我们可以猜测所有的监控都会被封装在 Opretion 中,监控提供的端点 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();
    }
}

最主要的是注册 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,然后注册到 mapping 中。

AbstractHealthIndicatorhealth() 方法处设置断点,
可以得到如下的栈:

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) ====>>>====== 该处正式调用 health 的方法
-------------------- 中间过程 \/--------------------------
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)
-------------------- 中间过程 /\--------------------------
handle:294, AbstractWebMvcEndpointHandlerMapping$ServletWebOperationAdapter (org.springframework.boot.actuate.endpoint.web.servlet) ====>>>====== 该处开始调用 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 中,允许多个 handlerMapping 同时运行,DispatcherServlet 根据优先级优先使用优先级在前的 HandlerMapping。如果当前的HandlerMapping能够返回可用的 HandlerDispatcherServlet 则使用当前返回的 Handler 进行 Web 请求的处理
  • WebMvcEndpointHandlerMapping 设置优先级为 -100,所以会有优先运行的权力。

相关文章

网友评论

      本文标题:Spring Boot 源码分析(二)

      本文链接:https://www.haomeiwen.com/subject/xskbsctx.html