From 945bd329fb2cc9b9b0272555ca5a1b6a9924efdd Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Tue, 23 Jun 2026 10:11:20 +0800 Subject: [PATCH 1/6] Bump microsphere-spring-boot to 0.1.23 Update microsphere-spring-boot.version in the parent POM from 0.1.22 to 0.1.23 to pick up the latest framework fixes and improvements. --- microsphere-spring-cloud-parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsphere-spring-cloud-parent/pom.xml b/microsphere-spring-cloud-parent/pom.xml index 913cc30d..568e7255 100644 --- a/microsphere-spring-cloud-parent/pom.xml +++ b/microsphere-spring-cloud-parent/pom.xml @@ -20,7 +20,7 @@ - 0.1.22 + 0.1.23 1.21.4 5.14.4 From 770049824794ae4662d930bd1091ee2ddc92e393 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Wed, 24 Jun 2026 17:16:22 +0800 Subject: [PATCH 2/6] Bump microsphere-spring-boot to 0.1.24 Update microsphere-spring-boot.version in parent POM from 0.1.23 to 0.1.24. This increments the BOM version used across modules to pick up the latest microsphere Spring Boot fixes and improvements. --- microsphere-spring-cloud-parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsphere-spring-cloud-parent/pom.xml b/microsphere-spring-cloud-parent/pom.xml index 568e7255..2ecf62c4 100644 --- a/microsphere-spring-cloud-parent/pom.xml +++ b/microsphere-spring-cloud-parent/pom.xml @@ -20,7 +20,7 @@ - 0.1.23 + 0.1.24 1.21.4 5.14.4 From 2aca15c6818f9b53c517d5d6510c9d3a8a3ba10c Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Wed, 24 Jun 2026 17:40:35 +0800 Subject: [PATCH 3/6] Introduce FeaturesProperties and utilities Add typed configuration binding and helpers for feature registration: FeaturesProperties, FeaturesUtils, NamedFeatureComparator, and FeaturesConstants. Refactor ConfigurationPropertyHasFeaturesAutoConfiguration to use @EnableConfigurationProperties, inject FeaturesProperties, detect bean types, and register HasFeatures via DefaultListableBeanFactory; switch internal collections to Sets and adapt conversion to lists for HasFeatures. Add default features.yaml and unit tests to cover constants, utils, and auto-configuration behavior. --- ...nPropertyHasFeaturesAutoConfiguration.java | 249 +++++++----------- .../client/actuator/FeaturesProperties.java | 64 +++++ .../cloud/client/actuator/FeaturesUtils.java | 108 ++++++++ .../actuator/NamedFeatureComparator.java | 48 ++++ .../actuator/constants/FeaturesConstants.java | 72 +++++ .../META-INF/config/default/features.yaml | 121 +++++++++ ...pertyHasFeaturesAutoConfigurationTest.java | 70 +++-- .../client/actuator/FeaturesUtilsTest.java | 61 +++++ .../constants/FeaturesConstantsTest.java | 58 ++++ 9 files changed, 664 insertions(+), 187 deletions(-) create mode 100644 microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/actuator/FeaturesProperties.java create mode 100644 microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/actuator/FeaturesUtils.java create mode 100644 microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/actuator/NamedFeatureComparator.java create mode 100644 microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/actuator/constants/FeaturesConstants.java create mode 100644 microsphere-spring-cloud-commons/src/main/resources/META-INF/config/default/features.yaml create mode 100644 microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/actuator/FeaturesUtilsTest.java create mode 100644 microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/actuator/constants/FeaturesConstantsTest.java diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/actuator/ConfigurationPropertyHasFeaturesAutoConfiguration.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/actuator/ConfigurationPropertyHasFeaturesAutoConfiguration.java index d28cb5af..53f446e1 100644 --- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/actuator/ConfigurationPropertyHasFeaturesAutoConfiguration.java +++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/actuator/ConfigurationPropertyHasFeaturesAutoConfiguration.java @@ -18,44 +18,47 @@ package io.microsphere.spring.cloud.client.actuator; import io.microsphere.logging.Logger; +import io.microsphere.spring.cloud.client.actuator.constants.FeaturesConstants; import io.microsphere.spring.cloud.client.condition.ConditionalOnFeaturesAvailable; import io.microsphere.spring.context.config.AutoRegistrationBean; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.config.SingletonBeanRegistry; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.actuator.HasFeatures; import org.springframework.cloud.client.actuator.NamedFeature; -import org.springframework.context.EnvironmentAware; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.Environment; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; -import static io.microsphere.collection.ListUtils.newLinkedList; +import static io.microsphere.collection.ListUtils.ofList; import static io.microsphere.collection.MapUtils.newLinkedHashMap; -import static io.microsphere.constants.PropertyConstants.MICROSPHERE_PROPERTY_NAME_PREFIX; -import static io.microsphere.constants.SymbolConstants.COMMA_CHAR; -import static io.microsphere.constants.SymbolConstants.DOT; -import static io.microsphere.constants.SymbolConstants.DOT_CHAR; +import static io.microsphere.collection.SetUtils.newLinkedHashSet; +import static io.microsphere.collection.SetUtils.newTreeSet; import static io.microsphere.logging.LoggerFactory.getLogger; -import static io.microsphere.spring.cloud.commons.constants.CommonsPropertyConstants.MICROSPHERE_SPRING_CLOUD_PROPERTY_NAME_PREFIX; -import static io.microsphere.spring.core.env.EnvironmentUtils.asConfigurableEnvironment; -import static io.microsphere.spring.core.env.PropertySourcesUtils.getSubProperties; +import static io.microsphere.spring.beans.BeanSource.BEAN_FACTORY; +import static io.microsphere.spring.beans.factory.BeanFactoryUtils.asDefaultListableBeanFactory; +import static io.microsphere.spring.cloud.client.actuator.FeaturesUtils.getAbstractFeaturePropertyName; +import static io.microsphere.spring.cloud.client.actuator.FeaturesUtils.getHasFeaturesBeanName; +import static io.microsphere.spring.cloud.client.actuator.FeaturesUtils.getNamedFeaturePropertyName; +import static io.microsphere.spring.cloud.client.actuator.FeaturesUtils.getQualifierFeatureName; +import static io.microsphere.spring.cloud.client.actuator.NamedFeatureComparator.INSTANCE; import static io.microsphere.text.FormatUtils.format; import static io.microsphere.util.ClassLoaderUtils.resolveClass; -import static io.microsphere.util.StringUtils.split; -import static java.lang.String.valueOf; +import static io.microsphere.util.ClassUtils.getSimpleName; /** * Auto-registrar for Spring Cloud Client Actuator's {@link HasFeatures} based on configuration properties. *

- * This class scans configuration properties under the prefix {@value #PROPERTY_PREFIX} to automatically register - * {@link HasFeatures} beans. It supports two types of feature definitions: + * This class scans configuration properties under the prefix {@value FeaturesConstants#PROPERTY_NAME_PREFIX} to + * automatically register {@link HasFeatures} beans. It supports two types of feature definitions: *

    *
  • Abstract Features: Defined by listing feature classes directly under a module name.
  • *
  • Named Features: Defined by mapping a specific feature name to a feature class under a module name.
  • @@ -66,13 +69,13 @@ *

    1. Abstract Features

    *
    {@code
      * # Defines abstract features for the 'jdbc' module
    - * microsphere.spring.cloud.features.jdbc=org.springframework.jdbc.core.JdbcTemplate,org.springframework.transaction.PlatformTransactionManager
    + * microsphere.spring.cloud.features.abstract.jdbc=org.springframework.jdbc.core.JdbcTemplate,org.springframework.transaction.PlatformTransactionManager
      * }
    * *

    2. Named Features

    *
    {@code
      * # Defines a named feature 'rest-template' for the 'web' module
    - * microsphere.spring.cloud.features.web.rest-template=org.springframework.web.client.RestTemplate
    + * microsphere.spring.cloud.features.named.web.rest-template=org.springframework.web.client.RestTemplate
      * }
    * *

    Resulting Beans

    @@ -87,38 +90,17 @@ */ @ConditionalOnFeaturesAvailable @AutoConfigureBefore(name = "org.springframework.cloud.client.CommonsClientAutoConfiguration") -public class ConfigurationPropertyHasFeaturesAutoConfiguration implements BeanFactoryAware, BeanClassLoaderAware, - EnvironmentAware { +@EnableConfigurationProperties(FeaturesProperties.class) +public class ConfigurationPropertyHasFeaturesAutoConfiguration implements BeanFactoryAware, BeanClassLoaderAware, InitializingBean { private static final Logger logger = getLogger(ConfigurationPropertyHasFeaturesAutoConfiguration.class); - static final String NAME = "features"; - - /** - * The prefix of the configuration properties for {@link HasFeatures} : "microsphere.spring.cloud.features." - */ - public static final String PROPERTY_PREFIX = MICROSPHERE_SPRING_CLOUD_PROPERTY_NAME_PREFIX + NAME + DOT; - - /** - * The pattern of the configuration properties for abstract features: "microsphere.spring.cloud.features.{module-name}" - */ - public static final String ABSTRACT_FEATURE_PROPERTY_NAME_PATTERN = PROPERTY_PREFIX + "{}"; - - /** - * The pattern of the configuration properties for named features: "microsphere.spring.cloud.features.{module-name}.{feature-name}" - */ - public static final String NAMED_FEATURE_PROPERTY_NAME_PATTERN = ABSTRACT_FEATURE_PROPERTY_NAME_PATTERN + DOT + "{}"; - - /** - * The suffix of the bean name for {@link HasFeatures} : ".features" - */ - public static final String BEAN_NAME_SUFFIX = DOT + NAME; - private ClassLoader classLoader; - private SingletonBeanRegistry singletonBeanRegistry; + private DefaultListableBeanFactory beanFactory; - private final Map moduleFeaturesMap = newLinkedHashMap(); + @Autowired + private FeaturesProperties featuresProperties; @Override public void setBeanClassLoader(ClassLoader classLoader) { @@ -127,141 +109,112 @@ public void setBeanClassLoader(ClassLoader classLoader) { @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.singletonBeanRegistry = (SingletonBeanRegistry) beanFactory; + this.beanFactory = asDefaultListableBeanFactory(beanFactory); } @Override - public void setEnvironment(Environment environment) { - ConfigurableEnvironment env = asConfigurableEnvironment(environment); - Map subProperties = getSubProperties(env, PROPERTY_PREFIX); - for (Entry entry : subProperties.entrySet()) { - String key = entry.getKey(); - String value = valueOf(entry.getValue()); - int index = key.indexOf(DOT_CHAR); - boolean isAbstract = index == -1; - if (isAbstract) { - String[] featureClassNames = split(value, COMMA_CHAR); - String moduleName = key; - addAbstractFeatureClassNames(moduleName, featureClassNames); - } else { - String moduleName = key.substring(0, index); - String featureName = key.substring(index + 1); - String featureClassName = value; - addNamedFeatureClassName(moduleName, featureName, featureClassName); - } - } - registerHasFeaturesBeans(); - } + public void afterPropertiesSet() { + FeaturesProperties featuresProperties = this.featuresProperties; - /** - * Gets the configuration property name for abstract features of the specified module. - *

    Example Usage

    - *
    {@code
    -     * // For module name "jdbc"
    -     * String propertyName = getAbstractFeaturePropertyName("jdbc");
    -     * // Result: "microsphere.spring.cloud.features.jdbc"
    -     * }
    - * - * @param moduleName the name of the module - * @return the configuration property name for abstract features - */ - public static String getAbstractFeaturePropertyName(String moduleName) { - return format(ABSTRACT_FEATURE_PROPERTY_NAME_PATTERN, moduleName); - } + Map moduleFeaturesMap = newLinkedHashMap(); + Map> abstractProperties = featuresProperties.getAbstract(); - /** - * Gets the configuration property name for a named feature of the specified module. - *

    Example Usage

    - *
    {@code
    -     * // For module name "web" and feature name "rest-template"
    -     * String propertyName = getNamedFeaturePropertyName("web", "rest-template");
    -     * // Result: "microsphere.spring.cloud.features.web.rest-template"
    -     * }
    - * - * @param moduleName the name of the module - * @param featureName the name of the feature - * @return the configuration property name for the named feature - */ - public static String getNamedFeaturePropertyName(String moduleName, String featureName) { - return format(NAMED_FEATURE_PROPERTY_NAME_PATTERN, moduleName, featureName); - } + abstractProperties.forEach((moduleName, featureClassNames) -> { + addAbstractFeatureClassNames(moduleName, featureClassNames, moduleFeaturesMap); + }); - /** - * Gets the bean name for {@link HasFeatures} of the specified module. - *

    Example Usage

    - *
    {@code
    -     * // For module name "jdbc"
    -     * String beanName = getBeanName("jdbc");
    -     * // Result: "jdbc.features"
    -     * }
    - * - * @param moduleName the name of the module - * @return the bean name for {@link HasFeatures} - */ - public static String getBeanName(String moduleName) { - return moduleName + BEAN_NAME_SUFFIX; - } + Map> namedProperties = featuresProperties.getNamed(); + + namedProperties.forEach((moduleName, namedFeatures) -> { + for (Entry entry : namedFeatures.entrySet()) { + String featureName = entry.getKey(); + String featureClassName = entry.getValue(); + addNamedFeatureClassName(moduleName, featureName, featureClassName, moduleFeaturesMap); + } + }); - /** - * Gets the qualified feature name for a named feature of the specified module. - *

    Example Usage

    - *
    {@code
    -     * // For module name "web" and feature name "rest-template"
    -     * String qualifiedName = getQualifierFeatureName("web", "rest-template");
    -     * // Result: "microsphere.web.rest-template"
    -     * }
    - * - * @param moduleName the name of the module - * @param featureName the name of the feature - * @return the qualified feature name - */ - public static String getQualifierFeatureName(String moduleName, String featureName) { - return MICROSPHERE_PROPERTY_NAME_PREFIX + moduleName + DOT_CHAR + featureName; + registerHasFeaturesBeans(moduleFeaturesMap); + moduleFeaturesMap.clear(); } - private void addAbstractFeatureClassNames(String moduleName, String[] featureClassNames) { - ModuleFeatures moduleFeatures = getModuleFeatures(moduleName); + private void addAbstractFeatureClassNames(String moduleName, List featureClassNames, Map moduleFeaturesMap) { for (String featureClassName : featureClassNames) { - Class featureClass = loadClass(featureClassName); - if (featureClass == null) { - String propertyName = getAbstractFeaturePropertyName(moduleName); - logger.warn("The class of abstract feature[class : '{}'] is not found in classpath, please check the configuration property : '{}'", - featureClassName, propertyName); - continue; + addAbstractFeatureClassName(moduleName, featureClassName, moduleFeaturesMap); + } + } + + private void addAbstractFeatureClassName(String moduleName, String featureClassName, Map moduleFeaturesMap) { + Class featureClass = loadClass(featureClassName); + if (featureClass == null) { + logger.warn("The class of AbstractFeature[class : '{}'] is not found in classpath, please check the configuration property : '{}'", + featureClassName, getAbstractFeaturePropertyName(moduleName)); + return; + } + + Set> beanTypes = getBeanTypes(featureClass); + if (beanTypes.isEmpty()) { // No bean type found + addAbstractFeatureClass(moduleName, featureClass, moduleFeaturesMap); + } else { + for (Class beanType : beanTypes) { + addNamedFeatureClass(moduleName, beanType, moduleFeaturesMap); } - moduleFeatures.abstractFeatures.add(featureClass); } } - private void addNamedFeatureClassName(String moduleName, String featureName, String featureClassName) { - ModuleFeatures moduleFeatures = getModuleFeatures(moduleName); + private void addNamedFeatureClassName(String moduleName, String featureName, String featureClassName, Map moduleFeaturesMap) { String name = getQualifierFeatureName(moduleName, featureName); Class featureClass = loadClass(featureClassName); + String propertyName = getNamedFeaturePropertyName(moduleName, featureName); if (featureClass == null) { - String propertyName = getNamedFeaturePropertyName(moduleName, featureName); logger.warn("The class of named feature[name : '{}' , class : '{}'] is not found in classpath, please check the configuration property : '{}'", name, featureClassName, propertyName); return; } + + addNamedFeatureClass(moduleName, featureName, featureClass, moduleFeaturesMap); + } + + private Set> getBeanTypes(Class featureClass) { + return (Set) BEAN_FACTORY.getBeanTypes(this.beanFactory, featureClass); + } + + private void addAbstractFeatureClass(String moduleName, Class featureClass, Map moduleFeaturesMap) { + ModuleFeatures moduleFeatures = getModuleFeatures(moduleFeaturesMap, moduleName); + logger.trace("The AbstractFeature[module : '{}' , class : '{}'] will be added in the HasFeatures.", moduleName, + featureClass.getName()); + moduleFeatures.abstractFeatures.add(featureClass); + } + + private void addNamedFeatureClass(String moduleName, Class featureClass, Map moduleFeaturesMap) { + String featureName = getSimpleName(featureClass); + addNamedFeatureClass(moduleName, featureName, featureClass, moduleFeaturesMap); + } + + private void addNamedFeatureClass(String moduleName, String featureName, Class featureClass, Map moduleFeaturesMap) { + String name = getQualifierFeatureName(moduleName, featureName); NamedFeature namedFeature = new NamedFeature(name, featureClass); + ModuleFeatures moduleFeatures = getModuleFeatures(moduleFeaturesMap, moduleName); + logger.trace("The NamedFeature[module : '{}' , name : '{}' , class : '{}'] will be added in the HasFeatures.", + moduleName, name, featureClass.getName()); moduleFeatures.namedFeatures.add(namedFeature); } + private Class loadClass(String className) { return resolveClass(className, this.classLoader); } - private ModuleFeatures getModuleFeatures(String moduleName) { - return this.moduleFeaturesMap.computeIfAbsent(moduleName, ModuleFeatures::new); + private ModuleFeatures getModuleFeatures(Map moduleFeaturesMap, String moduleName) { + return moduleFeaturesMap.computeIfAbsent(moduleName, ModuleFeatures::new); } - private void registerHasFeaturesBeans() { - for (Entry entry : this.moduleFeaturesMap.entrySet()) { + private void registerHasFeaturesBeans(Map moduleFeaturesMap) { + for (Entry entry : moduleFeaturesMap.entrySet()) { String moduleName = entry.getKey(); ModuleFeatures moduleFeatures = entry.getValue(); HasFeatures hasFeatures = moduleFeatures.toHasFeatures(); - String beanName = getBeanName(moduleName); - this.singletonBeanRegistry.registerSingleton(beanName, hasFeatures); + String beanName = getHasFeaturesBeanName(moduleName); + this.beanFactory.registerSingleton(beanName, hasFeatures); } } @@ -269,9 +222,9 @@ static class ModuleFeatures { private final String moduleName; - private final List> abstractFeatures = newLinkedList(); + private final Set> abstractFeatures = newLinkedHashSet(); - private final List namedFeatures = newLinkedList(); + private final Set namedFeatures = newTreeSet(INSTANCE); ModuleFeatures(String moduleName) { this.moduleName = moduleName; @@ -279,7 +232,7 @@ static class ModuleFeatures { HasFeatures toHasFeatures() { logger.info(toString()); - return new HasFeatures(this.abstractFeatures, this.namedFeatures); + return new HasFeatures(ofList(this.abstractFeatures), ofList(this.namedFeatures)); } @Override diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/actuator/FeaturesProperties.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/actuator/FeaturesProperties.java new file mode 100644 index 00000000..822a7a04 --- /dev/null +++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/actuator/FeaturesProperties.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.microsphere.spring.cloud.client.actuator; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.client.actuator.FeaturesEndpoint; + +import java.util.List; +import java.util.Map; + +import static io.microsphere.spring.cloud.client.actuator.constants.FeaturesConstants.PROPERTY_NAME_PREFIX; +import static java.util.Collections.emptyMap; + +/** + * The {@link ConfigurationProperties} for {@link FeaturesEndpoint} + * + * @author Mercy + * @see ConfigurationProperties + * @since 1.0.0 + */ +@ConfigurationProperties(prefix = PROPERTY_NAME_PREFIX) +public class FeaturesProperties { + + private Map> _abstract; + + private Map> named; + + public void setAbstract(Map> _abstract) { + this._abstract = _abstract; + } + + public Map> getAbstract() { + if (_abstract == null) { + return emptyMap(); + } + return _abstract; + } + + public void setNamed(Map> named) { + this.named = named; + } + + public Map> getNamed() { + if (named == null) { + return emptyMap(); + } + return named; + } +} diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/actuator/FeaturesUtils.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/actuator/FeaturesUtils.java new file mode 100644 index 00000000..baa0073d --- /dev/null +++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/actuator/FeaturesUtils.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.microsphere.spring.cloud.client.actuator; + +import io.microsphere.util.Utils; +import org.springframework.cloud.client.actuator.HasFeatures; + +import static io.microsphere.constants.PropertyConstants.MICROSPHERE_PROPERTY_NAME_PREFIX; +import static io.microsphere.constants.SymbolConstants.DOT_CHAR; +import static io.microsphere.spring.cloud.client.actuator.constants.FeaturesConstants.ABSTRACT_FEATURE_PROPERTY_NAME_PATTERN; +import static io.microsphere.spring.cloud.client.actuator.constants.FeaturesConstants.BEAN_NAME_SUFFIX; +import static io.microsphere.spring.cloud.client.actuator.constants.FeaturesConstants.NAMED_FEATURE_PROPERTY_NAME_PATTERN; +import static io.microsphere.text.FormatUtils.format; + +/** + * The {@link Utils utilities} class for Spring Cloud {@link HasFeatures features} + * + * @author Mercy + * @see HasFeatures + * @see Utils + * @since 1.0.0 + */ +public abstract class FeaturesUtils implements Utils { + + /** + * Gets the configuration property name for abstract features of the specified module. + *

    Example Usage

    + *
    {@code
    +     * // For module name "jdbc"
    +     * String propertyName = getAbstractFeaturePropertyName("jdbc");
    +     * // Result: "microsphere.spring.cloud.features.abstract.jdbc"
    +     * }
    + * + * @param moduleName the name of the module + * @return the configuration property name for abstract features + */ + public static String getAbstractFeaturePropertyName(String moduleName) { + return format(ABSTRACT_FEATURE_PROPERTY_NAME_PATTERN, moduleName); + } + + /** + * Gets the configuration property name for a named feature of the specified module. + *

    Example Usage

    + *
    {@code
    +     * // For module name "web" and feature name "rest-template"
    +     * String propertyName = getNamedFeaturePropertyName("web", "rest-template");
    +     * // Result: "microsphere.spring.cloud.features.named.web.rest-template"
    +     * }
    + * + * @param moduleName the name of the module + * @param featureName the name of the feature + * @return the configuration property name for the named feature + */ + public static String getNamedFeaturePropertyName(String moduleName, String featureName) { + return format(NAMED_FEATURE_PROPERTY_NAME_PATTERN, moduleName, featureName); + } + + /** + * Gets the bean name for {@link HasFeatures} of the specified module. + *

    Example Usage

    + *
    {@code
    +     * // For module name "jdbc"
    +     * String beanName = getHasFeaturesBeanName("jdbc");
    +     * // Result: "jdbc.features"
    +     * }
    + * + * @param moduleName the name of the module + * @return the bean name for {@link HasFeatures} + */ + public static String getHasFeaturesBeanName(String moduleName) { + return moduleName + BEAN_NAME_SUFFIX; + } + + /** + * Gets the qualified feature name for a named feature of the specified module. + *

    Example Usage

    + *
    {@code
    +     * // For module name "web" and feature name "rest-template"
    +     * String qualifiedName = getQualifierFeatureName("web", "rest-template");
    +     * // Result: "microsphere.web.rest-template"
    +     * }
    + * + * @param moduleName the name of the module + * @param featureName the name of the feature + * @return the qualified feature name + */ + public static String getQualifierFeatureName(String moduleName, String featureName) { + return MICROSPHERE_PROPERTY_NAME_PREFIX + moduleName + DOT_CHAR + featureName; + } + + private FeaturesUtils() { + } +} diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/actuator/NamedFeatureComparator.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/actuator/NamedFeatureComparator.java new file mode 100644 index 00000000..e9381ac0 --- /dev/null +++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/actuator/NamedFeatureComparator.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.microsphere.spring.cloud.client.actuator; + +import org.springframework.cloud.client.actuator.NamedFeature; + +import java.util.Comparator; + +/** + * The {@link Comparator} class for {@link NamedFeature} + * + * @author Mercy + * @see Comparator + * @see NamedFeature + * @since 1.0.0 + */ +public class NamedFeatureComparator implements Comparator { + + public static final NamedFeatureComparator INSTANCE = new NamedFeatureComparator(); + + @Override + public int compare(NamedFeature namedFeature1, NamedFeature namedFeature2) { + int c = namedFeature1.getName().compareTo(namedFeature2.getName()); + if (c == 0) { + c = compare(namedFeature1.getType(), namedFeature2.getType()); + } + return c; + } + + private int compare(Class type1, Class type2) { + return type1.getName().compareTo(type2.getName()); + } +} diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/actuator/constants/FeaturesConstants.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/actuator/constants/FeaturesConstants.java new file mode 100644 index 00000000..0fb71bde --- /dev/null +++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/actuator/constants/FeaturesConstants.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.microsphere.spring.cloud.client.actuator.constants; + +import org.springframework.cloud.client.actuator.HasFeatures; + +import static io.microsphere.constants.SymbolConstants.DOT; +import static io.microsphere.spring.cloud.commons.constants.CommonsPropertyConstants.MICROSPHERE_SPRING_CLOUD_PROPERTY_NAME_PREFIX; + +/** + * The constants class for Spring Cloud {@link HasFeatures features} + * + * @author Mercy + * @see HasFeatures + * @see org.springframework.cloud.client.actuator.FeaturesEndpoint + * @since 1.0.0 + */ +public interface FeaturesConstants { + + String FEATURES = "features"; + + String ABSTRACT = "abstract"; + + String NAMED = "named"; + + String PLACEHOLDER = "{}"; + + /** + * The prefix of the configuration properties for {@link HasFeatures} : "microsphere.spring.cloud.features" + */ + String PROPERTY_NAME_PREFIX = MICROSPHERE_SPRING_CLOUD_PROPERTY_NAME_PREFIX + FEATURES; + + /** + * The prefix of the configuration properties for abstract features: "microsphere.spring.cloud.features.abstract." + */ + String ABSTRACT_FEATURE_PROPERTY_NAME_PREFIX = PROPERTY_NAME_PREFIX + DOT + ABSTRACT + DOT; + + /** + * The prefix of the configuration properties for named features: "microsphere.spring.cloud.features.named." + */ + String NAMED_FEATURE_PROPERTY_NAME_PREFIX = PROPERTY_NAME_PREFIX + DOT + NAMED + DOT; + + /** + * The pattern of the configuration properties for abstract features: "microsphere.spring.cloud.features.abstract.{module-name}" + */ + String ABSTRACT_FEATURE_PROPERTY_NAME_PATTERN = ABSTRACT_FEATURE_PROPERTY_NAME_PREFIX + PLACEHOLDER; + + /** + * The pattern of the configuration properties for named features: "microsphere.spring.cloud.features.named.{module-name}.{feature-name}" + */ + String NAMED_FEATURE_PROPERTY_NAME_PATTERN = NAMED_FEATURE_PROPERTY_NAME_PREFIX + PLACEHOLDER + DOT + PLACEHOLDER; + + /** + * The suffix of the bean name for {@link HasFeatures} : ".features" + */ + String BEAN_NAME_SUFFIX = DOT + FEATURES; +} diff --git a/microsphere-spring-cloud-commons/src/main/resources/META-INF/config/default/features.yaml b/microsphere-spring-cloud-commons/src/main/resources/META-INF/config/default/features.yaml new file mode 100644 index 00000000..a3d5a9d0 --- /dev/null +++ b/microsphere-spring-cloud-commons/src/main/resources/META-INF/config/default/features.yaml @@ -0,0 +1,121 @@ +microsphere: + spring: + cloud: + features: + abstract: + microsphere-spring-context: + - io.microsphere.spring.beans.factory.annotation.AnnotatedInjectionBeanPostProcessor + - io.microsphere.spring.beans.factory.config.GenericBeanPostProcessorAdapter + - io.microsphere.spring.context.event.GenericApplicationListenerAdapter + - io.microsphere.spring.context.event.OnceApplicationContextEventListener + - io.microsphere.spring.context.lifecycle.AbstractSmartLifecycle + ## ListenableAutowireCandidateResolverInitializer + - io.microsphere.spring.beans.factory.support.ListenableAutowireCandidateResolver + - io.microsphere.spring.beans.factory.support.AutowireCandidateResolvingListener + ## @EnableTTLCaching + - io.microsphere.spring.cache.intereptor.TTLCacheResolver + ## @Enable* + - io.microsphere.spring.context.annotation.BeanCapableImportCandidate + ## @OverrideAnnotationAttributes + - io.microsphere.spring.context.annotation.OverrideAnnotationAttributesStrategy + ## @EnableAutoRegistrationBean + - io.microsphere.spring.context.config.AutoRegistrationBean + ## @EnableConfigurationBeanBinding + - io.microsphere.spring.context.config.ConfigurationBeanBinder + - io.microsphere.spring.context.config.ConfigurationBeanCustomizer + - io.microsphere.spring.beans.factory.annotation.ConfigurationBeanBindingPostProcessor + ## @EnableEventExtension + - io.microsphere.spring.context.event.ApplicationEventInterceptor + - io.microsphere.spring.context.event.ApplicationListenerInterceptor + - io.microsphere.spring.context.event.InterceptingApplicationEventMulticaster + - io.microsphere.spring.context.event.InterceptingApplicationEventMulticasterProxy + ## EventPublishingBean* + - io.microsphere.spring.context.event.BeanFactoryListener + - io.microsphere.spring.context.event.BeanListener + - io.microsphere.spring.context.event.EventPublishingBeanBeforeProcessor + - io.microsphere.spring.context.event.EventPublishingBeanAfterProcessor + ## ListenableConfigurableEnvironment + - io.microsphere.spring.core.env.EnvironmentListener + - io.microsphere.spring.core.env.ProfileListener + - io.microsphere.spring.core.env.PropertyResolverListener + ## SpringProtocolURLStreamHandler + - io.microsphere.spring.net.SpringProtocolURLStreamHandler + microsphere-spring-guice: + ## @EnableGuice + - io.microsphere.spring.guice.annotation.GuiceInjectAnnotationBeanPostProcessor + microsphere-spring-jdbc: + ## @EnableP6DataSource + - io.microsphere.spring.jdbc.p6spy.beans.factory.config.P6DataSourceBeanPostProcessor + microsphere-spring-web: + ## @EnableWebExtension + - io.microsphere.spring.web.metadata.WebEndpointMappingResolver + - io.microsphere.spring.web.metadata.WebEndpointMappingRegistry + - io.microsphere.spring.web.metadata.WebEndpointMappingFactory + - io.microsphere.spring.web.metadata.WebEndpointMappingFilter + - io.microsphere.spring.web.metadata.WebEndpointMappingRegistrar + - io.microsphere.spring.web.method.support.HandlerMethodAdvice + - io.microsphere.spring.web.method.support.HandlerMethodArgumentInterceptor + - io.microsphere.spring.web.method.support.HandlerMethodInterceptor + - io.microsphere.spring.web.event.WebEventPublisher + microsphere-spring-webflux: + - io.microsphere.spring.webflux.server.filter.DelegatingWebFilter + - io.microsphere.spring.webflux.server.filter.RequestContextWebFilter + ## @EnableWebFluxExtension + - io.microsphere.spring.webflux.metadata.HandlerMappingWebEndpointMappingResolver + - io.microsphere.spring.webflux.method.InterceptingHandlerMethodProcessor + - io.microsphere.spring.webflux.server.filter.RequestHandledEventPublishingWebFilter + - io.microsphere.spring.webflux.server.filter.RequestContextWebFilter + - io.microsphere.spring.webflux.method.StoringRequestBodyArgumentInterceptor + - io.microsphere.spring.webflux.method.StoringResponseBodyReturnValueInterceptor + - io.microsphere.spring.webflux.handler.ReversedProxyHandlerMapping + microsphere-spring-webmvc: + - io.microsphere.spring.web.servlet.filter.ContentCachingFilter + ## @EnableWebMvcExtension + - io.microsphere.spring.webmvc.metadata.HandlerMappingWebEndpointMappingResolver + - io.microsphere.spring.webmvc.method.support.InterceptingHandlerMethodProcessor + - io.microsphere.spring.webmvc.interceptor.LazyCompositeHandlerInterceptor + - io.microsphere.spring.webmvc.advice.StoringRequestBodyArgumentAdvice + - io.microsphere.spring.webmvc.advice.StoringResponseBodyReturnValueAdvice + - io.microsphere.spring.webmvc.handler.ReversedProxyHandlerMapping + - io.microsphere.spring.web.metadata.ServletWebEndpointMappingResolver + microsphere-spring-boot: + - io.microsphere.spring.boot.context.config.BindableConfigurationBeanBinder + - io.microsphere.spring.boot.context.properties.bind.BindListener + - io.microsphere.spring.boot.context.properties.ListenableConfigurationPropertiesBindHandlerAdvisor + - io.microsphere.spring.boot.context.OnceApplicationPreparedEventListener + - io.microsphere.spring.boot.context.OnceMainApplicationPreparedEventListener + + named: + microsphere-spring-context: + # InjectionPointDependencyResolver + ConstructionInjectionPointDependencyResolver: io.microsphere.spring.beans.factory.ConstructionInjectionPointDependencyResolver + AutowiredInjectionPointDependencyResolver: io.microsphere.spring.beans.factory.annotation.AutowiredInjectionPointDependencyResolver + ResourceInjectionPointDependencyResolver: io.microsphere.spring.beans.factory.annotation.ResourceInjectionPointDependencyResolver + # ApplicationContextInitializer + EventPublishingBeanInitializer: io.microsphere.spring.context.event.EventPublishingBeanInitializer + ListenableAutowireCandidateResolverInitializer: io.microsphere.spring.beans.factory.support.ListenableAutowireCandidateResolverInitializer + ListenableConfigurableEnvironmentInitializer: io.microsphere.spring.core.env.ListenableConfigurableEnvironmentInitializer + AutoRegistrationBeanInitializer: io.microsphere.spring.boot.context.config.AutoRegistrationBeanInitializer + microsphere-spring-webflux: + SpringWebFluxHelper: io.microsphere.spring.webflux.util.SpringWebFluxHelper + microsphere-spring-webmvc: + SpringWebMvcHelper: io.microsphere.spring.webmvc.util.SpringWebMvcHelper + microsphere-spring-boot: + # ApplicationContextInitializer + ConditionEvaluationReportInitializer: io.microsphere.spring.boot.report.ConditionEvaluationReportInitializer + OriginTrackedConfigurationPropertyInitializer: io.microsphere.spring.boot.env.config.OriginTrackedConfigurationPropertyInitializer + # SpringApplicationRunListener + BannedArtifactClassLoadingListener: io.microsphere.spring.boot.classloading.BannedArtifactClassLoadingListener + FailureReportSpringApplicationRunListener: io.microsphere.spring.boot.listener.FailureReportSpringApplicationRunListener + # ApplicationListener + ArtifactsCollisionDiagnosisListener: io.microsphere.spring.boot.diagnostics.ArtifactsCollisionDiagnosisListener + ConditionEvaluationReportListener: io.microsphere.spring.boot.report.ConditionEvaluationReportListener + DefaultPropertiesApplicationListener: io.microsphere.spring.boot.env.DefaultPropertiesApplicationListener + # SpringBootExceptionReporter + ConditionEvaluationSpringBootExceptionReporter: io.microsphere.spring.boot.report.ConditionEvaluationSpringBootExceptionReporter + # FailureAnalyzer + ArtifactsCollisionFailureAnalyzer: io.microsphere.spring.boot.diagnostics.ArtifactsCollisionFailureAnalyzer + # AutoConfigurationImportFilter + ConfigurableAutoConfigurationImportFilter: io.microsphere.spring.boot.autoconfigure.ConfigurableAutoConfigurationImportFilter + # DefaultPropertiesPostProcessor + SpringApplicationDefaultPropertiesPostProcessor: io.microsphere.spring.boot.env.SpringApplicationDefaultPropertiesPostProcessor diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/actuator/ConfigurationPropertyHasFeaturesAutoConfigurationTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/actuator/ConfigurationPropertyHasFeaturesAutoConfigurationTest.java index 67807fd0..1c51c30e 100644 --- a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/actuator/ConfigurationPropertyHasFeaturesAutoConfigurationTest.java +++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/actuator/ConfigurationPropertyHasFeaturesAutoConfigurationTest.java @@ -22,23 +22,18 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.client.actuator.FeaturesEndpoint; import org.springframework.cloud.client.actuator.HasFeatures; import org.springframework.cloud.client.actuator.NamedFeature; -import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; import java.util.List; import java.util.Map; -import static io.microsphere.spring.cloud.client.actuator.ConfigurationPropertyHasFeaturesAutoConfiguration.ABSTRACT_FEATURE_PROPERTY_NAME_PATTERN; -import static io.microsphere.spring.cloud.client.actuator.ConfigurationPropertyHasFeaturesAutoConfiguration.BEAN_NAME_SUFFIX; -import static io.microsphere.spring.cloud.client.actuator.ConfigurationPropertyHasFeaturesAutoConfiguration.NAME; -import static io.microsphere.spring.cloud.client.actuator.ConfigurationPropertyHasFeaturesAutoConfiguration.NAMED_FEATURE_PROPERTY_NAME_PATTERN; -import static io.microsphere.spring.cloud.client.actuator.ConfigurationPropertyHasFeaturesAutoConfiguration.PROPERTY_PREFIX; -import static io.microsphere.spring.cloud.client.actuator.ConfigurationPropertyHasFeaturesAutoConfiguration.getBeanName; -import static io.microsphere.spring.cloud.client.actuator.ConfigurationPropertyHasFeaturesAutoConfiguration.getQualifierFeatureName; +import static io.microsphere.spring.cloud.client.actuator.FeaturesUtils.getHasFeaturesBeanName; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -49,15 +44,19 @@ * @since 1.0.0 */ @SpringBootTest( - classes = ConfigurationPropertyHasFeaturesAutoConfigurationTest.class, + classes = { + RestTemplate.class, + ConfigurationPropertyHasFeaturesAutoConfigurationTest.class + }, properties = { - "microsphere.spring.cloud.features.jdbc.JdbcTemplate=org.springframework.jdbc.core.JdbcTemplate", - "microsphere.spring.cloud.features.rest.RestTemplate=org.springframework.web.client.RestTemplate", - "microsphere.spring.cloud.features.rest=org.springframework.web.client.RestOperations", - "microsphere.spring.cloud.features.redis.RedisConnection=org.springframework.data.redis.connection.RedisConnection", - "microsphere.spring.cloud.features.redis=org.springframework.data.redis.connection.RedisConnectionFactory," + + "microsphere.spring.cloud.features.abstract.rest=org.springframework.web.client.RestOperations", + "microsphere.spring.cloud.features.abstract.redis=org.springframework.data.redis.connection.RedisConnectionFactory," + "org.springframework.data.redis.core.RedisTemplate," + "org.springframework.data.redis.core.StringRedisTemplate", + "microsphere.spring.cloud.features.named.jdbc.JdbcTemplate=org.springframework.jdbc.core.JdbcTemplate", + "microsphere.spring.cloud.features.named.rest.RestTemplate=org.springframework.web.client.RestTemplate", + "microsphere.spring.cloud.features.named.redis.RedisConnection=org.springframework.data.redis.connection.RedisConnection", + "microsphere.spring.cloud.features.named.web.WebClient=org.springframework.web.reactive.function.client.WebClient" } ) @EnableAutoConfiguration @@ -66,42 +65,35 @@ class ConfigurationPropertyHasFeaturesAutoConfigurationTest { @Autowired private Map hasFeaturesBeansMap; + @Autowired + private FeaturesEndpoint featuresEndpoint; + @Test void test() { + HasFeatures hasFeatures = this.hasFeaturesBeansMap.get(getHasFeaturesBeanName("jdbc")); + assertNull(hasFeatures); - testConstants(); + hasFeatures = this.hasFeaturesBeansMap.get(getHasFeaturesBeanName("redis")); + assertNull(hasFeatures); - HasFeatures hasFeatures = this.hasFeaturesBeansMap.get(getBeanName("jdbc")); + hasFeatures = this.hasFeaturesBeansMap.get(getHasFeaturesBeanName("web")); assertNotNull(hasFeatures); - assertTrue(hasFeatures.getAbstractFeatures().isEmpty()); - assertTrue(hasFeatures.getNamedFeatures().isEmpty()); - - hasFeatures = this.hasFeaturesBeansMap.get(getBeanName("rest")); - assertNotNull(hasFeatures); - List> abstractFeatures = hasFeatures.getAbstractFeatures(); - assertEquals(1, abstractFeatures.size()); - assertEquals(RestOperations.class, abstractFeatures.get(0)); - List namedFeatures = hasFeatures.getNamedFeatures(); assertEquals(1, namedFeatures.size()); - NamedFeature namedFeature = namedFeatures.get(0); - assertEquals(getQualifierFeatureName("rest", "RestTemplate"), namedFeature.getName()); - assertEquals(RestTemplate.class, namedFeature.getType()); + assertEquals("microsphere.web.WebClient", namedFeatures.get(0).getName()); + assertTrue(abstractFeatures.isEmpty()); - hasFeatures = this.hasFeaturesBeansMap.get(getBeanName("redis")); + hasFeatures = this.hasFeaturesBeansMap.get(getHasFeaturesBeanName("rest")); assertNotNull(hasFeatures); - assertTrue(hasFeatures.getAbstractFeatures().isEmpty()); - assertTrue(hasFeatures.getNamedFeatures().isEmpty()); - } + abstractFeatures = hasFeatures.getAbstractFeatures(); + namedFeatures = hasFeatures.getNamedFeatures(); + assertEquals("microsphere.rest.RestTemplate", namedFeatures.get(0).getName()); + assertEquals(1, namedFeatures.size()); + assertTrue(abstractFeatures.isEmpty()); - private void testConstants() { - assertEquals("features", NAME); - assertEquals("microsphere.spring.cloud.features.", PROPERTY_PREFIX); - assertEquals("microsphere.spring.cloud.features.{}", ABSTRACT_FEATURE_PROPERTY_NAME_PATTERN); - assertEquals("microsphere.spring.cloud.features.{}.{}", NAMED_FEATURE_PROPERTY_NAME_PATTERN); - assertEquals(".features", BEAN_NAME_SUFFIX); - } + assertNotNull(featuresEndpoint.features()); + } } \ No newline at end of file diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/actuator/FeaturesUtilsTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/actuator/FeaturesUtilsTest.java new file mode 100644 index 00000000..85f85418 --- /dev/null +++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/actuator/FeaturesUtilsTest.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.microsphere.spring.cloud.client.actuator; + + +import org.junit.jupiter.api.Test; + +import static io.microsphere.spring.cloud.client.actuator.FeaturesUtils.getAbstractFeaturePropertyName; +import static io.microsphere.spring.cloud.client.actuator.FeaturesUtils.getHasFeaturesBeanName; +import static io.microsphere.spring.cloud.client.actuator.FeaturesUtils.getNamedFeaturePropertyName; +import static io.microsphere.spring.cloud.client.actuator.FeaturesUtils.getQualifierFeatureName; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * {@link FeaturesUtils} Test + * + * @author Mercy + * @see FeaturesUtils + * @since 1.0.0 + */ +class FeaturesUtilsTest { + + @Test + void testGetAbstractFeaturePropertyName() { + String propertyName = getAbstractFeaturePropertyName("jdbc"); + assertEquals("microsphere.spring.cloud.features.abstract.jdbc", propertyName); + } + + @Test + void testGetNamedFeaturePropertyName() { + String propertyName = getNamedFeaturePropertyName("jdbc", "JdbcTemplate"); + assertEquals("microsphere.spring.cloud.features.named.jdbc.JdbcTemplate", propertyName); + } + + @Test + void testGetHasFeaturesBeanName() { + String beanName = getHasFeaturesBeanName("jdbc"); + assertEquals("jdbc.features", beanName); + } + + @Test + void testGetQualifierFeatureName() { + String featureName = getQualifierFeatureName("web", "rest-template"); + assertEquals("microsphere.web.rest-template", featureName); + } +} \ No newline at end of file diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/actuator/constants/FeaturesConstantsTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/actuator/constants/FeaturesConstantsTest.java new file mode 100644 index 00000000..94b40948 --- /dev/null +++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/actuator/constants/FeaturesConstantsTest.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.microsphere.spring.cloud.client.actuator.constants; + + +import org.junit.jupiter.api.Test; + +import static io.microsphere.spring.cloud.client.actuator.constants.FeaturesConstants.ABSTRACT; +import static io.microsphere.spring.cloud.client.actuator.constants.FeaturesConstants.ABSTRACT_FEATURE_PROPERTY_NAME_PATTERN; +import static io.microsphere.spring.cloud.client.actuator.constants.FeaturesConstants.ABSTRACT_FEATURE_PROPERTY_NAME_PREFIX; +import static io.microsphere.spring.cloud.client.actuator.constants.FeaturesConstants.BEAN_NAME_SUFFIX; +import static io.microsphere.spring.cloud.client.actuator.constants.FeaturesConstants.FEATURES; +import static io.microsphere.spring.cloud.client.actuator.constants.FeaturesConstants.NAMED; +import static io.microsphere.spring.cloud.client.actuator.constants.FeaturesConstants.NAMED_FEATURE_PROPERTY_NAME_PATTERN; +import static io.microsphere.spring.cloud.client.actuator.constants.FeaturesConstants.NAMED_FEATURE_PROPERTY_NAME_PREFIX; +import static io.microsphere.spring.cloud.client.actuator.constants.FeaturesConstants.PLACEHOLDER; +import static io.microsphere.spring.cloud.client.actuator.constants.FeaturesConstants.PROPERTY_NAME_PREFIX; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * {@link FeaturesConstants} Test + * + * @author Mercy + * @see FeaturesConstants + * @since 1.0.0 + */ +class FeaturesConstantsTest { + + @Test + void testConstants() { + assertEquals("features", FEATURES); + assertEquals("abstract", ABSTRACT); + assertEquals("named", NAMED); + assertEquals("{}", PLACEHOLDER); + + assertEquals("microsphere.spring.cloud.features", PROPERTY_NAME_PREFIX); + assertEquals("microsphere.spring.cloud.features.abstract.", ABSTRACT_FEATURE_PROPERTY_NAME_PREFIX); + assertEquals("microsphere.spring.cloud.features.named.", NAMED_FEATURE_PROPERTY_NAME_PREFIX); + assertEquals("microsphere.spring.cloud.features.abstract.{}", ABSTRACT_FEATURE_PROPERTY_NAME_PATTERN); + assertEquals("microsphere.spring.cloud.features.named.{}.{}", NAMED_FEATURE_PROPERTY_NAME_PATTERN); + assertEquals(".features", BEAN_NAME_SUFFIX); + } +} \ No newline at end of file From 296f84a53cca2d3cbb6facee07b598ee28d21960 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Wed, 24 Jun 2026 17:46:05 +0800 Subject: [PATCH 4/6] Use colon as feature separator in FeaturesUtils Change the qualified feature name format from "microsphere.{module}.{feature}" to "{module}:{feature}". FeaturesUtils now imports and uses COLON_CHAR and removes the MICROSPHERE_PROPERTY_NAME_PREFIX/DOT_CHAR usage. Updated the unit test expectation in FeaturesUtilsTest to match the new "web:rest-template" format. --- .../spring/cloud/client/actuator/FeaturesUtils.java | 7 +++---- .../spring/cloud/client/actuator/FeaturesUtilsTest.java | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/actuator/FeaturesUtils.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/actuator/FeaturesUtils.java index baa0073d..46577c68 100644 --- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/actuator/FeaturesUtils.java +++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/actuator/FeaturesUtils.java @@ -20,8 +20,7 @@ import io.microsphere.util.Utils; import org.springframework.cloud.client.actuator.HasFeatures; -import static io.microsphere.constants.PropertyConstants.MICROSPHERE_PROPERTY_NAME_PREFIX; -import static io.microsphere.constants.SymbolConstants.DOT_CHAR; +import static io.microsphere.constants.SymbolConstants.COLON_CHAR; import static io.microsphere.spring.cloud.client.actuator.constants.FeaturesConstants.ABSTRACT_FEATURE_PROPERTY_NAME_PATTERN; import static io.microsphere.spring.cloud.client.actuator.constants.FeaturesConstants.BEAN_NAME_SUFFIX; import static io.microsphere.spring.cloud.client.actuator.constants.FeaturesConstants.NAMED_FEATURE_PROPERTY_NAME_PATTERN; @@ -92,7 +91,7 @@ public static String getHasFeaturesBeanName(String moduleName) { *
    {@code
          * // For module name "web" and feature name "rest-template"
          * String qualifiedName = getQualifierFeatureName("web", "rest-template");
    -     * // Result: "microsphere.web.rest-template"
    +     * // Result: "web:rest-template"
          * }
    * * @param moduleName the name of the module @@ -100,7 +99,7 @@ public static String getHasFeaturesBeanName(String moduleName) { * @return the qualified feature name */ public static String getQualifierFeatureName(String moduleName, String featureName) { - return MICROSPHERE_PROPERTY_NAME_PREFIX + moduleName + DOT_CHAR + featureName; + return moduleName + COLON_CHAR + featureName; } private FeaturesUtils() { diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/actuator/FeaturesUtilsTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/actuator/FeaturesUtilsTest.java index 85f85418..c8395c7e 100644 --- a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/actuator/FeaturesUtilsTest.java +++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/actuator/FeaturesUtilsTest.java @@ -56,6 +56,6 @@ void testGetHasFeaturesBeanName() { @Test void testGetQualifierFeatureName() { String featureName = getQualifierFeatureName("web", "rest-template"); - assertEquals("microsphere.web.rest-template", featureName); + assertEquals("web:rest-template", featureName); } } \ No newline at end of file From 2bed908673a0747b2dc8d58e165d047507677479 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Wed, 24 Jun 2026 17:47:45 +0800 Subject: [PATCH 5/6] Update expected feature names in tests Adjust test expectations in ConfigurationPropertyHasFeaturesAutoConfigurationTest to use the new feature naming format. The assertions now expect prefixed short names ("web:WebClient" and "rest:RestTemplate") instead of the previous fully-qualified class names, reflecting a refactor of NamedFeature naming. No functional behavior changed aside from the test assertions. --- ...ConfigurationPropertyHasFeaturesAutoConfigurationTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/actuator/ConfigurationPropertyHasFeaturesAutoConfigurationTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/actuator/ConfigurationPropertyHasFeaturesAutoConfigurationTest.java index 1c51c30e..a7aca84f 100644 --- a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/actuator/ConfigurationPropertyHasFeaturesAutoConfigurationTest.java +++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/actuator/ConfigurationPropertyHasFeaturesAutoConfigurationTest.java @@ -81,7 +81,7 @@ void test() { List> abstractFeatures = hasFeatures.getAbstractFeatures(); List namedFeatures = hasFeatures.getNamedFeatures(); assertEquals(1, namedFeatures.size()); - assertEquals("microsphere.web.WebClient", namedFeatures.get(0).getName()); + assertEquals("web:WebClient", namedFeatures.get(0).getName()); assertTrue(abstractFeatures.isEmpty()); hasFeatures = this.hasFeaturesBeansMap.get(getHasFeaturesBeanName("rest")); @@ -89,7 +89,7 @@ void test() { abstractFeatures = hasFeatures.getAbstractFeatures(); namedFeatures = hasFeatures.getNamedFeatures(); - assertEquals("microsphere.rest.RestTemplate", namedFeatures.get(0).getName()); + assertEquals("rest:RestTemplate", namedFeatures.get(0).getName()); assertEquals(1, namedFeatures.size()); assertTrue(abstractFeatures.isEmpty()); From d152e333c7ac55b67798793e23aa874b3cddcc13 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Sat, 27 Jun 2026 21:41:46 +0800 Subject: [PATCH 6/6] Bump microsphere-spring-boot to 0.1.25 Updates the parent BOM property in `microsphere-spring-cloud-parent/pom.xml` from `0.1.24` to `0.1.25` so all modules inherit the newer microsphere-spring-boot dependency version. --- microsphere-spring-cloud-parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsphere-spring-cloud-parent/pom.xml b/microsphere-spring-cloud-parent/pom.xml index 2ecf62c4..81d56506 100644 --- a/microsphere-spring-cloud-parent/pom.xml +++ b/microsphere-spring-cloud-parent/pom.xml @@ -20,7 +20,7 @@ - 0.1.24 + 0.1.25 1.21.4 5.14.4