目录
1.spring-boot 引入nacos-config 配置demo
1.先新建一个spring-boot工程,教程:https://spring.io/quickstart
2.工程pom文件引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2022.0.0.0-RC1</version>
</dependency>
3.Nacos 服务端初始化,教程:https://nacos.io/zh-cn/docs/v2/quickstart/quick-start.html
4.spring-boot工程application.yml 文件中配置nacos相关配置
spring:
application:
name: nacos-config
profiles:
active: develop
cloud:
nacos:
config:
serverAddr: 127.0.0.1:8848
file-extension: yaml
# config:
# import:
# - nacos:nacos-config.properties
config:
import: optional:nacos:${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
5.Nacos里面添加配置
本地nacos控制台地址:http://localhost:8848/nacos/index.html,添加配置
Data Id对应application.yml中的{spring.application.name}-{spring.profiles.active}.{spring.cloud.nacos.config.file-extension} , 通过config.import的方式引入nacos配置。
6.编写@SpringBootApplication类获取配置
@SpringBootApplication
public class NacosConfigServerApplication {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(NacosConfigServerApplication.class, args);
String userSex = applicationContext.getEnvironment().getProperty("user.sex");
String userAge = applicationContext.getEnvironment().getProperty("user.age");
String currentEnv = applicationContext.getEnvironment().getProperty("current.env");
System.err.println("in "+currentEnv+" enviroment; " + "user sex :" +userSex+"; age: "+userAge);
}
}
2.spring-cloud-alibaba是如何把nacos-config引入到spring体系的
通过spring-boot提供的starter支持,spring-cloud-alibaba实现了spring-cloud-starter-alibaba-nacos-config
2.1 spring-boot starter机制
什么是starer?
starter是一种对依赖的synthesize(合成), spring-boot 官网demo里面引入的spring-boot starter依赖树。
starter的实现:虽然不同的starter实现起来各有差异,但是他们基本上都会使用到两个相同的内容:ConfigurationProperties和AutoConfiguration。因为Spring Boot坚信“约定大于配置”这一理念,所以我们使用ConfigurationProperties来保存我们的配置,并且这些配置都可以有一个默认值,即在我们没有主动覆写原始配置的情况下,默认值就会生效,这在很多情况下是非常有用的。除此之外,starter的ConfigurationProperties还使得所有的配置属性被聚集到一个文件中(一般在resources目录下的application.properties),这样我们就告别了Spring项目中XML地狱。
starter的整体逻辑:
2.2 spring-cloud-starter-alibaba-nacos-config实现
spring-cloud-alibaba 也是通过starter机制引入nacos-config的。
1.定义ConfigurationProperties
通过实现类NacosConfigProperties( 源码在spring-cloud-alibaba/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config 中)。
可以看到NacosConfigProperties带有注解@ConfigurationProperties,那么这个配置类中的属性就可以在spring-boot启动时被application.yml中定义的值覆盖,类中定义了相应属性
@ConfigurationProperties(NacosConfigProperties.PREFIX)
public class NacosConfigProperties {
/**
* Prefix of {@link NacosConfigProperties}.
*/
public static final String PREFIX = "spring.cloud.nacos.config";
/**
* nacos config server address.
*/
private String serverAddr;
/**
* the nacos authentication username.
*/
private String username;
/**
* the nacos authentication password.
*/
private String password;
/**
* encode for nacos config content.
*/
private String encode;
/**
* nacos config group, group is config data meta info.
*/
private String group = "DEFAULT_GROUP";
/**
* nacos config dataId prefix.
*/
private String prefix;
/**
* the suffix of nacos config dataId, also the file extension of config content.
*/
private String fileExtension = "properties";
/**
* timeout for get config from nacos.
*/
private int timeout = 3000;
/**
* nacos maximum number of tolerable server reconnection errors.
*/
private String maxRetry;
/**
* nacos get config long poll timeout.
*/
private String configLongPollTimeout;
/**
* nacos get config failure retry time.
*/
private String configRetryTime;
...
}
对应我们的application.yml中可以配置相关属性,如nacos的服务器地址
spring:
cloud:
nacos:
config:
serverAddr: 127.0.0.1:8848
2.定义AutoConfiguration
spring.factories机制
Spring Boot 自动扫描包的时候,只会扫描自己模块下的类。这个是springboot约定俗成的内容。
如果想要被Spring容器管理的Bean的路径不再Spring Boot 的包扫描路径下,怎么办呢?也就是如何去加载第三方的Bean 呢?
目前较通用的方式有2种,一是使用注解进行实例化(在Spring Boot Application 主类上使用@Import注解),而是使用spring.factories机制。
spring.factories这种机制实际上是仿照java中的SPI扩展机制实现的。一个模块在resources/META-INFO/spring.factories文件中配置好springframework中预定义接口的实现类后,SpringFactoriesLoader会遍历整个ClassLoader 中所有Jar包下的spring.factories文件,读取这些配置文件并实例化这些实现类。也就是我们可以在自己jar中配置spring.factories文件,不会影响到其他地方的配置,也不回被别人的配置覆盖。
参考:https://zhuanlan.zhihu.com/p/444331676
NacosConfigBootstrapConfiguration
在spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/resources/META-INF/spring.factories 中定义了
org.springframework.cloud.bootstrap.BootstrapConfiguration=
com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration
这个configuration类会创造4个bean,NacosConfigProperties、NacosConfigManager、NacosPropertySourceLocator、ConfigurationPropertiesRebinder
2.3 nacos配置是如何注入到environment
要弄清nacos配置是如何注入到environment,先要弄清楚spring-boot启动过程中的配置加载和environment初始化原理
1. spring-boot配置加载 & 配置扩展机
spring-boot对配置相关的抽象
主要涉及 Environment、MutablePropertySources、PropertySource 三个接口
PropertySource 接口代表了可以提供一系列 kv 的配置的一个**“配置源”的抽象封装**,配置源可以是个 Map 对像,也可以是个 Properties 对象等,PropertySource 接口并提供了getName,getSource、getProperty 等方法。
PropertySources 接口及其实现 MutablePropertySources(看名字就知道是可变的配置源),内部是一个 PropertySource 列表,并封装了对 PropertySource 的操作,提供 add remove 等方法。
Environment 接口,及其子接口 ConfigurableEnvironment,提供了获取 MutablePropertySources 的方法,也直接提供了 getProperty 方法,和 PropertySource 接口中的 getProperty 方法不同,Environment 中的 getProperty 会遍历 MutablePropertySources 里面的 list,在 list 中排在前面的 PropertySource 中提供的配置具有更高的优先级。
接下来我们就介绍一下的 Environment 和 @Value 的原理,来寻找对 Spring Boot 配置进行扩展的可能。所谓的配置扩展就是指当前 Spring Boot 的配置都是在本地文件中,实际上不仅可以从本地文件加载,还可以从网络、db、configserver 等集中式加载。
spring-boot启动原理
任何一个被spring-boot 启动类都会通过以下语句启动容器
ConfigurableApplicationContext applicationContext = SpringApplication.run(xxxx.class, args);
这个语句最终会执行到
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
做了两件事:1、new 一个SpringApplication 对象出来 2、调用SpringApplication对象的run方法。
1.new SpringApplication对象
主要包括初始化几个属性
- List bootstrapRegistryInitializers :启动注册表初始化器
- List<ApplicationContextInitializer<?>> initializers:应用上下文初始化器
- List<ApplicationListener<?>> listeners:应用监听器
2.调用SpringApplication对象的run方法
run方法里面有个prepareEnvironment,顾名思义就是准备环境,这个方法依赖三个入参,SpringApplicationRunListeners,DefaultBootstrapContext,ApplicationArguments
环境(ConfigurableEnvironment)创建与配置
(1).创建环境
传入方法(ConfigurableEnvironment createEnvironment(WebApplicationType webApplicationType))只有一个参数,而BiFunction为两个参数,为啥能这样写参考:https://blog.csdn.net/qq_39505245/article/details/123142350。可以通过这个学习一次把函数作为参数传入,实现类似策略模式的效果
(2).配置环境
configurePropertySource用来给环境添加默认配置和命令行配置
configureProfiles没有做任何事情
(3).SpringApplicationRunListeners 广播environmentPrepared事件
ApplicationEvent为ApplicationEnvironmentPreparedEvent
ApplicationListener实现类定义在spring.factories中,主要有以下几个
可以看到有个EnvironmentPostProcessorApplicationListener,看名字猜测是对evironment进行后处理的。
接收到事件后,这个listener里面通过EnvironmentPostProcessor对evironment进行后处理,EnvironmentPostProcessor 实现类同样定义在spring.factories中
注册spring-cloud-alibaba-nacos-config-starter里面同样定义了一个EnvironmentPostProcessor
这个EnvironmentPostProcessor主要判断application.yml里面有没有配置spring.config.import:nacos,没有配置会抛异常
上下文(ConfigurableApplicationContext)创建与配置
1.创建上下文
createApplicationContext(),策略模式创建ApplicationContext,其实和创建环境一样的原理。最后会创建一个默认的GenericApplicationContext。
看下这个GenericApplicationContext,里面有几个属性
- DefaultListableBeanFactory
Spring’s default implementation of the ConfigurableListableBeanFactory and BeanDefinitionRegistry interfaces: a full-fledged bean factory based on bean definition metadata, extensible through post-processors.
DefaultListableBeanFactory是Spring对ConfigurableListableBeanFactory和BeanDefinitionRegistry两个接口的默认实现,提供bean注册、添加bean后置处理器、
- ResourceLoader
Strategy interface for loading resources (e.g., class path or file system resources). An org.springframework.context.ApplicationContext is required to provide this functionality plus extended org.springframework.core.io.support.ResourcePatternResolver support.
加载资源的一个策略接口,如果这个属性不为空,上下文(applicationContext)加载资源的行为都会委托给这个属性对象,如果为空,则用默认的resourceLoader加载资源,因为GenericApplicationContext继承了AbstractApplicationContext,而AbstractApplicationContext继承了DefaultResourceLoader
2.准备上下文
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner)
主要完成refreshContext前的一些准备工作,包括
- 对上下文应用ApplicationContextInitializer列表的初始化方法
- 注册bean
- springApplicationArguments
- springBootBanner
- 添加BeanFactoryPostProcessor
- LazyInitializationBeanFactoryPostProcessor
- PropertySourceOrderingBeanFactoryPostProcessor
- 广播contextPrepared和contextLoaded事件
- listeners.contextPrepared(context)
- listeners.contextLoaded(context)
3.刷新上下文
refreshContext(ConfigurableApplicationContext context)
- prepareRefresh:设置启动时间、设置active标记位、初始化property sources(initPropertySources)
- 对beanFactory进行后处理:
- 调用AbstractApplicationContext 子类的postProcessBeanFactory()方法
- invokeBeanFactoryPostProcessors,调用beanFactory后处理器
- 注册bean post processor
- 注册/设置messageSource、applicationEventMulticaster等bean
- onRefresh:交由AbstractApplicationContext 子类去实现,这个方法的目的是让其他子类去在这里去注册一些特殊的bean
- registerListeners() : 注册实现了ApplicationListener的bean
- finishBeanFactoryInitialization:这里完成了BeanFactory的初始化,同时也会完成所有已注册的非懒加载bean初始化
- finishRefresh():这个里面会广播 ContextRefreshedEvent事件
4.后处理操作
afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args)
目前这一步什么都没做
2.nacos-config配置注入到environment
ConfigDataEnvironmentPostProcessor通过调用postProcessEnvironment方法实现。
spring-cloud-alibaba中定义了两个PropertySourceLoader,一个ConfigDataLocationResolver,一个ConfigDataLoader共同完成
可以看到ConfigDataEnvironmentPostProcessor.postProcessEnvironment()方法中会创建一个ConfigDataEnvironment(),然后调用processAndApply()方法
2.1构造ConfigDataImporter
里面有两个参数
- ConfigDataLocationResolvers:this.resolvers
- ConfigDataLoaders:this.loaders
this.resolvers和this.loaders赋值时会实例化NacosConfigDataLocationResolver 和NacosConfigDataLoader 对象。
这里面有一个contributors对象,是把多个ConfigDataEnvironmentContributor组织起来的不可变树形结构
一个ConfigDataEnvironmentContributor 保存着一个需要加载至环境中的propertySource
2.2构造contributors
下面就是构造好这些contributors
ConfigDataEnvironmentContributors contributors = processInitial(this.contributors, importer);
ConfigDataActivationContext activationContext = createActivationContext(
contributors.getBinder(null,BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE));
contributors = processWithoutProfiles(contributors, importer, activationContext);
activationContext = withProfiles(contributors, activationContext);
contributors = processWithProfiles(contributors, importer, activationContext);
- processInitial(this.contributors, importer):
- this.contributors = createContributors(binder); Binder是Spring定义的一个容器对象,把一个或多个ConfigurationPropertySource对象集合到一起。
- processInitial -> contributors.withProcessedImports,导入一些初始的配置
2.3实现配置导入
下面是代码实现配置导入
/**
* Processes imports from all active contributors and return a new
* {@link ConfigDataEnvironmentContributors} instance.
* @param importer the importer used to import {@link ConfigData}
* @param activationContext the current activation context or {@code null} if the
* context has not yet been created
* @return a {@link ConfigDataEnvironmentContributors} instance with all relevant
* imports have been processed
*/
ConfigDataEnvironmentContributors withProcessedImports(ConfigDataImporter importer,
ConfigDataActivationContext activationContext) {
ImportPhase importPhase = ImportPhase.get(activationContext);
this.logger.trace(LogMessage.format("Processing imports for phase %s. %s", importPhase,
(activationContext != null) ? activationContext : "no activation context"));
ConfigDataEnvironmentContributors result = this;
int processed = 0;
while (true) {
ConfigDataEnvironmentContributor contributor = getNextToProcess(result, activationContext, importPhase);
if (contributor == null) {
this.logger.trace(LogMessage.format("Processed imports for of %d contributors", processed));
return result;
}
if (contributor.getKind() == Kind.UNBOUND_IMPORT) {
ConfigDataEnvironmentContributor bound = contributor.withBoundProperties(result, activationContext);
result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,
result.getRoot().withReplacement(contributor, bound));
continue;
}
ConfigDataLocationResolverContext locationResolverContext = new ContributorConfigDataLocationResolverContext(
result, contributor, activationContext);
ConfigDataLoaderContext loaderContext = new ContributorDataLoaderContext(this);
List<ConfigDataLocation> imports = contributor.getImports();
this.logger.trace(LogMessage.format("Processing imports %s", imports));
Map<ConfigDataResolutionResult, ConfigData> imported = importer.resolveAndLoad(activationContext,
locationResolverContext, loaderContext, imports);
this.logger.trace(LogMessage.of(() -> getImportedMessage(imported.keySet())));
ConfigDataEnvironmentContributor contributorAndChildren = contributor.withChildren(importPhase,
asContributors(imported));
result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,
result.getRoot().withReplacement(contributor, contributorAndChildren));
processed++;
}
}
这个函数会从根节点遍历ConfigDataEnvironmentContributors 里面每一个ConfigDataEnvironmentContributor,然后会importer.resolveAndLoad读取配置
Map<ConfigDataResolutionResult, ConfigData> imported = importer.resolveAndLoad(activationContext,locationResolverContext, loaderContext, imports);
importer.resolveAndLoad中就会调用spring-cloud-alibaba对ConfigDataLocationResolver的实现NacosConfigDataLocationResolver
得到List< NacosConfigDataResource>,包含nacos-config的加载配置
NacosConfigProperties,被@ConfigurationProperties 注解的配置类
加载配置resolve完后会实际load配置值,这里会调用NacosConfigDataLoader实现的load方法,从nacos-config配置中心加载配置
2.4contributor应用到environment
构造好contributors之后把这些contributor应用到environment中
其实就是简单的遍历contributors,然后获取propertySource,添加到this.environment.getPropertySources()的最后
2.4 nacos配置如何动态刷新
先来看demo事例:
这里定义个一个userConfig属性,调用http接口会把属性值打印出来,这个属性可以通过nacos控制台动态更新。
先通过下面命令向nacos-config写入配置
curl -X POST "http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=nacos-config.properties&group=DEFAULT_GROUP&content=userConfig=realName"
看下配置
curl -X GET "http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=nacos-config.properties&group=DEFAULT_GROUP"
启动工程,访问接口:http://localhost:8080/nacos/config/get
改一下配置
curl -X POST "http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=nacos-config.properties&group=DEFAULT_GROUP&content=userConfig=nickName"
再请求http接口,配置会实时改过来
1. @Value注解
Spring可以通过@Value注解把外部配置注入到属性中,具体用法:https://www.baeldung.com/spring-value-annotation
2.@RefreshScope注解
@RefreshScope顾名思义,就是可以在不用重启应用的时候刷新Bean中属性的刷新。“业界”称其为“热加载” or “热启动”。
@RefreshScope是Springcloud提供的一种scope实现,一般用来实现配置或者实例的热加载。
spring官方文档的说明
https://cloud.spring.io/spring-cloud-static/Greenwich.SR2/multi/multi__spring_cloud_context_application_context_services.html#refresh-scope
所有被@RefreshScope修饰的bean都是懒加载的,第一次访问才会初始化。所以刷新bean也是同理,调用时会创建一个新的对象。
2.1 @Scope注解
IOC容器中有5种Scope
1.singleton:单例模式(默认),全局有且仅有一个实例
2.prototype:原型模式,每次获取Bean的时候会有一个新的实例
3.request:request表示针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效
4.session:session作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效
5.global session:global session作用域类似于标准的HTTP Session作用域,不过它仅仅在基于portlet的web应用中才有意义
以下是AbstractBeanFactory#doGetBean中获取bean的方法
1.单例和原型scope的Bean是硬编码单独处理的
2.除了单例和原型Bean,其他Scope是由Scope对象处理的
3.具体创建Bean的过程都是由IOC做的,只不过Bean的获取是通过Scope对象的自定义方法
org.springframework.cloud包中定义了Scope接口的实现
2.2 RefreshScope extends GenericScope
RefreshScope是 GenericScope的子类,实现了Scope, BeanFactoryPostProcessor, BeanDefinitionRegistryPostProcessor, DisposableBean 几个接口。
这个bean是在RefreshAutoConfiguration中定义的,同时在这里定义的还有ContextRefresher,RefreshEventListener。
整个配置刷新的入口在ContextRefresher#refresh这个方法中
public synchronized Set<String> refresh() {
Set<String> keys = this.refreshEnvironment();
this.scope.refreshAll();
return keys;
}
有两种方式会触发到这个方法调用
- RefreshEndpoint:Http访问/refresh这个EndPoint
- RefreshEventListener:监听到RefreshEvent事件(观察者模式)
这里就会调用ContextRefresher.refresh()方法。
ContextRefresher.refresh()方法中会刷新environment以及调用RefreshScope.refreshAll()方法
this.refreshEnvironment()
刷新environment主要完成:获取旧的环境配置 -> 更新environment -> 广播EnvironmentChangeEvent
this.updateEnvironment是一个abstract方法,spring-cloud提供了ConfigDataContextRefresher实现
@Override
protected void updateEnvironment() {
if (logger.isTraceEnabled()) {
logger.trace("Re-processing environment to add config data");
}
StandardEnvironment environment = copyEnvironment(getContext().getEnvironment());
ConfigurableBootstrapContext bootstrapContext = getContext().getBeanProvider(ConfigurableBootstrapContext.class)
.getIfAvailable(DefaultBootstrapContext::new);
// run thru all EnvironmentPostProcessor instances. This lets things like vcap and
// decrypt happen after refresh. The hard coded call to
// ConfigDataEnvironmentPostProcessor.applyTo() is now automated as well.
DeferredLogFactory logFactory = new PassthruDeferredLogFactory();
List<String> classNames = SpringFactoriesLoader.loadFactoryNames(EnvironmentPostProcessor.class,
getClass().getClassLoader());
Instantiator<EnvironmentPostProcessor> instantiator = new Instantiator<>(EnvironmentPostProcessor.class,
(parameters) -> {
parameters.add(DeferredLogFactory.class, logFactory);
parameters.add(Log.class, logFactory::getLog);
parameters.add(ConfigurableBootstrapContext.class, bootstrapContext);
parameters.add(BootstrapContext.class, bootstrapContext);
parameters.add(BootstrapRegistry.class, bootstrapContext);
});
List<EnvironmentPostProcessor> postProcessors = instantiator.instantiate(classNames);
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(environment, application);
}
if (environment.getPropertySources().contains(REFRESH_ARGS_PROPERTY_SOURCE)) {
environment.getPropertySources().remove(REFRESH_ARGS_PROPERTY_SOURCE);
}
MutablePropertySources target = getContext().getEnvironment().getPropertySources();
String targetName = null;
for (PropertySource<?> source : environment.getPropertySources()) {
String name = source.getName();
if (target.contains(name)) {
targetName = name;
}
if (!this.standardSources.contains(name)) {
if (target.contains(name)) {
target.replace(name, source);
}
else {
if (targetName != null) {
target.addAfter(targetName, source);
// update targetName to preserve ordering
targetName = name;
}
else {
// targetName was null so we are at the start of the list
target.addFirst(source);
targetName = name;
}
}
}
}
}
这个方法会重新加载spring.factories下所有的BeanPostProcessor,然后再调用一遍postProcessor.postProcessEnvironment方法,这里当然也会调用到前面提到的ConfigDataEnvironmentPostProcessor.postProcessEnvironment()方法,重新拉取Nacos上的配置,然后用新值替换(添加)原来的PropertySource。
RefreshScope.refreshAll()
这里做了两件事情:
- 调用GenericScope#destroy()方法,清空beanWrapper缓存
- 广播RefreshScopeRefreshedEvent()事件
销毁缓存方法如下
this.cache是GenericScope的内部类BeanLifecycleWrapperCache一个实例,用于包装bean并进行缓存。这个类的缓存操作都委派给ScopeCache进行
ScopeCache是一个用于缓存bean的接口,有两个实现类:StandardScopeCache和ThreadLocalScopeCache。
比如StandardScopeCache就是用ConcurrentHashMap来保存bean的
需要注意的是clear方法处理调用ConcurrentHashMap.clear方法外,还把缓存中的对象统一塞到了一个list中返回。然后在上层方法(BeanLifecycleWrapperCache#clear)又把这个list进行去重(放到一个set中)进行返回。然后在GenericScope#destroy()方法中加锁逐一销毁BeanLifecycleWrapper实例。来看一下BeanLifecycleWrapper的设计
/**
* Wrapper for a bean instance and any destruction callback (DisposableBean etc.) that
* is registered for it. Also decorates the bean to optionally guard it from
* concurrent access (for instance).
*
* @author Dave Syer
*
*/
private static class BeanLifecycleWrapper {
private final String name;
private final ObjectFactory<?> objectFactory;
private volatile Object bean;
private Runnable callback;
BeanLifecycleWrapper(String name, ObjectFactory<?> objectFactory) {
this.name = name;
this.objectFactory = objectFactory;
}
public String getName() {
return this.name;
}
public void setDestroyCallback(Runnable callback) {
this.callback = callback;
}
public Object getBean() {
if (this.bean == null) {
synchronized (this.name) {
if (this.bean == null) {
this.bean = this.objectFactory.getObject();
}
}
}
return this.bean;
}
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;
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((this.name == null) ? 0 : this.name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
BeanLifecycleWrapper other = (BeanLifecycleWrapper) obj;
if (this.name == null) {
if (other.name != null) {
return false;
}
}
else if (!this.name.equals(other.name)) {
return false;
}
return true;
}
}
这个类有个私有属性Runnable callback,用于在销毁时进行回调操作。
bean属性更新
通过上面refreshEnvironment和refreshAll,environment里面已经有最新的配置了。那么对于demo中的NacosConfigController这个bean,bean属性userConfig已经在bean实例化的时候就已经被设置了,在没有重启spring情况下是如何获取最新配置的。
获取bean方法是现在AbstractBeanFactory#doGetBean中,这个方法之前也提到过对于加了@RefreshScope注解的bean,会把bean获取逻辑交给RefreshScope的自定义方法去实现(RefreshScope 是GenericScope子类)。
这里get方法的第二个参数(lambda表达式)就是GenericScope中定义的函数接口参数,定义了如果去创建bean。
然后看到这个get方法在获取bean时会先创建一个新的BeanLifecycleWrapper并加入到缓存中。
因为refresh的时候进行了缓存清空,所以这里value为最新创建出来的BeanLifecycleWrapper对象。
然后通过BeanLifecycleWrapper.getBean()方法获取bean,下面是这个方法
可以看到这个方法获取bean通过双重加锁机制,如果bean为空(),就会调用this.objectFactory.getObject()方法,会重新创建bean,这样就把environment中的最新属性值注入到了新bean中,实现动态更新的效果。