SpringCloud中使用Apollo实现动态刷新
普通字段
在需要刷新的字段上使用@value
注解即可,例如:
@Value("${test.user.name}")
private String name;
@Value("${test.user.age}")
private Integer age;
@Value("${test.user.sex}")
private Boolean sex;
bean使用@ConfigurationProperties动态刷新
bean使用@ConfigurationProperties注解目前还不支持自动刷新,得编写一定的代码实现刷新。目前官方提供2种刷新方案:
- 基于RefreshScope实现刷新
- 基于EnvironmentChangeEvent实现刷新
方法一:基于RefreshScope实现刷新
- 确保项目中已引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
</dependency>
- 实体类上使用
@RefreshScope
注解
@ConfigurationProperties("test.user")
@Data
@Component
@RefreshScope
public class TestUserProperties implements Serializable {
private Integer age;
private String name;
private Boolean sex;
}
在namespace=config的命名空间中定义配置:
test.user.name = zhangsan
test.user.age = 10
test.user.sex = 1
- 利用RefreshScope搭配@ApolloConfigChangeListener监听实现bean的动态刷新
@Component
public class ApolloDynamicConfigPropertiesRefresh {
@Resource
RefreshScope refreshScope;
@ApolloConfigChangeListener(value="config")
private void refresh(ConfigChangeEvent changeEvent){
refreshScope.refresh("testUserProperties");
PrintChangeKeyUtils.printChange(changeEvent);
}
}
@ApolloConfigChangeListener(value="config")
表示监听namespace=config的配置文件的变化refreshScope.refresh("testUserProperties");
表示如果触发监听事件,则刷新名为testUserProperties
的bean;PrintChangeKeyUtils.printChange(changeEvent);
表示打印发送变化的熟悉(可选),PrintChangeKeyUtils
定义为:
import com.ctrip.framework.apollo.model.ConfigChange;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import org.springframework.util.CollectionUtils;
import java.util.Set;
public class PrintChangeKeyUtils {
public static void printChange(ConfigChangeEvent changeEvent) {
Set<String> changeKeys = changeEvent.changedKeys();
if (!CollectionUtils.isEmpty(changeKeys)) {
for (String changeKey : changeKeys) {
ConfigChange configChange = changeEvent.getChange(changeKey);
System.out.println("key:" + changeKey + ";oldValue:" + configChange.getOldValue() + ";newValue:" + configChange.getNewValue());
}
}
}
}
方法二:基于EnvironmentChangeEvent实现刷新
- 定义实体类
@ConfigurationProperties("test.user")
@Data
@Component
public class TestUserProperties implements Serializable {
private Integer age;
private String name;
private Boolean sex;
}
与方法一的差异是不使用@RefreshScope
注解
- 利用spring的事件驱动配合@ApolloConfigChangeListener监听实现bean的动态刷新:
@Component
public class ApolloDynamicConfigPropertiesRefresh implements ApplicationContextAware {
private ApplicationContext applicationContext;
RefreshScope refreshScope;
@ApolloConfigChangeListener(value="config")
private void refresh(ConfigChangeEvent changeEvent){
applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys()));
PrintChangeKeyUtils.printChange(changeEvent);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
两种方式动态刷新原理浅析:
RefreshScope
首先了解Spring中几个相关的类:
- 注解@RefreshScope(
org.springframework.cloud.context.config.annotation.RefreshScope
)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Scope("refresh")
@Documented
public @interface RefreshScope {
/**
* @see Scope#proxyMode()
* @return proxy mode
*/
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
- 注解@Scope(
org.springframework.context.annotation.Scope
)
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {
@AliasFor("scopeName")
String value() default "";
@AliasFor("value")
String scopeName() default "";
ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}
- 接口Scope(
org.springframework.beans.factory.config.Scope
)
public interface Scope {
Object get(String name, ObjectFactory<?> objectFactory);
@Nullable
Object remove(String name);
void registerDestructionCallback(String name, Runnable callback);
@Nullable
Object resolveContextualObject(String key);
@Nullable
String getConversationId();
}
- 类RefreshScope(
org.springframework.cloud.context.scope.refresh.RefreshScope
)
@ManagedResource
public class RefreshScope extends GenericScope implements ApplicationContextAware,ApplicationListener<ContextRefreshedEvent>, Ordered {
private ApplicationContext context;
private BeanDefinitionRegistry registry;
private boolean eager = true;
private int order = Ordered.LOWEST_PRECEDENCE - 100;
/**
* Creates a scope instance and gives it the default name: "refresh".
*/
public RefreshScope() {
super.setName("refresh");
}
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
public void setEager(boolean eager) {
this.eager = eager;
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
throws BeansException {
this.registry = registry;
super.postProcessBeanDefinitionRegistry(registry);
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
start(event);
}
public void start(ContextRefreshedEvent event) {
if (event.getApplicationContext() == this.context && this.eager
&& this.registry != null) {
eagerlyInitialize();
}
}
private void eagerlyInitialize() {
for (String name : this.context.getBeanDefinitionNames()) {
BeanDefinition definition = this.registry.getBeanDefinition(name);
if (this.getName().equals(definition.getScope())
&& !definition.isLazyInit()) {
Object bean = this.context.getBean(name);
if (bean != null) {
bean.getClass();
}
}
}
}
@ManagedOperation(description = "Dispose of the current instance of bean name "
+ "provided and force a refresh on next method execution.")
public boolean refresh(String name) {
if (!name.startsWith(SCOPED_TARGET_PREFIX)) {
// User wants to refresh the bean with this name but that isn't the one in the
// cache...
name = SCOPED_TARGET_PREFIX + name;
}
// Ensure lifecycle is finished if bean was disposable
if (super.destroy(name)) {
this.context.publishEvent(new RefreshScopeRefreshedEvent(name));
return true;
}
return false;
}
@ManagedOperation(description = "Dispose of the current instance of all beans "
+ "in this scope and force a refresh on next method execution.")
public void refreshAll() {
super.destroy();
this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.context = context;
}
}
带有@RefreshScope
注解后的Bean,在初始话的过程中,会通过AnnotationScopeMetadataResolver#resolveScopeMetadata
提取元数据:
@Override
public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
ScopeMetadata metadata = new ScopeMetadata();
if (definition instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(
annDef.getMetadata(), this.scopeAnnotationType);
if (attributes != null) {
metadata.setScopeName(attributes.getString("value"));
ScopedProxyMode proxyMode = attributes.getEnum("proxyMode");
if (proxyMode == ScopedProxyMode.DEFAULT) {
proxyMode = this.defaultProxyMode;
}
metadata.setScopedProxyMode(proxyMode);
}
}
return metadata;
}
可以理解为 @RefreshScope
是scopeName="refresh"的 @Scope
,其Bean的注册将通过AnnotatedBeanDefinitionReader#registerBean
完成的:
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
@Nullable BeanDefinitionCustomizer[] customizers) {
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
}
abd.setInstanceSupplier(supplier);
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
abd.setScope(scopeMetadata.getScopeName());
String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
if (qualifiers != null) {
for (Class<? extends Annotation> qualifier : qualifiers) {
if (Primary.class == qualifier) {
abd.setPrimary(true);
}
else if (Lazy.class == qualifier) {
abd.setLazyInit(true);
}
else {
abd.addQualifier(new AutowireCandidateQualifier(qualifier));
}
}
}
if (customizers != null) {
for (BeanDefinitionCustomizer customizer : customizers) {
customizer.customize(abd);
}
}
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}
测试
@RestController
@RequestMapping("/test/dynamic/config")
public class DynamicConfigTestController {
@Value("${test.user.name}")
private String name;
@Value("${test.user.age}")
private Integer age;
@Value("${test.user.sex}")
private Boolean sex;
@Resource
private TestUserProperties userProperties;
@GetMapping("/user")
public Map<String, Object> properties() {
Map<String, Object> map = new HashMap<>();
map.put("name", name);
map.put("age", age);
map.put("sex", sex);
map.put("user", userProperties.toString());
return map;
}
}