普通字段 在需要刷新的字段上使用@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 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-context</artifactId > </dependency >
实体类上使用@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
利用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 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
注解
利用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 { 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 ; 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)) { name = SCOPED_TARGET_PREFIX + name; } 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; } }
参考
apollo与springboot集成实现动态刷新配置
@RefreshScope那些事