引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
配置bootstrap.yml
management:
endpoint:
refresh:
enabled: true #默认true
endpoints:
web:
exposure:
include: '*' #默认情况下只对外暴露了health和info端点
spring:
application:
name: ms-client-a
cloud:
config:
# discovery:
# enabled: true
# service-id: ms-config-server
uri: http://localhost:8080/
profile: dev
label: master
编码
在需要动态刷新的类上添加@RefreshScope注解并通过@value注解引用配置文件中的配置
@RestController
@RequestMapping("/adviser/loss")
@RefreshScope
public class AdviserLossController {
@Value("${custom.name}")
private String name;
@Value("${custom.age}")
private Integer age;
@GetMapping("/test")
public String test(){
return name + ":" + age;
}
}
配置git远程文件
custom:
name: 张三
age: 20
启动项目
首次启动时,config client会请求config server加载远程配置
测试
首次请求/adviser/loss/test接口,会发现返回=》张三:18,然后修改git上的配置,将年龄修改为20,再次请求/adviser/loss/test接口,发现配置并未更新。那么此时就需要我们手动刷新配置了:发送http post /actuator/refresh接口,此时再次请求/adviser/loss/test接口,会发现返回=》张三:20,说明配置被成功刷新。
源码分析
@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("refresh")
注解指明了作用域名为refresh
。
RefreshScope
public class RefreshScope extends GenericScope implements ApplicationContextAware,
ApplicationListener<ContextRefreshedEvent>, Ordered {
// 省略代码。。。。。。
}
该类继承自GenericScope
,其核心方法都在父类GenericScope
中,而GenericScope
继承BeanFactoryPostProcessor
GenericScope
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
this.beanFactory = beanFactory;
// 放入到缓存中
beanFactory.registerScope(this.name, this);
setSerializationId(beanFactory);
}
注册scope
,名字为refresh
,值为RefreshScope
实例。
添加了@RefreshScope
注解的Bean
对象会被@ComponentScan
注解扫描到,核心代码在ClassPathBeanDefinitionScanner
类中的doScan
方法中:
ClassPathBeanDefinitionScanner
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
// 关键代码
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
核心代码在this.scopeMetadataResolver.resolveScopeMetadata(candidate)
这一行,这里就不展开讲了,希望同学自己去扩展阅读相关源码。
注册的Scope
将会在AbstractBeanFactory#doGetBean
方法中调用,该方法中会先拿到当前BeanDefinition
中定义的Scope
,通过scopeName
从Map
集合中拿到Scope
类,最后调用Scope
的get
方法获取实例对象。
AbstractBeanFactory
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
// 省略部分代码
String scopeName = mbd.getScope();
if (!StringUtils.hasLength(scopeName)) {
throw new IllegalStateException("No scope name defined for bean '" + beanName + "'");
}
// 1.从缓存中取,在beanFactory的registerScope方法调用时放入缓存
Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
// 2.调用scope的get方法创建bean,通过匿名内部类创建ObjectFactory对象并实现getObject方法。在GenericScope的get方法会回调此处的实现,创建一个bean。
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
});
beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
throw new ScopeNotActiveException(beanName, scopeName, ex);
}
// 省略部分代码
}
在2处,最终会调用RefreshScope#get
方法:
GenericScope
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
BeanLifecycleWrapper value = this.cache.put(name,
new BeanLifecycleWrapper(name, objectFactory));
this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
try {
return value.getBean();
}
catch (RuntimeException e) {
this.errors.put(name, e);
throw e;
}
}
先是将ObjectFactory
实例对象包装成BeanLifecycleWrapper
放入缓存中(缓存中没有则放入,有则返回原对象),然后调用BeanLifecycleWrapper
的getBean
方法:
GenericScope@BeanLifecycleWrapper
public Object getBean() {
if (this.bean == null) {
synchronized (this.name) {
if (this.bean == null) {
this.bean = this.objectFactory.getObject();
}
}
}
return this.bean;
}
如果bean对象等于null,则通过this.objectFactory.getObject()
,又回到AbstractBeanFactory
的doGetBean
方法中,创建一个bean并返回。
至此已经分析完了RefreshScope
类和@RefreshScope
注解的来龙去脉,下面分析下Refresh端点触发时机。
Refresh端点触发时机
当调用/actuator/refresh
端点时,执行如下refresh:
@Endpoint(id = "refresh")
public class RefreshEndpoint {
private ContextRefresher contextRefresher;
public RefreshEndpoint(ContextRefresher contextRefresher) {
this.contextRefresher = contextRefresher;
}
@WriteOperation
public Collection<String> refresh() {
Set<String> keys = this.contextRefresher.refresh();
return keys;
}
}
RefreshEndpoint
是在RefreshEndpointAutoConfiguration
类中创建并配置的。内部会通过调用ContextRefresher#refresh
方法:
public synchronized Set<String> refresh() {
//1.刷新环境
Set<String> keys = refreshEnvironment();
//2.清空缓存
this.scope.refreshAll();
return keys;
}
public synchronized Set<String> refreshEnvironment() {
//1:提取配置信息修改之前的值,排除systemEnvironment、systemProperties、jndiProperties、servletConfigInitParams、servletContextInitParams、configurationProperties相关配置
Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources());
//2:重新加载读取配置信息,调用内部的抽象方法,由具体的子类实现
updateEnvironment();
//3:获取所有改变的配置
Set<String> keys = changes(before, extract(this.context.getEnvironment().getPropertySources())).keySet();
//4:发布EnvironmentChangeEvent事件
this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
return keys;
}
在2处,重新加载配置,在当前的版本,有2中实现方式:一种是兼容旧版本的实现,一种是新版本的实现,这两种实现分别在RefreshAutoConfiguration
中配置:
RefreshAutoConfiguration
//兼容旧版本的实现
@Bean
@ConditionalOnMissingBean
@ConditionalOnBootstrapEnabled
public LegacyContextRefresher legacyContextRefresher(ConfigurableApplicationContext context, RefreshScope scope,
RefreshProperties properties) {
return new LegacyContextRefresher(context, scope, properties);
}
//新版本的实现
@Bean
@ConditionalOnMissingBean
@ConditionalOnBootstrapDisabled
public ConfigDataContextRefresher configDataContextRefresher(ConfigurableApplicationContext context,
RefreshScope scope, RefreshProperties properties) {
return new ConfigDataContextRefresher(context, scope, properties);
}
旧版本的实现其原理是:内部启动一个非web环境的SpringBoot应用,重新读取配置信息,用新配置替换旧配置,这个很容易理解。 新版本的实现方式,我也跟踪了源码,也在网上找了很多资料,但是目前网上的都是基于旧版本的分析。关于新版本的实现属实不太理解(功力尚浅),我个人也在学习和总结,后续如果有新的进展,我也会及时更新的,如果您有相关资料或见解,也烦请指教。
在4处发布一个EnvironmentChangeEvent
事件,查看该事件的监听器:
ConfigurationPropertiesRebinder
@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
if (this.applicationContext.equals(event.getSource())
// Backwards compatible
|| event.getKeys().equals(event.getSource())) {
rebind();
}
}
@ManagedOperation
public void rebind() {
this.errors.clear();
//遍历所有的配置类(带有@ConfigurationProperties注解的类)
for (String name : this.beans.getBeanNames()) {
//对每一个bean进行重新绑定
rebind(name);
}
}
@ManagedOperation
public boolean rebind(String name) {
if (!this.beans.getBeanNames().contains(name)) {
return false;
}
if (this.applicationContext != null) {
try {
Object bean = this.applicationContext.getBean(name);
if (AopUtils.isAopProxy(bean)) {
bean = ProxyUtils.getTargetObject(bean);
}
if (bean != null) {
// TODO: determine a more general approach to fix this.
// see https://github.com/spring-cloud/spring-cloud-commons/issues/571
if (getNeverRefreshable().contains(bean.getClass().getName())) {
return false; // ignore
}
//销毁当前的bean this.applicationContext.getAutowireCapableBeanFactory().destroyBean(bean);
//初始化Bean this.applicationContext.getAutowireCapableBeanFactory().initializeBean(bean, name);
return true;
}
}
catch (RuntimeException e) {
this.errors.put(name, e);
throw e;
}
catch (Exception e) {
this.errors.put(name, e);
throw new IllegalStateException("Cannot rebind to " + name, e);
}
}
return false;
}
补充
在ConfigurationPropertiesRebinder
类中的beans
是通过构造函数传过来的,接下来先查看这个对象是如何被构造的:
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(ConfigurationPropertiesBindingPostProcessor.class)
public class ConfigurationPropertiesRebinderAutoConfiguration
implements ApplicationContextAware, SmartInitializingSingleton {
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.context = applicationContext;
}
@Bean
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
public static ConfigurationPropertiesBeans configurationPropertiesBeans() {
return new ConfigurationPropertiesBeans();
}
@Bean
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
public ConfigurationPropertiesRebinder configurationPropertiesRebinder(ConfigurationPropertiesBeans beans) {
ConfigurationPropertiesRebinder rebinder = new ConfigurationPropertiesRebinder(beans);
return rebinder;
}
@Override
public void afterSingletonsInstantiated() {
// After all beans are initialized explicitly rebind beans from the parent
// so that changes during the initialization of the current context are
// reflected. In particular this can be important when low level services like
// decryption are bootstrapped in the parent, but need to change their
// configuration before the child context is processed.
if (this.context.getParent() != null) {
// TODO: make this optional? (E.g. when creating child contexts that prefer to
// be isolated.)
ConfigurationPropertiesRebinder rebinder = this.context.getBean(ConfigurationPropertiesRebinder.class);
for (String name : this.context.getParent().getBeanDefinitionNames()) {
rebinder.rebind(name);
}
}
}
}
在这个自动配置类中创建了ConfigurationPropertiesRebinder
并且将ConfigurationPropertiesBeans
注入。ConfigurationPropertiesBeans
是个BeanPostProcessor
处理器:
@Component
public class ConfigurationPropertiesBeans implements BeanPostProcessor, ApplicationContextAware {
//省略部分代码...
private Map<String, ConfigurationPropertiesBean> beans = new HashMap<>();
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (isRefreshScoped(beanName)) {
return bean;
}
ConfigurationPropertiesBean propertiesBean = ConfigurationPropertiesBean.get(this.applicationContext, bean,
beanName);
if (propertiesBean != null) {
//添加有@ConfigurationProperties注解的bean都将保存在该集合中
this.beans.put(beanName, propertiesBean);
}
return bean;
}
private boolean isRefreshScoped(String beanName) {
if (this.refreshScope == null && !this.refreshScopeInitialized) {
this.refreshScopeInitialized = true;
for (String scope : this.beanFactory.getRegisteredScopeNames()) {
if (this.beanFactory.getRegisteredScope(
scope) instanceof org.springframework.cloud.context.scope.refresh.RefreshScope) {
this.refreshScope = scope;
break;
}
}
}
if (beanName == null || this.refreshScope == null) {
return false;
}
return this.beanFactory.containsBeanDefinition(beanName)
&& this.refreshScope.equals(this.beanFactory.getBeanDefinition(beanName).getScope());
}
//省略部分代码...
}
该BeanPostProcessor
的postProcessBeforeInitialization
方法执行,在初始化bean
的时候执行,在这里就会判断当前的Bean
是否是RefreshScope
Bean。
在isRefreshScoped
方法中遍历注册的所有Scope
并且判断是否是有RefreshScope
,先从注册的所有Scope
中查找RefreshScope
,如果不是返回false
,如果是则返回true
。如果isRefreshScoped
方法返回的false
就判断当前Bean
是否有@ConfigurationProperties
注解如果有会被包装成ConfigurationPropertiesBean
存入当前的beans
集合中(当有refresh
发生时会重新绑定这些bean
)。接下来继续进入到上面的ConfigurationPropertiesRebinder#rebind
方法中。
RefreshScope刷新处理
回到ContextRefresher
的refresh
方法:
public synchronized Set<String> refresh() {
Set<String> keys = refreshEnvironment();
//清空缓存
this.scope.refreshAll();
return keys;
}
RefreshScope#refreshAll
@ManagedOperation(description = "Dispose of the current instance of all beans "
+ "in this scope and force a refresh on next method execution.")
public void refreshAll() {
//调用父类GenericScope的destroy方法
super.destroy();
//发布RefreshScopeRefreshedEvent事件,我们可以写个监听程序监听该事件
this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
GenericScope
@Override
public void destroy() {
List<Throwable> errors = new ArrayList<Throwable>();
//清空缓存
Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();
for (BeanLifecycleWrapper wrapper : wrappers) {
try {
Lock lock = this.locks.get(wrapper.getName()).writeLock();
lock.lock();
try {
//清空上次创建的对象信息
wrapper.destroy();
}
finally {
lock.unlock();
}
}
catch (RuntimeException e) {
errors.add(e);
}
}
if (!errors.isEmpty()) {
throw wrapIfNecessary(errors.get(0));
}
this.errors.clear();
}
public void destroy() {
if (this.callback == null) {
return;
}
synchronized (this.name) {
Runnable callback = this.callback;
if (callback != null) {
callback.run();
}
this.callback = null;
this.bean = null;
}
}
当前清空了缓存对象后,下次再进入注入的时候会再次调用ObjectFacotry#getObject
方法创建新的对象。
总结
当触发了refresh
后,所有的带有@ConfigurationProperties
注解的Bean都会自动的刷新并不需要@RefreshScope
注解。而有@RefreshScope
注解的一般在应用在非配置类上,有成员属性使用@Value
注解的,如下:
@RestController
@RequestMapping("/refreshBeanProp")
@RefreshScope
public class RefreshScopeBeanPropController {
@Value("${custom}")
private String custom ;
@GetMapping("/get")
public String get() {
return custom ;
}
}
在此种情况下,调用/actuator/refresh
可使custom动态刷新,在ContextRefresher#refresh
中将缓存的Bean清空了后重新生成Bean。