SpringBoot源码之Actuator

需要先明确一个很重要的点:actuator支持多种框架,包括JMX、WebFlux(响应式web应用)、SpringMVC(基于servlet)、Servlet等,在不同的环境中,使用不同的配置。

 

Ps:关于JMX,它能够让我们管理、监视和配置应用。是可在线更改配置的那种,包括正在使用的配置bean。只要注册到了MBeanServer。所以用它来对外提供暴露端点,也是个不错的选择。

在了解Endpoint的创建和注册之前,需要先了解以下知识点。

EndpointsSupplier也就是Endpoint提供者,负责向ServletEndpointRegistrar暴露端点(在registrar创建时会调用get, 会发现并创建Endpoint),它具有端点过滤能力(ExposeExcludePropertyEndpointFilter)。

在SpringMVC工程下,将会在WebEndpointAutoConfiguration配置类中配置一个

WebEndpointDiscoverer作为supplier

 

类图:

SpringBoot源码之Actuator_第1张图片

ServletEndpointSupplier 负责@ServletEndpoint有以下四大类:

WebEndpointsSupplier 负责@WebEndpoint

JmxEndpointsSupplier 负责@JmxEndpoint

ControllerEndpointsSupplier 负责@ControllerEndpoint & @RestControllerEndpoint

其对应的实现类,相信从类图中不难看出。

 

处理器映射器HandlerMapping,这个概念应该比较熟悉了,就是SpringMVC的概念,负责路径映射到具体的控制器的。

 

Endpoint的主要注解有:

@Endpoint 官方推荐使用,不绑定特定技术(支持JMX, SpringMVC, Spring WebFlux, Jersey etc.)

@WebEndpoint 基于WEB技术

@ControllerEndpoint @RestControllerEndpoint 基于Controller

@ServletEndpoint 基于Servlet

@EndpointExtension 将Endpoint拓展到特定技术下使用,通常不直接使用该注解,而使用它的组合注解,例如:@EndpointWebExtension拓展到Web技术下(例:SpringMVC  Spring WebFlux)  @EndpointJmxExtension

 

Endpoint的封装

在访问Endpoint的时候,我们直接访问的并不是被@Endpoint注解的类,而是被经过封装的能被特定的技术下使用的类,情况与Supplier一一对应。(实际上先封装成EndpointBean然后再封装成下面的对应的Endpoint类型)

SpringBoot源码之Actuator_第2张图片


DiscoveredJmxEndpoint ServletEndpointSupplier与Supplier对应关系如下:

DiscoveredServletEndpoint ServletEndpointSupplier

DiscoveredWebEndpoint WebEndpointsSupplier

DiscoveredControllerEndpoint ControllerEndpointsSupplier

 

在类图中,应该注意到PathMappedEndpoint接口,在Web技术下,所有实现该接口的Endpoint类型都支持通过HTTP请求访问。而四大Endpoint也都实现了AbstractDiscoveredEndpoint,所以也是可以被暴露到外部实现真正访问的(前提是在配置中include)。

 

那么,Endpoint是何时被发现和封装的呢?

在配置WebEndpointAutoConfiguration#pathMappedEndpoints,注入PathMappedEndpoints时,会通过supplier获取所有PathMappedEndpoint类型的Endpoint,如果为空,则会进入Endpoint的发现与封装。这时就会把所有暴露的Endpoint封装成DiscoveredWebEndpoint (SpringMVC) 并且被PathMappedEndpoints所管理。PathMappedEndpoints有一个通过EndpointID来获取Endpoint的方法。到这里,相信聪明的你应该反映过来为什么请求路径就是@Endpoint注解时的id了吧。

在创建Endpoint时,会调用超类EndpointDiscoverer.converToEndpoint方法,他会根据@Endpoint中的@XXXOperation织入一个DiscoveredWebOperation(在注册Endpoint时,也会再次封装成RequestMappingInfo)。

SpringBoot源码之Actuator_第3张图片


那么DiscoveredWebOperation又是个啥?先上个类图

SpringBoot源码之Actuator_第4张图片

类图中,只有两类Operation,DiscoveredWebOperation见名思意,自然是负责HTTP发布的Endpoint,DiscoveredJmxOperation负责JMX发布的Endpoint。看超类AbstractDiscoveredOperation属性,这个invoker可是负责完成调用operation操作的。而operationMethod就是对应Endpoint带@XXXOperation注解的方法。由下面代码截图第二张中可以看出invoker是ReflectiveOperationInvoker通过反射调用的。operationMethod是DiscoveredOperationMethod。

 

SpringBoot源码之Actuator_第5张图片
DiscoveredOperationsFactory#createOperations(String id, Object target):71先来看看DiscoveredWebOperation是怎么创建的?

DiscoveredOperationsFactory#createOperation(String,Object, Method, OperationType, Class):77  ->

DiscoveredOperationsFactory#createOperation(String, Object, Method, OperationType, Class):84 ->

SpringBoot源码之Actuator_第6张图片

DiscoveredOperationsFactory#createOperation(String, DiscoveredOperationMethod, OperationInvoker)

SpringBoot源码之Actuator_第7张图片
Operation注意了,这里两种Operation都在这里创建的,具体创建的是哪一种,主要看EndpointDiscover.this是谁,如果是WebEndpointDiscoverer,创建的就是DiscoveredWebOperation。最后再将该Operation一起封装到Endpoint

SpringBoot源码之Actuator_第8张图片
Operation包含的信息:

SpringBoot源码之Actuator_第9张图片

 

 

但是这些端点虽然都持有了operation,又是在哪里注册的呢?怎么织入SpringMVC的呢?由此需要引出WebMvcEndpointHandlerMapping,在WebMvcEndpointManagementContextConfiguration#webEndpointServletHandlerMapping配置,当然,需要在各种supplier都加载完成各自的工作,例如发现封装Endpoint,之后。创建该类时,超类AbstractWebMvcEndpointHandlerMapping会执行initHandlerMethods进行初始化。他会调用registerMappingForOperation对Endpoint进行注册。

SpringBoot源码之Actuator_第10张图片

将operation再次封装成RequestMappingInfo,并创建一个handler再注册。如果对SpringMVC的请求映射信息RequestMappingInfo感兴趣的同学可以读一读:RequestMappingInfo相关文章

SpringBoot源码之Actuator_第11张图片

registerMapping方法其实是超类AbstractHandlerMethodMapping的方法,该超类其实是SpringMVC的范畴。SpringMVC注册控制器也是通过这个方法。只不过是在初始化方法AbstractHandlerMethodMapping#initHandlerMethods中,通过detectHandlerMethods的下面这段代码。(对SpringMVC注册Controller感兴趣的同学:献上文章)

methods.forEach((method, mapping) -> {
   Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
   registerHandlerMethod(handler, invocableMethod, mapping);
});

这两个方法都只有一句且一毛一样的代码:

this.mappingRegistry.register(mapping, handler, method);

 

回过头,从SpringMVC的角度,看Endpoint请求。

  1. 被DispatchServlet拦截,然后查找HandlerMapping,
  2. 找到上面注册Endpoint时创建的AbstractWebMvcEndpointHandlerMapping.OperationHandler(内部类),并得到一个执行链
  3. 然后交给处理器适配器,进行调用,这里是调用创建OperationHandler时传入的ServletWebOperationAdapter的handle(request, body)方法
  4. ServletWebOperationAdapter调用this.invoker.invoke,此时invoke就是ReflectiveOperationInvoker
  5. ReflectiveOperationInvoker会通过反射调用target也就是Endpoint对应的方法,并返回执行结果:ReflectionUtils.invokeMethod(method, this.target, resolvedArguments);
  6. 执行目标Endpoint也就是真实的@Endpoint注解的@XXXOperation方法。
  7. AbstractWebMvcEndpointHandlerMapping.ServletWebOperationAdapter#handleResult处理,封装成ResponseEntity也就是HTTP格式。并返回。
  8. 交给视图解析器处理,然后渲染视图,最后返回到浏览器。

 

HealthEndpoint在SpringMVC下,起作用的是HealthEndpointWebExtension哈。有兴趣的同学可以在上述步骤断点试试。自定义Endpoint时,直接使用@Endpoint注解不需要再写个@EndpointWebExtension,也是同样起作用的。

文章稍微有点乱,各位看官别介哈,已经是懒癌晚期。。。。

 

温馨提示:工程中使用的是SpringMVC,所以与WebEndpoint相关的配置都会起作用。

@Endpoint会自动选择这部分的配置来构建。

技能get:在看Spring源码的时候,一定要明确当前工程使用的技术.众所周知,Spring以良好的拓展性著称。

所以SpringBoot在构建默认的自动配置时,自然会考虑到这些技术的兼容配置。所以在看源码的时候一定要注意

当前系统所使用的技术,不然多走很多弯路。重点关注当前工程下的技术实现即可。

 

 

 

你可能感兴趣的:(SpringBoot源码)