Talk about springcloud’s featuresEndpoint

  springboot

Order

This paper mainly studies the featuresEndpoint of springcloud.

/actuator/features

{
  "enabled": [
    {
      "type": "com.netflix.discovery.EurekaClient",
      "name": "Eureka Client",
      "version": "1.8.8",
      "vendor": null
    },
    {
      "type": "org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClient",
      "name": "DiscoveryClient",
      "version": "2.0.0.RC1",
      "vendor": "Pivotal Software, Inc."
    },
    {
      "type": "org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient",
      "name": "LoadBalancerClient",
      "version": "2.0.0.RC1",
      "vendor": "Pivotal Software, Inc."
    },
    {
      "type": "com.netflix.ribbon.Ribbon",
      "name": "Ribbon",
      "version": "2.2.5",
      "vendor": null
    },
    {
      "type": "feign.Feign",
      "name": "Feign",
      "version": null,
      "vendor": null
    }
  ],
  "disabled": []
}

CommonsClientAutoConfiguration

spring-cloud-commons-2.0.0.RC1-sources.jar! /org/springframework/cloud/client/CommonsClientAutoConfiguration.java

@Configuration
@AutoConfigureOrder(0)
public class CommonsClientAutoConfiguration {

    @Configuration
    @EnableConfigurationProperties(DiscoveryClientHealthIndicatorProperties.class)
    @ConditionalOnClass(HealthIndicator.class)
    @ConditionalOnBean(DiscoveryClient.class)
    @ConditionalOnProperty(value = "spring.cloud.discovery.enabled", matchIfMissing = true)
    protected static class DiscoveryLoadBalancerConfiguration {
        @Bean
        @ConditionalOnProperty(value = "spring.cloud.discovery.client.health-indicator.enabled", matchIfMissing = true)
        public DiscoveryClientHealthIndicator discoveryClientHealthIndicator(
                DiscoveryClient discoveryClient, DiscoveryClientHealthIndicatorProperties properties) {
            return new DiscoveryClientHealthIndicator(discoveryClient, properties);
        }

        @Bean
        @ConditionalOnProperty(value = "spring.cloud.discovery.client.composite-indicator.enabled", matchIfMissing = true)
        @ConditionalOnBean(DiscoveryHealthIndicator.class)
        public DiscoveryCompositeHealthIndicator discoveryCompositeHealthIndicator(
                HealthAggregator aggregator, List<DiscoveryHealthIndicator> indicators) {
            return new DiscoveryCompositeHealthIndicator(aggregator, indicators);
        }

        @Bean
        public HasFeatures commonsFeatures() {
            return HasFeatures.abstractFeatures(DiscoveryClient.class,
                    LoadBalancerClient.class);
        }
    }

    @Configuration
    @ConditionalOnClass(Endpoint.class)
    @ConditionalOnProperty(value = "spring.cloud.features.enabled", matchIfMissing = true)
    protected static class ActuatorConfiguration {
        @Autowired(required = false)
        private List<HasFeatures> hasFeatures = new ArrayList<>();

        @Bean
        @ConditionalOnEnabledEndpoint
        public FeaturesEndpoint featuresEndpoint() {
            return new FeaturesEndpoint(this.hasFeatures);
        }
    }

}

Here is an ActuatorConfiguration to register featuresEndpoint.

FeaturesEndpoint

spring-cloud-commons-2.0.0.RC1-sources.jar! /org/springframework/cloud/client/actuator/FeaturesEndpoint.java

@Endpoint(id = "features")
public class FeaturesEndpoint
        implements ApplicationContextAware {

    private final List<HasFeatures> hasFeaturesList;
    private ApplicationContext context;

    public FeaturesEndpoint(List<HasFeatures> hasFeaturesList) {
        this.hasFeaturesList = hasFeaturesList;
    }

    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        this.context = context;
    }

    @ReadOperation
    public Features features() {
        Features features = new Features();

        for (HasFeatures hasFeatures : this.hasFeaturesList) {
            List<Class<?>> abstractFeatures = hasFeatures.getAbstractFeatures();
            if (abstractFeatures != null) {
                for (Class<?> clazz : abstractFeatures) {
                    addAbstractFeature(features, clazz);
                }
            }

            List<NamedFeature> namedFeatures = hasFeatures.getNamedFeatures();
            if (namedFeatures != null) {
                for (NamedFeature namedFeature : namedFeatures) {
                    addFeature(features, namedFeature);
                }
            }
        }

        return features;
    }

    private void addAbstractFeature(Features features, Class<?> type) {
        String featureName = type.getSimpleName();
        try {
            Object bean = this.context.getBean(type);
            Class<?> beanClass = bean.getClass();
            addFeature(features, new NamedFeature(featureName, beanClass));
        }
        catch (NoSuchBeanDefinitionException e) {
            features.getDisabled().add(featureName);
        }
    }

    private void addFeature(Features features, NamedFeature feature) {
        Class<?> type = feature.getType();
        features.getEnabled()
                .add(new Feature(feature.getName(), type.getCanonicalName(),
                        type.getPackage().getImplementationVersion(),
                        type.getPackage().getImplementationVendor()));
    }
    //......
}

Here, two types of features are assembled by hasFeaturesList, one is abstractFeatures, and the other is namedFeatures.

HasFeatures

spring-cloud-commons-2.0.0.RC1-sources.jar! /org/springframework/cloud/client/actuator/HasFeatures.java

public class HasFeatures {

    private final List<Class<?>> abstractFeatures = new ArrayList<>();

    private final List<NamedFeature> namedFeatures = new ArrayList<>();

    public static HasFeatures abstractFeatures(Class<?>... abstractFeatures) {
        return new HasFeatures(Arrays.asList(abstractFeatures),
                Collections.<NamedFeature> emptyList());
    }

    public static HasFeatures namedFeatures(NamedFeature... namedFeatures) {
        return new HasFeatures(Collections.<Class<?>> emptyList(),
                Arrays.asList(namedFeatures));
    }

    public static HasFeatures namedFeature(String name, Class<?> type) {
        return namedFeatures(new NamedFeature(name, type));
    }

    public static HasFeatures namedFeatures(String name1, Class<?> type1, String name2,
            Class<?> type2) {
        return namedFeatures(new NamedFeature(name1, type1),
                new NamedFeature(name2, type2));
    }

    public HasFeatures(List<Class<?>> abstractFeatures,
            List<NamedFeature> namedFeatures) {
        this.abstractFeatures.addAll(abstractFeatures);
        this.namedFeatures.addAll(namedFeatures);
    }

    public List<Class<?>> getAbstractFeatures() {
        return this.abstractFeatures;
    }

    public List<NamedFeature> getNamedFeatures() {
        return this.namedFeatures;
    }
}

Two types of features are defined here, one is abstractFeatures and the other is namedFeatures.

HasFeatures instance

spring-cloud-netflix-eureka-client-2.0.0.RC1-sources.jar! /org/springframework/cloud/netflix/eureka/EurekaClientAutoConfiguration.java

@Configuration
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@Import(DiscoveryClientOptionalArgsConfiguration.class)
@ConditionalOnBean(EurekaDiscoveryClientConfiguration.Marker.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class,
        CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class })
@AutoConfigureAfter(name = {"org.springframework.cloud.autoconfigure.RefreshAutoConfiguration",
        "org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration",
        "org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration"})
public class EurekaClientAutoConfiguration {

    private ConfigurableEnvironment env;

    public EurekaClientAutoConfiguration(ConfigurableEnvironment env) {
        this.env = env;
    }

    @Bean
    public HasFeatures eurekaFeature() {
        return HasFeatures.namedFeature("Eureka Client", EurekaClient.class);
    }
    //......
}

For example, eureka’s client registered a namedFeature called Eureka Client.

Summary

FeaturesEndpoint provided by springcloud can facilitate us to view some features started by the system and further understand the characteristics of the system.

doc