普通字段

在需要刷新的字段上使用@value注解即可,例如:

1
2
3
4
5
6
7
8
@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实现刷新

  1. 确保项目中已引入依赖
1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
</dependency>
  1. 实体类上使用@RefreshScope注解
1
2
3
4
5
6
7
8
9
@ConfigurationProperties("test.user")
@Data
@Component
@RefreshScope
public class TestUserProperties implements Serializable {
private Integer age;
private String name;
private Boolean sex;
}

在namespace=config的命名空间中定义配置:

1
2
3
test.user.name = zhangsan
test.user.age = 10
test.user.sex = 1
  1. 利用RefreshScope搭配@ApolloConfigChangeListener监听实现bean的动态刷新
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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定义为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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实现刷新

  1. 定义实体类
1
2
3
4
5
6
7
8
@ConfigurationProperties("test.user")
@Data
@Component
public class TestUserProperties implements Serializable {
private Integer age;
private String name;
private Boolean sex;
}

与方法一的差异是不使用@RefreshScope注解

  1. 利用spring的事件驱动配合@ApolloConfigChangeListener监听实现bean的动态刷新:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@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)
1
2
3
4
5
6
7
8
9
10
11
12
13
@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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
@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提取元数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@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完成的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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);
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@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;
}

}

参考

  1. apollo与springboot集成实现动态刷新配置
  2. @RefreshScope那些事