Spring Boot Actuator-WEB Endpoints

建议先预习Spring Boot Actuator,请戳《初识篇》


WEB Endpoints,以HTTP协议对外暴露提供访问接口。


█ 自动配置

META-INF/spring.factories:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration
  •  EndpointAutoConfiguration

公共配置,对于Web EndPoints与JMX EndPoints共用的配置。

@Configuration(proxyBeanMethods = false)
public class EndpointAutoConfiguration {

   // 负责方法参数的转换的参数值映射器
   @Bean
   @ConditionalOnMissingBean
   public ParameterValueMapper endpointOperationParameterMapper(
       // 自动注入被@EndpointConverter标注的Converter与GenericConverter类型的bean
       // Spring Boot Actuator默认没有提供,可自定义添加
         @EndpointConverter ObjectProvider> converters,
         @EndpointConverter ObjectProvider genericConverters) {
      ConversionService conversionService = createConversionService(
            converters.orderedStream().collect(Collectors.toList()),
            genericConverters.orderedStream().collect(Collectors.toList()));
      return new ConversionServiceParameterValueMapper(conversionService);
   }
   // 参数转换器 
   private ConversionService createConversionService(List> converters,
         List genericConverters) {
      // 默认为空,满足if条件       
      if (genericConverters.isEmpty() && converters.isEmpty()) {
         // 添加Spring Boot默认提供的转换器 
         // ApplicationConversionService.getSharedInstance()也会调用new ApplicationConversionService();来添加默认提供的转换器
         return ApplicationConversionService.getSharedInstance();
      }
      // 如果有自定义的转换器,在自定义的基础上,加上Spring Boot默认提供的转化器
      ApplicationConversionService conversionService = new ApplicationConversionService();
      converters.forEach(conversionService::addConverter);
      genericConverters.forEach(conversionService::addConverter);
      return conversionService;
   }

   // 缓存切面,第一次访问后,会将结果缓存起来。下一次再获取直接从缓存中取
   // 可以设置缓存失效时间
   @Bean
   @ConditionalOnMissingBean
   public CachingOperationInvokerAdvisor endpointCachingOperationInvokerAdvisor(Environment environment) {
      return new CachingOperationInvokerAdvisor(new EndpointIdTimeToLivePropertyFunction(environment));
   }

}

举例:自定义转换器

@Configuration
public class CustomConversionConfiguration{

    /**
     * 自定义Converter
     * Converter接口是org.springframework.core.convert.converter.Converter
     *
     * 将Integer类型的值转换成String类型
     *
     */
    @Bean
    // 加上注解标注给EndPoint提供服务
    @EndpointConverter
    public Converter customConverter() {
        return new CustomConverter();
    }

    static class CustomConverter implements Converter{
        @Override
        public String convert(Integer source) {
            if (source == null) {
                return null;
            }
            return source.toString();
        }
    }

    /**
     *
     * 自定义GenericConverter
     * GenericConverter接口是:org.springframework.core.convert.converter.GenericConverter
     *
     * @return
     */
    @Bean
    // 加上注解标注给EndPoint提供服务
    @EndpointConverter
    public GenericConverter customGenericConverter() {
        return new GenericConverter() {
            @Override
            public Set getConvertibleTypes() {
                Set convertiblePairSet = new HashSet<>(2);
                // 将Integer类型的值转换成String类型的值
                convertiblePairSet.add(new ConvertiblePair(Integer.class, String.class));
                return convertiblePairSet;
            }

            @Override
            public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
                // 实现自定义逻辑

                return null;
            }
        };
    }

}
  • WebEndpointAutoConfiguration

用于Web EndPoints构建的相关配置。

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication
@AutoConfigureAfter(EndpointAutoConfiguration.class)
// WebEndpointProperties:management.endpoints.web前缀的配置
@EnableConfigurationProperties(WebEndpointProperties.class)
public class WebEndpointAutoConfiguration {
    
    private final ApplicationContext applicationContext;

   private final WebEndpointProperties properties;
   
   // 通过构造器注入ApplicationContext与WebEndpointProperties
   public WebEndpointAutoConfiguration(ApplicationContext applicationContext, WebEndpointProperties properties) {
       this.applicationContext = applicationContext;
       this.properties = properties;
   }
    // 访问路径映射
    // 默认EndPoint的id就是访问路径,可通过配置management.endpoints.web.pathMapping更改访问路径。
    @Bean
    public PathMapper webEndpointPathMapper() {
        // 默认this.properties.getPathMapping()是空集合
        return new MappingWebEndpointPathMapper(this.properties.getPathMapping());
    }
    
    // 默认的接口响应Media类型
    @Bean
    @ConditionalOnMissingBean
    public EndpointMediaTypes endpointMediaTypes() {
        // application/vnd.spring-boot.actuator.v3+json
        // application/vnd.spring-boot.actuator.v2+json
        // application/json
        return EndpointMediaTypes.DEFAULT;
    }
    
    // EndPoint http服务暴露过滤器
    // 根据配置:management.endpoints.web.exposure.include 设置暴露服务
    // 根据配置:management.endpoints.web.exposure.exclude 设置不暴露服务
    @Bean
    public ExposeExcludePropertyEndpointFilter webExposeExcludePropertyEndpointFilter() {
        WebEndpointProperties.Exposure exposure = this.properties.getExposure();
       // 默认暴露 info、health 
       return new ExposeExcludePropertyEndpointFilter<>(ExposableWebEndpoint.class, exposure.getInclude(),
           exposure.getExclude(), "info", "health");
    }
    
    // Controller EndPoint服务暴露过滤
    @Bean
    public ExposeExcludePropertyEndpointFilter controllerExposeExcludePropertyEndpointFilter() {
        WebEndpointProperties.Exposure exposure = this.properties.getExposure();
        return new ExposeExcludePropertyEndpointFilter<>(ExposableControllerEndpoint.class, exposure.getInclude(),
        exposure.getExclude());
    }
    
    // 创建 Web EndPoints
    // 根据@EndPoint、@WebEndPoint注解标注的类
    @Bean
    @ConditionalOnMissingBean(WebEndpointsSupplier.class)
    public WebEndpointDiscoverer webEndpointDiscoverer(ParameterValueMapper parameterValueMapper,
        EndpointMediaTypes endpointMediaTypes, ObjectProvider endpointPathMappers,
        ObjectProvider invokerAdvisors,
        ObjectProvider> filters) {
       return new WebEndpointDiscoverer(this.applicationContext, parameterValueMapper, endpointMediaTypes,
         endpointPathMappers.orderedStream().collect(Collectors.toList()),
         invokerAdvisors.orderedStream().collect(Collectors.toList()),
         filters.orderedStream().collect(Collectors.toList()));
    }
    
    // 创建 Controller EndPoint
    // 根据@ControllerEndPoint标注的类
    @Bean
    @ConditionalOnMissingBean(ControllerEndpointsSupplier.class)
    public ControllerEndpointDiscoverer controllerEndpointDiscoverer(ObjectProvider endpointPathMappers,
        ObjectProvider>> filters) {
       return new ControllerEndpointDiscoverer(this.applicationContext,
         endpointPathMappers.orderedStream().collect(Collectors.toList()),
         filters.getIfAvailable(Collections::emptyList));
    }
    
    // web应用类型为servlet,创建Servlet EndPoint
    // 根据ServletEndPoint标注的类
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnWebApplication(type = Type.SERVLET)
    static class WebEndpointServletConfiguration {

       @Bean
       @ConditionalOnMissingBean(ServletEndpointsSupplier.class)
       ServletEndpointDiscoverer servletEndpointDiscoverer(ApplicationContext applicationContext,
            ObjectProvider endpointPathMappers,
            ObjectProvider> filters) {
          return new ServletEndpointDiscoverer(applicationContext,
            endpointPathMappers.orderedStream().collect(Collectors.toList()),
            filters.orderedStream().collect(Collectors.toList()));
       }

    }
    
    // 根据WebEndpointDiscoverer、ControllerEndpointDiscoverer、ServletEndpointDiscoverer 记录各个EndPoint的id与访问路径关系
    @Bean
    @ConditionalOnMissingBean
    public PathMappedEndpoints pathMappedEndpoints(Collection> endpointSuppliers) {
        return new PathMappedEndpoints(this.properties.getBasePath(), endpointSuppliers);
        }
    
}

ManagementContextConfiguration

四种ManagementContextConfiguration分别对应四种类型的WEB应用:Servlet、Spring WebFlux、Spring WebMVC、Jersey。

org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration=\
org.springframework.boot.actuate.autoconfigure.endpoint.web.ServletEndpointManagementContextConfiguration,\
org.springframework.boot.actuate.autoconfigure.endpoint.web.reactive.WebFluxEndpointManagementContextConfiguration,\
org.springframework.boot.actuate.autoconfigure.endpoint.web.servlet.WebMvcEndpointManagementContextConfiguration,\
org.springframework.boot.actuate.autoconfigure.endpoint.web.jersey.JerseyWebEndpointManagementContextConfiguration

org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration配置的类结合注解@ManagementContextConfiguration使用时会在ManagementContextAutoConfiguration自动配置时被创建,在ManagementContextAutoConfiguration中会触发@EnableManagementContext功能,关键在于ManagementContextConfigurationImportSelector的作用:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ManagementContextConfigurationImportSelector.class)
@interface EnableManagementContext {

   /**
    * The management context type that should be enabled.
    * @return the management context type
    */
   ManagementContextType value();

}
  • WebMvcEndpointManagementContextConfiguration

对Spring MVC类型的WEB应用对外提供HTTP访问接口。

@ManagementContextConfiguration(proxyBeanMethods = false)
// Spring MVC环境
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@ConditionalOnBean({ DispatcherServlet.class, WebEndpointsSupplier.class })
// CorsEndpointProperties:management.endpoints.web.cors前缀的配置,与跨域有关
@EnableConfigurationProperties(CorsEndpointProperties.class)
public class WebMvcEndpointManagementContextConfiguration {

   // 将WEB EndPoints提供HTTP 访问接口
   // 通过参数注入WebEndpointAutoConfiguration中创建的各种类型的Bean
   @Bean
   @ConditionalOnMissingBean
   public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier,
         ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier,
         EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties,
         WebEndpointProperties webEndpointProperties, Environment environment) {
      List> allEndpoints = new ArrayList<>();
      // 获取WEB EndPoints
      Collection 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);
   }
   
   // 将Controller转换成EndPoints访问映射
   @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());
   }

}

对于方法webEndpointServletHandlerMapping,会通过参数自动注入在配置类WebEndpointAutoConfiguration中创建的Bean:WebEndpointDiscovererServletEndpointDiscovererControllerEndpointDiscovererEndpointMediaTypes

  • JerseyWebEndpointManagementContextConfiguration

对Spring WebFlux类型的WEB应用对外提供HTTP访问接口。

  • WebFluxEndpointManagementContextConfiguration

对Jersey类型的WEB应用对外提供HTTP访问接口。

  • ServletEndpointManagementContextConfiguration

对Servlet类型的WEB应用对外提供HTTP访问接口。


█ 创建和暴露服务

以在Spring MVC环境为例,即WebMvcEndpointManagementContextConfiguration

在webEndpointServletHandlerMapping方法中负责创建WEB EndPoints以及HTTP服务暴露。

public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier,
         ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier,
         EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties,
         WebEndpointProperties webEndpointProperties, Environment environment) {
      List> allEndpoints = new ArrayList<>();
      // 创建
      // 获取WEB EndPoints
      Collection 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);
   }

创建过程:

1、webEndpointsSupplier#getEndpoints:

从getEndPoints方法开始,调用webEndpointsSupplier.getEndpoints(),即调用WebEndpointDiscoverer的getEndpoints方法。getEndpoints方法是继承自抽象类EndpointDiscoverer

public final Collection getEndpoints() {
   // this.endpoints为:private volatile Collection endpoints; 
   if (this.endpoints == null) {
      this.endpoints = discoverEndpoints();
   }
   return this.endpoints;
}

当初始化时,第一次调用getEndpoints方法,this.endpoints是为null的,需要调用discoverEndpoints方法进行初始化。

private Collection discoverEndpoints() {
   // 创建EndpointBean 
   Collection endpointBeans = createEndpointBeans();
   // 对EndpointBean添加扩展,@EndpointExtension的功能 
   addExtensionBeans(endpointBeans);
   // 将EndpointBean转换成EndPoint
   return convertToEndpoints(endpointBeans);
}

(1)createEndpointBeans

创建EndpointBean,首先获取Spring容器中被@Endpoint、@WebEndpoint、@JmxEndpoint、@ServletEndpoint、@ControllerEndpoint等标注的bean的名称,再根据bean的名称创建EndpointBean:

private Collection createEndpointBeans() {
   Map byId = new LinkedHashMap<>();
   // 去Spring容器中获取@Endpoint注解标注的bean名称
   String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(this.applicationContext,
         Endpoint.class);
   for (String beanName : beanNames) {
      if (!ScopedProxyUtils.isScopedTarget(beanName)) {
         // 根据bean的名称依次创建EndpointBean 
         EndpointBean endpointBean = createEndpointBean(beanName);
         // 校验id是否唯一,有重复报错
         EndpointBean previous = byId.putIfAbsent(endpointBean.getId(), endpointBean);
         Assert.state(previous == null, () -> "Found two endpoints with the id '" + endpointBean.getId() + "': '"
               + endpointBean.getBeanName() + "' and '" + previous.getBeanName() + "'");
      }
   }
   // 返回EndpointBean集合
   return byId.values();
}

createEndpointBean,根据bean的名称创建EndPointBean:

private EndpointBean createEndpointBean(String beanName) {
   Object bean = this.applicationContext.getBean(beanName);
   // 创建EndpointBean对象
   return new EndpointBean(this.applicationContext.getEnvironment(), beanName, bean);
}
EndpointBean(Environment environment, String beanName, Object bean) {
   MergedAnnotation annotation = MergedAnnotations
         .from(bean.getClass(), SearchStrategy.TYPE_HIERARCHY).get(Endpoint.class);
   // @Endpoint注解中的属性id值
   String id = annotation.getString("id");
   Assert.state(StringUtils.hasText(id),
         () -> "No @Endpoint id attribute specified for " + bean.getClass().getName());
   // bean的名称      
   this.beanName = beanName;
   this.bean = bean;
   // EndpointId
   this.id = EndpointId.of(environment, id);
   // 默认是否可用,@Endpoint注解中的属性enableByDefault值 
   this.enabledByDefault = annotation.getBoolean("enableByDefault");
   // 过滤器,比如@ServletEndpoint上标注了@FilteredEndpoint(ServletEndpointFilter.class)
   this.filter = getFilter(this.bean.getClass());
}

(2)addExtensionBeans

对EndpointBean,添加扩展。是@EndpointExtension的功能的处理:

private void addExtensionBeans(Collection endpointBeans) {
   Map byId = endpointBeans.stream()
         .collect(Collectors.toMap(EndpointBean::getId, Function.identity()));
   // 去Spring容器中,获取所有被@EndpointExtension注解标注的bean      
   String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(this.applicationContext,
         EndpointExtension.class);
   for (String beanName : beanNames) {
      // 创建ExtensionBean
      ExtensionBean extensionBean = createExtensionBean(beanName);
      // 根据属性endpoint配置的值获取对应的Endpoint
      EndpointBean endpointBean = byId.get(extensionBean.getEndpointId());
      Assert.state(endpointBean != null, () -> ("Invalid extension '" + extensionBean.getBeanName()
            + "': no endpoint found with id '" + extensionBean.getEndpointId() + "'"));
      addExtensionBean(endpointBean, extensionBean);
   }
}

创建ExtensionBean:

private ExtensionBean createExtensionBean(String beanName) {
   Object bean = this.applicationContext.getBean(beanName);
   return new ExtensionBean(this.applicationContext.getEnvironment(), beanName, bean);
}

添加扩展:

private void addExtensionBean(EndpointBean endpointBean, ExtensionBean extensionBean) {
   // 判断,根据过滤器条件和EndpointBean是否对外暴露
   if (isExtensionExposed(endpointBean, extensionBean)) {
      Assert.state(isEndpointExposed(endpointBean) || isEndpointFiltered(endpointBean),
            () -> "Endpoint bean '" + endpointBean.getBeanName() + "' cannot support the extension bean '"
                  + extensionBean.getBeanName() + "'");
      // extensionBean添加到endpointBean中
      endpointBean.addExtension(extensionBean);
   }
}

(3)convertToEndpoints

将EndpointBean转换成Endpoint。

private Collection convertToEndpoints(Collection endpointBeans) {
   Set endpoints = new LinkedHashSet<>();
   for (EndpointBean endpointBean : endpointBeans) {
       // 是否暴露
      if (isEndpointExposed(endpointBean)) {
         endpoints.add(convertToEndpoint(endpointBean));
      }
   }
   return Collections.unmodifiableSet(endpoints);
}
private E convertToEndpoint(EndpointBean endpointBean) {
  // 使用 MultiValueMap,一个键可以对应多个值。
  // 一个OperationKey对应一个Operation注解方法
   MultiValueMap indexed = new LinkedMultiValueMap<>();
   EndpointId id = endpointBean.getId();
   // 解析@ReadOperation、@WriteOperation、@DeleteOperation,并记录
   addOperations(indexed, id, endpointBean.getBean(), false);
   if (endpointBean.getExtensions().size() > 1) {
      String extensionBeans = endpointBean.getExtensions().stream().map(ExtensionBean::getBeanName)
            .collect(Collectors.joining(", "));
      throw new IllegalStateException("Found multiple extensions for the endpoint bean "
            + endpointBean.getBeanName() + " (" + extensionBeans + ")");
   }
   // 继续扩展ExtensionBean中的@ReadOperation、@WriteOperation、@DeleteOperation,并记录
   for (ExtensionBean extensionBean : endpointBean.getExtensions()) {
      addOperations(indexed, id, extensionBean.getBean(), true);
   }
   // 判断在一个Endpoint中是否存在多个相同的@*Operation注解
   // 即同一个注解只能在一个Endpoint用一次
   assertNoDuplicateOperations(endpointBean, indexed);
   List operations = indexed.values().stream().map(this::getLast).filter(Objects::nonNull)
         .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
   return createEndpoint(endpointBean.getBean(), id, endpointBean.isEnabledByDefault(), operations);
}

createEndpoint是抽象方法,具体由子类实现,创建什么类型的Endpoint,由子类决定:

protected abstract E createEndpoint(Object endpointBean, EndpointId id, boolean enabledByDefault,
      Collection operations);

WebEndpointDiscoverer实现createEndpoint方法,创建DiscoveredWebEndpoint对象:

@Override
protected ExposableWebEndpoint createEndpoint(Object endpointBean, EndpointId id, boolean enabledByDefault,
      Collection operations) {
   String rootPath = PathMapper.getRootPath(this.endpointPathMappers, id);
   // 创建DiscoveredWebEndpoint
   return new DiscoveredWebEndpoint(this, endpointBean, id, rootPath, enabledByDefault, operations);
}

2、servletEndpointsSupplier#getEndpoints:

即调用ServletEndpointDiscoverer,ServletEndpointDiscoverer与webEndpointsSupplier有共同的父类EndpointDiscoverer,前面所有的方法调用都相同,就是最后createEndpoint实现不同,创建DiscoveredServletEndpoint对象:

@Override
protected ExposableServletEndpoint createEndpoint(Object endpointBean, EndpointId id, boolean enabledByDefault,
      Collection operations) {
   String rootPath = PathMapper.getRootPath(this.endpointPathMappers, id);
   return new DiscoveredServletEndpoint(this, endpointBean, id, rootPath, enabledByDefault);
}

3、controllerEndpointsSupplier#getEndpoints:

调用的是ControllerEndpointDiscoverer,创建DiscoveredControllerEndpoint对象:

@Override
protected ExposableControllerEndpoint createEndpoint(Object endpointBean, EndpointId id, boolean enabledByDefault,
      Collection operations) {
   String rootPath = PathMapper.getRootPath(this.endpointPathMappers, id);
   return new DiscoveredControllerEndpoint(this, endpointBean, id, rootPath, enabledByDefault);
}

补充:如何判断重复Operation方法:

在《初识篇》的末尾测试了在同一个Endpoint多次使用同一个Operation注解的情况,下面来具体看看对于重复的Operation是如何判断的。

在上面的代码中,convertToEndpoint方法中有解析Endpoint中Operation注解的过程,解析后的结果放进了MultiValueMap中:

MultiValueMap indexed = new LinkedMultiValueMap<>();

MultiValueMap是一个键可以对应多个值的map,一个OperationKey就是对应一个Operation注解方法。当一个Operation注解方法对应了相同的OperationKey就表示重复了。所以需要查看对于OperationKey是如何判断是否是同一个对象的。判断是MultiValueMap的功能,通过key的equals与hashCode方法,查看OperationKey的equals和hashCode方法:

@Override
public boolean equals(Object obj) {
   if (obj == this) {
      return true;
   }
   if (obj == null || getClass() != obj.getClass()) {
      return false;
   }
   return this.key.equals(((OperationKey) obj).key);
}

@Override
public int hashCode() {
   return this.key.hashCode();
}

从equals方法可以看出,对于不同的Operation对象,如何它们的key相等就表示相等了,于是下面就要对key的比较了,key是在调用构造器的时候被赋值的:

public OperationKey(Object key, Supplier description) {
   Assert.notNull(key, "Key must not be null");
   Assert.notNull(description, "Description must not be null");
   this.key = key;
   this.description = description;
}

JmxEndpointDiscoverer调用,key是Operation的name属性值(operation#getName),也就是方法名,是String类型的,通过String的equals方法比较两个对象:

@Override
protected OperationKey createOperationKey(JmxOperation operation) {
   return new OperationKey(operation.getName(), () -> "MBean call '" + operation.getName() + "'");
}

WebEndpointDiscoverer调用,key是requestPredicate属性值,是WebOperationRequestPredicate类型的,通过WebOperationRequestPredicate的equals方法比较两个对象:

@Override
protected OperationKey createOperationKey(WebOperation operation) {
   return new OperationKey(operation.getRequestPredicate(),
         () -> "web request predicate " + operation.getRequestPredicate());
}

查看WebOperationRequestPredicate的equals方法,需要consumes,httpMethod(Operation注解类型),id(注解@Endpoint的id属性值),produces的值全都相等,两个对象才是相等的。在同一个WEB Endpoint中,同一种Operation注解方法的httpMethod和id都是相同的:

@Override
public boolean equals(Object obj) {
   if (this == obj) {
      return true;
   }
   if (obj == null || getClass() != obj.getClass()) {
      return false;
   }
   WebOperationRequestPredicate other = (WebOperationRequestPredicate) obj;
   boolean result = true;
   result = result && this.consumes.equals(other.consumes);
   result = result && this.httpMethod == other.httpMethod;
   result = result && this.canonicalPath.equals(other.canonicalPath);
   result = result && this.produces.equals(other.produces);
   return result;
}

综上所述,对于JMX Endpoint,在同一个Endpoint中,重复的Operation的判断条件就是方法名是否相同。对于WEB Endpoint,在同一个Endpoint中,重复的Operation的判断条件就是consumes,httpMethod,id,produces的值全都相等。

 

暴露过程:

WebMvcEndpointHandlerMapping

new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes,
            corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath),
            shouldRegisterLinksMapping);

类图:

Spring Boot Actuator-WEB Endpoints_第1张图片

WebMvcEndpointHandlerMapping继承抽象类AbstractWebMvcEndpointHandlerMapping,间接实现了InitializingBean。Spring初始化Bean的时候,会调用InitializingBean的afterPropertiesSet。所以入口就从afterPropertiesSet的方法实现找起,在抽象类AbstractHandlerMethodMapping中实现了afterPropertiesSet方法:

@Override
public void afterPropertiesSet() {
   // 调用 initHandlerMethods
   initHandlerMethods();
}

(1)此时调用的initHandlerMethods方法并不是AbstractHandlerMethodMapping中自身的这个方法,因为在AbstractWebMvcEndpointHandlerMapping中重写了此方法:

// AbstractWebMvcEndpointHandlerMapping#initHandlerMethods
@Override
protected void initHandlerMethods() {
   // 遍历webEndpoints集合 
   for (ExposableWebEndpoint endpoint : this.endpoints) {
      for (WebOperation operation : endpoint.getOperations()) {
         // 创建和注册请求接口
         registerMappingForOperation(endpoint, operation);
      }
   }
   // 注册根目录接口,即/actuator
   if (this.shouldRegisterLinksMapping) {
      registerLinksMapping();
   }
}

this.endpoints是在创建WebMvcEndpointHandlerMapping对象的时候,传进来的参数webEndpoints:

public WebMvcEndpointHandlerMapping(EndpointMapping endpointMapping, Collection endpoints,
      EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration,
      EndpointLinksResolver linksResolver, boolean shouldRegisterLinksMapping) {
   super(endpointMapping, endpoints, endpointMediaTypes, corsConfiguration, shouldRegisterLinksMapping);
   this.linksResolver = linksResolver;
   setOrder(-100);
}

(2)registerMappingForOperation

// AbstractWebMvcEndpointHandlerMapping#registerMappingForOperation
private void registerMappingForOperation(ExposableWebEndpoint endpoint, WebOperation operation) {
   // 获取请求方式、consumers、produces等值 
   WebOperationRequestPredicate predicate = operation.getRequestPredicate();
   // 请求路径,即@Endpoint注解的id值
   String path = predicate.getPath();
   String matchAllRemainingPathSegmentsVariable = predicate.getMatchAllRemainingPathSegmentsVariable();
   if (matchAllRemainingPathSegmentsVariable != null) {
      path = path.replace("{*" + matchAllRemainingPathSegmentsVariable + "}", "**");
   }
   // 创建ServletWebOperation,可以理解成Spring MVC中的HandlerAdapter
   ServletWebOperation servletWebOperation = wrapServletWebOperation(endpoint, operation,
         new ServletWebOperationAdapter(operation));
   // createRequestMappingInfo创建HanlderMapping接口路径映射信息      
   registerMapping(createRequestMappingInfo(predicate, path), new OperationHandler(servletWebOperation),
         this.handleMethod);
}

(3)registerMapping

public void registerMapping(T mapping, Object handler, Method method) {
   if (logger.isTraceEnabled()) {
      logger.trace("Register \"" + mapping + "\" to " + method.toGenericString());
   }
   // 往Spring容器中注册接口信息
   this.mappingRegistry.register(mapping, handler, method);
}

接下来就是Spring MVC的功能了。

你可能感兴趣的:(博客,spring,boot)