美文网首页
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