diff --git a/.github/workflows/maven-build.yml b/.github/workflows/maven-build.yml index 8a5bf63..c197848 100644 --- a/.github/workflows/maven-build.yml +++ b/.github/workflows/maven-build.yml @@ -20,7 +20,9 @@ jobs: strategy: matrix: java: [ '8', '11' , '17' , '21' ] - maven-profile-spring-boot: [ 'spring-boot-2.4' , 'spring-boot-2.5' , 'spring-boot-2.6' , 'spring-boot-2.7' ] + maven-profile-spring-boot: [ 'spring-boot-2.1' , 'spring-boot-2.2' , 'spring-boot-2.3', + 'spring-boot-2.4' ,'spring-boot-2.5' , 'spring-boot-2.6' , + 'spring-boot-2.7' ] steps: - name: Checkout Source uses: actions/checkout@v4 @@ -38,6 +40,11 @@ jobs: --update-snapshots --file pom.xml -Drevision=0.0.1-SNAPSHOT - -DargLine="${{ matrix.java >= 16 && '--add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED' || '' }}" - -P${{ matrix.maven-profile-spring-boot }} - test \ No newline at end of file + test + --activate-profiles test,coverage,${{ matrix.maven-profile-spring-boot }} + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + slug: microsphere-projects/microsphere-spring-boot \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9203168..e551b45 100644 --- a/.gitignore +++ b/.gitignore @@ -65,5 +65,8 @@ compiler/.gradle/* .extract .java-version +# vscode +.vscode/ + # others build.txt \ No newline at end of file diff --git a/README.md b/README.md index 6b23389..2dfb02d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,9 @@ # microsphere-spring-boot -Microsphere Projects for Spring Boot +> Microsphere Projects for Spring Boot + +[![Maven Build](https://github.com/microsphere-projects/microsphere-spring-boot/actions/workflows/maven-build.yml/badge.svg)](https://github.com/microsphere-projects/microsphere-spring-boot/actions/workflows/maven-build.yml) +[![Codecov](https://codecov.io/gh/microsphere-projects/microsphere-spring-boot/branch/dev-1.x/graph/badge.svg)](https://app.codecov.io/gh/microsphere-projects/microsphere-spring-boot) +![Maven](https://img.shields.io/maven-central/v/io.github.microsphere-projects/microsphere-spring-boot.svg) +![License](https://img.shields.io/github/license/microsphere-projects/microsphere-spring-boot.svg) +[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/microsphere-projects/microsphere-spring-boot.svg)](http://isitmaintained.com/project/microsphere-projects/microsphere-spring-boot "Average time to resolve an issue") +[![Percentage of issues still open](http://isitmaintained.com/badge/open/microsphere-projects/microsphere-spring-boot.svg)](http://isitmaintained.com/project/microsphere-projects/microsphere-spring-boot "Percentage of issues still open") diff --git a/microsphere-core-spring-boot-starter/pom.xml b/microsphere-core-spring-boot-starter/pom.xml index cb0af68..7c4c5a2 100644 --- a/microsphere-core-spring-boot-starter/pom.xml +++ b/microsphere-core-spring-boot-starter/pom.xml @@ -43,6 +43,12 @@ + + org.junit.jupiter + junit-jupiter + test + + org.springframework.boot spring-boot-starter-test @@ -62,4 +68,42 @@ + + + + spring-boot-2.1 + + + + io.github.microsphere-projects + microsphere-spring-boot-compatible + ${revision} + + + + + + spring-boot-2.2 + + + + io.github.microsphere-projects + microsphere-spring-boot-compatible + ${revision} + + + + + + spring-boot-2.3 + + + + io.github.microsphere-projects + microsphere-spring-boot-compatible + ${revision} + + + + \ No newline at end of file diff --git a/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/autoconfigure/ConfigurableAutoConfigurationImportFilter.java b/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/autoconfigure/ConfigurableAutoConfigurationImportFilter.java index fecf5d5..d9a9708 100644 --- a/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/autoconfigure/ConfigurableAutoConfigurationImportFilter.java +++ b/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/autoconfigure/ConfigurableAutoConfigurationImportFilter.java @@ -8,8 +8,6 @@ import org.springframework.core.env.Environment; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; import java.util.LinkedHashSet; import java.util.Set; @@ -19,6 +17,7 @@ import static java.util.Arrays.asList; import static java.util.Collections.emptySet; import static java.util.Collections.unmodifiableSet; +import static org.springframework.util.Assert.isInstanceOf; import static org.springframework.util.StringUtils.collectionToCommaDelimitedString; import static org.springframework.util.StringUtils.commaDelimitedListToSet; import static org.springframework.util.StringUtils.hasText; @@ -103,7 +102,7 @@ private static MutablePropertySources getPropertySources(Environment environment } private static ConfigurableEnvironment getConfigurableEnvironment(Environment environment) { - Assert.isInstanceOf(ConfigurableEnvironment.class, environment); + isInstanceOf(ConfigurableEnvironment.class, environment); return (ConfigurableEnvironment) environment; } diff --git a/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/classloading/BannedArtifactClassLoadingListener.java b/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/classloading/BannedArtifactClassLoadingListener.java index 59ac68d..10f9002 100644 --- a/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/classloading/BannedArtifactClassLoadingListener.java +++ b/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/classloading/BannedArtifactClassLoadingListener.java @@ -1,17 +1,18 @@ package io.microsphere.spring.boot.classloading; import io.microsphere.classloading.BannedArtifactClassLoadingExecutor; +import io.microsphere.logging.Logger; import io.microsphere.spring.boot.listener.SpringApplicationRunListenerAdapter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.event.ApplicationStartingEvent; import org.springframework.context.ApplicationListener; import org.springframework.core.Ordered; -import org.springframework.util.ClassUtils; import java.util.Arrays; +import static io.microsphere.logging.LoggerFactory.getLogger; +import static org.springframework.util.ClassUtils.isPresent; + /** * {@link ApplicationStartingEvent ApplicationStartingEvent} {@link ApplicationListener Listener} bans * the load of Artifacts collision class @@ -21,15 +22,13 @@ */ public class BannedArtifactClassLoadingListener extends SpringApplicationRunListenerAdapter implements Ordered { - private static final Logger logger = LoggerFactory.getLogger(BannedArtifactClassLoadingListener.class); + private static final Logger logger = getLogger(BannedArtifactClassLoadingListener.class); private static final boolean artifactsBanned = Boolean.getBoolean("microsphere.artifacts.banned"); private static final String SPRING_BOOT_LAUNCHER_CLASS_NAME = "org.springframework.boot.loader.Launcher"; - private static final ClassLoader defaultClassLoader = ClassUtils.getDefaultClassLoader(); - - static final boolean SPRING_BOOT_LAUNCHER_CLASS_PRESENT = ClassUtils.isPresent(SPRING_BOOT_LAUNCHER_CLASS_NAME, defaultClassLoader); + static final boolean SPRING_BOOT_LAUNCHER_CLASS_PRESENT = isPresent(SPRING_BOOT_LAUNCHER_CLASS_NAME, null); private static boolean banned = false; diff --git a/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/context/OnceApplicationPreparedEventListener.java b/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/context/OnceApplicationPreparedEventListener.java index 96c1879..7865d81 100644 --- a/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/context/OnceApplicationPreparedEventListener.java +++ b/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/context/OnceApplicationPreparedEventListener.java @@ -1,7 +1,6 @@ package io.microsphere.spring.boot.context; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import io.microsphere.logging.Logger; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.event.ApplicationPreparedEvent; import org.springframework.context.ApplicationListener; @@ -13,6 +12,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListSet; +import static io.microsphere.logging.LoggerFactory.getLogger; + /** * Once execution {@link ApplicationPreparedEvent} {@link ApplicationListener} * @@ -24,7 +25,7 @@ public abstract class OnceApplicationPreparedEventListener implements Applicatio private static Map, Set> listenerProcessedContextIds = new ConcurrentHashMap<>(); - protected final Logger logger = LoggerFactory.getLogger(getClass()); + protected final Logger logger = getLogger(getClass()); private final Set processedContextIds; diff --git a/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/context/OnceMainApplicationPreparedEventListener.java b/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/context/OnceMainApplicationPreparedEventListener.java index 3974cf7..a94ff33 100644 --- a/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/context/OnceMainApplicationPreparedEventListener.java +++ b/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/context/OnceMainApplicationPreparedEventListener.java @@ -6,11 +6,11 @@ import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.Environment; -import org.springframework.util.ClassUtils; import java.util.List; import static java.util.Arrays.asList; +import static org.springframework.util.ClassUtils.isPresent; /** @@ -25,7 +25,7 @@ public abstract class OnceMainApplicationPreparedEventListener extends OnceAppli private static final String BOOTSTRAP_APPLICATION_LISTENER_ENABLED_PROPERTY_NAME = "spring.cloud.bootstrap.enabled"; - private static final boolean BOOTSTRAP_APPLICATION_LISTENER_PRESENT = ClassUtils.isPresent(BOOTSTRAP_APPLICATION_LISTENER_CLASS_NAME, null); + private static final boolean BOOTSTRAP_APPLICATION_LISTENER_PRESENT = isPresent(BOOTSTRAP_APPLICATION_LISTENER_CLASS_NAME, null); private static final String BOOTSTRAP_CONTEXT_ID = "bootstrap"; diff --git a/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/context/properties/bind/ConfigurationPropertiesBeanContext.java b/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/context/properties/bind/ConfigurationPropertiesBeanContext.java index d9959cb..4d98edf 100644 --- a/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/context/properties/bind/ConfigurationPropertiesBeanContext.java +++ b/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/context/properties/bind/ConfigurationPropertiesBeanContext.java @@ -17,14 +17,12 @@ package io.microsphere.spring.boot.context.properties.bind; import io.microsphere.spring.core.convert.support.ConversionServiceResolver; -import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanWrapperImpl; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.source.ConfigurationProperty; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.convert.ConversionService; -import org.springframework.util.ClassUtils; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; @@ -34,6 +32,8 @@ import static io.microsphere.spring.boot.context.properties.source.util.ConfigurationPropertyUtils.toDashedForm; import static org.springframework.beans.BeanUtils.copyProperties; +import static org.springframework.beans.BeanUtils.getPropertyDescriptors; +import static org.springframework.util.ClassUtils.isPrimitiveOrWrapper; /** * The context for the bean annotated {@link ConfigurationProperties @ConfigurationProperties} @@ -95,7 +95,7 @@ private void initBinding(Object bean) { private void initBinding(Class beanClass, String prefix, Map bindingPropertyNames, String nestedPath) { if (isCandidateClass(beanClass)) { - PropertyDescriptor[] descriptors = BeanUtils.getPropertyDescriptors(beanClass); + PropertyDescriptor[] descriptors = getPropertyDescriptors(beanClass); int descriptorSize = descriptors.length; for (int i = 0; i < descriptorSize; i++) { PropertyDescriptor descriptor = descriptors[i]; @@ -117,7 +117,7 @@ private boolean isCandidateProperty(PropertyDescriptor descriptor) { } private boolean isCandidateClass(Class beanClass) { - if (ClassUtils.isPrimitiveOrWrapper(beanClass)) { + if (isPrimitiveOrWrapper(beanClass)) { return false; } if (beanClass.isInterface() || beanClass.isEnum() || beanClass.isAnnotation() || beanClass.isArray() || beanClass.isSynthetic()) { diff --git a/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/context/properties/bind/EventPublishingConfigurationPropertiesBeanPropertyChangedListener.java b/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/context/properties/bind/EventPublishingConfigurationPropertiesBeanPropertyChangedListener.java index 8a59423..59db6c2 100644 --- a/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/context/properties/bind/EventPublishingConfigurationPropertiesBeanPropertyChangedListener.java +++ b/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/context/properties/bind/EventPublishingConfigurationPropertiesBeanPropertyChangedListener.java @@ -16,8 +16,7 @@ */ package io.microsphere.spring.boot.context.properties.bind; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import io.microsphere.logging.Logger; import org.springframework.beans.BeansException; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; @@ -30,17 +29,18 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.util.Assert; import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; +import static io.microsphere.logging.LoggerFactory.getLogger; import static io.microsphere.spring.boot.context.properties.bind.util.BindUtils.isBoundProperty; import static io.microsphere.spring.boot.context.properties.bind.util.BindUtils.isConfigurationPropertiesBean; import static io.microsphere.spring.boot.context.properties.source.util.ConfigurationPropertyUtils.getPrefix; import static io.microsphere.spring.boot.context.properties.util.ConfigurationPropertiesUtils.CONFIGURATION_PROPERTIES_CLASS; import static io.microsphere.spring.boot.context.properties.util.ConfigurationPropertiesUtils.findConfigurationProperties; +import static org.springframework.util.Assert.isInstanceOf; /** * A {@link BindListener} implementation of {@link ConfigurationProperties @ConfigurationProperties} Bean to publish @@ -54,7 +54,7 @@ */ public class EventPublishingConfigurationPropertiesBeanPropertyChangedListener implements BindListener, BeanFactoryPostProcessor, ApplicationContextAware, SmartInitializingSingleton { - private final static Logger logger = LoggerFactory.getLogger(EventPublishingConfigurationPropertiesBeanPropertyChangedListener.class); + private final static Logger logger = getLogger(EventPublishingConfigurationPropertiesBeanPropertyChangedListener.class); private static final Class CONFIGURABLE_APPLICATION_CONTEXT_CLASS = ConfigurableApplicationContext.class; @@ -126,7 +126,7 @@ private void initConfigurationPropertiesBeanContexts(ConfigurableListableBeanFac @Override public void setApplicationContext(ApplicationContext context) throws BeansException { Class expectedType = CONFIGURABLE_APPLICATION_CONTEXT_CLASS; - Assert.isInstanceOf(expectedType, context, "The 'context' argument is not an instance of " + expectedType.getName()); + isInstanceOf(expectedType, context, "The 'context' argument is not an instance of " + expectedType.getName()); this.context = expectedType.cast(context); } diff --git a/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/context/properties/bind/ListenableBindHandlerAdapter.java b/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/context/properties/bind/ListenableBindHandlerAdapter.java index a154fa5..b898b60 100644 --- a/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/context/properties/bind/ListenableBindHandlerAdapter.java +++ b/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/context/properties/bind/ListenableBindHandlerAdapter.java @@ -51,9 +51,13 @@ public Object onSuccess(ConfigurationPropertyName name, Bindable target, Bind return returnValue; } - @Override + /** + * {@inheritDoc} + * + * @since Spring Boot 2.2.2 + */ public Object onCreate(ConfigurationPropertyName name, Bindable target, BindContext context, Object result) { - Object returnValue = super.onCreate(name, target, context, result); + Object returnValue = result; bindHandlers.onCreate(name, target, context, result); return returnValue; } diff --git a/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/context/properties/metadata/ConfigurationMetadataReader.java b/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/context/properties/metadata/ConfigurationMetadataReader.java index f25208a..b80aac6 100644 --- a/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/context/properties/metadata/ConfigurationMetadataReader.java +++ b/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/context/properties/metadata/ConfigurationMetadataReader.java @@ -16,8 +16,7 @@ */ package io.microsphere.spring.boot.context.properties.metadata; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import io.microsphere.logging.Logger; import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata; import org.springframework.boot.configurationprocessor.metadata.JsonMarshaller; import org.springframework.context.ResourceLoaderAware; @@ -28,6 +27,7 @@ import java.io.IOException; +import static io.microsphere.logging.LoggerFactory.getLogger; import static org.springframework.core.io.support.ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX; /** @@ -38,7 +38,7 @@ */ public class ConfigurationMetadataReader implements ResourceLoaderAware { - private final static Logger logger = LoggerFactory.getLogger(ConfigurationMetadataReader.class); + private final static Logger logger = getLogger(ConfigurationMetadataReader.class); public static final String METADATA_PATH = CLASSPATH_ALL_URL_PREFIX + "/META-INF/spring-configuration-metadata.json"; diff --git a/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/env/DefaultPropertiesApplicationListener.java b/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/env/DefaultPropertiesApplicationListener.java index 898ede2..87584a0 100644 --- a/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/env/DefaultPropertiesApplicationListener.java +++ b/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/env/DefaultPropertiesApplicationListener.java @@ -1,7 +1,6 @@ package io.microsphere.spring.boot.env; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import io.microsphere.logging.Logger; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.context.logging.LoggingApplicationListener; @@ -14,7 +13,6 @@ import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.ResourcePatternResolver; -import org.springframework.util.ObjectUtils; import java.io.IOException; import java.net.URL; @@ -25,11 +23,13 @@ import java.util.Properties; import java.util.Set; +import static io.microsphere.logging.LoggerFactory.getLogger; import static io.microsphere.spring.boot.util.SpringApplicationUtils.getDefaultPropertiesResources; import static io.microsphere.spring.boot.util.SpringApplicationUtils.getResourceLoader; -import static io.microsphere.spring.util.PropertySourcesUtils.getDefaultProperties; -import static io.microsphere.spring.util.ResourceLoaderUtils.getResourcePatternResolver; +import static io.microsphere.spring.core.env.PropertySourcesUtils.getDefaultProperties; +import static io.microsphere.spring.core.io.ResourceLoaderUtils.getResourcePatternResolver; import static org.springframework.core.io.support.SpringFactoriesLoader.loadFactories; +import static org.springframework.util.ObjectUtils.containsElement; /** * Listable {@link ApplicationEnvironmentPreparedEvent} {@link ApplicationListener} Class @@ -44,7 +44,7 @@ public class DefaultPropertiesApplicationListener implements ApplicationListener public static final int DEFAULT_ORDER = LoggingApplicationListener.LOWEST_PRECEDENCE - 1; - private static final Logger logger = LoggerFactory.getLogger(DefaultPropertiesApplicationListener.class); + private static final Logger logger = getLogger(DefaultPropertiesApplicationListener.class); private int order = DEFAULT_ORDER; @@ -81,7 +81,7 @@ private void loadDefaultPropertiesResources(List propertyS ResourceLoader resourceLoader, Map defaultProperties) { Set defaultPropertiesResources = getDefaultPropertiesResources(); - logger.debug("Start loading from SpringApplicationUtils. GetDefaultPropertiesResources () 'defaultProperties resources: {}", defaultPropertiesResources); + logger.debug("Start loading from SpringApplicationUtils.loadDefaultPropertiesResources() 'defaultProperties resources: {}", defaultPropertiesResources); loadDefaultProperties(defaultPropertiesResources, propertySourceLoaders, resourceLoader, defaultProperties); } @@ -178,7 +178,7 @@ private void merge(EnumerablePropertySource propertySource, MapMercy + * @see PropertySourceLoader + * @since 1.0.0 + */ +public class PropertySourceLoaders implements PropertySourceLoader { + + private static final Logger logger = getLogger(PropertySourceLoaders.class); + + private final ResourceLoader resourceLoader; + + private final List loaders; + + public PropertySourceLoaders() { + this(getDefaultClassLoader()); + } + + public PropertySourceLoaders(ClassLoader classLoader) { + this(new DefaultResourceLoader(classLoader)); + } + + public PropertySourceLoaders(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + this.loaders = loadFactories(PropertySourceLoader.class, resourceLoader.getClassLoader()); + } + + @Override + public String[] getFileExtensions() { + String[] fileExtensions = loaders.stream() + .map(PropertySourceLoader::getFileExtensions) + .map(Arrays::asList) + .flatMap(List::stream) + .toArray(String[]::new); + return fileExtensions; + } + + @Override + public List> load(String name, Resource resource) throws IOException { + List> propertySources = new LinkedList<>(); + URL url = resource.getURL(); + for (PropertySourceLoader loader : loaders) { + if (supports(loader, url)) { + propertySources.addAll(loader.load(name, resource)); + } + } + return propertySources; + } + + /** + * Reload the {@link PropertySource} as an instance of {@link PropertySource} with {@link OriginLookup} + * + * @param propertySource {@link PropertySource} + * @return an instance of {@link PropertySource} with {@link OriginLookup} + * @throws IOException + */ + public PropertySource reloadAsOriginTracked(PropertySource propertySource) throws IOException { + if (propertySource instanceof OriginLookup) { + debug("The PropertySource[name : '{}', class : '{}'] is already an instance of OriginLookup", + propertySource.getName(), propertySource.getClass().getName()); + return propertySource; + } + // the name is source from Resource#getDescription() + String name = propertySource.getName(); + String location = substringBetween(name, "[", "]"); + // the location or uri can be resolved from FileSystemResource, ClassPathResource and UrlResource + if (hasText(location)) { + return loadAsOriginTracked(name, location); + } + return propertySource; + } + + /** + * Load the {@link PropertySource} as an instance of {@link PropertySource} with {@link OriginLookup} + * + * @param name the name of {@link PropertySource} + * @param location the location of {@link Resource} for {@link PropertySource} + * @return an instance of {@link PropertySource} with {@link OriginLookup} + * @throws IOException + */ + public PropertySource loadAsOriginTracked(String name, String location) throws IOException { + Resource resource = resourceLoader.getResource(location); + List> propertySources = load(name, resource); + int size = propertySources.size(); + if (size > 1) { + throw new IllegalStateException("The resource : " + resource + " can load more than one PropertySource"); + } + return propertySources.get(0); + } + + private boolean supports(PropertySourceLoader loader, URL resourceURL) { + String[] fileExtensions = loader.getFileExtensions(); + String path = resourceURL.getPath(); + boolean supported = false; + for (String fileExtension : fileExtensions) { + if (path.endsWith(fileExtension)) { + supported = true; + break; + } + } + return supported; + } + + private void debug(String messagePattern, Object... args) { + if (logger.isDebugEnabled()) { + logger.debug(messagePattern, args); + } + } +} diff --git a/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/env/config/OriginTrackedConfigurationPropertyInitializer.java b/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/env/config/OriginTrackedConfigurationPropertyInitializer.java new file mode 100644 index 0000000..f179b4e --- /dev/null +++ b/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/env/config/OriginTrackedConfigurationPropertyInitializer.java @@ -0,0 +1,138 @@ +/* + * 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.boot.env.config; + +import io.microsphere.logging.Logger; +import io.microsphere.spring.boot.env.PropertySourceLoaders; +import io.microsphere.spring.context.event.BeanFactoryListenerAdapter; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.boot.env.OriginTrackedMapPropertySource; +import org.springframework.boot.origin.Origin; +import org.springframework.boot.origin.OriginLookup; +import org.springframework.boot.origin.OriginTrackedValue; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.EnumerablePropertySource; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.support.ResourcePropertySource; + +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; + +import static io.microsphere.logging.LoggerFactory.getLogger; +import static io.microsphere.spring.beans.factory.support.BeanRegistrar.registerBean; + +/** + * {@link ApplicationContextInitializer} class supports origin tracked configuration property. + * + * @author Mercy + * @see ConfigurableEnvironment + * @since ApplicationContextInitializer + */ +public class OriginTrackedConfigurationPropertyInitializer implements BeanFactoryListenerAdapter, ApplicationContextInitializer { + + public static final String BEAN_NAME = "originTrackedConfigurationPropertyInitializer"; + + private static final Logger logger = getLogger(OriginTrackedConfigurationPropertyInitializer.class); + + private ConfigurableApplicationContext applicationContext; + + private PropertySourceLoaders propertySourceLoaders; + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + this.applicationContext = applicationContext; + this.propertySourceLoaders = new PropertySourceLoaders(applicationContext.getClassLoader()); + ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory(); + BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; + registerBean(registry, BEAN_NAME, this); + } + + @Override + public void onBeanFactoryConfigurationFrozen(ConfigurableListableBeanFactory beanFactory) { + ConfigurableEnvironment environment = applicationContext.getEnvironment(); + MutablePropertySources propertySources = environment.getPropertySources(); + initializePropertySources(propertySources); + } + + private void initializePropertySources(MutablePropertySources propertySources) { + for (PropertySource propertySource : propertySources) { + if (isPropertySourceCandidate(propertySource)) { + String name = propertySource.getName(); + try { + PropertySource originTrackedPropertySource = createOriginTrackedPropertySource(propertySource); + propertySources.replace(name, originTrackedPropertySource); + } catch (IOException e) { + logger.error("Failed to create the origin tracked PropertySource[name : '{}', class : '{}']", + name, propertySource.getClass().getName()); + } + } + } + } + + private boolean isPropertySourceCandidate(PropertySource propertySource) { + return (propertySource instanceof EnumerablePropertySource) && + !(propertySource instanceof OriginLookup); + } + + private PropertySource createOriginTrackedPropertySource(PropertySource propertySource) throws IOException { + if (propertySource instanceof ResourcePropertySource) { + return propertySourceLoaders.reloadAsOriginTracked(propertySource); + } + + EnumerablePropertySource enumerablePropertySource = (EnumerablePropertySource) propertySource; + String[] propertyNames = enumerablePropertySource.getPropertyNames(); + int size = propertyNames.length; + Map source = new LinkedHashMap<>(size); + for (int i = 0; i < size; i++) { + String propertyName = propertyNames[i]; + Object propertyValue = enumerablePropertySource.getProperty(propertyName); + // Skip if propertyValue is OriginTrackedValue + if (propertyValue instanceof OriginTrackedValue) { + continue; + } + Origin origin = resolveOrigin(propertySource); + // propertyValue with origin + propertyValue = OriginTrackedValue.of(propertyValue, origin); + source.put(propertyName, propertyValue); + } + return new OriginTrackedMapPropertySource(propertySource.getName(), source); + } + + private Origin resolveOrigin(PropertySource propertySource) { + // TODO more Origin implementations + return new NamedOrigin(propertySource.getName()); + } + + static class NamedOrigin implements Origin { + + private final String name; + + NamedOrigin(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } + } +} diff --git a/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/env/config/metadata/ConfigurationMetadataRepository.java b/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/env/config/metadata/ConfigurationMetadataRepository.java new file mode 100644 index 0000000..44efe6e --- /dev/null +++ b/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/env/config/metadata/ConfigurationMetadataRepository.java @@ -0,0 +1,138 @@ +/* + * 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.boot.env.config.metadata; + +import io.microsphere.spring.boot.context.properties.metadata.ConfigurationMetadataReader; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata; +import org.springframework.boot.configurationprocessor.metadata.ItemHint; +import org.springframework.boot.configurationprocessor.metadata.ItemMetadata; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static java.util.Collections.emptyList; +import static org.springframework.boot.configurationprocessor.metadata.ItemMetadata.ItemType.GROUP; +import static org.springframework.boot.configurationprocessor.metadata.ItemMetadata.ItemType.PROPERTY; + +/** + * The Repository for {@link ConfigurationMetadata} + * + * @author Mercy + * @see ConfigurationMetadata + * @see ConfigurationMetadataReader + * @since 1.0.0 + */ +public class ConfigurationMetadataRepository implements CommandLineRunner { + + private final ConfigurationMetadataReader configurationMetadataReader; + + private Map namedGroups; + + private Map namedProperties; + + private Map> namedHints; + + public ConfigurationMetadataRepository(ConfigurationMetadataReader configurationMetadataReader) { + this.configurationMetadataReader = configurationMetadataReader; + } + + @NonNull + public Set getPropertyGroups() { + return this.namedGroups.keySet(); + } + + @NonNull + public Set getPropertyNames() { + return this.namedProperties.keySet(); + } + + @NonNull + public Collection getGroups() { + return this.namedGroups.values(); + } + + @NonNull + public Collection getProperties() { + return this.namedProperties.values(); + } + + @Nullable + public ItemMetadata getGroup(String name) { + return this.namedGroups.get(name); + } + + @Nullable + public ItemMetadata getProperty(String name) { + return this.namedProperties.get(name); + } + + @NonNull + public List getHints(String name) { + return this.namedHints.getOrDefault(name, emptyList()); + } + + @NonNull + public ConfigurationMetadataReader getConfigurationMetadataReader() { + return configurationMetadataReader; + } + + @Override + public void run(String... args) throws Exception { + ConfigurationMetadata configurationMetadata = this.configurationMetadataReader.read(); + // ConfigurationMetadata can't return the underlying items as Map + init(configurationMetadata); + } + + private void init(ConfigurationMetadata configurationMetadata) { + List items = configurationMetadata.getItems(); + initNamedGroupItems(items); + initNamedPropertyItems(items); + initNamedHints(configurationMetadata.getHints()); + } + + private void initNamedGroupItems(List items) { + this.namedGroups = createNamedItems(items, GROUP); + } + + private void initNamedPropertyItems(List items) { + this.namedProperties = createNamedItems(items, PROPERTY); + } + + private void initNamedHints(List items) { + Map> namedHints = new LinkedHashMap<>(items.size()); + items.stream().forEach(itemHint -> { + List itemHints = namedHints.computeIfAbsent(itemHint.getName(), i -> new LinkedList<>()); + itemHints.add(itemHint); + }); + this.namedHints = namedHints; + } + + private Map createNamedItems(List items, ItemMetadata.ItemType itemType) { + Map namedItems = new LinkedHashMap<>(items.size()); + items.stream().filter(item -> item.isOfItemType(itemType)).forEach(item -> { + namedItems.put(item.getName(), item); + }); + return namedItems; + } +} diff --git a/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/report/ConditionsReportMessageBuilder.java b/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/report/ConditionsReportMessageBuilder.java index e65ff70..98174ba 100644 --- a/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/report/ConditionsReportMessageBuilder.java +++ b/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/report/ConditionsReportMessageBuilder.java @@ -1,6 +1,5 @@ package io.microsphere.spring.boot.report; -import io.microsphere.text.FormatUtils; import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; @@ -11,6 +10,8 @@ import java.util.Map; import java.util.Set; +import static io.microsphere.text.FormatUtils.format; + /** * Spring Boot Conditions Report builder * @@ -106,6 +107,6 @@ private Set getBasePackages(ConfigurableApplicationContext context) { } private void appendLine(StringBuilder stringBuilder, String text, Object... args) { - stringBuilder.append(FormatUtils.format(text, args)).append(System.lineSeparator()); + stringBuilder.append(format(text, args)).append(System.lineSeparator()); } } diff --git a/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/util/SpringApplicationUtils.java b/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/util/SpringApplicationUtils.java index ca9d8f5..6e93f34 100644 --- a/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/util/SpringApplicationUtils.java +++ b/microsphere-core-spring-boot-starter/src/main/java/io/microsphere/spring/boot/util/SpringApplicationUtils.java @@ -3,12 +3,13 @@ import org.springframework.boot.SpringApplication; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.ResourceLoader; -import org.springframework.util.StringUtils; import java.util.Collections; import java.util.LinkedHashSet; import java.util.Set; +import static org.springframework.util.StringUtils.hasText; + /** * {@link SpringApplication} Utilities class * @@ -29,7 +30,7 @@ private SpringApplicationUtils() throws InstantiationException { * @param resourceLocation "defaultProperties" resource path */ public static void addDefaultPropertiesResource(String resourceLocation) { - if (StringUtils.hasText(resourceLocation)) { + if (hasText(resourceLocation)) { defaultPropertiesResources.add(resourceLocation); } } diff --git a/microsphere-core-spring-boot-starter/src/main/resources/META-INF/spring.factories b/microsphere-core-spring-boot-starter/src/main/resources/META-INF/spring.factories index fe59ee9..aeb2f19 100644 --- a/microsphere-core-spring-boot-starter/src/main/resources/META-INF/spring.factories +++ b/microsphere-core-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -1,6 +1,10 @@ # ApplicationContextInitializer org.springframework.context.ApplicationContextInitializer=\ -io.microsphere.spring.boot.report.ConditionEvaluationReportInitializer +io.microsphere.spring.context.event.EventPublishingBeanInitializer,\ +io.microsphere.spring.boot.report.ConditionEvaluationReportInitializer,\ +io.microsphere.spring.beans.factory.support.ListenableAutowireCandidateResolverInitializer,\ +io.microsphere.spring.core.env.ListenableConfigurableEnvironmentInitializer,\ +io.microsphere.spring.boot.env.config.OriginTrackedConfigurationPropertyInitializer # SpringApplicationRunListener org.springframework.boot.SpringApplicationRunListener=\ @@ -27,4 +31,5 @@ io.microsphere.spring.boot.autoconfigure.ConfigurableAutoConfigurationImportFilt # DefaultPropertiesPostProcessor io.microsphere.spring.boot.env.DefaultPropertiesPostProcessor=\ -io.microsphere.spring.boot.env.SpringApplicationDefaultPropertiesPostProcessor \ No newline at end of file +io.microsphere.spring.boot.env.SpringApplicationDefaultPropertiesPostProcessor + diff --git a/microsphere-core-spring-boot-starter/src/test/java/io/microsphere/spring/boot/autoconfigure/ApplicationAutoConfigurationTest.java b/microsphere-core-spring-boot-starter/src/test/java/io/microsphere/spring/boot/autoconfigure/ApplicationAutoConfigurationTest.java index 6bf3184..66093b8 100644 --- a/microsphere-core-spring-boot-starter/src/test/java/io/microsphere/spring/boot/autoconfigure/ApplicationAutoConfigurationTest.java +++ b/microsphere-core-spring-boot-starter/src/test/java/io/microsphere/spring/boot/autoconfigure/ApplicationAutoConfigurationTest.java @@ -22,7 +22,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; @@ -65,7 +64,7 @@ static class TestConfig { @Autowired private ObjectProvider loggingBeanListenerObjectProvider; - @Resource(name = "io.microsphere.spring.context.event.BeanTimeStatistics#0") + @Resource private BeanTimeStatistics beanTimeStatistics; @Resource(type = LoggingBeanListener.class) @@ -77,8 +76,7 @@ public void setLoggingBeanListener(LoggingBeanListener loggingBeanListener) { } public TestConfig(ObjectProvider beanListeners, - ObjectProvider> beanListenersList, - @Qualifier("io.microsphere.spring.context.event.LoggingBeanListener#0") LoggingBeanListener loggingBeanListener) { + ObjectProvider> beanListenersList) { this.beanListeners = beanListeners; this.beanListenersList = beanListenersList; } diff --git a/microsphere-core-spring-boot-starter/src/test/java/io/microsphere/spring/boot/context/config/BindableConfigurationBeanBinderTest.java b/microsphere-core-spring-boot-starter/src/test/java/io/microsphere/spring/boot/context/config/BindableConfigurationBeanBinderTest.java index 05ee67a..dbb82f2 100644 --- a/microsphere-core-spring-boot-starter/src/test/java/io/microsphere/spring/boot/context/config/BindableConfigurationBeanBinderTest.java +++ b/microsphere-core-spring-boot-starter/src/test/java/io/microsphere/spring/boot/context/config/BindableConfigurationBeanBinderTest.java @@ -28,7 +28,8 @@ import java.util.Map; -import static io.microsphere.spring.util.PropertySourcesUtils.getSubProperties; +import static io.microsphere.spring.core.env.PropertySourcesUtils.getSubProperties; +import static java.lang.Integer.valueOf; import static org.junit.jupiter.api.Assertions.assertEquals; /** @@ -59,6 +60,6 @@ void testBind() { Map properties = getSubProperties(environment.getPropertySources(), "user"); beanBinder.bind(properties, true, true, user); assertEquals("mercyblitz", user.getName()); - assertEquals(37, user.getAge()); + assertEquals(valueOf(37), user.getAge()); } } diff --git a/microsphere-core-spring-boot-starter/src/test/java/io/microsphere/spring/boot/context/properties/ListenableConfigurationPropertiesBindHandlerAdvisorTest.java b/microsphere-core-spring-boot-starter/src/test/java/io/microsphere/spring/boot/context/properties/ListenableConfigurationPropertiesBindHandlerAdvisorTest.java index 328c753..24faedb 100644 --- a/microsphere-core-spring-boot-starter/src/test/java/io/microsphere/spring/boot/context/properties/ListenableConfigurationPropertiesBindHandlerAdvisorTest.java +++ b/microsphere-core-spring-boot-starter/src/test/java/io/microsphere/spring/boot/context/properties/ListenableConfigurationPropertiesBindHandlerAdvisorTest.java @@ -16,10 +16,9 @@ */ package io.microsphere.spring.boot.context.properties; +import io.microsphere.logging.Logger; import io.microsphere.spring.boot.context.properties.bind.BindListener; import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.bind.BindContext; @@ -29,6 +28,8 @@ import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; +import static io.microsphere.logging.LoggerFactory.getLogger; + /** * {@link ListenableConfigurationPropertiesBindHandlerAdvisor} Test * @@ -50,7 +51,7 @@ @EnableConfigurationProperties public class ListenableConfigurationPropertiesBindHandlerAdvisorTest { - private static final Logger logger = LoggerFactory.getLogger(ListenableConfigurationPropertiesBindHandlerAdvisorTest.class); + private static final Logger logger = getLogger(ListenableConfigurationPropertiesBindHandlerAdvisorTest.class); @Test public void test() { diff --git a/microsphere-core-spring-boot-starter/src/test/java/io/microsphere/spring/boot/context/properties/bind/EventPublishingConfigurationPropertiesBeanPropertyChangedListenerTest.java b/microsphere-core-spring-boot-starter/src/test/java/io/microsphere/spring/boot/context/properties/bind/EventPublishingConfigurationPropertiesBeanPropertyChangedListenerTest.java index f7d8a25..4d0b5fe 100644 --- a/microsphere-core-spring-boot-starter/src/test/java/io/microsphere/spring/boot/context/properties/bind/EventPublishingConfigurationPropertiesBeanPropertyChangedListenerTest.java +++ b/microsphere-core-spring-boot-starter/src/test/java/io/microsphere/spring/boot/context/properties/bind/EventPublishingConfigurationPropertiesBeanPropertyChangedListenerTest.java @@ -30,10 +30,11 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.MutablePropertySources; import org.springframework.mock.env.MockPropertySource; -import org.springframework.test.context.TestPropertySource; import javax.annotation.PostConstruct; +import static java.lang.Integer.valueOf; +import static java.util.Locale.SIMPLIFIED_CHINESE; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -43,10 +44,11 @@ * @author Mercy * @since 1.0.0 */ -@SpringBootTest(classes = {ListenableConfigurationPropertiesBindHandlerAdvisor.class, - EventPublishingConfigurationPropertiesBeanPropertyChangedListener.class, - EventPublishingConfigurationPropertiesBeanPropertyChangedListenerTest.class}) -@TestPropertySource(properties = {"server.error.path=/error.jsp"}) +@SpringBootTest(classes = + {ListenableConfigurationPropertiesBindHandlerAdvisor.class, + EventPublishingConfigurationPropertiesBeanPropertyChangedListener.class, + EventPublishingConfigurationPropertiesBeanPropertyChangedListenerTest.class}, + webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @EnableAutoConfiguration @EnableConfigurationProperties public class EventPublishingConfigurationPropertiesBeanPropertyChangedListenerTest { @@ -74,52 +76,52 @@ public void init() { @Test public void testJacksonProperties() { - assertNull(jacksonProperties.getDateFormat()); + assertNull(jacksonProperties.getLocale()); context.addApplicationListener((ApplicationListener) event -> { ConfigurationProperty configurationProperty = event.getConfigurationProperty(); String propertyName = event.getPropertyName(); - if ("dateFormat".equals(propertyName)) { + if ("locale".equals(propertyName)) { assertEquals(jacksonProperties, event.getSource()); assertNull(event.getOldValue()); - assertEquals("yyyy-MM-dd HH:mm:ss", event.getNewValue()); - assertEquals("spring.jackson.date-format", configurationProperty.getName().toString()); - assertEquals(event.getNewValue(), configurationProperty.getValue()); + assertEquals(SIMPLIFIED_CHINESE, event.getNewValue()); + assertEquals("spring.jackson.locale", configurationProperty.getName().toString()); + assertEquals(event.getNewValue().toString(), configurationProperty.getValue()); } }); - mockPropertySource.setProperty("spring.jackson.dateFormat", "yyyy-MM-dd HH:mm:ss"); + mockPropertySource.setProperty("spring.jackson.locale", "zh_CN"); beanFactory.destroyBean(jacksonProperties); - beanFactory.initializeBean(jacksonProperties, "spring.jackson-jacksonProperties"); - assertEquals("yyyy-MM-dd HH:mm:ss", jacksonProperties.getDateFormat()); + beanFactory.initializeBean(jacksonProperties, getBeanName(jacksonProperties)); + assertEquals(SIMPLIFIED_CHINESE, jacksonProperties.getLocale()); } @Test public void testServerProperties() { assertNull(serverProperties.getPort()); + String newPortPropertyValue = "9527"; + context.addApplicationListener((ApplicationListener) event -> { ConfigurationProperty configurationProperty = event.getConfigurationProperty(); String propertyName = event.getPropertyName(); - if ("error.path".equals(propertyName)) { - assertEquals(serverProperties, event.getSource()); - assertEquals("/error", event.getOldValue()); - assertEquals("/error-page", event.getNewValue()); - assertEquals("server.error.path", configurationProperty.getName().toString()); - assertEquals(event.getNewValue(), configurationProperty.getValue()); - } else if ("compression.enabled".equals(propertyName)) { + if ("port".equals(propertyName)) { assertEquals(serverProperties, event.getSource()); - assertEquals(false, event.getOldValue()); - assertEquals(true, event.getNewValue()); - assertEquals("server.compression.enabled", configurationProperty.getName().toString()); - assertEquals("true", configurationProperty.getValue()); + assertNull(event.getOldValue()); + assertEquals(valueOf(newPortPropertyValue), event.getNewValue()); + assertEquals(valueOf((String) configurationProperty.getValue()), event.getNewValue()); } }); - mockPropertySource.setProperty("server.error.path", "/error-page"); - mockPropertySource.setProperty("server.compression.enabled", "true"); + mockPropertySource.setProperty("server.port", newPortPropertyValue); beanFactory.destroyBean(serverProperties); - beanFactory.initializeBean(serverProperties, "server-serverProperties"); + beanFactory.initializeBean(serverProperties, getBeanName(serverProperties)); + + } + private String getBeanName(Object configurationPropertiesBean) { + Class beanClass = configurationPropertiesBean.getClass(); + String[] beanNames = this.context.getBeanNamesForType(beanClass); + return beanNames[0]; } } diff --git a/microsphere-core-spring-boot-starter/src/test/java/io/microsphere/spring/boot/env/PropertySourceLoadersTest.java b/microsphere-core-spring-boot-starter/src/test/java/io/microsphere/spring/boot/env/PropertySourceLoadersTest.java new file mode 100644 index 0000000..6482b93 --- /dev/null +++ b/microsphere-core-spring-boot-starter/src/test/java/io/microsphere/spring/boot/env/PropertySourceLoadersTest.java @@ -0,0 +1,86 @@ +/* + * 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.boot.env; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.env.OriginTrackedMapPropertySource; +import org.springframework.boot.origin.OriginLookup; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; + +import java.io.IOException; +import java.util.List; + +import static io.microsphere.util.ArrayUtils.of; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * {@link PropertySourceLoaders} + * + * @author Mercy + * @see PropertySourceLoaders + * @since 1.0.0 + */ +public class PropertySourceLoadersTest { + + private static final PropertySourceLoaders propertySourceLoaders = new PropertySourceLoaders(); + + private static final String TEST_PROPERTY_NAME = "core"; + + private static final String TEST_RESOURCE_LOCATION = "classpath:/config/default/core.properties"; + + @Test + public void testGetFileExtensions() { + String[] fileExtensions = propertySourceLoaders.getFileExtensions(); + assertArrayEquals(of("properties", "xml", "yml", "yaml"), fileExtensions); + } + + @Test + public void testLoad() throws IOException { + ResourceLoader resourceLoader = new DefaultResourceLoader(); + Resource resource = resourceLoader.getResource(TEST_RESOURCE_LOCATION); + List> propertySources = propertySourceLoaders.load(TEST_PROPERTY_NAME, resource); + assertEquals(1, propertySources.size()); + + PropertySource propertySource = propertySources.get(0); + assertPropertySource(propertySource); + } + + @Test + public void testLoadAsOriginTracked() throws IOException { + PropertySource propertySource = propertySourceLoaders.loadAsOriginTracked(TEST_PROPERTY_NAME, TEST_RESOURCE_LOCATION); + assertTrue(propertySource instanceof OriginLookup); + assertPropertySource(propertySource); + } + + @Test + public void testReloadAsOriginTracked() throws IOException { + PropertySource propertySource = propertySourceLoaders.loadAsOriginTracked(TEST_PROPERTY_NAME, TEST_RESOURCE_LOCATION); + assertSame(propertySource, propertySourceLoaders.reloadAsOriginTracked(propertySource)); + } + + private void assertPropertySource(PropertySource propertySource) { + assertTrue(propertySource instanceof OriginTrackedMapPropertySource); + assertEquals(TEST_PROPERTY_NAME, propertySource.getName()); + assertEquals("graceful", propertySource.getProperty("server.shutdown")); + } +} diff --git a/microsphere-core-spring-boot-starter/src/test/resources/META-INF/spring.factories b/microsphere-core-spring-boot-starter/src/test/resources/META-INF/spring.factories index c4d0955..3e82d88 100644 --- a/microsphere-core-spring-boot-starter/src/test/resources/META-INF/spring.factories +++ b/microsphere-core-spring-boot-starter/src/test/resources/META-INF/spring.factories @@ -9,5 +9,4 @@ io.microsphere.spring.context.event.BeanTimeStatistics # BeanFactoryListener io.microsphere.spring.context.event.BeanFactoryListener=\ -io.microsphere.spring.context.event.LoggingBeanFactoryListener,\ -io.microsphere.spring.context.event.ParallelPreInstantiationSingletonsBeanFactoryListener \ No newline at end of file +io.microsphere.spring.context.event.LoggingBeanFactoryListener \ No newline at end of file diff --git a/microsphere-core-spring-boot-starter/src/test/resources/logback.xml b/microsphere-core-spring-boot-starter/src/test/resources/logback.xml new file mode 100644 index 0000000..fbbd2bd --- /dev/null +++ b/microsphere-core-spring-boot-starter/src/test/resources/logback.xml @@ -0,0 +1,18 @@ + + + + + + + ${ENCODER_PATTERN} + + + + + + + + + + + \ No newline at end of file diff --git a/microsphere-spring-boot-actuator/pom.xml b/microsphere-spring-boot-actuator/pom.xml index d6fffca..3d2a8ab 100644 --- a/microsphere-spring-boot-actuator/pom.xml +++ b/microsphere-spring-boot-actuator/pom.xml @@ -27,6 +27,13 @@ ${revision} + + + org.jolokia + jolokia-core + ${jolokia.version} + + org.springframework.boot @@ -46,13 +53,19 @@ true - - org.jolokia - jolokia-core + org.springframework.boot + spring-boot-configuration-processor + true + + org.junit.jupiter + junit-jupiter + test + + org.springframework.boot spring-boot-starter-test @@ -61,4 +74,25 @@ + + + spring-boot-2.1 + + + io.micrometer + micrometer-core + 1.3.20 + true + + + + org.springframework + spring-core + 5.2.25.RELEASE + true + + + + + \ No newline at end of file diff --git a/microsphere-spring-boot-actuator/src/main/java/io/microsphere/spring/boot/actuate/MonitoredThreadPoolTaskScheduler.java b/microsphere-spring-boot-actuator/src/main/java/io/microsphere/spring/boot/actuate/MonitoredThreadPoolTaskScheduler.java index 77af057..08064f7 100644 --- a/microsphere-spring-boot-actuator/src/main/java/io/microsphere/spring/boot/actuate/MonitoredThreadPoolTaskScheduler.java +++ b/microsphere-spring-boot-actuator/src/main/java/io/microsphere/spring/boot/actuate/MonitoredThreadPoolTaskScheduler.java @@ -18,7 +18,6 @@ import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.binder.jvm.ExecutorServiceMetrics; -import io.micrometer.core.instrument.internal.TimedScheduledExecutorService; import io.microsphere.concurrent.DelegatingScheduledExecutorService; import org.springframework.beans.BeansException; import org.springframework.beans.factory.SmartInitializingSingleton; @@ -38,7 +37,7 @@ * @author Mercy * @see ThreadPoolTaskScheduler * @see ExecutorServiceMetrics - * @see TimedScheduledExecutorService + * @see io.micrometer.core.instrument.internal.TimedScheduledExecutorService * @since 1.0.0 */ public class MonitoredThreadPoolTaskScheduler extends ThreadPoolTaskScheduler implements ApplicationContextAware, SmartInitializingSingleton { diff --git a/microsphere-spring-boot-actuator/src/main/java/io/microsphere/spring/boot/actuate/autoconfigure/ActuatorEndpointsAutoConfiguration.java b/microsphere-spring-boot-actuator/src/main/java/io/microsphere/spring/boot/actuate/autoconfigure/ActuatorEndpointsAutoConfiguration.java index 464a927..428ad89 100644 --- a/microsphere-spring-boot-actuator/src/main/java/io/microsphere/spring/boot/actuate/autoconfigure/ActuatorEndpointsAutoConfiguration.java +++ b/microsphere-spring-boot-actuator/src/main/java/io/microsphere/spring/boot/actuate/autoconfigure/ActuatorEndpointsAutoConfiguration.java @@ -16,17 +16,21 @@ */ package io.microsphere.spring.boot.actuate.autoconfigure; +import io.microsphere.spring.boot.actuate.condition.ConditionalOnConfigurationProcessorPresent; import io.microsphere.spring.boot.actuate.endpoint.ArtifactsEndpoint; +import io.microsphere.spring.boot.actuate.endpoint.ConfigurationMetadataEndpoint; import io.microsphere.spring.boot.actuate.endpoint.WebEndpoints; +import io.microsphere.spring.boot.context.properties.metadata.ConfigurationMetadataReader; +import io.microsphere.spring.boot.env.config.metadata.ConfigurationMetadataRepository; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; -import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints; import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; /** * Actuator {@link Endpoint @Endpoint} Auto-Configuration class @@ -38,6 +42,7 @@ @ConditionalOnClass(name = { "org.springframework.boot.actuate.endpoint.annotation.Endpoint" }) +@Import(value = {ActuatorEndpointsAutoConfiguration.ConfigurationProcessorConfiguration.class}) public class ActuatorEndpointsAutoConfiguration implements BeanClassLoaderAware { private ClassLoader classLoader; @@ -57,6 +62,30 @@ public WebEndpoints webEndpoints(WebEndpointsSupplier webEndpointsSupplier) { return new WebEndpoints(webEndpointsSupplier); } + @ConditionalOnConfigurationProcessorPresent + static class ConfigurationProcessorConfiguration { + + @Bean + @ConditionalOnMissingBean + public ConfigurationMetadataReader configurationMetadataReader() { + return new ConfigurationMetadataReader(); + } + + @Bean + @ConditionalOnMissingBean + public ConfigurationMetadataRepository configurationMetadataRepository(ConfigurationMetadataReader configurationMetadataReader) { + return new ConfigurationMetadataRepository(configurationMetadataReader); + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnAvailableEndpoint + public ConfigurationMetadataEndpoint configurationMetadataEndpoint(ConfigurationMetadataRepository configurationMetadataRepository) { + return new ConfigurationMetadataEndpoint(configurationMetadataRepository); + } + + } + @Override public void setBeanClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; diff --git a/microsphere-spring-boot-actuator/src/main/java/io/microsphere/spring/boot/actuate/condition/ConditionalOnConfigurationProcessorPresent.java b/microsphere-spring-boot-actuator/src/main/java/io/microsphere/spring/boot/actuate/condition/ConditionalOnConfigurationProcessorPresent.java new file mode 100644 index 0000000..c45d278 --- /dev/null +++ b/microsphere-spring-boot-actuator/src/main/java/io/microsphere/spring/boot/actuate/condition/ConditionalOnConfigurationProcessorPresent.java @@ -0,0 +1,41 @@ +/* + * 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.boot.actuate.condition; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Conditional; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * {@link Conditional @Conditional} that checks whether the artifact "org.springframework.boot:spring-boot-configuration-processor" + * is present + * + * @author Mercy + * @see Conditional + * @since 1.0.0 + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@ConditionalOnClass(name = "org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata") +public @interface ConditionalOnConfigurationProcessorPresent { +} diff --git a/microsphere-spring-boot-actuator/src/main/java/io/microsphere/spring/boot/actuate/endpoint/ConfigurationMetadataEndpoint.java b/microsphere-spring-boot-actuator/src/main/java/io/microsphere/spring/boot/actuate/endpoint/ConfigurationMetadataEndpoint.java new file mode 100644 index 0000000..715999a --- /dev/null +++ b/microsphere-spring-boot-actuator/src/main/java/io/microsphere/spring/boot/actuate/endpoint/ConfigurationMetadataEndpoint.java @@ -0,0 +1,78 @@ +/* + * 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.boot.actuate.endpoint; + +import io.microsphere.spring.boot.env.config.metadata.ConfigurationMetadataRepository; +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata; +import org.springframework.boot.configurationprocessor.metadata.ItemMetadata; + +import java.util.Collection; + +/** + * {@link Endpoint @Endpoint} to expose the {@link ConfigurationMetadata Configuration Metadata} that was generated by + * "org.springframework.boot:spring-boot-configuration-processor" + * + * @author Mercy + * @see ConfigurationMetadata + * @see ConfigurationMetadataRepository + * @since 1.0.0 + */ +@Endpoint(id = "configMetadata") +public class ConfigurationMetadataEndpoint { + + private final ConfigurationMetadataRepository configurationMetadataRepository; + + public ConfigurationMetadataEndpoint(ConfigurationMetadataRepository configurationMetadataRepository) { + this.configurationMetadataRepository = configurationMetadataRepository; + } + + @ReadOperation + public ConfigurationMetadataDescriptor getConfigurationMetadata() { + ConfigurationMetadataDescriptor configurationMetadata = new ConfigurationMetadataDescriptor(); + configurationMetadata.groups = this.configurationMetadataRepository.getGroups(); + configurationMetadata.properties = this.configurationMetadataRepository.getProperties(); + return configurationMetadata; + } + + /** + * The Descriptor class for {@link ConfigurationMetadata} + */ + public static class ConfigurationMetadataDescriptor { + + private Collection groups; + + private Collection properties; + + public Collection getGroups() { + return groups; + } + + public void setGroups(Collection groups) { + this.groups = groups; + } + + public Collection getProperties() { + return properties; + } + + public void setProperties(Collection properties) { + this.properties = properties; + } + } +} diff --git a/microsphere-spring-boot-actuator/src/main/java/io/microsphere/spring/boot/actuate/endpoint/ConfigurationPropertiesEndpoint.java b/microsphere-spring-boot-actuator/src/main/java/io/microsphere/spring/boot/actuate/endpoint/ConfigurationPropertiesEndpoint.java new file mode 100644 index 0000000..b553ec0 --- /dev/null +++ b/microsphere-spring-boot-actuator/src/main/java/io/microsphere/spring/boot/actuate/endpoint/ConfigurationPropertiesEndpoint.java @@ -0,0 +1,74 @@ +/* + * 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.boot.actuate.endpoint; + +import io.microsphere.spring.boot.env.config.metadata.ConfigurationMetadataRepository; +import io.microsphere.spring.config.ConfigurationProperty; +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.event.EventListener; + +import java.util.List; + +/** + * {@link Endpoint @Endpoint} to expose the configuration properties. + * + * @author Mercy + * @see ConfigurationMetadata + * @see ConfigurationProperties + * @since 1.0.0 + */ +@Endpoint(id = "configProperties") +public class ConfigurationPropertiesEndpoint { + + private final ConfigurationMetadataRepository configurationMetadataRepository; + + public ConfigurationPropertiesEndpoint(ConfigurationMetadataRepository configurationMetadataRepository) { + this.configurationMetadataRepository = configurationMetadataRepository; + } + + @EventListener(ApplicationReadyEvent.class) + public void onApplicationReadyEvent(ApplicationReadyEvent event) { + ConfigurableApplicationContext context = event.getApplicationContext(); + } + + @ReadOperation + public ConfigurationPropertiesDescriptor getConfigurationProperties() { + ConfigurationPropertiesDescriptor configurationProperties = new ConfigurationPropertiesDescriptor(); + return configurationProperties; + } + + + public static class ConfigurationPropertiesDescriptor { + + private List configurationProperties; + + public List getConfigurationProperties() { + return configurationProperties; + } + + public void setConfigurationProperties(List configurationProperties) { + this.configurationProperties = configurationProperties; + } + } + + +} diff --git a/microsphere-spring-boot-actuator/src/main/resources/META-INF/config/default/endpoints.properties b/microsphere-spring-boot-actuator/src/main/resources/META-INF/config/default/endpoints.properties index 091964e..8aa300d 100644 --- a/microsphere-spring-boot-actuator/src/main/resources/META-INF/config/default/endpoints.properties +++ b/microsphere-spring-boot-actuator/src/main/resources/META-INF/config/default/endpoints.properties @@ -75,6 +75,12 @@ management.endpoint.artifacts.enabled = true management.endpoints.web.path-mapping.artifacts = microsphere/artifacts management.endpoint.artifacts.cache.time-to-live = ${microsphere.cache.long-long-time-to-live} +### WebEndpoints Endpoint management.endpoint.webEndpoints.enabled = true management.endpoints.web.path-mapping.webEndpoints = microsphere/web/endpoints management.endpoint.webEndpoints.cache.time-to-live = ${microsphere.cache.short-time-to-live} + +### ConfigurationMetadata Endpoint +management.endpoint.configMetadata.enabled = true +management.endpoints.web.path-mapping.configMetadata = microsphere/config/metadata +management.endpoint.configMetadata.cache.time-to-live = ${microsphere.cache.long-long-time-to-live} \ No newline at end of file diff --git a/microsphere-spring-boot-actuator/src/test/java/io/microsphere/spring/boot/actuate/autoconfigure/ActuatorEndpointsAutoConfigurationTest.java b/microsphere-spring-boot-actuator/src/test/java/io/microsphere/spring/boot/actuate/autoconfigure/ActuatorEndpointsAutoConfigurationTest.java index 8c8b1cd..7ce1881 100644 --- a/microsphere-spring-boot-actuator/src/test/java/io/microsphere/spring/boot/actuate/autoconfigure/ActuatorEndpointsAutoConfigurationTest.java +++ b/microsphere-spring-boot-actuator/src/test/java/io/microsphere/spring/boot/actuate/autoconfigure/ActuatorEndpointsAutoConfigurationTest.java @@ -1,6 +1,7 @@ package io.microsphere.spring.boot.actuate.autoconfigure; import io.microsphere.spring.boot.actuate.endpoint.ArtifactsEndpoint; +import io.microsphere.spring.boot.actuate.endpoint.ConfigurationMetadataEndpoint; import io.microsphere.spring.boot.actuate.endpoint.WebEndpoints; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -9,6 +10,7 @@ import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.PropertySource; +import org.springframework.test.context.TestPropertySource; import java.util.Map; @@ -25,10 +27,15 @@ webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { ActuatorEndpointsAutoConfigurationTest.class, - }) + }, + properties = { + "management.endpoint.loggers.enabled=false" + } +) @PropertySource(value = "classpath:META-INF/config/default/endpoints.properties") +@TestPropertySource(value = "classpath:META-INF/config/default/endpoints.properties") @EnableAutoConfiguration -class ActuatorEndpointsAutoConfigurationTest { +public class ActuatorEndpointsAutoConfigurationTest { @Autowired private ArtifactsEndpoint artifactsEndpoint; @@ -36,6 +43,9 @@ class ActuatorEndpointsAutoConfigurationTest { @Autowired private WebEndpoints webEndpoints; + @Autowired + private ConfigurationMetadataEndpoint configurationMetadataEndpoint; + @Test void testArtifactsEndpoint() { assertFalse(artifactsEndpoint.getArtifactMetaInfoList().isEmpty()); @@ -47,6 +57,13 @@ public void testInvokeReadOperations() { assertFalse(aggregatedResults.isEmpty()); } + @Test + public void testGetConfigurationMetadata() { + ConfigurationMetadataEndpoint.ConfigurationMetadataDescriptor configurationMetadata = configurationMetadataEndpoint.getConfigurationMetadata(); + assertFalse(configurationMetadata.getGroups().isEmpty()); + assertFalse(configurationMetadata.getProperties().isEmpty()); + } + public static void main(String[] args) { new SpringApplicationBuilder(ActuatorEndpointsAutoConfigurationTest.class) .web(WebApplicationType.SERVLET) diff --git a/microsphere-spring-boot-actuator/src/test/java/io/microsphere/spring/boot/actuate/env/DefaultPropertiesTest.java b/microsphere-spring-boot-actuator/src/test/java/io/microsphere/spring/boot/actuate/env/DefaultPropertiesTest.java index 175ba78..8b38131 100644 --- a/microsphere-spring-boot-actuator/src/test/java/io/microsphere/spring/boot/actuate/env/DefaultPropertiesTest.java +++ b/microsphere-spring-boot-actuator/src/test/java/io/microsphere/spring/boot/actuate/env/DefaultPropertiesTest.java @@ -57,7 +57,7 @@ public void testEndpointsDefaultProperties() { assertProperty("management.endpoint.httptrace.enabled", "false"); assertProperty("management.endpoint.info.enabled", "true"); assertProperty("management.endpoint.integrationgraph.enabled", "false"); - assertProperty("management.endpoint.loggers.enabled", "true"); + assertProperty("management.endpoint.loggers.enabled", "false"); assertProperty("management.endpoint.liquibase.enabled", "false"); assertProperty("management.endpoint.metrics.enabled", "true"); assertProperty("management.endpoint.mappings.enabled", "true"); @@ -74,6 +74,6 @@ public void testEndpointsDefaultProperties() { } private void assertProperty(String propertyName, String expectedValue) { - assertEquals(environment.getRequiredProperty(propertyName), expectedValue); + assertEquals(expectedValue, environment.getRequiredProperty(propertyName)); } } diff --git a/microsphere-spring-boot-actuator/src/test/resources/application.properties b/microsphere-spring-boot-actuator/src/test/resources/application.properties new file mode 100644 index 0000000..1636c41 --- /dev/null +++ b/microsphere-spring-boot-actuator/src/test/resources/application.properties @@ -0,0 +1 @@ +management.endpoint.loggers.enabled = false \ No newline at end of file diff --git a/microsphere-spring-boot-compatible/pom.xml b/microsphere-spring-boot-compatible/pom.xml new file mode 100644 index 0000000..5391180 --- /dev/null +++ b/microsphere-spring-boot-compatible/pom.xml @@ -0,0 +1,78 @@ + + + 4.0.0 + + io.github.microsphere-projects + microsphere-spring-boot-parent + ${revision} + ../microsphere-spring-boot-parent/pom.xml + + + io.github.microsphere-projects + microsphere-spring-boot-compatible + ${revision} + jar + + Microsphere :: Spring Boot :: Compatible + Microsphere Spring Boot Compatible + + + + + + org.springframework.boot + spring-boot-autoconfigure + true + + + + org.springframework.boot + spring-boot-actuator-autoconfigure + true + + + + + org.springframework + spring-core + true + + + + org.springframework + spring-context + true + + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + + + + + + spring-boot-2.1 + + + org.springframework + spring-core + 5.2.25.RELEASE + true + + + + + + \ No newline at end of file diff --git a/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/BootstrapContext.java b/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/BootstrapContext.java new file mode 100644 index 0000000..f3c4941 --- /dev/null +++ b/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/BootstrapContext.java @@ -0,0 +1,87 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed 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 + * + * https://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 org.springframework.boot; + +import org.springframework.context.ApplicationContext; +import org.springframework.core.env.Environment; + +import java.util.function.Supplier; + +/** + * A simple bootstrap context that is available during startup and {@link Environment} + * post-processing up to the point that the {@link ApplicationContext} is prepared. + *

+ * Provides lazy access to singletons that may be expensive to create, or need to be + * shared before the {@link ApplicationContext} is available. + * + * @author Phillip Webb + * @since 2.4.0 + */ +public interface BootstrapContext { + + /** + * Return an instance from the context if the type has been registered. The instance + * will be created it if it hasn't been accessed previously. + * @param the instance type + * @param type the instance type + * @return the instance managed by the context + * @throws IllegalStateException if the type has not been registered + */ + T get(Class type) throws IllegalStateException; + + /** + * Return an instance from the context if the type has been registered. The instance + * will be created it if it hasn't been accessed previously. + * @param the instance type + * @param type the instance type + * @param other the instance to use if the type has not been registered + * @return the instance + */ + T getOrElse(Class type, T other); + + /** + * Return an instance from the context if the type has been registered. The instance + * will be created it if it hasn't been accessed previously. + * @param the instance type + * @param type the instance type + * @param other a supplier for the instance to use if the type has not been registered + * @return the instance + */ + T getOrElseSupply(Class type, Supplier other); + + /** + * Return an instance from the context if the type has been registered. The instance + * will be created it if it hasn't been accessed previously. + * @param the instance type + * @param the exception to throw if the type is not registered + * @param type the instance type + * @param exceptionSupplier the supplier which will return the exception to be thrown + * @return the instance managed by the context + * @throws X if the type has not been registered + * @throws IllegalStateException if the type has not been registered + */ + T getOrElseThrow(Class type, Supplier exceptionSupplier) throws X; + + /** + * Return if a registration exists for the given type. + * @param the instance type + * @param type the instance type + * @return {@code true} if the type has already been registered + */ + boolean isRegistered(Class type); + +} diff --git a/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/BootstrapContextClosedEvent.java b/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/BootstrapContextClosedEvent.java new file mode 100644 index 0000000..2be023e --- /dev/null +++ b/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/BootstrapContextClosedEvent.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed 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 + * + * https://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 org.springframework.boot; + +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * {@link ApplicationEvent} published by a {@link BootstrapContext} when it's closed. + * + * @author Phillip Webb + * @since 2.4.0 + * @see BootstrapRegistry#addCloseListener(org.springframework.context.ApplicationListener) + */ +public class BootstrapContextClosedEvent extends ApplicationEvent { + + private final ConfigurableApplicationContext applicationContext; + + BootstrapContextClosedEvent(BootstrapContext source, ConfigurableApplicationContext applicationContext) { + super(source); + this.applicationContext = applicationContext; + } + + /** + * Return the {@link BootstrapContext} that was closed. + * @return the bootstrap context + */ + public BootstrapContext getBootstrapContext() { + return (BootstrapContext) this.source; + } + + /** + * Return the prepared application context. + * @return the application context + */ + public ConfigurableApplicationContext getApplicationContext() { + return this.applicationContext; + } + +} diff --git a/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/BootstrapRegistry.java b/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/BootstrapRegistry.java new file mode 100644 index 0000000..74cf06d --- /dev/null +++ b/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/BootstrapRegistry.java @@ -0,0 +1,186 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed 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 + * + * https://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 org.springframework.boot; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationListener; +import org.springframework.core.env.Environment; +import org.springframework.util.Assert; + +import java.util.function.Supplier; + +/** + * A simple object registry that is available during startup and {@link Environment} + * post-processing up to the point that the {@link ApplicationContext} is prepared. + *

+ * Can be used to register instances that may be expensive to create, or need to be shared + * before the {@link ApplicationContext} is available. + *

+ * The registry uses {@link Class} as a key, meaning that only a single instance of a + * given type can be stored. + *

+ * The {@link #addCloseListener(ApplicationListener)} method can be used to add a listener + * that can perform actions when {@link BootstrapContext} has been closed and the + * {@link ApplicationContext} is fully prepared. For example, an instance may choose to + * register itself as a regular Spring bean so that it is available for the application to + * use. + * + * @author Phillip Webb + * @since 2.4.0 + * @see BootstrapContext + * @see ConfigurableBootstrapContext + */ +public interface BootstrapRegistry { + + /** + * Register a specific type with the registry. If the specified type has already been + * registered and has not been obtained as a {@link Scope#SINGLETON singleton}, it + * will be replaced. + * @param the instance type + * @param type the instance type + * @param instanceSupplier the instance supplier + */ + void register(Class type, InstanceSupplier instanceSupplier); + + /** + * Register a specific type with the registry if one is not already present. + * @param the instance type + * @param type the instance type + * @param instanceSupplier the instance supplier + */ + void registerIfAbsent(Class type, InstanceSupplier instanceSupplier); + + /** + * Return if a registration exists for the given type. + * @param the instance type + * @param type the instance type + * @return {@code true} if the type has already been registered + */ + boolean isRegistered(Class type); + + /** + * Return any existing {@link InstanceSupplier} for the given type. + * @param the instance type + * @param type the instance type + * @return the registered {@link InstanceSupplier} or {@code null} + */ + InstanceSupplier getRegisteredInstanceSupplier(Class type); + + /** + * Add an {@link ApplicationListener} that will be called with a + * {@link BootstrapContextClosedEvent} when the {@link BootstrapContext} is closed and + * the {@link ApplicationContext} has been prepared. + * @param listener the listener to add + */ + void addCloseListener(ApplicationListener listener); + + /** + * Supplier used to provide the actual instance when needed. + * + * @param the instance type + * @see Scope + */ + @FunctionalInterface + interface InstanceSupplier { + + /** + * Factory method used to create the instance when needed. + * @param context the {@link BootstrapContext} which may be used to obtain other + * bootstrap instances. + * @return the instance + */ + T get(BootstrapContext context); + + /** + * Return the scope of the supplied instance. + * @return the scope + * @since 2.4.2 + */ + default Scope getScope() { + return Scope.SINGLETON; + } + + /** + * Return a new {@link InstanceSupplier} with an updated {@link Scope}. + * @param scope the new scope + * @return a new {@link InstanceSupplier} instance with the new scope + * @since 2.4.2 + */ + default InstanceSupplier withScope(Scope scope) { + Assert.notNull(scope, "Scope must not be null"); + InstanceSupplier parent = this; + return new InstanceSupplier() { + + @Override + public T get(BootstrapContext context) { + return parent.get(context); + } + + @Override + public Scope getScope() { + return scope; + } + + }; + } + + /** + * Factory method that can be used to create an {@link InstanceSupplier} for a + * given instance. + * @param the instance type + * @param instance the instance + * @return a new {@link InstanceSupplier} + */ + static InstanceSupplier of(T instance) { + return (registry) -> instance; + } + + /** + * Factory method that can be used to create an {@link InstanceSupplier} from a + * {@link Supplier}. + * @param the instance type + * @param supplier the supplier that will provide the instance + * @return a new {@link InstanceSupplier} + */ + static InstanceSupplier from(Supplier supplier) { + return (registry) -> (supplier != null) ? supplier.get() : null; + } + + } + + /** + * The scope of an instance. + * + * @since 2.4.2 + */ + enum Scope { + + /** + * A singleton instance. The {@link InstanceSupplier} will be called only once and + * the same instance will be returned each time. + */ + SINGLETON, + + /** + * A prototype instance. The {@link InstanceSupplier} will be called whenever an + * instance is needed. + */ + PROTOTYPE + + } + +} diff --git a/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/ConfigurableBootstrapContext.java b/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/ConfigurableBootstrapContext.java new file mode 100644 index 0000000..28eaa34 --- /dev/null +++ b/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/ConfigurableBootstrapContext.java @@ -0,0 +1,31 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed 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 + * + * https://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 org.springframework.boot; + +/** + * A {@link BootstrapContext} that also provides configuration methods through the + * {@link BootstrapRegistry} interface. + * + * @author Phillip Webb + * @since 2.4.0 + * @see BootstrapRegistry + * @see BootstrapContext + * @see DefaultBootstrapContext + */ +public interface ConfigurableBootstrapContext extends BootstrapRegistry, BootstrapContext { + +} diff --git a/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/DefaultBootstrapContext.java b/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/DefaultBootstrapContext.java new file mode 100644 index 0000000..0ba272b --- /dev/null +++ b/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/DefaultBootstrapContext.java @@ -0,0 +1,136 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed 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 + * + * https://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 org.springframework.boot; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.event.ApplicationEventMulticaster; +import org.springframework.context.event.SimpleApplicationEventMulticaster; +import org.springframework.util.Assert; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +/** + * Default {@link ConfigurableBootstrapContext} implementation. + * + * @author Phillip Webb + * @since 2.4.0 + */ +public class DefaultBootstrapContext implements ConfigurableBootstrapContext { + + private final Map, InstanceSupplier> instanceSuppliers = new HashMap<>(); + + private final Map, Object> instances = new HashMap<>(); + + private final ApplicationEventMulticaster events = new SimpleApplicationEventMulticaster(); + + @Override + public void register(Class type, InstanceSupplier instanceSupplier) { + register(type, instanceSupplier, true); + } + + @Override + public void registerIfAbsent(Class type, InstanceSupplier instanceSupplier) { + register(type, instanceSupplier, false); + } + + private void register(Class type, InstanceSupplier instanceSupplier, boolean replaceExisting) { + Assert.notNull(type, "Type must not be null"); + Assert.notNull(instanceSupplier, "InstanceSupplier must not be null"); + synchronized (this.instanceSuppliers) { + boolean alreadyRegistered = this.instanceSuppliers.containsKey(type); + if (replaceExisting || !alreadyRegistered) { + Assert.state(!this.instances.containsKey(type), () -> type.getName() + " has already been created"); + this.instanceSuppliers.put(type, instanceSupplier); + } + } + } + + @Override + public boolean isRegistered(Class type) { + synchronized (this.instanceSuppliers) { + return this.instanceSuppliers.containsKey(type); + } + } + + @Override + @SuppressWarnings("unchecked") + public InstanceSupplier getRegisteredInstanceSupplier(Class type) { + synchronized (this.instanceSuppliers) { + return (InstanceSupplier) this.instanceSuppliers.get(type); + } + } + + @Override + public void addCloseListener(ApplicationListener listener) { + this.events.addApplicationListener(listener); + } + + @Override + public T get(Class type) throws IllegalStateException { + return getOrElseThrow(type, () -> new IllegalStateException(type.getName() + " has not been registered")); + } + + @Override + public T getOrElse(Class type, T other) { + return getOrElseSupply(type, () -> other); + } + + @Override + public T getOrElseSupply(Class type, Supplier other) { + synchronized (this.instanceSuppliers) { + InstanceSupplier instanceSupplier = this.instanceSuppliers.get(type); + return (instanceSupplier != null) ? getInstance(type, instanceSupplier) : other.get(); + } + } + + @Override + public T getOrElseThrow(Class type, Supplier exceptionSupplier) throws X { + synchronized (this.instanceSuppliers) { + InstanceSupplier instanceSupplier = this.instanceSuppliers.get(type); + if (instanceSupplier == null) { + throw exceptionSupplier.get(); + } + return getInstance(type, instanceSupplier); + } + } + + @SuppressWarnings("unchecked") + private T getInstance(Class type, InstanceSupplier instanceSupplier) { + T instance = (T) this.instances.get(type); + if (instance == null) { + instance = (T) instanceSupplier.get(this); + if (instanceSupplier.getScope() == Scope.SINGLETON) { + this.instances.put(type, instance); + } + } + return instance; + } + + /** + * Method to be called when {@link BootstrapContext} is closed and the + * {@link ApplicationContext} is prepared. + * @param applicationContext the prepared context + */ + public void close(ConfigurableApplicationContext applicationContext) { + this.events.multicastEvent(new BootstrapContextClosedEvent(this, applicationContext)); + } + +} diff --git a/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/AbstractEndpointCondition.java b/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/AbstractEndpointCondition.java new file mode 100644 index 0000000..dc25d78 --- /dev/null +++ b/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/AbstractEndpointCondition.java @@ -0,0 +1,124 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed 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 + * + * https://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 org.springframework.boot.actuate.autoconfigure.endpoint.condition; + +import org.springframework.boot.actuate.endpoint.EndpointId; +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension; +import org.springframework.boot.autoconfigure.condition.ConditionMessage; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; +import org.springframework.core.env.Environment; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.core.type.MethodMetadata; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ConcurrentReferenceHashMap; + +import java.lang.annotation.Annotation; +import java.util.Map; +import java.util.Optional; + +/** + * Base class for {@link Endpoint @Endpoint} related {@link SpringBootCondition} + * implementations. + * + * @author Stephane Nicoll + * @author Andy Wilkinson + * @author Phillip Webb + */ +abstract class AbstractEndpointCondition extends SpringBootCondition { + + private static final String ENABLED_BY_DEFAULT_KEY = "management.endpoints.enabled-by-default"; + + private static final ConcurrentReferenceHashMap> enabledByDefaultCache = new ConcurrentReferenceHashMap<>(); + + AnnotationAttributes getEndpointAttributes(Class annotationClass, ConditionContext context, + AnnotatedTypeMetadata metadata) { + return getEndpointAttributes(getEndpointType(annotationClass, context, metadata)); + } + + protected ConditionOutcome getEnablementOutcome(ConditionContext context, AnnotatedTypeMetadata metadata, + Class annotationClass) { + Environment environment = context.getEnvironment(); + AnnotationAttributes attributes = getEndpointAttributes(annotationClass, context, metadata); + EndpointId id = EndpointId.of(environment, attributes.getString("id")); + String key = "management.endpoint." + id.toLowerCaseString() + ".enabled"; + Boolean userDefinedEnabled = environment.getProperty(key, Boolean.class); + if (userDefinedEnabled != null) { + return new ConditionOutcome(userDefinedEnabled, ConditionMessage.forCondition(annotationClass) + .because("found property " + key + " with value " + userDefinedEnabled)); + } + Boolean userDefinedDefault = isEnabledByDefault(environment); + if (userDefinedDefault != null) { + return new ConditionOutcome(userDefinedDefault, ConditionMessage.forCondition(annotationClass).because( + "no property " + key + " found so using user defined default from " + ENABLED_BY_DEFAULT_KEY)); + } + boolean endpointDefault = attributes.getBoolean("enableByDefault"); + return new ConditionOutcome(endpointDefault, ConditionMessage.forCondition(annotationClass) + .because("no property " + key + " found so using endpoint default")); + } + + protected Class getEndpointType(Class annotationClass, ConditionContext context, + AnnotatedTypeMetadata metadata) { + Map attributes = metadata.getAnnotationAttributes(annotationClass.getName()); + if (attributes != null && attributes.containsKey("endpoint")) { + Class target = (Class) attributes.get("endpoint"); + if (target != Void.class) { + return target; + } + } + Assert.state(metadata instanceof MethodMetadata && metadata.isAnnotated(Bean.class.getName()), + "EndpointCondition must be used on @Bean methods when the endpoint is not specified"); + MethodMetadata methodMetadata = (MethodMetadata) metadata; + try { + return ClassUtils.forName(methodMetadata.getReturnTypeName(), context.getClassLoader()); + } + catch (Throwable ex) { + throw new IllegalStateException("Failed to extract endpoint id for " + + methodMetadata.getDeclaringClassName() + "." + methodMetadata.getMethodName(), ex); + } + } + + protected AnnotationAttributes getEndpointAttributes(Class type) { + MergedAnnotations annotations = MergedAnnotations.from(type, SearchStrategy.TYPE_HIERARCHY); + MergedAnnotation endpoint = annotations.get(Endpoint.class); + if (endpoint.isPresent()) { + return endpoint.asAnnotationAttributes(); + } + MergedAnnotation extension = annotations.get(EndpointExtension.class); + Assert.state(extension.isPresent(), "No endpoint is specified and the return type of the @Bean method is " + + "neither an @Endpoint, nor an @EndpointExtension"); + return getEndpointAttributes(extension.getClass("endpoint")); + } + + private Boolean isEnabledByDefault(Environment environment) { + Optional enabledByDefault = enabledByDefaultCache.get(environment); + if (enabledByDefault == null) { + enabledByDefault = Optional.ofNullable(environment.getProperty(ENABLED_BY_DEFAULT_KEY, Boolean.class)); + enabledByDefaultCache.put(environment, enabledByDefault); + } + return enabledByDefault.orElse(null); + } + +} diff --git a/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnAvailableEndpoint.java b/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnAvailableEndpoint.java new file mode 100644 index 0000000..bd143c8 --- /dev/null +++ b/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnAvailableEndpoint.java @@ -0,0 +1,115 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed 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 + * + * https://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 org.springframework.boot.actuate.autoconfigure.endpoint.condition; + +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension; +import org.springframework.context.annotation.Conditional; +import org.springframework.core.env.Environment; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * {@link Conditional @Conditional} that checks whether an endpoint is available. An + * endpoint is considered available if it is both enabled and exposed. Matches enablement + * according to the endpoints specific {@link Environment} property, falling back to + * {@code management.endpoints.enabled-by-default} or failing that + * {@link Endpoint#enableByDefault()}. Matches exposure according to any of the + * {@code management.endpoints.web.exposure.} or + * {@code management.endpoints.jmx.exposure.} specific properties or failing that to + * whether the application runs on + * {@link org.springframework.boot.cloud.CloudPlatform#CLOUD_FOUNDRY}. Both those + * conditions should match for the endpoint to be considered available. + *

+ * When placed on a {@code @Bean} method, the endpoint defaults to the return type of the + * factory method: + * + *

+ * @Configuration
+ * public class MyConfiguration {
+ *
+ *     @ConditionalOnAvailableEndpoint
+ *     @Bean
+ *     public MyEndpoint myEndpoint() {
+ *         ...
+ *     }
+ *
+ * }
+ *

+ * It is also possible to use the same mechanism for extensions: + * + *

+ * @Configuration
+ * public class MyConfiguration {
+ *
+ *     @ConditionalOnAvailableEndpoint
+ *     @Bean
+ *     public MyEndpointWebExtension myEndpointWebExtension() {
+ *         ...
+ *     }
+ *
+ * }
+ *

+ * In the sample above, {@code MyEndpointWebExtension} will be created if the endpoint is + * available as defined by the rules above. {@code MyEndpointWebExtension} must be a + * regular extension that refers to an endpoint, something like: + * + *

+ * @EndpointWebExtension(endpoint = MyEndpoint.class)
+ * public class MyEndpointWebExtension {
+ *
+ * }
+ *

+ * Alternatively, the target endpoint can be manually specified for components that should + * only be created when a given endpoint is available: + * + *

+ * @Configuration
+ * public class MyConfiguration {
+ *
+ *     @ConditionalOnAvailableEndpoint(endpoint = MyEndpoint.class)
+ *     @Bean
+ *     public MyComponent myComponent() {
+ *         ...
+ *     }
+ *
+ * }
+ * + * @author Brian Clozel + * @author Stephane Nicoll + * @since 2.2.0 + * @see Endpoint + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Documented +@Conditional(OnAvailableEndpointCondition.class) +public @interface ConditionalOnAvailableEndpoint { + + /** + * The endpoint type that should be checked. Inferred when the return type of the + * {@code @Bean} method is either an {@link Endpoint @Endpoint} or an + * {@link EndpointExtension @EndpointExtension}. + * @return the endpoint type to check + */ + Class endpoint() default Void.class; + +} diff --git a/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnAvailableEndpointCondition.java b/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnAvailableEndpointCondition.java new file mode 100644 index 0000000..e4d5b6a --- /dev/null +++ b/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnAvailableEndpointCondition.java @@ -0,0 +1,111 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed 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 + * + * https://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 org.springframework.boot.actuate.autoconfigure.endpoint.condition; + +import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludeExcludeEndpointFilter; +import org.springframework.boot.actuate.autoconfigure.endpoint.expose.IncludeExcludeEndpointFilter.DefaultIncludes; +import org.springframework.boot.actuate.endpoint.EndpointId; +import org.springframework.boot.actuate.endpoint.ExposableEndpoint; +import org.springframework.boot.autoconfigure.condition.ConditionMessage; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.cloud.CloudPlatform; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.env.Environment; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.util.ConcurrentReferenceHashMap; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * A condition that checks if an endpoint is available (i.e. enabled and exposed). + * + * @author Brian Clozel + * @author Stephane Nicoll + * @author Phillip Webb + * @see ConditionalOnAvailableEndpoint + */ +class OnAvailableEndpointCondition extends AbstractEndpointCondition { + + private static final String JMX_ENABLED_KEY = "spring.jmx.enabled"; + + private static final Map> exposuresCache = new ConcurrentReferenceHashMap<>(); + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { + ConditionOutcome enablementOutcome = getEnablementOutcome(context, metadata, + ConditionalOnAvailableEndpoint.class); + if (!enablementOutcome.isMatch()) { + return enablementOutcome; + } + ConditionMessage message = enablementOutcome.getConditionMessage(); + Environment environment = context.getEnvironment(); + if (CloudPlatform.CLOUD_FOUNDRY.isActive(environment)) { + return new ConditionOutcome(true, message.andCondition(ConditionalOnAvailableEndpoint.class) + .because("application is running on Cloud Foundry")); + } + EndpointId id = EndpointId.of(environment, + getEndpointAttributes(ConditionalOnAvailableEndpoint.class, context, metadata).getString("id")); + Set exposures = getExposures(environment); + for (Exposure exposure : exposures) { + if (exposure.isExposed(id)) { + return new ConditionOutcome(true, + message.andCondition(ConditionalOnAvailableEndpoint.class) + .because("marked as exposed by a 'management.endpoints." + exposure.getPrefix() + + ".exposure' property")); + } + } + return new ConditionOutcome(false, message.andCondition(ConditionalOnAvailableEndpoint.class) + .because("no 'management.endpoints' property marked it as exposed")); + } + + private Set getExposures(Environment environment) { + Set exposures = exposuresCache.get(environment); + if (exposures == null) { + exposures = new HashSet<>(2); + if (environment.getProperty(JMX_ENABLED_KEY, Boolean.class, false)) { + exposures.add(new Exposure(environment, "jmx", DefaultIncludes.JMX)); + } + exposures.add(new Exposure(environment, "web", DefaultIncludes.WEB)); + exposuresCache.put(environment, exposures); + } + return exposures; + } + + static class Exposure extends IncludeExcludeEndpointFilter> { + + private final String prefix; + + @SuppressWarnings({ "rawtypes", "unchecked" }) + Exposure(Environment environment, String prefix, DefaultIncludes defaultIncludes) { + super((Class) ExposableEndpoint.class, environment, "management.endpoints." + prefix + ".exposure", + defaultIncludes); + this.prefix = prefix; + } + + String getPrefix() { + return this.prefix; + } + + boolean isExposed(EndpointId id) { + return super.match(id); + } + + } + +} diff --git a/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/expose/IncludeExcludeEndpointFilter.java b/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/expose/IncludeExcludeEndpointFilter.java new file mode 100644 index 0000000..03df597 --- /dev/null +++ b/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/expose/IncludeExcludeEndpointFilter.java @@ -0,0 +1,247 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed 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 + * + * https://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 org.springframework.boot.actuate.autoconfigure.endpoint.expose; + +import org.springframework.boot.actuate.endpoint.EndpointFilter; +import org.springframework.boot.actuate.endpoint.EndpointId; +import org.springframework.boot.actuate.endpoint.ExposableEndpoint; +import org.springframework.boot.context.properties.bind.Bindable; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.core.env.Environment; +import org.springframework.util.Assert; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * {@link EndpointFilter} that will filter endpoints based on {@code include} and + * {@code exclude} patterns. + * + * @param the endpoint type + * @author Phillip Webb + * @since 2.2.7 + */ +public class IncludeExcludeEndpointFilter> implements EndpointFilter { + + private final Class endpointType; + + private final EndpointPatterns include; + + private final EndpointPatterns defaultIncludes; + + private final EndpointPatterns exclude; + + /** + * Create a new {@link IncludeExcludeEndpointFilter} with include/exclude rules bound + * from the {@link Environment}. + * @param endpointType the endpoint type that should be considered (other types always + * match) + * @param environment the environment containing the properties + * @param prefix the property prefix to bind + * @param defaultIncludes the default {@code includes} to use when none are specified. + * @deprecated since 2.6.0 for removal in 3.0.0 in favor of + * {@link #IncludeExcludeEndpointFilter(Class, Environment, String, String[])} + */ + @Deprecated + public IncludeExcludeEndpointFilter(Class endpointType, Environment environment, String prefix, + DefaultIncludes defaultIncludes) { + this(endpointType, environment, prefix, DefaultIncludes.patterns(defaultIncludes)); + } + + /** + * Create a new {@link IncludeExcludeEndpointFilter} with include/exclude rules bound + * from the {@link Environment}. + * @param endpointType the endpoint type that should be considered (other types always + * match) + * @param environment the environment containing the properties + * @param prefix the property prefix to bind + * @param defaultIncludes the default {@code includes} to use when none are specified. + */ + public IncludeExcludeEndpointFilter(Class endpointType, Environment environment, String prefix, + String... defaultIncludes) { + this(endpointType, environment, prefix, new EndpointPatterns(defaultIncludes)); + } + + /** + * Create a new {@link IncludeExcludeEndpointFilter} with specific include/exclude + * rules. + * @param endpointType the endpoint type that should be considered (other types always + * match) + * @param include the include patterns + * @param exclude the exclude patterns + * @param defaultIncludes the default {@code includes} to use when none are specified. + */ + public IncludeExcludeEndpointFilter(Class endpointType, Collection include, Collection exclude, + String... defaultIncludes) { + this(endpointType, include, exclude, new EndpointPatterns(defaultIncludes)); + } + + /** + * Create a new {@link IncludeExcludeEndpointFilter} with specific include/exclude + * rules. + * @param endpointType the endpoint type that should be considered (other types always + * match) + * @param include the include patterns + * @param exclude the exclude patterns + * @param defaultIncludes the default {@code includes} to use when none are specified. + * @deprecated since 2.6.0 for removal in 3.0.0 in favor of + * {@link #IncludeExcludeEndpointFilter(Class, Environment, String, String[])} + */ + @Deprecated + public IncludeExcludeEndpointFilter(Class endpointType, Collection include, Collection exclude, + DefaultIncludes defaultIncludes) { + this(endpointType, include, exclude, DefaultIncludes.patterns(defaultIncludes)); + } + + private IncludeExcludeEndpointFilter(Class endpointType, Environment environment, String prefix, + EndpointPatterns defaultIncludes) { + Assert.notNull(endpointType, "EndpointType must not be null"); + Assert.notNull(environment, "Environment must not be null"); + Assert.hasText(prefix, "Prefix must not be empty"); + Assert.notNull(defaultIncludes, "DefaultIncludes must not be null"); + Binder binder = Binder.get(environment); + this.endpointType = endpointType; + this.include = new EndpointPatterns(bind(binder, prefix + ".include")); + this.defaultIncludes = defaultIncludes; + this.exclude = new EndpointPatterns(bind(binder, prefix + ".exclude")); + } + + private IncludeExcludeEndpointFilter(Class endpointType, Collection include, Collection exclude, + EndpointPatterns defaultIncludes) { + Assert.notNull(endpointType, "EndpointType Type must not be null"); + Assert.notNull(defaultIncludes, "DefaultIncludes must not be null"); + this.endpointType = endpointType; + this.include = new EndpointPatterns(include); + this.defaultIncludes = defaultIncludes; + this.exclude = new EndpointPatterns(exclude); + } + + private List bind(Binder binder, String name) { + return binder.bind(name, Bindable.listOf(String.class)).orElseGet(ArrayList::new); + } + + @Override + public boolean match(E endpoint) { + if (!this.endpointType.isInstance(endpoint)) { + // Leave non-matching types for other filters + return true; + } + return match(endpoint.getEndpointId()); + } + + /** + * Return {@code true} if the filter matches. + * @param endpointId the endpoint ID to check + * @return {@code true} if the filter matches + * @since 2.6.0 + */ + public final boolean match(EndpointId endpointId) { + return isIncluded(endpointId) && !isExcluded(endpointId); + } + + private boolean isIncluded(EndpointId endpointId) { + if (this.include.isEmpty()) { + return this.defaultIncludes.matches(endpointId); + } + return this.include.matches(endpointId); + } + + private boolean isExcluded(EndpointId endpointId) { + if (this.exclude.isEmpty()) { + return false; + } + return this.exclude.matches(endpointId); + } + + /** + * Default include patterns that can be used. + * + * @deprecated since 2.6.0 for removal in 3.0.0 in favor of {@link EndpointExposure}. + */ + @Deprecated + public enum DefaultIncludes { + + /** + * The default set of include patterns used for JMX. + */ + JMX("*"), + + /** + * The default set of include patterns used for web. + */ + WEB("health"); + + private final EndpointPatterns patterns; + + DefaultIncludes(String... patterns) { + this.patterns = new EndpointPatterns(patterns); + } + + static EndpointPatterns patterns(DefaultIncludes defaultIncludes) { + return (defaultIncludes != null) ? defaultIncludes.patterns : (EndpointPatterns) null; + } + + } + + /** + * A set of endpoint patterns used to match IDs. + */ + private static class EndpointPatterns { + + private final boolean empty; + + private final boolean matchesAll; + + private final Set endpointIds; + + EndpointPatterns(String[] patterns) { + this((patterns != null) ? Arrays.asList(patterns) : (Collection) null); + } + + EndpointPatterns(Collection patterns) { + patterns = (patterns != null) ? patterns : Collections.emptySet(); + boolean matchesAll = false; + Set endpointIds = new LinkedHashSet<>(); + for (String pattern : patterns) { + if ("*".equals(pattern)) { + matchesAll = true; + } + else { + endpointIds.add(EndpointId.fromPropertyValue(pattern)); + } + } + this.empty = patterns.isEmpty(); + this.matchesAll = matchesAll; + this.endpointIds = endpointIds; + } + + boolean isEmpty() { + return this.empty; + } + + boolean matches(EndpointId endpointId) { + return this.matchesAll || this.endpointIds.contains(endpointId); + } + + } + +} diff --git a/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/actuate/endpoint/EndpointId.java b/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/actuate/endpoint/EndpointId.java new file mode 100644 index 0000000..38e8725 --- /dev/null +++ b/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/actuate/endpoint/EndpointId.java @@ -0,0 +1,158 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed 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 + * + * https://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 org.springframework.boot.actuate.endpoint; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.env.Environment; +import org.springframework.util.Assert; + +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; +import java.util.regex.Pattern; + +/** + * An identifier for an actuator endpoint. Endpoint IDs may contain only letters and + * numbers. They must begin with a lower-case letter. Case and syntax characters are + * ignored when comparing endpoint IDs. + * + * @author Phillip Webb + * @since 2.0.6 + */ +public final class EndpointId { + + private static final Log logger = LogFactory.getLog(EndpointId.class); + + private static final Set loggedWarnings = new HashSet<>(); + + private static final Pattern VALID_PATTERN = Pattern.compile("[a-zA-Z0-9.-]+"); + + private static final Pattern WARNING_PATTERN = Pattern.compile("[.-]+"); + + private static final String MIGRATE_LEGACY_NAMES_PROPERTY = "management.endpoints.migrate-legacy-ids"; + + private final String value; + + private final String lowerCaseValue; + + private final String lowerCaseAlphaNumeric; + + private EndpointId(String value) { + Assert.hasText(value, "Value must not be empty"); + Assert.isTrue(VALID_PATTERN.matcher(value).matches(), "Value must only contain valid chars"); + Assert.isTrue(!Character.isDigit(value.charAt(0)), "Value must not start with a number"); + Assert.isTrue(!Character.isUpperCase(value.charAt(0)), "Value must not start with an uppercase letter"); + if (WARNING_PATTERN.matcher(value).find()) { + logWarning(value); + } + this.value = value; + this.lowerCaseValue = value.toLowerCase(Locale.ENGLISH); + this.lowerCaseAlphaNumeric = getAlphaNumerics(this.lowerCaseValue); + } + + private String getAlphaNumerics(String value) { + StringBuilder result = new StringBuilder(value.length()); + for (int i = 0; i < value.length(); i++) { + char ch = value.charAt(i); + if (ch >= 'a' && ch <= 'z' || ch >= '0' && ch <= '9') { + result.append(ch); + } + } + return result.toString(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + return this.lowerCaseAlphaNumeric.equals(((EndpointId) obj).lowerCaseAlphaNumeric); + } + + @Override + public int hashCode() { + return this.lowerCaseAlphaNumeric.hashCode(); + } + + /** + * Return a lower-case version of the endpoint ID. + * @return the lower-case endpoint ID + */ + public String toLowerCaseString() { + return this.lowerCaseValue; + } + + @Override + public String toString() { + return this.value; + } + + /** + * Factory method to create a new {@link EndpointId} of the specified value. + * @param value the endpoint ID value + * @return an {@link EndpointId} instance + */ + public static EndpointId of(String value) { + return new EndpointId(value); + } + + /** + * Factory method to create a new {@link EndpointId} of the specified value. This + * variant will respect the {@code management.endpoints.migrate-legacy-names} property + * if it has been set in the {@link Environment}. + * @param environment the Spring environment + * @param value the endpoint ID value + * @return an {@link EndpointId} instance + * @since 2.2.0 + */ + public static EndpointId of(Environment environment, String value) { + Assert.notNull(environment, "Environment must not be null"); + return new EndpointId(migrateLegacyId(environment, value)); + } + + private static String migrateLegacyId(Environment environment, String value) { + if (environment.getProperty(MIGRATE_LEGACY_NAMES_PROPERTY, Boolean.class, false)) { + return value.replaceAll("[-.]+", ""); + } + return value; + } + + /** + * Factory method to create a new {@link EndpointId} from a property value. More + * lenient than {@link #of(String)} to allow for common "relaxed" property variants. + * @param value the property value to convert + * @return an {@link EndpointId} instance + */ + public static EndpointId fromPropertyValue(String value) { + return new EndpointId(value.replace("-", "")); + } + + static void resetLoggedWarnings() { + loggedWarnings.clear(); + } + + private static void logWarning(String value) { + if (logger.isWarnEnabled() && loggedWarnings.add(value)) { + logger.warn("Endpoint ID '" + value + "' contains invalid characters, please migrate to a valid format."); + } + } + +} diff --git a/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/package-info.java b/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/package-info.java new file mode 100644 index 0000000..b36be1e --- /dev/null +++ b/microsphere-spring-boot-compatible/src/main/java/org/springframework/boot/package-info.java @@ -0,0 +1,24 @@ +/* + * 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. + */ +/** + * The classes in this package or sub-packages are forked from the Spring Boot 2.7.x(latest), + * which will be used to be compatible with the lower versions of Spring Boot. + * + * @author
Mercy + * @since 1.0.0 + */ +package org.springframework.boot; \ No newline at end of file diff --git a/microsphere-spring-boot-parent/pom.xml b/microsphere-spring-boot-parent/pom.xml index fc41584..c226129 100644 --- a/microsphere-spring-boot-parent/pom.xml +++ b/microsphere-spring-boot-parent/pom.xml @@ -19,7 +19,8 @@ Microsphere Spring Boot Parent - 0.0.3 + 0.1.0 + 1.7.2 @@ -45,13 +46,6 @@ - - spring-boot-2.0 - - 2.0.9.RELEASE - - - spring-boot-2.1 @@ -103,6 +97,5 @@ 2.7.18 - \ No newline at end of file diff --git a/pom.xml b/pom.xml index 91002da..3b6ab23 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ io.github.microsphere-projects microsphere-build - 0.0.21 + 0.1.0 4.0.0 @@ -51,12 +51,13 @@ - 0.0.3 + 0.1.0-SNAPSHOT microsphere-spring-boot-parent microsphere-spring-boot-dependencies + microsphere-spring-boot-compatible microsphere-core-spring-boot-starter microsphere-spring-boot-actuator