diff --git a/CHANGELOG.md b/CHANGELOG.md index 819415784..6c37a97df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ - [feat:Add GitHub action of codecov.yml.](https://github.com/Tencent/spring-cloud-tencent/pull/328) - [Feature: add spring cloud tencent logo](https://github.com/Tencent/spring-cloud-tencent/pull/329) - [Feature: Optimize static metadata manager](https://github.com/Tencent/spring-cloud-tencent/pull/327) +- [Feature: optimization refreshScope](https://github.com/Tencent/spring-cloud-tencent/pull/326) - [Feature: support actuator for sct core components](https://github.com/Tencent/spring-cloud-tencent/pull/343) - [test:update junit of metadata.](https://github.com/Tencent/spring-cloud-tencent/pull/340) - [Optimize code style & unit test case](https://github.com/Tencent/spring-cloud-tencent/pull/336) 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 fb70489c7..e0fe81024 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 @@ -23,10 +23,13 @@ import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager; import com.tencent.cloud.polaris.config.annotation.PolarisConfigAnnotationProcessor; 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; +import com.tencent.cloud.polaris.config.spring.property.PlaceholderHelper; +import com.tencent.cloud.polaris.config.spring.property.SpringValueDefinitionProcessor; +import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry; import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.cloud.context.refresh.ContextRefresher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -44,9 +47,10 @@ public class PolarisConfigAutoConfiguration { public PolarisPropertySourceAutoRefresher polarisPropertySourceAutoRefresher( PolarisConfigProperties polarisConfigProperties, PolarisPropertySourceManager polarisPropertySourceManager, - ContextRefresher contextRefresher) { + SpringValueRegistry springValueRegistry, + PlaceholderHelper placeholderHelper) { return new PolarisPropertySourceAutoRefresher(polarisConfigProperties, - polarisPropertySourceManager, contextRefresher); + polarisPropertySourceManager, springValueRegistry, placeholderHelper); } @Bean @@ -58,4 +62,25 @@ public class PolarisConfigAutoConfiguration { public PolarisConfigChangeEventListener polarisConfigChangeEventListener() { return new PolarisConfigChangeEventListener(); } + + @Bean + public SpringValueRegistry springValueRegistry() { + return new SpringValueRegistry(); + } + + @Bean + public SpringValueProcessor springValueProcessor(PlaceholderHelper placeholderHelper, SpringValueRegistry springValueRegistry, PolarisConfigProperties polarisConfigProperties) { + return new SpringValueProcessor(placeholderHelper, springValueRegistry, polarisConfigProperties); + } + + @Bean + public PlaceholderHelper placeholderHelper() { + return new PlaceholderHelper(); + } + + @Bean + public SpringValueDefinitionProcessor springValueDefinitionProcessor(PlaceholderHelper placeholderHelper, PolarisConfigProperties polarisConfigProperties) { + return new SpringValueDefinitionProcessor(placeholderHelper, polarisConfigProperties); + } + } diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertySourceAutoRefresher.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertySourceAutoRefresher.java index 25af8ff32..bbd86d030 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertySourceAutoRefresher.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertySourceAutoRefresher.java @@ -18,27 +18,36 @@ package com.tencent.cloud.polaris.config.adapter; +import java.util.Collection; import java.util.List; -import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; +import com.tencent.cloud.common.util.JacksonUtils; import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; +import com.tencent.cloud.polaris.config.spring.property.PlaceholderHelper; +import com.tencent.cloud.polaris.config.spring.property.SpringValue; +import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry; import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeListener; -import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; +import org.springframework.beans.TypeConverter; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.boot.context.event.ApplicationReadyEvent; -import org.springframework.cloud.context.refresh.ContextRefresher; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.util.CollectionUtils; /** * 1. Listen to the Polaris server configuration publishing event 2. Write the changed * configuration content to propertySource 3. Refresh the context through contextRefresher + *

Refer to the Apollo project implementation: + * + * AutoUpdateConfigChangeListener + * @author zhangzheng * * @author lepdou 2022-03-28 */ @@ -46,26 +55,30 @@ public class PolarisPropertySourceAutoRefresher implements ApplicationListener, ApplicationContextAware { private static final Logger LOGGER = LoggerFactory.getLogger(PolarisPropertySourceAutoRefresher.class); - private final PolarisConfigProperties polarisConfigProperties; - private final PolarisPropertySourceManager polarisPropertySourceManager; - private final ContextRefresher contextRefresher; + private TypeConverter typeConverter; + private final SpringValueRegistry springValueRegistry; + private ConfigurableBeanFactory beanFactory; + private final PlaceholderHelper placeholderHelper; + private final AtomicBoolean registered = new AtomicBoolean(false); - private ApplicationContext applicationContext; public PolarisPropertySourceAutoRefresher( PolarisConfigProperties polarisConfigProperties, PolarisPropertySourceManager polarisPropertySourceManager, - ContextRefresher contextRefresher) { + SpringValueRegistry springValueRegistry, + PlaceholderHelper placeholderHelper) { this.polarisConfigProperties = polarisConfigProperties; this.polarisPropertySourceManager = polarisPropertySourceManager; - this.contextRefresher = contextRefresher; + this.springValueRegistry = springValueRegistry; + this.placeholderHelper = placeholderHelper; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; + this.beanFactory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory(); + this.typeConverter = this.beanFactory.getTypeConverter(); } @Override @@ -98,28 +111,66 @@ public class PolarisPropertySourceAutoRefresher polarisPropertySource.getGroup(), polarisPropertySource.getFileName()); - Map source = polarisPropertySource.getSource(); - for (String changedKey : configKVFileChangeEvent.changedKeys()) { - ConfigPropertyChangeInfo configPropertyChangeInfo = configKVFileChangeEvent - .getChangeInfo(changedKey); - - LOGGER.info("[SCT Config] changed property = {}", configPropertyChangeInfo); - - switch (configPropertyChangeInfo.getChangeType()) { - case MODIFIED: - case ADDED: - source.put(changedKey, configPropertyChangeInfo.getNewValue()); - break; - case DELETED: - source.remove(changedKey); - break; + // 1. check whether the changed key is relevant + Collection targetValues = springValueRegistry.get(beanFactory, changedKey); + if (targetValues == null || targetValues.isEmpty()) { + continue; + } + // 2. update the value + for (SpringValue val : targetValues) { + updateSpringValue(val); } } - // rebuild beans with @RefreshScope annotation - contextRefresher.refresh(); }); } } + + + private void updateSpringValue(SpringValue springValue) { + try { + Object value = resolvePropertyValue(springValue); + springValue.update(value); + + LOGGER.info("Auto update polaris changed value successfully, new value: {}, {}", value, + springValue); + } + catch (Throwable ex) { + LOGGER.error("Auto update polaris changed value failed, {}", springValue.toString(), ex); + } + } + + + /** + * Logic transplanted from DefaultListableBeanFactory. + * + * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency(org.springframework.beans.factory.config.DependencyDescriptor, + * java.lang.String, java.util.Set, org.springframework.beans.TypeConverter) + */ + private Object resolvePropertyValue(SpringValue springValue) { + // value will never be null + Object value = placeholderHelper + .resolvePropertyValue(beanFactory, springValue.getBeanName(), springValue.getPlaceholder()); + + if (springValue.isJson()) { + value = parseJsonValue((String) value, springValue.getTargetType()); + } + else { + value = springValue.isField() ? this.typeConverter.convertIfNecessary(value, springValue.getTargetType(), springValue.getField()) : + this.typeConverter.convertIfNecessary(value, springValue.getTargetType(), + springValue.getMethodParameter()); + } + return value; + } + + private Object parseJsonValue(String json, Class targetType) { + try { + return JacksonUtils.json2JavaBean(json, targetType); + } + catch (Throwable ex) { + LOGGER.error("Parsing json '{}' to type {} failed!", json, targetType, ex); + throw ex; + } + } } diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/exceptions/PolarisConfigException.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/exceptions/PolarisConfigException.java new file mode 100644 index 000000000..10312a57f --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/exceptions/PolarisConfigException.java @@ -0,0 +1,34 @@ +/* + * 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.exceptions; + +/** + *@author : wh + *@date : 2022/6/28 09:31 + *@description: + */ +public class PolarisConfigException extends RuntimeException { + public PolarisConfigException(String message) { + super(message); + } + + public PolarisConfigException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/AbstractPolarisProcessor.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/AbstractPolarisProcessor.java new file mode 100644 index 000000000..ac81f6b96 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/AbstractPolarisProcessor.java @@ -0,0 +1,102 @@ +/* + * 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.spring.annotation; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.LinkedList; +import java.util.List; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.core.Ordered; +import org.springframework.core.PriorityOrdered; +import org.springframework.util.ReflectionUtils; + +/** + *@author : wh + *@date : 2022/6/28 09:18 + *@description: + */ +public abstract class AbstractPolarisProcessor implements BeanPostProcessor, PriorityOrdered { + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + Class clazz = bean.getClass(); + for (Field field : findAllField(clazz)) { + processField(bean, beanName, field); + } + for (Method method : findAllMethod(clazz)) { + processMethod(bean, beanName, method); + } + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + + /** + * subclass should implement this method to process field. + * @param bean bean + * @param beanName beanName + * @param field field + */ + protected abstract void processField(Object bean, String beanName, Field field); + + /** + * subclass should implement this method to process method. + * @param bean bean + * @param beanName beanName + * @param method method + */ + protected abstract void processMethod(Object bean, String beanName, Method method); + + + @Override + public int getOrder() { + //make it as late as possible + return Ordered.LOWEST_PRECEDENCE; + } + + private List findAllField(Class clazz) { + final List res = new LinkedList<>(); + ReflectionUtils.doWithFields(clazz, new ReflectionUtils.FieldCallback() { + @Override + public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { + res.add(field); + } + }); + return res; + } + + private List findAllMethod(Class clazz) { + final List res = new LinkedList<>(); + ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() { + @Override + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + res.add(method); + } + }); + return res; + } +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/SpringValueProcessor.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/SpringValueProcessor.java new file mode 100644 index 000000000..c17a21efc --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/SpringValueProcessor.java @@ -0,0 +1,166 @@ +package com.tencent.cloud.polaris.config.spring.annotation; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Set; + +import com.google.common.collect.LinkedListMultimap; +import com.google.common.collect.Multimap; +import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; +import com.tencent.cloud.polaris.config.spring.property.PlaceholderHelper; +import com.tencent.cloud.polaris.config.spring.property.SpringValue; +import com.tencent.cloud.polaris.config.spring.property.SpringValueDefinition; +import com.tencent.cloud.polaris.config.spring.property.SpringValueDefinitionProcessor; +import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.Bean; + +/** + *@author : wh + *@date : 2022/6/28 09:19 + *@description: + */ +public class SpringValueProcessor extends AbstractPolarisProcessor implements BeanFactoryPostProcessor, BeanFactoryAware { + + private static final Logger logger = LoggerFactory.getLogger(SpringValueProcessor.class); + + private final PlaceholderHelper placeholderHelper; + private final SpringValueRegistry springValueRegistry; + + private final PolarisConfigProperties polarisConfigProperties; + + private BeanFactory beanFactory; + private Multimap beanName2SpringValueDefinitions; + + public SpringValueProcessor(PlaceholderHelper placeholderHelper, SpringValueRegistry springValueRegistry, PolarisConfigProperties polarisConfigProperties) { + this.placeholderHelper = placeholderHelper; + this.springValueRegistry = springValueRegistry; + beanName2SpringValueDefinitions = LinkedListMultimap.create(); + this.polarisConfigProperties = polarisConfigProperties; + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) + throws BeansException { + if (polarisConfigProperties.isAutoRefresh() && beanFactory instanceof BeanDefinitionRegistry) { + beanName2SpringValueDefinitions = SpringValueDefinitionProcessor + .getBeanName2SpringValueDefinitions((BeanDefinitionRegistry) beanFactory); + } + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + if (polarisConfigProperties.isAutoRefresh()) { + super.postProcessBeforeInitialization(bean, beanName); + processBeanPropertyValues(bean, beanName); + } + return bean; + } + + + @Override + protected void processField(Object bean, String beanName, Field field) { + // register @Value on field + Value value = field.getAnnotation(Value.class); + if (value == null) { + return; + } + + doRegister(bean, beanName, field, value); + } + + @Override + protected void processMethod(Object bean, String beanName, Method method) { + //register @Value on method + Value value = method.getAnnotation(Value.class); + if (value == null) { + return; + } + //skip Configuration bean methods + if (method.getAnnotation(Bean.class) != null) { + return; + } + if (method.getParameterTypes().length != 1) { + logger.error("Ignore @Value setter {}.{}, expecting 1 parameter, actual {} parameters", + bean.getClass().getName(), method.getName(), method.getParameterTypes().length); + return; + } + + doRegister(bean, beanName, method, value); + } + + private void doRegister(Object bean, String beanName, Member member, Value value) { + Set keys = placeholderHelper.extractPlaceholderKeys(value.value()); + if (keys.isEmpty()) { + return; + } + + for (String key : keys) { + SpringValue springValue; + if (member instanceof Field) { + Field field = (Field) member; + springValue = new SpringValue(key, value.value(), bean, beanName, field, false); + } + else if (member instanceof Method) { + Method method = (Method) member; + springValue = new SpringValue(key, value.value(), bean, beanName, method, false); + } + else { + logger.error("polaris @Value annotation currently only support to be used on methods and fields, " + + "but is used on {}", member.getClass()); + return; + } + springValueRegistry.register(beanFactory, key, springValue); + logger.info("Monitoring {}", springValue); + } + } + + private void processBeanPropertyValues(Object bean, String beanName) { + Collection propertySpringValues = beanName2SpringValueDefinitions + .get(beanName); + if (propertySpringValues == null || propertySpringValues.isEmpty()) { + return; + } + + for (SpringValueDefinition definition : propertySpringValues) { + try { + PropertyDescriptor pd = BeanUtils + .getPropertyDescriptor(bean.getClass(), definition.getPropertyName()); + Method method = pd.getWriteMethod(); + if (method == null) { + continue; + } + SpringValue springValue = new SpringValue(definition.getKey(), definition.getPlaceholder(), + bean, beanName, method, false); + springValueRegistry.register(beanFactory, definition.getKey(), springValue); + logger.debug("Monitoring {}", springValue); + } + catch (Throwable ex) { + logger.error("Failed to enable auto update feature for {}.{}", bean.getClass(), + definition.getPropertyName()); + } + } + + // clear + beanName2SpringValueDefinitions.removeAll(beanName); + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/PlaceholderHelper.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/PlaceholderHelper.java new file mode 100644 index 000000000..a272d2bfe --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/PlaceholderHelper.java @@ -0,0 +1,193 @@ +/* + * 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.spring.property; + +import java.util.Set; +import java.util.Stack; + +import com.google.common.base.Strings; +import com.google.common.collect.Sets; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanExpressionContext; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.config.Scope; +import org.springframework.util.StringUtils; + +/** + *@author : wh + *@date : 2022/6/28 09:24 + *@description: + */ +public class PlaceholderHelper { + + private static final String PLACEHOLDER_PREFIX = "${"; + private static final String PLACEHOLDER_SUFFIX = "}"; + private static final String VALUE_SEPARATOR = ":"; + private static final String SIMPLE_PLACEHOLDER_PREFIX = "{"; + private static final String EXPRESSION_PREFIX = "#{"; + private static final String EXPRESSION_SUFFIX = "}"; + + + /** + * Resolve placeholder property values, e.g. + * @param beanFactory beanFactory + * @param beanName beanName + * @param placeholder placeholder + * @return "${somePropertyValue}" -> "the actual property value" + */ + public Object resolvePropertyValue(ConfigurableBeanFactory beanFactory, String beanName, String placeholder) { + // resolve string value + String strVal = beanFactory.resolveEmbeddedValue(placeholder); + + BeanDefinition bd = (beanFactory.containsBean(beanName) ? beanFactory + .getMergedBeanDefinition(beanName) : null); + + // resolve expressions like "#{systemProperties.myProp}" + return evaluateBeanDefinitionString(beanFactory, strVal, bd); + } + + private Object evaluateBeanDefinitionString(ConfigurableBeanFactory beanFactory, String value, + BeanDefinition beanDefinition) { + if (beanFactory.getBeanExpressionResolver() == null) { + return value; + } + Scope scope = (beanDefinition != null ? beanFactory + .getRegisteredScope(beanDefinition.getScope()) : null); + return beanFactory.getBeanExpressionResolver() + .evaluate(value, new BeanExpressionContext(beanFactory, scope)); + } + + /** + * Extract keys from placeholder, e.g. + * @param propertyString propertyString + * @return + *

+ */ + public Set extractPlaceholderKeys(String propertyString) { + Set placeholderKeys = Sets.newHashSet(); + + if (Strings.isNullOrEmpty(propertyString) || (!isNormalizedPlaceholder(propertyString) && !isExpressionWithPlaceholder(propertyString))) { + return placeholderKeys; + } + + Stack stack = new Stack<>(); + stack.push(propertyString); + + while (!stack.isEmpty()) { + String strVal = stack.pop(); + int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX); + if (startIndex == -1) { + placeholderKeys.add(strVal); + continue; + } + int endIndex = findPlaceholderEndIndex(strVal, startIndex); + if (endIndex == -1) { + // invalid placeholder? + continue; + } + + String placeholderCandidate = strVal.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex); + + // ${some.key:other.key} + if (placeholderCandidate.startsWith(PLACEHOLDER_PREFIX)) { + stack.push(placeholderCandidate); + } + else { + // some.key:${some.other.key:100} + int separatorIndex = placeholderCandidate.indexOf(VALUE_SEPARATOR); + + if (separatorIndex == -1) { + stack.push(placeholderCandidate); + } + else { + stack.push(placeholderCandidate.substring(0, separatorIndex)); + String defaultValuePart = + normalizeToPlaceholder(placeholderCandidate.substring(separatorIndex + VALUE_SEPARATOR.length())); + if (!Strings.isNullOrEmpty(defaultValuePart)) { + stack.push(defaultValuePart); + } + } + } + + // has remaining part, e.g. ${a}.${b} + if (endIndex + PLACEHOLDER_SUFFIX.length() < strVal.length() - 1) { + String remainingPart = normalizeToPlaceholder(strVal.substring(endIndex + PLACEHOLDER_SUFFIX.length())); + if (!Strings.isNullOrEmpty(remainingPart)) { + stack.push(remainingPart); + } + } + } + + return placeholderKeys; + } + + private boolean isNormalizedPlaceholder(String propertyString) { + return propertyString.startsWith(PLACEHOLDER_PREFIX) && propertyString.contains(PLACEHOLDER_SUFFIX); + } + + private boolean isExpressionWithPlaceholder(String propertyString) { + return propertyString.startsWith(EXPRESSION_PREFIX) && propertyString.contains(EXPRESSION_SUFFIX) + && propertyString.contains(PLACEHOLDER_PREFIX) && propertyString.contains(PLACEHOLDER_SUFFIX); + } + + private String normalizeToPlaceholder(String strVal) { + int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX); + if (startIndex == -1) { + return null; + } + int endIndex = strVal.lastIndexOf(PLACEHOLDER_SUFFIX); + if (endIndex == -1) { + return null; + } + + return strVal.substring(startIndex, endIndex + PLACEHOLDER_SUFFIX.length()); + } + + private int findPlaceholderEndIndex(CharSequence buf, int startIndex) { + int index = startIndex + PLACEHOLDER_PREFIX.length(); + int withinNestedPlaceholder = 0; + while (index < buf.length()) { + if (StringUtils.substringMatch(buf, index, PLACEHOLDER_SUFFIX)) { + if (withinNestedPlaceholder > 0) { + withinNestedPlaceholder--; + index = index + PLACEHOLDER_SUFFIX.length(); + } + else { + return index; + } + } + else if (StringUtils.substringMatch(buf, index, SIMPLE_PLACEHOLDER_PREFIX)) { + withinNestedPlaceholder++; + index = index + SIMPLE_PLACEHOLDER_PREFIX.length(); + } + else { + index++; + } + } + return -1; + } +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValue.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValue.java new file mode 100644 index 000000000..05eb38622 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValue.java @@ -0,0 +1,152 @@ +/* + * 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.spring.property; + +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Type; + +import org.springframework.core.MethodParameter; + +/** + *@author : wh + *@date : 2022/6/28 09:25 + *@description: + */ +public class SpringValue { + + private MethodParameter methodParameter; + private Field field; + private final WeakReference beanRef; + private final String beanName; + private final String key; + private final String placeholder; + private final Class targetType; + private Type genericType; + private final boolean isJson; + + public SpringValue(String key, String placeholder, Object bean, String beanName, Field field, boolean isJson) { + this.beanRef = new WeakReference<>(bean); + this.beanName = beanName; + this.field = field; + this.key = key; + this.placeholder = placeholder; + this.targetType = field.getType(); + this.isJson = isJson; + if (isJson) { + this.genericType = field.getGenericType(); + } + } + + public SpringValue(String key, String placeholder, Object bean, String beanName, Method method, boolean isJson) { + this.beanRef = new WeakReference<>(bean); + this.beanName = beanName; + this.methodParameter = new MethodParameter(method, 0); + this.key = key; + this.placeholder = placeholder; + Class[] paramTps = method.getParameterTypes(); + this.targetType = paramTps[0]; + this.isJson = isJson; + if (isJson) { + this.genericType = method.getGenericParameterTypes()[0]; + } + } + + public void update(Object newVal) throws IllegalAccessException, InvocationTargetException { + if (isField()) { + injectField(newVal); + } + else { + injectMethod(newVal); + } + } + + private void injectField(Object newVal) throws IllegalAccessException { + Object bean = beanRef.get(); + if (bean == null) { + return; + } + boolean accessible = field.isAccessible(); + field.setAccessible(true); + field.set(bean, newVal); + field.setAccessible(accessible); + } + + private void injectMethod(Object newVal) + throws InvocationTargetException, IllegalAccessException { + Object bean = beanRef.get(); + if (bean == null) { + return; + } + methodParameter.getMethod().invoke(bean, newVal); + } + + public String getBeanName() { + return beanName; + } + + public Class getTargetType() { + return targetType; + } + + public String getPlaceholder() { + return this.placeholder; + } + + public MethodParameter getMethodParameter() { + return methodParameter; + } + + public boolean isField() { + return this.field != null; + } + + public Field getField() { + return field; + } + + public Type getGenericType() { + return genericType; + } + + public boolean isJson() { + return isJson; + } + + boolean isTargetBeanValid() { + return beanRef.get() != null; + } + + @Override + public String toString() { + Object bean = beanRef.get(); + if (bean == null) { + return ""; + } + if (isField()) { + return String + .format("key: %s, beanName: %s, field: %s.%s", key, beanName, bean.getClass() + .getName(), field.getName()); + } + return String.format("key: %s, beanName: %s, method: %s.%s", key, beanName, bean.getClass().getName(), + methodParameter.getMethod().getName()); + } +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueDefinition.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueDefinition.java new file mode 100644 index 000000000..7de28a528 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueDefinition.java @@ -0,0 +1,49 @@ +/* + * 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.spring.property; + +/** + *@author : wh + *@date : 2022/6/28 09:28 + *@description: + */ +public class SpringValueDefinition { + + private final String key; + private final String placeholder; + private final String propertyName; + + public SpringValueDefinition(String key, String placeholder, String propertyName) { + this.key = key; + this.placeholder = placeholder; + this.propertyName = propertyName; + } + + public String getKey() { + return key; + } + + public String getPlaceholder() { + return placeholder; + } + + public String getPropertyName() { + return propertyName; + } +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueDefinitionProcessor.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueDefinitionProcessor.java new file mode 100644 index 000000000..6eaff0293 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueDefinitionProcessor.java @@ -0,0 +1,114 @@ +/* + * 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.spring.property; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.common.collect.LinkedListMultimap; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; +import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; + +import org.springframework.beans.BeansException; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.PropertyValue; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.TypedStringValue; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; + +/** + *@author : wh + *@date : 2022/6/28 09:30 + *@description: + */ +public class SpringValueDefinitionProcessor implements BeanDefinitionRegistryPostProcessor { + private static final Map> beanName2SpringValueDefinitions = + Maps.newConcurrentMap(); + private static final Set PROPERTY_VALUES_PROCESSED_BEAN_FACTORIES = Sets.newConcurrentHashSet(); + private final PlaceholderHelper placeholderHelper; + + private final PolarisConfigProperties polarisConfigProperties; + + public SpringValueDefinitionProcessor(PlaceholderHelper placeholderHelper, PolarisConfigProperties polarisConfigProperties) { + this.placeholderHelper = placeholderHelper; + this.polarisConfigProperties = polarisConfigProperties; + } + + @Override + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { + if (polarisConfigProperties.isAutoRefresh()) { + processPropertyValues(registry); + } + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + + } + + public static Multimap getBeanName2SpringValueDefinitions(BeanDefinitionRegistry registry) { + Multimap springValueDefinitions = beanName2SpringValueDefinitions.get(registry); + if (springValueDefinitions == null) { + springValueDefinitions = LinkedListMultimap.create(); + } + + return springValueDefinitions; + } + + private void processPropertyValues(BeanDefinitionRegistry beanRegistry) { + if (!PROPERTY_VALUES_PROCESSED_BEAN_FACTORIES.add(beanRegistry)) { + // already initialized + return; + } + + if (!beanName2SpringValueDefinitions.containsKey(beanRegistry)) { + beanName2SpringValueDefinitions.put(beanRegistry, LinkedListMultimap.create()); + } + + Multimap springValueDefinitions = beanName2SpringValueDefinitions.get(beanRegistry); + + String[] beanNames = beanRegistry.getBeanDefinitionNames(); + for (String beanName : beanNames) { + BeanDefinition beanDefinition = beanRegistry.getBeanDefinition(beanName); + MutablePropertyValues mutablePropertyValues = beanDefinition.getPropertyValues(); + List propertyValues = mutablePropertyValues.getPropertyValueList(); + for (PropertyValue propertyValue : propertyValues) { + Object value = propertyValue.getValue(); + if (!(value instanceof TypedStringValue)) { + continue; + } + String placeholder = ((TypedStringValue) value).getValue(); + Set keys = placeholderHelper.extractPlaceholderKeys(placeholder); + + if (keys.isEmpty()) { + continue; + } + + for (String key : keys) { + springValueDefinitions.put(beanName, new SpringValueDefinition(key, placeholder, propertyValue.getName())); + } + } + } + } +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueRegistry.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueRegistry.java new file mode 100644 index 000000000..e8dd12909 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueRegistry.java @@ -0,0 +1,103 @@ +/* + * 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.spring.property; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.google.common.collect.LinkedListMultimap; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; +import com.tencent.polaris.client.util.NamedThreadFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.beans.factory.BeanFactory; + +/** + *@author : wh + *@date : 2022/6/28 09:24 + *@description: + */ +public class SpringValueRegistry { + private static final Logger logger = LoggerFactory.getLogger(SpringValueRegistry.class); + + private static final long CLEAN_INTERVAL_IN_SECONDS = 5; + private final Map> registry = Maps.newConcurrentMap(); + private final AtomicBoolean initialized = new AtomicBoolean(false); + private final Object LOCK = new Object(); + + public void register(BeanFactory beanFactory, String key, SpringValue springValue) { + if (!registry.containsKey(beanFactory)) { + synchronized (LOCK) { + if (!registry.containsKey(beanFactory)) { + registry.put(beanFactory, Multimaps.synchronizedListMultimap(LinkedListMultimap.create())); + } + } + } + + registry.get(beanFactory).put(key, springValue); + + // lazy initialize + if (initialized.compareAndSet(false, true)) { + initialize(); + } + } + + public Collection get(BeanFactory beanFactory, String key) { + Multimap beanFactorySpringValues = registry.get(beanFactory); + if (beanFactorySpringValues == null) { + return null; + } + return beanFactorySpringValues.get(key); + } + + private void initialize() { + Executors.newSingleThreadScheduledExecutor( + new NamedThreadFactory("polaris-spring-value-registry")).scheduleAtFixedRate( + () -> { + try { + scanAndClean(); + } + catch (Throwable ex) { + logger.error(ex.getMessage(), ex); + } + }, CLEAN_INTERVAL_IN_SECONDS, CLEAN_INTERVAL_IN_SECONDS, TimeUnit.SECONDS); + } + + private void scanAndClean() { + Iterator> iterator = registry.values().iterator(); + while (!Thread.currentThread().isInterrupted() && iterator.hasNext()) { + Multimap springValues = iterator.next(); + Iterator> springValueIterator = springValues.entries().iterator(); + while (springValueIterator.hasNext()) { + Map.Entry springValue = springValueIterator.next(); + if (!springValue.getValue().isTargetBeanValid()) { + // clear unused spring values + springValueIterator.remove(); + } + } + } + } +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/MockedConfigChange.java b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/MockedConfigChange.java new file mode 100644 index 000000000..230016de9 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/MockedConfigChange.java @@ -0,0 +1,19 @@ +package com.tencent.cloud.polaris.config.adapter; + +/** + *@author : wh + *@date : 2022/6/29 10:01 + *@description: + */ +public class MockedConfigChange { + + private String k1; + + String getK1() { + return k1; + } + + void setK1(String k1) { + this.k1 = k1; + } +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertiesSourceAutoRefresherTest.java b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertiesSourceAutoRefresherTest.java index 779ddfb47..8edd1e05a 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertiesSourceAutoRefresherTest.java +++ b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertiesSourceAutoRefresherTest.java @@ -18,11 +18,17 @@ package com.tencent.cloud.polaris.config.adapter; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.Map; import com.google.common.collect.Lists; import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; +import com.tencent.cloud.polaris.config.spring.property.PlaceholderHelper; +import com.tencent.cloud.polaris.config.spring.property.SpringValue; +import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry; import com.tencent.polaris.configuration.api.core.ChangeType; import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeEvent; import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo; @@ -32,9 +38,12 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.cloud.context.refresh.ContextRefresher; +import org.springframework.beans.TypeConverter; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ConfigurableApplicationContext; -import static org.mockito.Mockito.verify; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** @@ -53,13 +62,32 @@ public class PolarisPropertiesSourceAutoRefresherTest { @Mock private PolarisPropertySourceManager polarisPropertySourceManager; @Mock - private ContextRefresher contextRefresher; + private SpringValueRegistry springValueRegistry; + + @Mock + private PlaceholderHelper placeholderHelper; @Test - public void testConfigFileChanged() { + public void testConfigFileChanged() throws Exception { PolarisPropertySourceAutoRefresher refresher = new PolarisPropertySourceAutoRefresher(polarisConfigProperties, - polarisPropertySourceManager, contextRefresher); - + polarisPropertySourceManager, springValueRegistry, placeholderHelper); + + ConfigurableApplicationContext applicationContext = mock(ConfigurableApplicationContext.class); + ConfigurableListableBeanFactory beanFactory = mock(ConfigurableListableBeanFactory.class); + TypeConverter typeConverter = mock(TypeConverter.class); + when(beanFactory.getTypeConverter()).thenReturn(typeConverter); + when(applicationContext.getBeanFactory()).thenReturn(beanFactory); + refresher.setApplicationContext(applicationContext); + when(typeConverter.convertIfNecessary(any(), any(), (Field) any())).thenReturn("v11"); + Collection springValues = new ArrayList<>(); + MockedConfigChange mockedConfigChange = new MockedConfigChange(); + mockedConfigChange.setK1("v1"); + Field field = mockedConfigChange.getClass().getDeclaredField("k1"); + SpringValue springValue = new SpringValue("v1", "placeholder", mockedConfigChange, "mockedConfigChange", field, false); + + springValues.add(springValue); + + when(springValueRegistry.get(any(), any())).thenReturn(springValues); when(polarisConfigProperties.isAutoRefresh()).thenReturn(true); Map content = new HashMap<>(); @@ -85,37 +113,7 @@ public class PolarisPropertiesSourceAutoRefresherTest { file.fireChangeListener(event); - Assert.assertEquals("v11", polarisPropertySource.getProperty("k1")); - Assert.assertEquals("v3", polarisPropertySource.getProperty("k3")); - Assert.assertNull(polarisPropertySource.getProperty("k2")); - Assert.assertEquals("v4", polarisPropertySource.getProperty("k4")); - verify(contextRefresher).refresh(); - } - - @Test - public void testNewConfigFile() { - PolarisPropertySourceAutoRefresher refresher = new PolarisPropertySourceAutoRefresher(polarisConfigProperties, - polarisPropertySourceManager, contextRefresher); - - when(polarisConfigProperties.isAutoRefresh()).thenReturn(true); - - Map emptyContent = new HashMap<>(); - MockedConfigKVFile file = new MockedConfigKVFile(emptyContent); - PolarisPropertySource polarisPropertySource = new PolarisPropertySource(testNamespace, testServiceName, testFileName, - file, emptyContent); - - when(polarisPropertySourceManager.getAllPropertySources()).thenReturn(Lists.newArrayList(polarisPropertySource)); - - ConfigPropertyChangeInfo changeInfo = new ConfigPropertyChangeInfo("k1", null, "v1", ChangeType.ADDED); - Map changeInfos = new HashMap<>(); - changeInfos.put("k1", changeInfo); - - ConfigKVFileChangeEvent event = new ConfigKVFileChangeEvent(changeInfos); - refresher.onApplicationEvent(null); - - file.fireChangeListener(event); + Assert.assertEquals("v11", mockedConfigChange.getK1()); - Assert.assertEquals("v1", polarisPropertySource.getProperty("k1")); - verify(contextRefresher).refresh(); } } diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/JacksonUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/JacksonUtils.java index 4ed4d2ea1..088b73bde 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/JacksonUtils.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/JacksonUtils.java @@ -84,4 +84,15 @@ public final class JacksonUtils { throw new RuntimeException("Json to map failed.", e); } } + + public static T json2JavaBean(String content, Class valueType) { + try { + return OM.readValue(content, valueType); + } + catch (Exception e) { + LOG.error("json {} to class {} failed. ", content, valueType, e); + throw new RuntimeException("json to class failed.", e); + } + } + }