Spring Boot Actuator监控:【从零开始学Spring Boot】-8.Spring Boot Actuator监控
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;
}
}
}
// ...
}