【官方文档翻译】SpringBootActuator生产特性

翻译自 spring-boot 官方文档(版本:2.2.2.RELEASE)
Spring Boot包含了许多附加功能,可以帮助您在将应用程序推送到生产环境时监视和管理它。您可以选择使用HTTP端点或与JMX一起管理和监视应用程序。审核、运行状况和度量数据收集也可以自动应用于您的应用程序。
启用生产特性
spring-boot-actuator 模块提供所有 SpringBoot 生产特性。启动这些特性的最简单的方式就是引入此依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
端点
actuator 端点允许你监控应用程序并与之互动。Spring Boot包含许多内置端点,允许你添加自己的端点。例如,health 端点提供基本的应用程序健康信息。
每个端点都可以启用或禁用。这控制了端点是否被创建并且它的bean是否存在于应用程序上下文中。要远程访问端点,还必须通过JMX或HTTP暴露端点。大多数应用程序选择HTTP,其中端点的ID和前缀 /actuator 被映射到URL。例如,默认情况下,health 端点映射到 /actuator/health。
以下列出了可用端点:
| ID | 说明 |
|---|---|
| auditevents | 暴露当前应用程序的审核事件信息。需要 AuditEventRepository bean |
| beans | 显示应用程序中所有 spring bean 的完整列表。 |
| caches | 暴露可用缓存 |
| conditions | 显示在配置和自动配置类上评估的条件,以及它们匹配或不匹配的原因。 |
| configprops | 显示所有 @ConfigurationProperties 的整理列表。 |
| env | 暴露 Spring ConfigurableEnvironment 属性 |
| flyway | 显示已应用的所有 Flyway 数据库迁移。需要 Flyway beans |
| health | 显示应用健康信息 |
| httptrace | 显示HTTP跟踪信息(默认情况下,最后100个HTTP请求-响应交换)。需要 HttpTraceRepository bean |
| info | 显示任意应用程序信息 |
| integrationgraph | 显示了Spring集成图。需要依赖 spring-integration-core 依赖 |
| loggers | 显示并修改应用程序中 loggers 的配置 |
| liquibase | 显示已应用的任何 Liquibase 数据库迁移。需要 Liquibase bean |
| metrics | 显示当前应用的度量信息 |
| mappings | 显示所有 @RequestMapping 路径的整理列表 |
| scheduledtasks | 显示应用的定时器任务 |
| sessions | 允许从支持Spring session的 session存储中检索和删除用户 session。需要使用Spring会话的基于Servlet的web应用程序 |
| threaddump | 执行 thread dump |
如果是 web 应用(Spring MVC、Spring WebFlux、Jersey),还可以使用以下端点:
| ID | 说明 |
|---|---|
| heapdump | 返回 hprof 堆 dump 文件 |
| jolokia | 通过HTTP暴露 jmx bean(当 Jolokia 位于类路径上时,WebFlux不可用)。需要依赖 jolokia-core |
| logfile | 返回日志文件的内容(如果已设置 logging.file.name 或 logging.file.path 属性)。支持使用HTTP Range header 来检索日志文件的部分内容 |
| prometheus | 以 Prometheus 服务器可以擦除的格式暴露 metric。需要依赖 micrometer-registry-prometheus |
开启端点
默认情况下,除了 shutdown 以外的所有端点都可用。要配置端点的启用,使用其 management.endpoint.<id>.enabled 属性。以下示例启用 shutdown 端点:
management.endpoint.shutdown.enabled=true
如果您希望端点启用是选择可用而不是选择不可用,将 management.endpoints.enabled-by-default 属性设置为 false ,并使用各个端点的 enabled 属性选择重新启用。以下示例启用 info 端点并禁用所有其他端点:
management.endpoints.enabled-by-default=false
management.endpoint.info.enabled=true
注意:禁用的端点将完全从应用程序上下文中删除。如果只想更改端点暴露的技术,请改用
include和exclude属性。
暴露端点
由于端点可能包含敏感信息,因此应仔细考虑何时暴露它们。下表显示了内置端点的默认开启情况:
JMX端点基本都暴露,Web端点基本不暴露(除了 health 和 info)

要更改暴露的端点,使用以下 include 和 exclude 属性:

include 属性列出暴露端点的id。exclude 属性列出不应不应暴露端点的ID。 exclude 属性优先于 include 属性。include 和 exclude 属性都可以配置同一个端点id。
例如,要取消通过JMX暴露所有端点,并且只暴露 health 和 info 端点,请使用以下属性:
management.endpoints.jmx.exposure.include=health,info
* 可以用来表示所有端点。例如,要通过 HTTP 暴露除 env 和 beans 端点之外的所有内容,请使用以下属性:
management.endpoints.web.exposure.include=*
management.endpoints.web.exposure.exclude=env,beans
注意:* 号在YAML中有特殊含义,因此如果要包括(或排除)所有端点,请确保添加引号,如下例所示:
management:
endpoints:
web:
exposure:
include: "*"
注意:如果应用是暴露在公共环境中,强烈建议保护你的应用端点。
如果想自定义端点暴露策略,可以注册一个 EndpointFilter bean。
保护 HTTP 端点
应该像保护其他敏感URL一样保护HTTP端点。如果存在Spring Security,那么在默认情况下,使用 SpringSecurity 的内容协商策略来保护端点。如果希望为 HTTP 端点配置自定义安全性,例如,只允许具有特定角色的用户访问它们,那么 Spring Boot 提供了一些方便的 RequestMatcher 对象,可以与 Spring Security 结合使用。
典型的 Spring Security 配置可能类似于以下示例:
@Configuration(proxyBeanMethods = false)
public class ActuatorSecurity extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatcher(EndpointRequest.toAnyEndpoint()).authorizeRequests((requests) ->
requests.anyRequest().hasRole("ENDPOINT_ADMIN"));
http.httpBasic();
}
}
上面的示例使用 EndpointRequest.toAnyEndpoint() 将请求与所有端点匹配,然后确保所有端点都具有 ENDPOINT_ADMIN 角色。EndpointRequest 上还提供了其他几种匹配方法。有关详细信息,请参阅API文档(HTML或PDF)。
如果在防火墙后部署应用程序,您可能希望可以访问所有 actuator 端点,而不需要身份验证。可以通过更改 management.endpoints.web.exposure.include 属性来执行此操作,如下所示:
management.endpoints.web.exposure.include=*
此外,如果存在Spring Security,则需要添加自定义安全配置,以允许未经身份验证的对端点的访问,如以下示例所示:
@Configuration(proxyBeanMethods = false)
public class ActuatorSecurity extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatcher(EndpointRequest.toAnyEndpoint()).authorizeRequests((requests) ->
requests.anyRequest().permitAll());
}
}
配置端点
端点对不带任何参数的读取操作的响应自动缓存。要配置端点缓存响应的时间量,使用其 cache.time-to-live 属性。以下示例将bean端点缓存的生存时间设置为10秒:
# application.properties
management.endpoint.beans.cache.time-to-live=10s
注意:
management.endpoint.<name>前缀用来唯一标识正在配置的端点。
当发出经过身份验证的HTTP请求时,Principal被视为端点的输入,因此,响应将不会被缓存。
Actuator web 端点超媒体
添加了一个“发现页”,其中包含指向所有端点的链接。默认情况下,“发现页”在 /actuator 可用。
配置自定义管理上下文路径时,“发现页”会自动从 /actuator 移动到管理上下文的根目录。例如,如果管理上下文路径是 /management ,那么可以从 /management 获得发现页。当管理上下文路径设置为 / 时,将禁用发现页,以防止与其他映射发生冲突。
CORS 跨域支持
跨域资源共享(CORS)是一个 W3C 规范,它允许您灵活地指定什么样的跨域请求被授权。如果使用Spring MVC 或 Spring WebFlux,则可以配置 actuator 的 web 端点来支持此类场景。
默认情况下禁用CORS支持,并且仅在设置了 management.endpoints.web.cors.allowed-origins 属性后才启用。以下配置允许从 example.com 域进行 GET 和 POST 调用:
management.endpoints.web.cors.allowed-origins=https://example.com
management.endpoints.web.cors.allowed-methods=GET,POST
有关配置的完整项可以查看 CorsEndpointProperties.java
实现自定义端点
如果添加了一个使用 @Endpoint 的 @Bean,则用 @ReadOperation、@WriteOperation 或 @DeleteOperation 注解的任何方法都将自动通过JMX暴露,在web应用程序中,也可以通过HTTP暴露。端点可以使用Jersey、Spring MVC 或 Spring WebFlux 通过 HTTP 暴露。如果 Jersey 和 Spring MVC 都可用,那么将使用Spring MVC。
还可以使用 @JmxEndpoint 或 @WebEndpoint 编写特定于技术的端点。这些端点仅限于其各自的技术。例如,@WebEndpoint 仅通过 HTTP 而不是 JMX 暴露。
可以使用 @EndpointWebExtension 和 @EndpointJmxExtension 编写特定于技术的扩展。这些注解允许你供特定于技术的操作以扩充现有端点。
最后,如果需要访问特定于 web 框架的功能,可以实现 Servlet 或 Spring @Controller 和 @RestController 端点,但代价是它们不能通过JMX使用,或者使用不同的web框架。
接收输入
端点上的操作通过其参数接收输入。当通过 web 暴露时,这些参数的值取自URL的查询参数和JSON request body。当通过 JMX 暴露时,参数被映射到 MBean 操作的参数。默认情况下,参数是必需的。通过使用 @org.springframework.lang.Nullable 对它们进行注解,可以使它们成为可选的。
JSON请求体中的每个根属性都可以映射到端点的一个参数。考虑以下JSON请求体:
{
"name": "test",
"counter": 42
}
这可用于调用采用String name 和 int counter 参数的写操作。
由于端点与技术无关,因此只能在方法签名中指定简单类型。特别是,不支持使用定义 name 和 counter 属性的自定义类来声明单个参数。
注意:为了允许将输入映射到操作方法的参数,实现端点的 Java 代码应使用
-parameters编译,实现端点的 Kotlin 代码应使用-java-parameters编译。如果使用的是 SpringBoot 的Gradle插件,或者使用的是 Maven 和spring-boot-starter-parent插件,那么这将自动发生。
输入类型转换
如果需要,传递给端点操作方法的参数将自动转换为所需类型。在调用操作方法之前,通过 JMX 或 HTTP 请求接收的输入将使用 ApplicationConversionService 的实例以及使用 @EndpointConverter 限定的任何 Converter 或 GenericConverter bean转换为所需的类型。
自定义 web 端点
在 @Endpoint、@WebEndpoint 或 @EndpointWebExtension 上的操作将使用Jersey、Spring MVC 或 Spring WebFlux 通过 HTTP 自动暴露。如果Jersey和Spring MVC都可用,那么将使用Spring MVC。
Web 端点请求的 Predicates
为 web 暴露端点上的每个操作自动生成请求predicate。
path
predicate 的 path 由端点的 ID 和 web 暴露端点的基本路径决定。默认的基本路径是 /actuator。例如,具有ID sessions 的端点将在 predicate 中使用 /actuator/sessions 作为其路径。
可以通过使用@Selector 注解操作方法的一个或多个参数来进一步自定义路径。这样的参数作为路径变量添加到 path predicate 中。调用端点操作时,变量的值将传递到操作方法中。如果要捕获所有剩余的 path 元素,可以将 @Selector(Match=ALL_REMAINING) 添加到最后一个参数中,并使其成为与 String[] 兼容的转换类型。
HTTP 方法
predicate 的HTTP方法由操作类型决定,如下表所示:
| 操作 | HTTP 方法 |
|---|---|
| @ReadOperation | GET |
| @WriteOperation | POST |
| @DeleteOperation | DELETE |
consumes
对于使用 request body 的 @WriteOperation(HTTP POST),predicate 的 consumes 条款是 application/vnd.spring-boot.actuator.v2+json, application/json。对于所有其他操作,consumes 条款为空。
produces
predicate 的 produces 条款可以由 @DeleteOperation、@ReadOperation 和 @WriteOperation 注解的 produces 属性确定。该属性是可选的。如果不使用,则自动确定products条款。
如果操作方法返回 void 或 Void,则 products 条款为空。如果操作方法返回 org.springframework.core.io.Resource,则 produces 条款是 application/octet stream。对于所有其他操作,produces 条款是 application/vnd.spring-boot.actuator.v2+json, application/json。
Web 端点响应状态
端点操作的默认响应状态取决于操作类型(读、写或删除)以及操作返回的内容(如果有)。
@ReadOperation 返回一个值,响应状态为200(OK)。如果不返回值,则响应状态为404(Not Found)。
如果 @WriteOperation 或 @deleteOperation 返回一个值,则响应状态将为200(OK)。如果不返回值,则响应状态将为204(No Content)。
如果调用的操作没有传必需的参数,或者参数不能转换为必需的类型,则不会调用操作方法,响应状态将为400(Bad Request)。
Web 端点范围请求
HTTP 范围请求可用于请求 HTTP 资源的一部分。当使用Spring MVC或Spring WebFlux 时,返回 org.springframework.core.io.Resource 的操作会自动支持范围请求。
注意:使用 Jersey 时不支持范围请求
Web 端点安全
对 web 端点或特定于 web 的端点扩展的操作可以接收当前的 java.security.Principal 或 org.springframework.boot.actuate.endpoint.SecurityContext 作为方法参数。前者通常与 @Nullable 结合使用,为经过身份验证和未经身份验证的用户提供不同的行为。后者通常用于使用其 isUserInRole(String) 方法执行授权检查。
servlet 端点
通过实现一个用 @ServletEndpoint 的类,Servlet 可以作为端点暴露出来,该类还实现了 Supplier<EndpointServlet>。Servlet 端点提供了与 Servlet 容器的更深入的集成,但以可移植性为代价。它们旨在用于将现有servlet暴露为端点。对于新端点,应尽可能首选 @Endpoint 和 @WebEndpoint 注解。
controller 端点
@ControllerEndpoint 和 @RestControllerEndpoint 可用于实现仅由Spring MVC或Spring WebFlux 暴露的端点。使用Spring MVC 和 Spring WebFlux 的标准注解(如 @RequestMapping 和 @GetMapping)的方法进行映射,端点的ID用作路径的前缀。controller 端点提供了与Spring web框架的更深层次的集成,但以可移植性为代价。只要可能,就应该首选 @Endpoint 和 @WebEndpoint 注解。
健康信息
可以使用运行状况信息检查正在运行的应用程序的状态。它经常被监控软件用来在生产系统故障时提醒某人。health 端点暴露的信息取决于 management.endpoint.health.show-details 和 management.endpoint.health.show-components 属性,这些属性可以配置为以下值之一:
| 属性名 | 说明 |
|---|---|
| never | 不显示详细信息 |
| when-authorized | 详细信息仅显示给授权用户。可以使用 management.endpoint.health.roles 配置授权角色。 |
| always | 对所有用户显示详细信息 |
默认值为 never。当用户处于端点的一个或多个角色时,该用户被视为已被授权。如果端点没有配置的角色(默认),则所有经过身份验证的用户都被视为已授权。可以使用 management.endpoint.health.roles 属性配置角色。
注意:如果已保护应用程序并希望使用
always,则安全配置必须允许已验证和未验证用户访问运行状况端点。
运行状况信息从 HealthContributorRegistry 的内容中收集(默认情况下,所有在 ApplicationContext 中定义的 HealthContributor 实例)。Spring Boot包括许多自动配置的 HealthContributors,也可以编写自己的。
HealthContributor 可以是 HealthIndicator 或 CompositeHealthContributor。 HealthIndicator 提供实际的健康信息,包括 Status。CompositeHealthContributor 提供其他 HealthIndicator 的组合。这些贡献者共同组成一个树结构来表示整个系统的健康状况。
默认情况下,最终系统运行状况由 StatusAggregator 派生,StatusAggregator 根据状态的有序列表对每个 HealthIndicator 的状态进行排序。排序列表中的第一个状态用作整体运行状况。如果没有 HealthIndicator 返回 StatusAggregator 已知的状态,则使用 UNKNOWN 状态。
注意:
HealthContributorRegistry可用于在运行时注册和注销运行状况指标。
自动配置 HealthIndicator
在适当情况下,Spring Boot会自动配置以下运行 HealthIndicator:
| 类名 | 描述 |
|---|---|
| CassandraHealthIndicator | 检查 Cassandra 数据库是否已启动 |
| CouchbaseHealthIndicator | 检查 Couchbase 集群是否已启动 |
| DiskSpaceHealthIndicator | 检查磁盘空间是否不足 |
| ElasticSearchRestHealthContributorAutoConfiguration | 检查 ES 集群是否已启动 |
| HazelcastHealthIndicator | 检查 Hazelcast 服务是否已启动 |
| InfluxDbHealthIndicator | 检查 InfluxDB 服务是否已启动 |
| JmsHealthIndicator | 检查 JMS 代理是否已启动 |
| LdapHealthIndicator | 检查 LDAP 服务是否已启动 |
| MailHealthIndicator | 检查邮件服务是否已启动 |
| MongoHealthIndicator | 检查 Mongo 数据库是否已启动 |
| Neo4jHealthIndicator | 检查 Neo4j 数据库是否已启动 |
| PingHealthIndicator | 始终响应为 UP (已启动) |
| RabbitHealthIndicator | 检查 Rabbit 服务是否已启动 |
| RedisHealthIndicator | 检查 Redis 服务是否已启动 |
| SolrHealthIndicator | 检查 Solr 服务是否已启动 |
注意:可以通过设置
management.health.defaults.enabled属性来禁用它们。
写自定义 HealthIndicator
为了提供定制化的健康信息,可以注册实现 HealthIndicator 接口的 spring bean。需要提供 health() 方法的实现并返回一个 Health 响应。Health 响应应包含一个状态,并且可以选择包含要显示的其他详细信息。以下代码显示了 HealthIndicator 实现示例:
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class MyHealthIndicator implements HealthIndicator {
@Override
public Health health() {
int errorCode = check(); // perform some specific health check
if (errorCode != 0) {
return Health.down().withDetail("Error Code", errorCode).build();
}
return Health.up().build();
}
}
注意:给定的
HealthIndicator的标识符是没有HealthIndicator后缀的bean的名称,如果存在的话。在前面的示例中,健康信息在名为my的条目中可用。
除了Spring Boot预定义的 Status 类型之外,Health 还可以返回表示新系统状态的自定义 Status。在这种情况下,还需要提供 StatusAggregator 接口的自定义实现,或者必须使用 management.endpoint.health.status.order 配置属性配置默认实现。
例如,假设某个 HealthIndicator 实现中正在使用代码为 FATAL 的新 Status。要配置严重性顺序,将以下属性添加到应用程序属性中:
management.endpoint.health.status.order=fatal,down,out-of-service,unknown,up
response 中的 HTTP 状态码反映了总体运行状况(例如,UP 映射到200,而 OUT_OF_SERVICE 和 DOWN 映射到503)。如果通过HTTP访问健康端点,可能还希望注册自定义状态映射。例如,以下属性将 FATAL 映射到503(service unavailable):
management.endpoint.health.status.http-mapping.fatal=503
注意:如果需要更多的控制,可以定义自己的
HttpCodeStatusMapperbean。
下表显示了内置状态的默认状态映射:
| 状态 | 映射 |
|---|---|
| DOWN | SERVICE_UNAVAILABLE (503) |
| OUT_OF_SERVICE | SERVICE_UNAVAILABLE (503) |
| UP | 默认无映射, 因此状态码是 200 |
| UNKNOWN | 默认无映射, 因此状态码是 200 |
响应式 Health Indicators(健康指示器)
对于响应式应用程序,例如使用 Spring WebFlux 的应用程序,ReactiveHealthContributor 提供了一个获取应用程序运行状况的非阻塞协议。与传统的 HealthContributor 类似,运行状况信息从 ReactiveHealthContributorRegistry 的内容中收集(默认情况下,所有在 ApplicationContext 中定义的 HealthContributor 和 ReactiveHealthContributor 实例)。在弹性调度程序上执行常规的 HealthContributors 不检查响应式 API。
注意:在响应式应用程序中,可使用
ReactiveHealthContributorRegistry在运行时注册和注销健康指示器。
要从响应式 API 提供定制的健康信息,可以注册实现 ReactiveHealthIndicator 接口的 spring bean。下面的代码显示了一个示例 ReactiveHealthIndicator 实现:
@Component
public class MyReactiveHealthIndicator implements ReactiveHealthIndicator {
@Override
public Mono<Health> health() {
return doHealthCheck() //perform some specific health check that returns a Mono<Health>
.onErrorResume(ex -> Mono.just(new Health.Builder().down(ex).build()));
}
}
注意:要自动处理错误,请考虑从
AbstractReactiveHealthIndicator继承。
自动配置 ReactiveHealthIndicators
在适当的情况下,Spring Boot 会自动配置以下 ReactiveHealthIndicators:
| 名称 | 描述 |
|---|---|
| CassandraReactiveHealthIndicator | 检查 Cassandra 数据库是否启动 |
| CouchbaseReactiveHealthIndicator | 检查 Couchbase 集群是否启动 |
| MongoReactiveHealthIndicator | 检查 Mongo 数据库是否启动 |
| RedisReactiveHealthIndicator | 检查 Redis 服务是否启动 |
注意:如有必要,用响应式指示器代替常规指示器。此外,未显式处理的任何
HealthIndicator都将自动包装。
健康分组
有时将健康指标组织成可用于不同目的的组是有用的。例如,如果将应用程序部署到Kubernetes,可能需要一组不同的健康指标来表示您的“liveness”和“readiness”探测。
要创建运行状况指示器分组,可以使用 management.endpoint.health.group.<name> 属性,并指定要 include 或 exclude 的运行状况指示器ID列表。例如,要创建仅包含数据库指标的分组,可以定义以下内容:
management.endpoint.health.group.custom.include=db
然后可以点击 localhost:8080/actuator/health/custom 来检查结果。
默认情况下,分组将继承与系统运行状况相同的 StatusAggregator 和 HttpCodeStatusMapper 设置,也可以按组定义这些设置。如果需要,还可以覆盖 show-details 和 roles 属性。
management.endpoint.health.group.custom.show-details=when-authorized
management.endpoint.health.group.custom.roles=admin
management.endpoint.health.group.custom.status.order=fatal,up
management.endpoint.health.group.custom.status.http-mapping.fatal=500
注意:如果需要注册自定义
StatusAggregator或HttpCodeStatusMapperbean以用于分组,则可以使用@Qualifier("groupname")。
应用信息
应用程序信息暴露从 ApplicationContext 中定义的所有 InfoContributor bean收集的各种信息。Spring Boo t包含许多自动配置的 InfoContributor bean,可以编写自己的bean。
自动配置 InfoContribotors(信息贡献者)
如果合适,以下 InfoContributor bean由 Spring Boot 自动配置:
| 名称 | 描述 |
|---|---|
| EnvironmentInfoContributor | 暴露在 info key下的 Environment 的任意 key |
| GitInfoContributor | 如果 git.properties 文件可用,则暴露 git 信息 |
| BuildInfoContributor | 如果 META-INF/build-info.properties 文件可用,则暴露构建信息 |
注意:通过设置
management.info.defaults.enabled属性,可以将它们全部禁用。
自定义应用信息
通过设置 info.* Spring属性,可以自定义 info 端点暴露的数据。info key 下的所有 Environment 属性都将自动暴露。例如,可以将以下设置添加到 application.properties 文件中:
info.app.encoding=UTF-8
info.app.java.source=1.8
info.app.java.target=1.8
注意:还可以在构建时扩展信息属性,而不是硬编码这些值。
假设使用Maven,可以重写前面的示例,如下所示:
info.app.encoding=@project.build.sourceEncoding@
info.app.java.source=@java.version@
info.app.java.target=@java.version@
git commit 信息
info 端点的另一个有用特性是,它能够发布有关项目生成时 git 源代码仓库状态的信息。如果 GitProperties bean可用,那么 git.branch、git.commit.id 和 git.commit.time 属性将被暴露。
注意:如果
git.properties文件在类路径的根目录下可用,则会自动配置GitPropertiesbean。有关更多详细信息,请参阅“生成git信息”。
如果要显示完整的git信息(即 git.properties 的完整内容),请使用 management.info.git.mode 属性,如下所示:
management.info.git.mode=full
构建信息
如果 BuildProperties bean可用,那么info 端点也可以发布关于你的构建信息。如果类路径中有一个 META-INF/build-info.properties 文件,就会发生这种情况。
注意:Maven 和 Gradle 插件都可以生成该文件。有关详细信息,请参阅“生成生成信息”。
写自定义 InfoContributors
为了提供定制的应用程序信息,可以注册实现 InfoContributor 接口的 spring bean。
以下示例提供了一个具有单个值的示例键值对:
import java.util.Collections;
import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.stereotype.Component;
@Component
public class ExampleInfoContributor implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
builder.withDetail("example",
Collections.singletonMap("key", "value"));
}
}
如果使用 info 端点,则应看到包含以下附加键值对的响应:
{
"example": {
"key" : "value"
}
}
使用 HTTP 监控和管理
如果正在开发一个web应用程序,Spring Boot Actuator 会自动配置所有启用的端点,使其通过 HTTP 暴露。默认约定是使用前缀为 /actuator 的端点的 id 作为URL路径。例如,health 暴露为 /actuator/health。
注意:actuator 由 Spring MVC、Spring WebFlux 和 Jersey 提供本地支持。如果 Jersey 和 Spring MVC 都可用,那么将使用 Spring MVC。
自定义management端点路径
有时,自定义 management 端点的前缀很有用。例如,应用程序可能已经将 /actuator 用于其他目的。可以使用 management.endpoints.web.base-path 属性更改 management 端点的前缀,如下例所示:
management.endpoints.web.base-path=/manage
前面的 application.properties 示例将端点从 /actuator/{id} 更改为 /manage/{id}(例如 /manage/info)。
注意:除非 management 端口已配置为 使用其他HTTP端口暴露端点,否则
management.endpoints.web.base-path是基于server.servlet.context-path的相对路径。如果配置了management.server.port,则management.endpoints.web.base-path是基于management.server.servlet.context-path的相对路径。
如果要将端点映射到其他路径,可以使用 management.endpoints.web.path-mapping 属性。以下示例将 /actuator/health 映射到 /healthcheck:
# application.properties
management.endpoints.web.base-path=/
management.endpoints.web.path-mapping.health=healthcheck
自定义management服务端口
基于云的部署,使用默认 HTTP 端口暴露 management 端点是一个明智的选择。但是,如果应用程序在自己的数据中心内运行,可能更愿意使用不同的HTTP端口来暴露端点。
可以设置 management.server.port 属性以更改HTTP端口,如下例所示:
management.server.port=8081
注意:在Cloud Foundry上,默认情况下,应用程序仅在端口8080上接收HTTP和TCP路由请求。如果要在Cloud Foundry上使用自定义管理端口,则需要显式设置应用程序的路由以将流量转发到自定义端口。
配置特定management SSL
当配置为使用自定义端口时,还可以使用各种 management.server.ssl.* 属性使用自己的SSL配置management服务器。例如,这样做可以让management服务器通过HTTP可用,而主应用程序使用HTTPS,如以下属性设置所示:
server.port=8443
server.ssl.enabled=true
server.ssl.key-store=classpath:store.jks
server.ssl.key-password=secret
management.server.port=8080
management.server.ssl.enabled=false
或者,主服务和management服务都可以使用SSL,但使用不同的key stores,如下所示:
server.port=8443
server.ssl.enabled=true
server.ssl.key-store=classpath:main.jks
server.ssl.key-password=secret
management.server.port=8080
management.server.ssl.enabled=true
management.server.ssl.key-store=classpath:management.jks
management.server.ssl.key-password=secret
自定义management服务地址
通过设置 management.server.address 属性,可以自定义management端点可用的地址。如果只想监听内部或面向ops的网络,或者只监听本地主机的连接,那么这样做会很有用。
注意:只有当端口与主服务器端口不同时,才能监听其他地址。
以下 application.properties 示例不允许远程management连接:
management.server.port=8081
management.server.address=127.0.0.1
禁用 HTTP 端点
如果不想通过HTTP暴露端点,可以将management端口设置为 -1,如下例所示:
management.server.port=-1
也可以使用 management.endpoints.web.exposure.exclude 属性来实现,如下例所示:
management.endpoints.web.exposure.exclude=*
使用 JMX 监控和管理
Java Management Extensions(JMX)提供了监控和管理应用程序的标准机制。默认情况下,此功能未启用,可以通过将配置属性 spring.jmx.enabled 设置为 true 来启用。默认情况下,SpringBoot 将管理端点暴露为 org.springframework.boot 域下的JMX MBeans。
自定义MBean名称
MBean 的名称通常由端点的 id 生成。例如,health 端点暴露为 org.springframework.boot:type=Endpoint, name=Health。
如果应用程序包含多个Spring ApplicationContext,则可能会发现名称冲突。要解决此问题,可以将 spring.jmx.unique-names 属性设置为 true,以便MBean名称始终是唯一的。
还可以自定义端点在其下暴露的JMX域。以下设置显示了在 application.properties 中执行此操作的示例:
spring.jmx.unique-names=true
management.endpoints.jmx.domain=com.example.myapp
禁用 JMX 端点
management.endpoints.jmx.exposure.exclude=*
通过HTTP为JMX使用Jolokia
Jolokia是一个JMX-HTTP桥梁,它提供了访问 JMX bean的另一种方法。要使用Jolokia,需包含对 org.jolokia:jolokia-core 的依赖。
<dependency>
<groupId>org.jolokia</groupId>
<artifactId>jolokia-core</artifactId>
</dependency>
然后,可以通过将 Jolokia 或 * 添加到 management.endpoints.web.exposure.include 属性中来暴露 Jolokia 端点。然后可以在管理HTTP服务器上使用 /actuator/jolokia 访问它。
自定义Jolokia
Jolokia有许多设置,传统上可以通过设置servlet参数来配置这些设置。使用Spring Boot,可以使用 application.properties 文件。为此,在参数前面加上 management.endpoint.jolokia.config.,如下例所示:
management.endpoint.jolokia.config.debug=true
禁用Jolokia
如果使用Jolokia但不希望Spring Boot对其进行配置,如下设置:
management.endpoint.jolokia.enabled=false
Loggers
Spring Boot Actuator 能够在运行时查看和配置应用程序的日志级别。可以查看整个列表或单个 logger 的配置,该配置由显式配置的日志记录级别和日志框架为其提供的有效 logging level 组成。这些 level 可以是:
- TRACE
- DEBUG
- INFO
- WARN
- ERROR
- FATAL
- OFF
- null
null 表示没有显式配置。
配置 Logger
要配置给定的 logger,将部分实体 POST 到资源的URI,如下例所示:
{
"configuredLevel": "DEBUG"
}
注意:要“重置”logger的特定级别(并改用默认配置),可以将null值作为
configuredLevel传递。
指标
Spring Boot Actuator为Micrometer提供依赖管理和自动配置,Micrometer 是一个支持多种监视系统的应用程序度量门面(外观模式,相当于slf4j 与 logback),包括:
- AppOptics
- Atlas
- Datadog
- Dynatrace
- Elastic
- Ganglia
- Graphite
- Humio
- Influx
- JMX
- KairosDB
- New Relic
- Prometheus
- SignalFx
- Simple(in-memory)
- StatsD
- Wavefront
启动
Spring Boot 自动配置一个复合的 MeterRegistry,并为在类路径上找到的每个受支持的实现向复合添加一个注册表。在运行时类路径中依赖于 micrometer-registry-{system} 就足够让Spring Boot配置注册表。
大多数注册中心都有共同的特点。例如,即使 Micrometer 注册表实现位于类路径上,也可以禁用特定注册。例如,要禁用 Datadog:
management.metrics.export.datadog.enabled=false
Spring Boot还将向 Metrics 类上的全局静态组合注册表添加任何自动配置的注册,除非明确告诉它不要:
management.metrics.use-global-registry=false
在向注册表注册任何 meter (仪表)之前,可以注册任意数量的 MeterRegistryCustomizer bean以进一步配置注册表,例如应用公共标记:
@Bean
MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config().commonTags("region", "us-east-1");
}
通过指定更加具体泛型类型,可以将自定义应用于特定的注册表实现:
@Bean
MeterRegistryCustomizer<GraphiteMeterRegistry> graphiteMetricsNamingConvention() {
return registry -> registry.config().namingConvention(MY_CUSTOM_CONVENTION);
}
设置就绪后,可以在组件中插入 MeterRegistry 并注册 metrics:
@Component
public class SampleBean {
private final Counter counter;
public SampleBean(MeterRegistry registry) {
this.counter = registry.counter("received.messages");
}
public void handleMessage(String message) {
this.counter.increment();
// handle message implementation
}
}
Spring Boot 还配置了内置的工具(即 MeterBinder 实现),可以通过配置或专用的注解标记来控制这些工具。
支持监控系统
略
支持指标(metrics)
如果可用,Spring Boot将注册以下核心指标:
- JVM 指标,利用率报告:
- 各种内存和缓存池
- 与 GC(垃圾收集)相关的统计信息
- 线程利用率
- 记载/卸载类的数量
- CPU 指标
- 文件描述符指标
- Kafka 消费指标
- Log4j2 指标:记录每个 Log4j2 日志级别的事件数
- Logback 指标:记录每个级别 Log4j2 日志级别的事件数
- Uptime 指标:报告正常运行时间的指标和表示应用程序绝对启动时间的固定指标
- Tomcat 指标:要注册所有 tomcat 指标,必须将
server.tomcat.mbeanregistry.enabled设置为true - Spring 集成指标
Spring MVC 指标
自动配置允许检测Spring MVC处理的请求。当 management.metrics.web.server.request.autotime.enabled 为 true 时,将对所有请求执行此检测。或者,当设置为 false 时,可以通过向请求处理方法添加 @Timed 来启用检测:
@RestController
@Timed // ①
public class MyController {
@GetMapping("/api/people")
@Timed(extraTags = { "region", "us-east-1" }) // ②
@Timed(value = "all.people", longTask = true) // ③
public List<Person> listPeople() { ... }
}
①:controller 类,用于对 controller 中的每个请求处理程序启用计时。
②:为单个端点启用的方法。如果在类上有它,则这里不是必要的,但可以用于进一步自定义此特定端点的计时器。
③:使用参数为 longtask = true 的方法为该方法启用长任务计时器。长任务计时器需要单独的度量名称,并且可以与短任务计时器堆叠在一起。
默认情况下,使用名称 http.server.requests 生成指标。可以通过设置 management.metrics.web.server.request.metric-name 属性自定义名称。
默认情况下,与 Spring MVC 相关的指标标签有以下信息:
| 标签 | 说明 |
|---|---|
exception |
处理请求时引发的任何异常的简单类名。 |
method |
请求方法(例如 GET 或 POST) |
outcome |
基于响应状态码的请求结果。1xx是信息性的,2xx是成功的,3xx是重定向,4xx是客户端错误,5xx是服务器错误 |
status |
响应的HTTP状态码(例如,200 或 500) |
uri |
如果可能,在变量替换之前请求的URI模板(例如,/api/person/{id}) |
要自定义标签,需提供一个实现 WebMvcTagsProvider 的 @Bean。
Spring WebFlux 指标
自动配置允许检测由 WebFlux controller 和功能处理程序处理的所有请求。
默认情况下,使用 http.server.requests 生成指标。可以通过设置 management.metrics.web.server.request.metric-name 属性自定义名称。
默认情况下,与WebFlux相关的度量标签有以下信息:(与Spring WVC 一样)
| 标签 | 说明 |
|---|---|
exception |
处理请求时引发的任何异常的简单类名。 |
method |
请求方法(例如 GET 或 POST) |
outcome |
基于响应状态码的请求结果。1xx是信息性的,2xx是成功的,3xx是重定向,4xx是客户端错误,5xx是服务器错误 |
status |
响应的HTTP状态码(例如,200 或 500) |
uri |
如果可能,在变量替换之前请求的URI模板(例如,/api/person/{id}) |
要自定义标签,需提供一个实现 WebFluxTagsProvider 的 @Bean。
Jersey 服务指标
略
HTTP Client 指标
Spring Boot Actuator 管理 RestTemplate 和 WebClient 的检测。为此,必须注入自动配置的生成器并使用它创建实例:
- 用于构建
RestTemplate的RestTemplateBuilder - 用于构建
WebClient的WebClient.Builder
也可以手动应用负责此检测的自定义程序,即 MetricsRestTemplateCustomizer 和 MetricsWebClientCustomizer。
默认情况下,使用 http.client.requests 生成指标。可以通过设置 management.metrics.web.client.request.metric-name 属性自定义名称。
默认情况下,由检测客户端生成的指标标签有以下信息:
| 标签 | 说明 |
|---|---|
clientName |
URI 的 host 部分 |
method |
请求方法(例如 GET 或 POST) |
outcome |
基于响应状态码的请求结果。1xx是信息性的,2xx是成功的,3xx是重定向,4xx是客户端错误,5xx是服务器错误 |
status |
响应的HTTP状态码(例如,200 或 500) |
uri |
如果可能,在变量替换之前请求的URI模板(例如,/api/person/{id}) |
要自定义标签,并且根据你选择的客户端,可以提供一个实现 RestTemplateExchangeTagsProvider 或 WebClientExchangeTagsProvider 的 @Bean。其中有一些方便的静态函数。
缓存指标
自动配置在启动时启用所有可用 Cache 的检测,并以 cache 为指标前缀。缓存检测是为一组基本的度量标准而标准化的。此外,还提供了特定于缓存的指标。
支持以下缓存库:
- Caffeine
- EhCache 2
- Hazelcast
- 任何兼容的JCache(JSR-107)实现
指标由缓存的名称和从 bean 名称派生的 CacheManager 的名称进行标记。
注意:只有启动时可用的缓存才绑定到注册表。对于在启动阶段之后动态或以编程方式创建的缓存,需要显式注册。
CacheMetricsRegistrarbean 可用于简化该过程。
DataSource 指标
自动配置启用所有可用 DataSource 对象的检测,这些对象的指标以 jdbc.connections 为前缀。数据源仪表的结果是表示池中当前活跃(active)、空闲(idle)、最大允许(maximum allowed)和最小允许(minimum allowed)连接的仪表。
指标也由基于 bean 名称计算的 DataSource 的名称进行标记。
注意:默认情况下,Spring Boot为所有受支持的数据源提供元数据;如果喜爱的数据源不受现成支持,则可以添加其他
DataSourcePoolMetadataProviderbean。有关示例,请参阅DataSourcePoolMetadataProvidersConfiguration配置。
此外,Hikari 特定的指标也以 hikaricp 前缀暴露。每个指标都由池的名称标记(可以用 spring.datasource.name 控制)。
Hibernate 指标
自动配置允许检测所有可用的Hibernate EntityManagerFactory 实例,这些实例使用名为 hibernate 的指标启用了统计信息。
指标也由从bean名称派生的 EntityManagerFactory 的名称进行标记。
要启用统计信息,标准JPA属性 hibernate.generate_statistics 必须设置为 true。可以在自动配置的 EntityManagerFactory 上启用该功能,如下例所示:
spring.jpa.properties.hibernate.generate_statistics=true
RabbitMQ 指标
自动配置将使用名为 rabbitmq 的指标启用所有可用 RabbitMQ 连接工厂的检测。
注册自定义metrics(指标)
要注册自定义指标,将 MeterRegistry 注入组件,如下例所示:
class Dictionary {
private final List<String> words = new CopyOnWriteArrayList<>();
Dictionary(MeterRegistry registry) {
registry.gaugeCollectionSize("dictionary.size", Tags.empty(), this.words);
}
// …
}
如果发现在组件或应用程序之间反复插入一组指标,则可以将该组封装在 MeterBinder 实现中。默认情况下,来自所有 MeterBinder bean的指标将自动绑定到Spring管理的 MeterRegistry。
定制个别metrics(指标)
如果需要将自定义应用于特定的 Meter 实例,可以使用 io.micrometer.core.instrument.config.MeterFilter 接口。默认情况下,所有 MeterFilter bean都将自动应用于 micrometer MeterRegistry.Config。
例如,如果要将以 com.example 开头的所有meter(仪表) ID的 mytag.region 标签重命名为 mytag.area,可以执行以下操作:
@Bean
public MeterFilter renameRegionTagMeterFilter() {
return MeterFilter.renameTag("com.example", "mytag.region", "mytag.area");
}
公共标签
公共标签通常用于操作环境(如主机、实例、区域、堆栈等)上的维度向下钻取。公共标记应用于所有仪表,可以配置为如下示例所示:
management.metrics.tags.region=us-east-1
management.metrics.tags.stack=prod
上面的示例将 region 和 stack 标签分别添加到值为 us-east-1 和 prod 的所有仪表。
注意:如果使用Graphite,公共标签的顺序很重要。由于使用这种方法无法保证公共标签的顺序,建议 Graphite 用户定义一个自定义
MeterFilter。
每个仪表的属性
除了 MeterFilter beans之外,还可以使用属性在每个仪表的基础上应用一组有限的定制。每个仪表自定义适用于以给定名称开头的所有仪表 ID。例如,下面将禁用以 example.remote 开头的ID的任何仪表
management.metrics.enable.example.remote=false
以下属性允许按仪表自定义:
| 属性 | 说明 |
|---|---|
management.metrics.enable |
是否拒绝仪表发出任何指标 |
management.metrics.distribution.percentiles-histogram |
是否发布适合计算可聚合(跨维度)百分位近似的直方图。 |
management.metrics.distribution.minimum-expected-value, management.metrics.distribution.maximum-expected-value |
通过限制期望值的范围,发布更少的直方图桶。 |
management.metrics.distribution.percentiles |
发布应用程序中计算的百分比值 |
management.metrics.distribution.sla |
发布包含由sla定义的桶的累积直方图 |
有关百分位直方图、百分位和sla背后概念的更多详细信息,请参阅
micrometer 文档的“直方图和百分位”部分。
指标端点
Spring Boot提供了一个 metrics 端点,可用于诊断性地检查应用程序收集的指标。默认情况下,端点不可用,必须暴露,有关详细信息,请参阅暴露端点。
导航到 /actuator/metrics 将显示可用仪表名称的列表。可以通过提供特定仪表的名称作为选择器(例如 /actuator/metrics/jvm.memory.max)来深入查看该仪表的信息。
注意:在这里使用的名称应该与代码中使用的名称匹配,而不是在为它所传送到的监视系统规范化命名约定之后使用的名称。换句话说,如果由于 Prometheus 中的下划线命名约定,
jvm.memory.max在 Prometheus 中显示为jvm_memory_max,则在检查指标端点中的仪表时,仍应使用jvm.memory.max作为选择器。
还可以在URL的末尾添加任意数量的 tag=KEY:VALUE 查询参数,以便在仪表上按维度向下钻取,例如 /actuator/metrics/jvm.memory.max?tag=area:nonheap。
报告的测量值是与仪表名称匹配的所有仪表和已应用的任何标签的统计值之和。因此,在上面的示例中,返回的“值”统计量是堆的“代码缓存”、“压缩类空间”和“元区域”区域的最大内存足迹的总和。如果只想看到“元区域”的最大大小,可以添加一个额外的 tag=id:Metaspace,即 /actuator/metrics/jvm.memory.max?tag=area:nonheap&tag=id:Metaspace。
审计
一旦Spring Security发挥作用,Spring Boot Actuator 就有一个灵活的审计框架来发布事件(默认情况下,“验证成功”、“失败”和“拒绝访问”异常)。此功能对于报告和实现基于身份验证失败的锁定策略非常有用。
可以通过在应用程序的配置中提供 AuditEventRepository 类型的bean来启用审核。为了方便起见,Spring Boot提供了一个 InMemoryAuditEventRepository。InMemoryAuditEventReportory 的功能有限,建议仅在开发环境中使用它。对于生产环境,请考虑创建自己的替代 AuditEventRepository 实现。
自定义审计
要自定义已发布的安全事件,可以提供自己的 AbstractAuthenticationAuditListener 和 AbstractAuthorizationAuditListener 实现。
还可以为业务事件使用审计服务。为此,将 AuditEventRepository bean注入自己的组件并直接使用它,或者使用 Spring ApplicationEventPublisher 发布 AuditApplicationEvent(通过实现 ApplicationEventPublisherAware)。
HTTP Tracing(调用链)
可以通过在应用程序的配置中提供 HttpTraceRepository 类型的bean来启用HTTP Tracing。为了方便起见,SpringBoot 提供了一个 InMemoryHttpTraceRepository,默认情况下,它存储最后100个请求-响应的追踪。默认情况下。与其他跟踪解决方案相比, InMemoryHttpTraceRepository 功能有限,建议仅在开发环境中使用。对于生产环境,建议使用生产就绪 tracing 或可观测性解决方案,如 Zipkin 或 Spring Cloud Sleuth。或者,创建自己的 HttpTraceRepository 来满足需求。
httptrace 端点可用于获取有关存储在 HttpTraceRepository 中的请求-响应的信息。
自定义 HTTP Tracing
要自定义每个 trace 中包含的项,使用 management.trace.http.include 配置属性。对于高级自定义,注册自己的 HttpExchangeTracer 实现。
进程监控
在 spring-boot 模块中,可以找到两个类来创建通常对进程监视有用的文件:
ApplicationPidFileWriter创建一个包含应用程序PID的文件(默认情况下,在文件名为application.pid的应用程序目录中)。WebServerPortFileWriter创建一个(或多个)文件,其中包含正在运行的web服务器的端口(默认情况下,在文件名为application.port的应用程序目录中)。
默认情况下,这些写入程序未激活,但可以启用:
- 通过扩展配置
- 以编程方式
扩展配置方式
在 META-INF/spring.factories 文件中,可以激活编写PID文件的监听器,如下:
org.springframework.context.ApplicationListener=\
org.springframework.boot.context.ApplicationPidFileWriter,\
org.springframework.boot.web.context.WebServerPortFileWriter
编程方式
还可以通过调用 SpringApplication.addListeners() 方法并传递适当的 Writer 对象来激活监听器。此方法还允许自定义 Writer 构造函数中的文件名和路径。
支持 Cloud Foundry
Spring Boo 的 actuator 模块包括在部署到兼容的 Cloud Foundry 实例时激活的附加支持。/cloudfoundryapplication 路径提供了到所有 @Endpoint bean的安全路由。
扩展的支持使 Cloud Foundry 管理ui(例如可以用来查看已部署应用程序的web应用程序)得到了 Spring Boot actuator 信息的增强。例如,应用程序状态页可能包含完整的运行状况信息,而不是典型的“运行”或“停止”状态。
注意:普通用户无法直接访问
/cloudfoundryapplication路径。要使用端点,必须随请求一起传递有效的 UAA 令牌。
禁用扩展Cloud Foundry Actuator 支持
如果要完全禁用 /cloudfoundryapplication 端点,可以将以下设置添加到 application.properties文件中:
management.cloudfoundry.enabled=false
Cloud Foundry 自签名证书
默认情况下,/cloudfoundryapplication 端点的安全验证对各种Cloud Foundry服务进行SSL调用。如果 Cloud Foundry UAA 或云控制器服务使用自签名证书,则需要设置以下属性:
management.cloudfoundry.skip-ssl-validation=true
自定义上下文路径
如果服务器的上下文路径已配置为/,则Cloud Foundry 端点在应用程序的根目录中将不可用。例如,如果 server.servlet.context-path=/app,那么 Cloud Foundry 端点将在 /app/cloudfoundryapplication/* 上可用。
如果希望 Cloud Foundry 端点始终在 /cloudfoundryapplication/* 处可用,则无论服务器的上下文路径如何,都需要在应用程序中显式配置它。根据使用的web服务器,配置将有所不同。对于Tomcat,可以添加以下配置:
@Bean
public TomcatServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory() {
@Override
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
super.prepareContext(host, initializers);
StandardContext child = new StandardContext();
child.addLifecycleListener(new Tomcat.FixContextListener());
child.setPath("/cloudfoundryapplication");
ServletContainerInitializer initializer = getServletContextInitializer(getContextPath());
child.addServletContainerInitializer(initializer, Collections.emptySet());
child.setCrossContext(true);
host.addChild(child);
}
};
}
private ServletContainerInitializer getServletContextInitializer(String contextPath) {
return (c, context) -> {
Servlet servlet = new GenericServlet() {
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
ServletContext context = req.getServletContext().getContext(contextPath);
context.getRequestDispatcher("/cloudfoundryapplication").forward(req, res);
}
};
context.addServlet("cloudfoundry", servlet).addMapping("/*");
};
}
资料
官方文档
公众号:逸飞兮(专注于 Java 领域知识的深入学习,从源码到原理,系统有序的学习)


浙公网安备 33010602011771号