Administrator
发布于 2023-02-01 / 40 阅读
0
0

Nacos Config Client & Bootstrap

Install & Config

配置Mysql:
refer to : https://nacos.io/zh-cn/docs/v2/guide/admin/deployment.html


修改Nacos密码
refer to : https://www.cnblogs.com/wangyuanguang/p/17057441.html


启动nacos

sh startup.sh -m standalone
# 以下3个端口,必须都打开
firewall-cmd --add-port=8848/tcp --permanent
firewall-cmd --add-port=9848/tcp --permanent
firewall-cmd --add-port=9849/tcp --permanent

firewall-cmd --reload

Best Practise

在SpringBoot 2.4.x的版本之后,如果需要使用bootstrap的方式,去连接配置中心(nacos config server),那么需要增加spring-cloud-starter-bootstrap 这个Jar包

配置方式如下:

 <dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-bootstrap</artifactId>
   <version>3.0.6</version>
</dependency>

接下来,在bootstrap.yaml中,指定nacos config server的地址

bootstrap.yml

# Spring配置
spring:
  application:
    name: iot-manage
  profiles: 
    active: dev

nacos:
  config:
    server-addr: 221.181.222.135:8848
    # 重要!和nacos控制台Data-Id一致
    data-id: iot-manage-dev.yml
    # 重要!和nacos控制台group一致
    group: DEFAULT_GROUP
    type: yaml
    context-path: /nacos
    username: nacos
    password: long123456
    auto-refresh: true
    bootstrap:
      enable: true
      log-enable: true

这里需要注意的是,nacos.config.bootstrap.enable=true


Nacos Config Client 源码分析

nacos 是怎么在项目启动时,拉取远程配置文件的呢?

从源头抓起


// org.springframework.boot.SpringApplication#run(java.lang.String...)

public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
		ConfigurableApplicationContext context = null;
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting(bootstrapContext, this.mainApplicationClass);
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 准备环境信息,即获取到 配置的环境信息
            
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			context.setApplicationStartup(this.applicationStartup);
            // 准备好上下文信息
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            // 刷新上下文
			refreshContext(context);
			....
            ...

上面的代码中,步骤如下:

  • 准备环境信息,即获取到 配置的环境信息
  • 准备好上下文信息
  • 刷新上下文

其中,我们重点要谈的就是,准备环境信息,即获取到 配置的环境信息,即prepareEnvironment方法

// org.springframework.boot.SpringApplication#prepareEnvironment

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
		// Create and configure the environment
        // 首先是创建了一个环境对象
		ConfigurableEnvironment environment = getOrCreateEnvironment();
        
        // 将命令行参数,放入环境中
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		ConfigurationPropertySources.attach(environment);
        
       // 调用所有的监听器,让监听器,可以开始去进行配置环境信息了
		listeners.environmentPrepared(bootstrapContext, environment);
		DefaultPropertiesPropertySource.moveToEnd(environment);
		Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
				"Environment prefix cannot be set via properties.");
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = convertEnvironment(environment);
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

在这里我们可以看到,首先是创建了一个环境对象,接下来,将命令行参数,放入环境中,然后,调用所有的监听器,让监听器,可以开始去进行配置环境信息了。

流程图如下:

NacosClient获取Server配置文件内容

从上面的流程图,可以看出,NacosConfigEnvironmentProcessor是关键类。
下面,围绕着NacosConfigEnvironmentProcessor,详细说下

NacosConfigEnvironmentProcessor

NacosConfigEnvironmentProcessor利用的EnvironmentPostProcessor接口的postProcessEnvironment

// com.alibaba.boot.nacos.config.autoconfigure.NacosConfigEnvironmentProcessor#postProcessEnvironment

@Override
	public void postProcessEnvironment(ConfigurableEnvironment environment,
			SpringApplication application) {
		application.addInitializers(new NacosConfigApplicationContextInitializer(this));
		nacosConfigProperties = NacosConfigPropertiesUtils
				.buildNacosConfigProperties(environment);
		if (enable()) {
			System.out.println(
					"[Nacos Config Boot] : The preload log configuration is enabled");
			loadConfig(environment);
			NacosConfigLoader nacosConfigLoader = NacosConfigLoaderFactory.getSingleton(nacosConfigProperties, environment, builder);
			LogAutoFreshProcess.build(environment, nacosConfigProperties, nacosConfigLoader, builder).process();
		}
	}

在postProcessEnvironment主要完成了以下几件事:

  • 首先为SpringApplication加入一个初始化监听器NacosConfigApplicationContextInitializer,
  • 随后构建一个全局的NacosConfigProperties,
  • 最后如果Bootstrap开启了logEnable,则立即开始加载远程的配置文件。

首先NacosConfigProperties,就是一个实体类,与bootstrap.yaml中配置项,一一对应,代码如下:

@ConfigurationProperties(NacosConfigConstants.PREFIX)
public class NacosConfigProperties {

	private String serverAddr = "127.0.0.1:8848";

	private String contextPath;

	private String encode;

	private String endpoint;

	private String namespace;

	private String accessKey;

	private String secretKey;

	private String ramRoleName;

	private boolean autoRefresh = false;

	private String dataId;

	private String dataIds;

	private String group = Constants.DEFAULT_GROUP;

	private ConfigType type = ConfigType.PROPERTIES;

这块很容易理解,暂时不深入讨论了。

主要看下loadConfig方法。这个loadConfig,主要作用就是,拉取远程配置文件的内容。

具体来说,利用NacosConfigLoader完成的。在NacosConfigLoader的loadConfig函数中,我们可以比较清晰的看到配置文件的加载过程

public void loadConfig() {
		MutablePropertySources mutablePropertySources = environment.getPropertySources();
        
        // 在这一步,发起http请求,获取远程配置文件的内容
		List<NacosPropertySource> sources = reqGlobalNacosConfig(globalProperties,
				nacosConfigProperties.getType());
		for (NacosConfigProperties.Config config : nacosConfigProperties.getExtConfig()) {
			List<NacosPropertySource> elements = reqSubNacosConfig(config,
					globalProperties, config.getType());
			sources.addAll(elements);
		}
		if (nacosConfigProperties.isRemoteFirst()) {
			for (ListIterator<NacosPropertySource> itr = sources.listIterator(sources.size()); itr.hasPrevious();) {
				mutablePropertySources.addAfter(
						StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, itr.previous());
			}
		} else {
			for (NacosPropertySource propertySource : sources) {
				mutablePropertySources.addLast(propertySource);
			}
		}
	}

NacosConfigAutoConfiguration

说完了NacosConfigEnvironmentProcessor,我们再来,从宏观的角度,看下nacos config client是怎么自动配置的.

这个自动配置类,就是NacosConfigAutoConfiguration

@ConditionalOnProperty(name = NacosConfigConstants.ENABLED, matchIfMissing = true)
@ConditionalOnMissingBean(name = CONFIG_GLOBAL_NACOS_PROPERTIES_BEAN_NAME)
@EnableConfigurationProperties(value = NacosConfigProperties.class)
@ConditionalOnClass(name = "org.springframework.boot.context.properties.bind.Binder")
@Import(value = { NacosConfigBootBeanDefinitionRegistrar.class })
@EnableNacosConfig
public class NacosConfigAutoConfiguration {

}

NacosConfigAutoConfiguration做了以下几件事:

  • 首先NacosConfigAutoConfiguration会根据配置属性nacos.config.enabled来决定是否要开启Nacos,如果已经定义了全局的Nacos配置也不会继续配置。

  • 随后会开启NacosConfigProperties,定义了Nacos配置的基本配置属性,例如远端Server地址serverAddr、明明空间namespace等等。特别的是NacosConfigProperties里面定义了extConfig属性,用于在应用中加载多个配置属性。bootstrap属性定义了Nacos启动时的加载行为配置。

  • 最后引入了NacosConfigBootBeanDefinitionRegistrar,在NacosConfigBootBeanDefinitionRegistrar中定义了NacosBootConfigurationPropertiesBinder。

下面介绍下NacosBootConfigurationPropertiesBinder

NacosConfigurationPropertiesBinder & NacosBootConfigurationPropertiesBinder

在Spring Boot项目中我们可以利用@ConfigurationProperties来将应用的配置属性自动的绑定到配置类的属性字段上,在@ConfigurationProperties注解上设置属性的前缀

@ConfigurationProperties(prefix = "test")
public Test{
		private String host;
}

如相面的代码所示,即可直接将test.host属性注入到host字段中。

@NacosConfigurationProperties注解实现的也是同样的功能,额外提供了从远程配置文件上注入属性的功能。


@NacosConfigurationProperties(prefix = "test", dataId = "user.info", groupId = "springbootvue", autoRefreshed = true)
public Test{
		private String host;
}

@NacosConfigurationProperties这个注解,具体是怎么实现这个功能的呢?

主要是利用了postProcessBeforeInitialization接口,在Bean完成初始化时获取到对应Bean的NacosConfigurationProperties注解,然后利用NacosConfigurationPropertiesBinder完成属性的绑定



NacosBootConfigurationPropertiesBinder, 是在NacosConfigurationPropertiesBinder基础上,做了功能上的增强


public class NacosBootConfigurationPropertiesBinder
		extends NacosConfigurationPropertiesBinder {

	private final ConfigurableApplicationContext applicationContext;

	private final StandardEnvironment environment = new StandardEnvironment();

	public NacosBootConfigurationPropertiesBinder(
			ConfigurableApplicationContext applicationContext) {
		super(applicationContext);
		this.applicationContext = applicationContext;
	}

	@Override
	protected void doBind(Object bean, String beanName, String dataId, String groupId,
			String configType, NacosConfigurationProperties properties, String content,
			ConfigService configService) {
		synchronized (this) {
			String name = "nacos-bootstrap-" + beanName;
			NacosPropertySource propertySource = new NacosPropertySource(dataId, groupId, name, content, configType);
			environment.getPropertySources().addLast(propertySource);
			ObjectUtils.cleanMapOrCollectionField(bean);
			Binder binder = Binder.get(environment);
			ResolvableType type = getBeanType(bean, beanName);
			Bindable<?> target = Bindable.of(type).withExistingValue(bean);
			binder.bind(properties.prefix(), target);
			publishBoundEvent(bean, beanName, dataId, groupId, properties, content, configService);
			publishMetadataEvent(bean, beanName, dataId, groupId, properties);
			environment.getPropertySources().remove(name);
		}
	}

在NacosConfigurationPropertiesBinder中,属性的解析仅依赖于注解本身配置的配置文件,而在NacosBootConfigurationPropertiesBinder中的做法是将注解配置的配置文件临时加入到默认的StandardEnvironment中(包含jvm属性与系统属性),结合Environment进行属性的注入,解析完成后再移除注入。


评论