1.简介
1.1 概述
Spring Boot includes a number of additional features to help you monitor and manage your application when you push it to production. You can choose to manage and monitor your application by using HTTP endpoints or with JMX. Auditing, health, and metrics gathering can also be automatically applied to your application.
Spring Boot包含许多附加功能,可在您将应用程序投入生产时帮助您监控和管理应用程序。您可以选择使用HTTP端点或JMX管理和监控您的应用程序。审计,健康状况和指标收集也可以自动应用于您的应用程序。
1.2 特点
Actuator endpoints let you monitor and interact with your application. Spring Boot includes a number of built-in endpoints and lets you add your own. For example, the
health
endpoint provides basic application health information.
监控端点使您可以监控应用程序并与之交互。 Spring Boot包含许多内置端点,您可以添加自己的端点。例如,运行健康状况端点提供基本的应用程序运行状况信息。
The following technology-agnostic endpoints are available:
ID | Description |
---|---|
auditevents | Exposes audit events information for the current application. Requires an AuditEventRepository bean. |
beans | Displays a complete list of all the Spring beans in your application. |
caches | Exposes available caches. |
conditions | Shows the conditions that were evaluated on configuration and auto-configuration classes and the reasons why they did or did not match. |
configprops | Displays a collated list of all @ConfigurationProperties. |
env | Exposes properties from Spring’s ConfigurableEnvironment. |
flyway | Shows any Flyway database migrations that have been applied. Requires one or more Flyway beans. |
health | Shows application health information. |
httptrace | Displays HTTP trace information (by default, the last 100 HTTP request-response exchanges). Requires an HttpTraceRepository bean. |
info | Displays arbitrary application info. |
integrationgraph | Shows the Spring Integration graph. Requires a dependency on spring-integration-core. |
loggers | Shows and modifies the configuration of loggers in the application. |
liquibase | Shows any Liquibase database migrations that have been applied. Requires one or more Liquibase beans. |
metrics | Shows ‘metrics’ information for the current application. |
mappings | Displays a collated list of all @RequestMapping paths. |
scheduledtasks | Displays the scheduled tasks in your application. |
sessions | Allows retrieval and deletion of user sessions from a Spring Session-backed session store. Requires a Servlet-based web application using Spring Session. |
shutdown | Lets the application be gracefully shutdown. Disabled by default. |
threaddump | Performs a thread dump. |
If your application is a web application (Spring MVC, Spring WebFlux, or Jersey), you can use the following additional endpoints:
ID | Description |
---|---|
heapdump | Returns an hprof heap dump file. |
jolokia | Exposes JMX beans over HTTP (when Jolokia is on the classpath, not available for WebFlux). Requires a dependency on jolokia-core. |
logfile | Returns the contents of the logfile (if logging.file.name or logging.file.path properties have been set). Supports the use of the HTTP Range header to retrieve part of the log file’s content. |
prometheus | Exposes metrics in a format that can be scraped by a Prometheus server. Requires a dependency on micrometer-registry-prometheus. |
2.演示环境
- JDK 1.8.0_201
- Spring Boot 2.2.0.RELEASE
- 构建工具(apache maven 3.6.3)
- 开发工具(IntelliJ IDEA )
3.演示代码
3.1 代码说明
开启 endpoint,使用 http 进行访问
3.2 代码结构
3.3 maven 依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
3.4 配置文件
info.app.version=1.0.0
info.app.name=spring-boot-actuator
info.app.test=test
# 启动所有endpoint
management.endpoints.web.exposure.include=*
# 显示详细信息
management.endpoint.health.show-details=always
# 关闭应用程序
management.endpoint.shutdown.enabled=true
3.5 java代码
HelloController.java
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "hello world";
}
}
3.6 git 地址
spring-boot/spring-boot-05-basis/spring-boot-actuator
4.效果展示
启动 SpringBootActuatorApplication.main 方法,在 spring-boot-actuator.http 访问下列地址,观察输出信息。
### GET /actuator
GET http://localhost:8080/actuator
请求响应信息如下
GET http://localhost:8080/actuator
HTTP/1.1 200
Content-Type: application/vnd.spring-boot.actuator.v3+json
Transfer-Encoding: chunked
Date: Sun, 19 Jul 2020 10:05:32 GMT
{
"_links": {
"self": {
"href": "http://localhost:8080/actuator",
"templated": false
},
"beans": {
"href": "http://localhost:8080/actuator/beans",
"templated": false
},
"caches-cache": {
"href": "http://localhost:8080/actuator/caches/{cache}",
"templated": true
},
"caches": {
"href": "http://localhost:8080/actuator/caches",
"templated": false
},
"health": {
"href": "http://localhost:8080/actuator/health",
"templated": false
},
"health-path": {
"href": "http://localhost:8080/actuator/health/{*path}",
"templated": true
},
"info": {
"href": "http://localhost:8080/actuator/info",
"templated": false
},
"conditions": {
"href": "http://localhost:8080/actuator/conditions",
"templated": false
},
"shutdown": {
"href": "http://localhost:8080/actuator/shutdown",
"templated": false
},
"configprops": {
"href": "http://localhost:8080/actuator/configprops",
"templated": false
},
"env": {
"href": "http://localhost:8080/actuator/env",
"templated": false
},
"env-toMatch": {
"href": "http://localhost:8080/actuator/env/{toMatch}",
"templated": true
},
"loggers": {
"href": "http://localhost:8080/actuator/loggers",
"templated": false
},
"loggers-name": {
"href": "http://localhost:8080/actuator/loggers/{name}",
"templated": true
},
"heapdump": {
"href": "http://localhost:8080/actuator/heapdump",
"templated": false
},
"threaddump": {
"href": "http://localhost:8080/actuator/threaddump",
"templated": false
},
"metrics": {
"href": "http://localhost:8080/actuator/metrics",
"templated": false
},
"metrics-requiredMetricName": {
"href": "http://localhost:8080/actuator/metrics/{requiredMetricName}",
"templated": true
},
"scheduledtasks": {
"href": "http://localhost:8080/actuator/scheduledtasks",
"templated": false
},
"mappings": {
"href": "http://localhost:8080/actuator/mappings",
"templated": false
}
}
}
Response code: 200; Time: 48ms; Content length: 1659 bytes
5.源码分析
5.1 web endpoint 请求原理
spring boot actuator 中有很多 endpoints,这里以 web endpoints 来简单分析一下原理。
spring-boot-starter-actuator.jar 依赖于 spring-boot-starter-actuator-autoconfigure.jar,spring-boot-starter-actuator-autoconfigure.jar 的结构如下,其中有用于自动装配的 spring.factories、源码、元数据的json信息以及元数据的配置信息。
在 spring.factories 中配置了很多的自动装配类,其中有一个叫 WebMvcEndpointManagementContextConfiguration,这个类中声明了 WebMvcEndpointHandlerMapping 的Bean。
@ManagementContextConfiguration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@ConditionalOnBean({ DispatcherServlet.class, WebEndpointsSupplier.class })
@EnableConfigurationProperties(CorsEndpointProperties.class)
public class WebMvcEndpointManagementContextConfiguration {
@Bean
@ConditionalOnMissingBean
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier,
ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier,
EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties,
WebEndpointProperties webEndpointProperties, Environment environment) {
List<ExposableEndpoint<?>> allEndpoints = new ArrayList<>();
Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
allEndpoints.addAll(webEndpoints);
allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
String basePath = webEndpointProperties.getBasePath();
EndpointMapping endpointMapping = new EndpointMapping(basePath);
boolean shouldRegisterLinksMapping = StringUtils.hasText(basePath)
|| ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT);
return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes, corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath), shouldRegisterLinksMapping);
}
@Bean
@ConditionalOnMissingBean
public ControllerEndpointHandlerMapping controllerEndpointHandlerMapping(
ControllerEndpointsSupplier controllerEndpointsSupplier, CorsEndpointProperties corsProperties,
WebEndpointProperties webEndpointProperties) {
EndpointMapping endpointMapping = new EndpointMapping(webEndpointProperties.getBasePath());
return new ControllerEndpointHandlerMapping(endpointMapping, controllerEndpointsSupplier.getEndpoints(),
corsProperties.toCorsConfiguration());
}
}
WebMvcEndpointHandlerMapping 的类图如下
它由一个内部类 WebMvcLinksHandler,实现了 links 方法,通过 request 请求中的 url 找到对应的 handler,这里的 linksResolver 是一个 EndpointLinksResolver,他里面包含所有已经开启的 endpoint 信息
public class WebMvcEndpointHandlerMapping extends AbstractWebMvcEndpointHandlerMapping {
private final EndpointLinksResolver linksResolver;
/**
* Creates a new {@code WebMvcEndpointHandlerMapping} instance that provides mappings
* for the given endpoints.
* @param endpointMapping the base mapping for all endpoints
* @param endpoints the web endpoints
* @param endpointMediaTypes media types consumed and produced by the endpoints
* @param corsConfiguration the CORS configuration for the endpoints or {@code null}
* @param linksResolver resolver for determining links to available endpoints
* @param shouldRegisterLinksMapping whether the links endpoint should be registered
*/
public WebMvcEndpointHandlerMapping(EndpointMapping endpointMapping, Collection<ExposableWebEndpoint> endpoints,
EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration,
EndpointLinksResolver linksResolver, boolean shouldRegisterLinksMapping) {
super(endpointMapping, endpoints, endpointMediaTypes, corsConfiguration, shouldRegisterLinksMapping);
this.linksResolver = linksResolver;
setOrder(-100);
}
@Override
protected LinksHandler getLinksHandler() {
return new WebMvcLinksHandler();
}
/**
* Handler for root endpoint providing links.
*/
class WebMvcLinksHandler implements LinksHandler {
@Override
@ResponseBody
public Map<String, Map<String, Link>> links(HttpServletRequest request, HttpServletResponse response) {
return Collections.singletonMap("_links",
WebMvcEndpointHandlerMapping.this.linksResolver.resolveLinks(request.getRequestURL().toString()));
}
@Override
public String toString() {
return "Actuator root web endpoint";
}
}
}
WebMvcEndpointHandlerMapping 继承自 AbstractWebMvcEndpointHandlerMapping,AbstractWebMvcEndpointHandlerMapping 继承自 RequestMappingInfoHandlerMapping,最终继承自 AbstractHandlerMethodMapping。AbstractHandlerMethodMapping 实现了 InitializingBean 接口,重写了 afterPropertiesSet(),所以在初始化的时候会调用该方法。该方法实现如下
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
在 AbstractWebMvcEndpointHandlerMapping 中进行了重写
@Override
protected void initHandlerMethods() {
for (ExposableWebEndpoint endpoint : this.endpoints) {
for (WebOperation operation : endpoint.getOperations()) {
registerMappingForOperation(endpoint, operation);
}
}
if (this.shouldRegisterLinksMapping) {
registerLinksMapping();
}
}
在 registerLinksMapping 方法中将映射关系保存起来
private void registerLinksMapping() {
PatternsRequestCondition patterns = patternsRequestConditionForPattern("");
RequestMethodsRequestCondition methods = new RequestMethodsRequestCondition(RequestMethod.GET);
ProducesRequestCondition produces = new ProducesRequestCondition(this.endpointMediaTypes.getProduced()
.toArray(StringUtils.toStringArray(this.endpointMediaTypes.getProduced())));
RequestMappingInfo mapping = new RequestMappingInfo(patterns, methods, null, null, null, produces, null);
// 获取 handler
LinksHandler linksHandler = getLinksHandler();
// 注册映射关系
registerMapping(mapping, linksHandler, ReflectionUtils.findMethod(linksHandler.getClass(), "links",
HttpServletRequest.class, HttpServletResponse.class));
}
这里的 getLinksHandler 获取的 handler 即为上面提到的 WebMvcLinksHandler,最终调用 registerMapping 方法保存到 mappingRegistry 中
public void registerMapping(T mapping, Object handler, Method method) {
if (logger.isTraceEnabled()) {
logger.trace("Register "" + mapping + "" to " + method.toGenericString());
}
this.mappingRegistry.register(mapping, handler, method);
}
在请求 http://localhost:8080/actuator 时,通过 DispatcherServlet 获取到一个 HandlerMapping,即为 AbstractWebMvcEndpointHandlerMapping,然后再调用 invokeHandlerMethod 方法时,最终调用到
WebMvcEndpointHandlerMapping.WebMvcLinksHandler#links 方法,它的实现如下
@Override
@ResponseBody
public Map<String, Map<String, Link>> links(HttpServletRequest request, HttpServletResponse response) {
return Collections.singletonMap("_links",
WebMvcEndpointHandlerMapping.this.linksResolver.resolveLinks(request.getRequestURL().toString()));
}
最终返回 endpoints 对应的映射关系
根据这里的返回结果,会调用到 HandlerMethodReturnValueHandlerComposite#handleReturnValue ,选择一个具体的 Handler 实现为 RequestResponseBodyMethodProcessor,最终调用 它的 handleReturnValue 方法
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
最终选择合适的 MessageConverter 将范湖值进行输出
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
// ..
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
if (body != null) {
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn ->
"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
addContentDispositionHeader(inputMessage, outputMessage);
if (genericConverter != null) {
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
}
else {
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Nothing to write: null body");
}
}
return;
}
}
}
// ...
}