-
-
Notifications
You must be signed in to change notification settings - Fork 10.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Spring 中 ConfigurationProperties 配置热更新问题 #5108
Comments
Based on the information you provided and the context from the Apollo repository, it seems you're looking for a way to dynamically register There is an open issue in the Apollo repository that discusses a similar requirement: @ApolloConfigChangeListener interestedKeyPrefixes When can dynamic parameterization be supported. However, as of the last update on January 24, 2024, there is no direct solution provided in the discussion comments. A contributor, @huxleyliau, has shown interest in implementing dynamic parameterization for In the meantime, you can use a workaround to achieve your goal. Here's a conceptual approach:
Here's a code snippet that demonstrates how to achieve this: import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@Component
public class ApolloConfigChangeListenerRegistrar implements BeanPostProcessor, ApplicationContextAware {
private ApplicationContext applicationContext;
private final Set<String> registeredPrefixes = new HashSet<>();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
ConfigurationProperties annotation = applicationContext.findAnnotationOnBean(beanName, ConfigurationProperties.class);
if (annotation != null) {
String prefix = annotation.prefix();
// Ensure that we only register a listener once per prefix
if (!registeredPrefixes.contains(prefix)) {
registerApolloConfigChangeListener(prefix);
registeredPrefixes.add(prefix);
}
}
return bean;
}
private void registerApolloConfigChangeListener(String prefix) {
try {
// Assuming there's a method to add listeners dynamically, otherwise, you'll need to adapt this part
Method method = this.getClass().getMethod("onChange", String.class);
ApolloConfigChangeListener listener = method.getAnnotation(ApolloConfigChangeListener.class);
if (listener == null) {
listener = new ApolloConfigChangeListener() {
@Override
public Class<? extends java.lang.annotation.Annotation> annotationType() {
return ApolloConfigChangeListener.class;
}
@Override
public String[] value() {
return new String[0]; // default value
}
@Override
public String[] interestedKeyPrefixes() {
return new String[]{prefix}; // set the interested prefix
}
};
// Here you should add the listener to Apollo client. This step depends on how your Apollo client is configured.
// This is a placeholder to show where the listener should be registered.
// addApolloConfigChangeListener(listener);
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
// Placeholder for the method that would be called on configuration changes
public void onChange(String changedKey) {
// Logic to handle configuration change
}
} Please note that this code is a conceptual demonstration. The actual implementation of dynamically registering For more information on how Apollo handles the registration of I hope this helps! If you have further questions or need more clarification, feel free to ask. Sources
|
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@RefreshScope
public @interface ApolloRefreshScope {
/**
* @see ApolloConfigChangeListener#value()
*/
String[] value() default {ConfigConsts.NAMESPACE_APPLICATION};
/**
* @see ApolloConfigChangeListener#interestedKeys()
*/
String[] interestedKeys() default {};
/**
* @see ApolloConfigChangeListener#interestedKeyPrefixes()
*/
String[] interestedKeyPrefixes() default {};
} import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.ConfigService;
import com.google.common.base.Splitter;
import com.google.common.collect.Sets;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Set;
@Component
@RequiredArgsConstructor
@Slf4j
public class ApolloRefreshScopeProcessor implements BeanPostProcessor {
private static final String NAMESPACE_DELIMITER = ",";
private static final Splitter NAMESPACE_SPLITTER = Splitter.on(NAMESPACE_DELIMITER)
.omitEmptyStrings().trimResults();
private final Environment environment;
private final org.springframework.cloud.context.scope.refresh.RefreshScope refreshScope;
@Override
public Object postProcessBeforeInitialization(@NonNull Object bean, @NonNull String beanName) throws BeansException {
Class<?> beanClass = bean.getClass();
ApolloRefreshScope annotation = AnnotationUtils
.findAnnotation(beanClass, ApolloRefreshScope.class);
if (annotation != null) {
String[] namespaces = annotation.value();
String[] annotatedInterestedKeys = annotation.interestedKeys();
String[] annotatedInterestedKeyPrefixes = annotation.interestedKeyPrefixes();
if (annotatedInterestedKeyPrefixes.length == 0) {
annotatedInterestedKeyPrefixes = findKeyPrefixesByConfigurationProperties(beanClass);
}
Set<String> interestedKeys =
annotatedInterestedKeys.length > 0 ? Sets.newHashSet(annotatedInterestedKeys) : null;
Set<String> interestedKeyPrefixes =
annotatedInterestedKeyPrefixes.length > 0 ? Sets.newHashSet(annotatedInterestedKeyPrefixes)
: null;
Set<String> resolvedNamespaces = processResolveNamespaceValue(namespaces);
for (String namespace : resolvedNamespaces) {
Config config = ConfigService.getConfig(namespace);
if (interestedKeys == null && interestedKeyPrefixes == null) {
config.addChangeListener(createListener(bean, beanName, beanClass));
} else {
config.addChangeListener(createListener(bean, beanName, beanClass), interestedKeys, interestedKeyPrefixes);
}
}
}
return bean;
}
private String[] findKeyPrefixesByConfigurationProperties(Class<?> beanClass) {
ConfigurationProperties annotation = AnnotationUtils
.findAnnotation(beanClass, ConfigurationProperties.class);
if (annotation != null) {
return new String[]{annotation.prefix()};
}
return new String[0];
}
private ConfigChangeListener createListener(final Object bean, final String beanName, final Class<?> beanClass) {
return changeEvent -> {
if (bean instanceof ConfigChangeListener) {
((ConfigChangeListener) bean).onChange(changeEvent);
}
refreshScope.refresh(beanName);
};
}
private Set<String> processResolveNamespaceValue(String[] namespaces) {
Set<String> resolvedNamespaces = new HashSet<>();
for (String namespace : namespaces) {
final String resolvedNamespace = this.environment.resolveRequiredPlaceholders(namespace);
if (resolvedNamespace.contains(NAMESPACE_DELIMITER)) {
resolvedNamespaces.addAll(NAMESPACE_SPLITTER.splitToList(resolvedNamespace));
} else {
resolvedNamespaces.add(resolvedNamespace);
}
}
return resolvedNamespaces;
}
} |
This issue has been automatically marked as stale because it has not had activity in the last 30 days. It will be closed in 7 days unless it is tagged "help wanted" or other activity occurs. Thank you for your contributions. |
使用applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys())); 和 refreshScope 失效不起作用
pom.xml是
changeListener是
|
需要添加 @RefreshScope 注解 |
能否提供demo代码,我想本地看看。 @shawncny |
就两个java文件,如果你要的话我发你 |
This issue has been automatically marked as stale because it has not had activity in the last 30 days. It will be closed in 7 days unless it is tagged "help wanted" or other activity occurs. Thank you for your contributions. |
This issue has been automatically closed because it has not had activity in the last 7 days. If this issue is still valid, please ping a maintainer and ask them to label it as "help wanted". Thank you for your contributions. |
你的特性请求和某个问题有关吗?请描述
在使用 @ConfigurationProperties 配置类如果需要热更新的话,需要为每个配置类写一个 ApolloConfigChangeListener 来实现热更新,这样配置类多的时候处理起来很重复
清晰简洁地描述一下你希望的解决方案
能不能从 spring 容器内得到所有的 @ConfigurationProperties 配置 Bean ,然后解析得到注解里面的 prefix 属性,传递给 @ApolloConfigChangeListener 注解的 interestedKeyPrefixes 属性,这样就不需要使用者为每一个配置 Bean 去创建 changeListener
The text was updated successfully, but these errors were encountered: