侧边栏壁纸
博主头像
soulballad博主等级

技术文章记录及总结

  • 累计撰写 169 篇文章
  • 累计创建 26 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

【源码分析-Spring Boot】-8.Spring Boot WebEndpoint 请求原理

soulballad
2020-07-20 / 0 评论 / 0 点赞 / 67 阅读 / 8,408 字
温馨提示:
本文最后更新于 2022-03-03,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

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;
         }
      }
   }

   // ...
}
0

评论区