diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigAutoConfiguration.java index b82a2ddcf..3976a7d01 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigAutoConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigAutoConfiguration.java @@ -20,7 +20,9 @@ package com.tencent.cloud.polaris.config; import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceAutoRefresher; import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager; +import com.tencent.cloud.polaris.config.adapter.SmartConfigurationPropertiesRebinder; import com.tencent.cloud.polaris.config.annotation.PolarisConfigAnnotationProcessor; +import com.tencent.cloud.polaris.config.condition.ConditionalOnNonDefaultBehavior; import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; import com.tencent.cloud.polaris.config.listener.PolarisConfigChangeEventListener; import com.tencent.cloud.polaris.config.spring.annotation.SpringValueProcessor; @@ -28,7 +30,11 @@ import com.tencent.cloud.polaris.config.spring.property.PlaceholderHelper; import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry; import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.SearchStrategy; +import org.springframework.cloud.context.properties.ConfigurationPropertiesBeans; +import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -77,4 +83,14 @@ public class PolarisConfigAutoConfiguration { return new SpringValueProcessor(placeholderHelper, springValueRegistry, polarisConfigProperties); } + @Bean + @ConditionalOnMissingBean(search = SearchStrategy.CURRENT) + @ConditionalOnNonDefaultBehavior + public ConfigurationPropertiesRebinder smartConfigurationPropertiesRebinder( + ConfigurationPropertiesBeans beans) { + // If using default behavior, not use SmartConfigurationPropertiesRebinder. + // Minimize te possibility of making mistakes. + return new SmartConfigurationPropertiesRebinder(beans); + } + } diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigBootstrapAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigBootstrapAutoConfiguration.java index a67c8a89d..7c7afdf87 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigBootstrapAutoConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigBootstrapAutoConfiguration.java @@ -19,6 +19,8 @@ package com.tencent.cloud.polaris.config; import com.tencent.cloud.polaris.config.adapter.PolarisConfigFileLocator; import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager; +import com.tencent.cloud.polaris.config.adapter.SmartConfigurationPropertiesRebinder; +import com.tencent.cloud.polaris.config.condition.ConditionalOnNonDefaultBehavior; import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled; import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration; @@ -27,7 +29,11 @@ import com.tencent.polaris.client.api.SDKContext; import com.tencent.polaris.configuration.api.core.ConfigFileService; import com.tencent.polaris.configuration.factory.ConfigFileServiceFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.SearchStrategy; +import org.springframework.cloud.context.properties.ConfigurationPropertiesBeans; +import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -79,4 +85,14 @@ public class PolarisConfigBootstrapAutoConfiguration { PolarisContextProperties polarisContextProperties) { return new ConfigurationModifier(polarisConfigProperties, polarisContextProperties); } + + @Bean + @ConditionalOnMissingBean(search = SearchStrategy.CURRENT) + @ConditionalOnNonDefaultBehavior + public ConfigurationPropertiesRebinder smartConfigurationPropertiesRebinder( + ConfigurationPropertiesBeans beans) { + // If using default behavior, not use SmartConfigurationPropertiesRebinder. + // Minimize te possibility of making mistakes. + return new SmartConfigurationPropertiesRebinder(beans); + } } diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/SmartConfigurationPropertiesRebinder.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/SmartConfigurationPropertiesRebinder.java new file mode 100644 index 000000000..bc275482c --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/SmartConfigurationPropertiesRebinder.java @@ -0,0 +1,123 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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 com.tencent.cloud.polaris.config.adapter; + +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import com.tencent.cloud.polaris.config.enums.RefreshBehavior; + +import org.springframework.beans.BeansException; +import org.springframework.boot.context.properties.ConfigurationPropertiesBean; +import org.springframework.cloud.context.environment.EnvironmentChangeEvent; +import org.springframework.cloud.context.properties.ConfigurationPropertiesBeans; +import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder; +import org.springframework.context.ApplicationContext; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.ReflectionUtils; + +import static com.tencent.cloud.polaris.config.condition.NonDefaultBehaviorCondition.POLARIS_CONFIG_REFRESH_BEHAVIOR; +import static com.tencent.cloud.polaris.config.enums.RefreshBehavior.ALL_BEANS; + +/** + * Extend {@link ConfigurationPropertiesRebinder}. + *

+ * Spring team doesn't seem to support single {@link ConfigurationPropertiesBean} refresh. + *

+ * SmartConfigurationPropertiesRebinder can refresh specific + * {@link ConfigurationPropertiesBean} base on the change keys. + *

+ * NOTE: We still use Spring's default behavior (full refresh) as default + * behavior, This feature can be considered an advanced feature, it may not be as stable + * as the default behavior. + * + * SmartConfigurationPropertiesRebinder + * + * @author weihubeats 2022-7-10 + */ +public class SmartConfigurationPropertiesRebinder extends ConfigurationPropertiesRebinder { + + private static final String BEANS = "beans"; + + private Map beanMap; + + private ApplicationContext applicationContext; + + private RefreshBehavior refreshBehavior; + + public SmartConfigurationPropertiesRebinder(ConfigurationPropertiesBeans beans) { + super(beans); + fillBeanMap(beans); + } + + @SuppressWarnings("unchecked") + private void fillBeanMap(ConfigurationPropertiesBeans beans) { + this.beanMap = new HashMap<>(); + Field field = ReflectionUtils.findField(beans.getClass(), BEANS); + if (field != null) { + field.setAccessible(true); + this.beanMap.putAll((Map) Optional + .ofNullable(ReflectionUtils.getField(field, beans)) + .orElse(Collections.emptyMap())); + } + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException { + super.setApplicationContext(applicationContext); + this.applicationContext = applicationContext; + this.refreshBehavior = this.applicationContext.getEnvironment().getProperty( + POLARIS_CONFIG_REFRESH_BEHAVIOR, RefreshBehavior.class, + ALL_BEANS); + } + + @Override + public void onApplicationEvent(EnvironmentChangeEvent event) { + if (this.applicationContext.equals(event.getSource()) + // Backwards compatible + || event.getKeys().equals(event.getSource())) { + switch (refreshBehavior) { + case SPECIFIC_BEAN: + rebindSpecificBean(event); + break; + default: + rebind(); + break; + } + } + } + + private void rebindSpecificBean(EnvironmentChangeEvent event) { + Set refreshedSet = new HashSet<>(); + beanMap.forEach((name, bean) -> event.getKeys().forEach(changeKey -> { + String prefix = AnnotationUtils.getValue(bean.getAnnotation()).toString(); + // prevent multiple refresh one ConfigurationPropertiesBean. + if (changeKey.startsWith(prefix) && refreshedSet.add(name)) { + rebind(name); + } + })); + } + +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/condition/ConditionalOnNonDefaultBehavior.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/condition/ConditionalOnNonDefaultBehavior.java new file mode 100644 index 000000000..d799ec28b --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/condition/ConditionalOnNonDefaultBehavior.java @@ -0,0 +1,40 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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 com.tencent.cloud.polaris.config.condition; + +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; + +import org.springframework.context.annotation.Conditional; + +/** + * custom annotation. + * + * @author weihubeats 2022-7-13 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Documented +@Conditional(NonDefaultBehaviorCondition.class) +public @interface ConditionalOnNonDefaultBehavior { + +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/condition/NonDefaultBehaviorCondition.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/condition/NonDefaultBehaviorCondition.java new file mode 100644 index 000000000..98b14318e --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/condition/NonDefaultBehaviorCondition.java @@ -0,0 +1,57 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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 com.tencent.cloud.polaris.config.condition; + +import com.tencent.cloud.polaris.config.enums.RefreshBehavior; + +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * Extend SpringBootCondition. + * + * @author weihubeats 2022-7-13 + */ +public class NonDefaultBehaviorCondition extends SpringBootCondition { + + /** + * refresh behavior config. + */ + public static final String POLARIS_CONFIG_REFRESH_BEHAVIOR = "spring.cloud.polaris.config.refresh-behavior"; + + /** + * refresh behavior config default value. + */ + private static final RefreshBehavior DEFAULT_REFRESH_BEHAVIOR = RefreshBehavior.ALL_BEANS; + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, + AnnotatedTypeMetadata metadata) { + RefreshBehavior behavior = context.getEnvironment().getProperty( + POLARIS_CONFIG_REFRESH_BEHAVIOR, RefreshBehavior.class, + DEFAULT_REFRESH_BEHAVIOR); + if (DEFAULT_REFRESH_BEHAVIOR == behavior) { + return ConditionOutcome.noMatch("no matched"); + } + return ConditionOutcome.match("matched"); + } + +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/enums/RefreshBehavior.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/enums/RefreshBehavior.java new file mode 100644 index 000000000..12abd79cb --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/enums/RefreshBehavior.java @@ -0,0 +1,40 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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 com.tencent.cloud.polaris.config.enums; + +import org.springframework.boot.context.properties.ConfigurationPropertiesBean; + +/** + * Refresh behavior. + * + * @author weihubeats 2022-7-13 + */ +public enum RefreshBehavior { + + /** + * Refresh all {@link ConfigurationPropertiesBean}s. + */ + ALL_BEANS, + /** + * Refresh specific {@link ConfigurationPropertiesBean} base on change key. + */ + SPECIFIC_BEAN, + +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-starter-tencent-polaris-config/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 9954a62ce..3de4948d4 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-cloud-starter-tencent-polaris-config/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -41,6 +41,12 @@ "defaultValue": "true", "description": "Whether to connect to a remote server, suitable for local development mode.", "sourceType": "com.tencent.cloud.polaris.config.config.PolarisConfigProperties" + }, + { + "name": "spring.cloud.polaris.config.refresh-behavior", + "type": "com.tencent.cloud.polaris.config.enums.RefreshBehavior", + "defaultValue": "all_beans", + "description": "ConfigurationPropertiesBean refresh behavior." } ] }