Spring Cloud Alibaba Nacos统一配置源码分析-nacos配置初始化加载

概述

搭建SpringCloudAlibabaNacos环境

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.study.nacos</groupId>
    <artifactId>nacosStudy</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>nacosStudy</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <version>2.1.2.RELEASE</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.1.3.RELEASE</version>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

配置Nacos相关属性

spring.application.name=nacos-config-test
spring.cloud.nacos.config.server-addr=192.168.10.18:8858

启动类中添加测试代码

package com.study.nacos.nacosstudy;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class NacosStudyApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(NacosStudyApplication.class, args);
        // 从Environment获取配置
        String test = context.getEnvironment().getProperty("test");
        System.out.println(test);
    }
}

通过context.getEnvironment().getProperty(“test”);获取nacos管理的配置项。

nacos统一配置的作用

集中管理配置的CRUD

配置的动态更新

springcloud实现nacos配置初始化加载

启动类方法入口

@SpringBootApplication
public class NacosStudyApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(NacosStudyApplication.class, args);
    }
}

SpringApplication.java的run方法

public static ConfigurableApplicationContext run(Class<?>[] primarySources,
			String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

我们继续跟踪new SpringApplication(primarySources).run(args);的run方法。
进入run方法,我们就看到的核心代码,真正创建应用的上下文的地方.
这里,我会只摘取对nacos配置加载和动态更新有用的入口代码。

public ConfigurableApplicationContext run(String... args) {
		ConfigurableApplicationContext context = null;
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
			// 这里预处理环境(就是找到所有配置文件的位置)
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
			// 预处理上下文,加载所有配置信息到enviroment中,供创建bean使用。
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
			refreshContext(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}
		return context;
	}

nacos配置位置查找

ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
//
private ConfigurableEnvironment prepareEnvironment(
			SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		// 环境与处理
		listeners.environmentPrepared(environment);
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader())
					.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}
  • 接下来发送ApplicationEnvironmentPreparedEvent事件
public void environmentPrepared(ConfigurableEnvironment environment) {
		this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
				this.application, this.args, environment));
	}
// 中间省略中间的跟踪代码,注意走executor为空的代码分支。
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
		try {
			listener.onApplicationEvent(event);
		}
		...
	}
  • 找到事件接收者
    我们在越多spring源码时会经常迷失,迷失的原因是spring中使用了大量的观察者模式,往往都是先根据被观察者的类型收集大量的观察观察者(比如Listener),然后循环执行观察者方法。
    这里介绍一个技巧,找实现了ApplicationEnvironmentPreparedEvent事件的接口(观察者),因为预处理环境就发送的这个事件。
    在这里插入图片描述
  • 这样我们就来到了BootstrapApplicationListener.java的onApplicationEvent方法中
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        ConfigurableEnvironment environment = event.getEnvironment();
        if ((Boolean)environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class, true)) {
            if (!environment.getPropertySources().contains("bootstrap")) {
                ConfigurableApplicationContext context = null;
                ....
                if (context == null) {
                	// 设置应用上下文
                    context = this.bootstrapServiceContext(environment, event.getSpringApplication(), configName);
                    event.getSpringApplication().addListeners(new ApplicationListener[]{new BootstrapApplicationListener.CloseContextOnFailureApplicationListener(context)});
                }
                this.apply(context, event.getSpringApplication(), environment);
            }
        }
    }
// 设置应用上下文
private ConfigurableApplicationContext bootstrapServiceContext(ConfigurableEnvironment environment, final SpringApplication application, String configName) {
        StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
        MutablePropertySources bootstrapProperties = bootstrapEnvironment.getPropertySources();
        ...
        // 自动装配对象
        builder.sources(new Class[]{BootstrapImportSelectorConfiguration.class});
        ConfigurableApplicationContext context = builder.run(new String[0]);
        context.setId("bootstrap");
        this.addAncestorInitializer(application, context);
        bootstrapProperties.remove("bootstrap");
        this.mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
        return context;
    }

自动装配nacos配置类型

@Import({BootstrapImportSelector.class})
public class BootstrapImportSelectorConfiguration {
    public BootstrapImportSelectorConfiguration() {
    }
}
 public String[] selectImports(AnnotationMetadata annotationMetadata) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // 从META-INF/spring.factories中加载BootstrapConfiguration.class类
        List<String> names = new ArrayList(SpringFactoriesLoader.loadFactoryNames(BootstrapConfiguration.class, classLoader));
        ..
        return classNames;
    }
  • 可以找到两处加载org.springframework.cloud.bootstrap.BootstrapConfiguration的地方
    spring-cloud-starter-alibaba-nacos-config-2.1.2.RELEASE.jar的META-INF/spring.factories中
    com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.cloud.nacos.NacosConfigAutoConfiguration,\
com.alibaba.cloud.nacos.endpoint.NacosConfigEndpointAutoConfiguration
org.springframework.boot.diagnostics.FailureAnalyzer=\
com.alibaba.cloud.nacos.diagnostics.analyzer.NacosConnectionFailureAnalyzer

spring-cloud-context-3.0.3.jar的META-INF/spring.factories中
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration

# AutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.cloud.autoconfigure.LifecycleMvcEndpointAutoConfiguration,\
org.springframework.cloud.autoconfigure.RefreshAutoConfiguration,\
org.springframework.cloud.autoconfigure.RefreshEndpointAutoConfiguration,\
org.springframework.cloud.autoconfigure.WritableEnvironmentEndpointAutoConfiguration
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.cloud.bootstrap.BootstrapApplicationListener,\
org.springframework.cloud.bootstrap.LoggingSystemShutdownListener,\
org.springframework.cloud.context.restart.RestartListener
# Spring Cloud Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
# Spring Boot BootstrapRegistryInitializer
org.springframework.boot.BootstrapRegistryInitializer=\
org.springframework.cloud.bootstrap.RefreshBootstrapRegistryInitializer
# Spring Boot Bootstrappers
org.springframework.boot.Bootstrapper=\
org.springframework.cloud.bootstrap.TextEncryptorConfigBootstrapper
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.cloud.bootstrap.encrypt.DecryptEnvironmentPostProcessor,\
org.springframework.cloud.util.random.CachedRandomPropertySourceEnvironmentPostProcessor

加载nacos客户端的配置信息

@Configuration
@ConditionalOnProperty(
    name = {"spring.cloud.nacos.config.enabled"},
    matchIfMissing = true
)
public class NacosConfigBootstrapConfiguration {
    public NacosConfigBootstrapConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean
    public NacosConfigProperties nacosConfigProperties() {
        return new NacosConfigProperties();
    }

    @Bean
    @ConditionalOnMissingBean
    public NacosConfigManager nacosConfigManager(NacosConfigProperties nacosConfigProperties) {
        return new NacosConfigManager(nacosConfigProperties);
    }

    @Bean
    public NacosPropertySourceLocator nacosPropertySourceLocator(NacosConfigManager nacosConfigManager) {
        return new NacosPropertySourceLocator(nacosConfigManager);
    }
}

可以看到,自动装配NacosConfigBootstrapConfiguration 对象后,紧接着就是生成NacosConfigProperties对象,最后初始化nacos的配置信息。

public NacosConfigProperties() {
    }

    @PostConstruct
    public void init() {
        this.overrideFromEnv();
    }

    private void overrideFromEnv() {
        if (StringUtils.isEmpty(this.getServerAddr())) {
            String serverAddr = this.environment.resolvePlaceholders("${spring.cloud.nacos.config.server-addr:}");
            if (StringUtils.isEmpty(serverAddr)) {
                serverAddr = this.environment.resolvePlaceholders("${spring.cloud.nacos.server-addr:localhost:8848}");
            }

            this.setServerAddr(serverAddr);
        }

        if (StringUtils.isEmpty(this.getUsername())) {
            this.setUsername(this.environment.resolvePlaceholders("${spring.cloud.nacos.username:}"));
        }

        if (StringUtils.isEmpty(this.getPassword())) {
            this.setPassword(this.environment.resolvePlaceholders("${spring.cloud.nacos.password:}"));
        }
    }

上下文加载nacos中的配置信息

回到SpringApplication.java的run方法

prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);

我们关注的是上下文的初始化方法applyInitializers(context)方法

private void prepareContext(ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
		context.setEnvironment(environment);
		postProcessApplicationContext(context);
		// 上下文初始化
		applyInitializers(context);
		...
	}
protected void applyInitializers(ConfigurableApplicationContext context) {
		for (ApplicationContextInitializer initializer : getInitializers()) {
			Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
					initializer.getClass(), ApplicationContextInitializer.class);
			Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
			initializer.initialize(context);
		}
	}

我们找到之前spring-cloud-context-3.0.3.jar的META-INF/spring.factories中
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration
对应的PropertySourceBootstrapConfiguration类。
在Initialize方法中

 PropertySourceLocator locator = (PropertySourceLocator)var5.next();
                    source = locator.locateCollection(environment);

我们找到PropertySourceLocator的实现类NacosPropertySourceLocator,
我们查看locate方法

public PropertySource<?> locate(Environment env) {
        this.nacosConfigProperties.setEnvironment(env);
       // 创建configService服务
        ConfigService configService = this.nacosConfigManager.getConfigService();
        if (null == configService) {
            log.warn("no instance of config service found, can't load config from nacos");
            return null;
        } else {
            long timeout = (long)this.nacosConfigProperties.getTimeout();
            this.nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService, timeout);
            String name = this.nacosConfigProperties.getName();
            String dataIdPrefix = this.nacosConfigProperties.getPrefix();
            if (StringUtils.isEmpty(dataIdPrefix)) {
                dataIdPrefix = name;
            }

            if (StringUtils.isEmpty(dataIdPrefix)) {
                dataIdPrefix = env.getProperty("spring.application.name");
            }

            CompositePropertySource composite = new CompositePropertySource("NACOS");
            this.loadSharedConfiguration(composite);
            this.loadExtConfiguration(composite);
            // 加载nacos配置
            this.loadApplicationConfiguration(composite, dataIdPrefix, this.nacosConfigProperties, env);
            return composite;
        }
    }
  • 加载nacos配置
private void loadApplicationConfiguration(CompositePropertySource compositePropertySource, String dataIdPrefix, NacosConfigProperties properties, Environment environment) {
        String fileExtension = properties.getFileExtension();
        String nacosGroup = properties.getGroup();
        this.loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup, fileExtension, true);
        ...
    }
private NacosPropertySource loadNacosPropertySource(final String dataId, final String group, String fileExtension, boolean isRefreshable) {
        return NacosContextRefresher.getRefreshCount() != 0L && !isRefreshable ? NacosPropertySourceRepository.getNacosPropertySource(dataId, group) : this.nacosPropertySourceBuilder.build(dataId, group, fileExtension, isRefreshable);
    }
NacosPropertySource build(String dataId, String group, String fileExtension, boolean isRefreshable) {
        Map<String, Object> p = this.loadNacosData(dataId, group, fileExtension);
        NacosPropertySource nacosPropertySource = new NacosPropertySource(group, dataId, p, new Date(), isRefreshable);
        NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);
        return nacosPropertySource;
    }
private Map<String, Object> loadNacosData(String dataId, String group, String fileExtension) {
        String data = null;

        try {
        	// 调用nacos接口查询nacos服务端中的配置信息
            data = this.configService.getConfig(dataId, group, this.timeout);
            ...
    }

spring cloud实现nacos配置的动态更新

应用上下文加载成功后会发送一个ready事件,nacosRefresh实现了该事件作为观察者;
执行事件逻辑中,把对nacos配置感兴趣的listener注册到服务器端。
客户端通过长连接+pull的方式获取服务器端配置变化,一旦配置发送变化,会直接通知到客户端,客户端根据注册的listener发送refreshEvent事件。
RefreshEventListener监听到refreshEvent事件会把更新的配置拷贝到上下文的environment中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring Cloud Alibaba Nacos 是一个服务注册中心和配置中心,可以实现服务的注册与发现、配置的动态管理等功能,同时还提供了容灾和高可用的支持。下面简单介绍如何使用 Nacos 实现 Spring Cloud配置容灾。 首先,在应用的 `pom.xml` 文件中添加如下依赖: ```xml <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2.2.3.RELEASE</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <version>2.2.3.RELEASE</version> </dependency> ``` 然后在 `application.properties` 中配置 Nacos 的地址和应用的名称: ```properties spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 spring.cloud.nacos.discovery.namespace=your-namespace spring.cloud.nacos.config.server-addr=127.0.0.1:8848 spring.cloud.nacos.config.namespace=your-namespace spring.cloud.nacos.config.file-extension=properties spring.application.name=your-application-name ``` 其中 `server-addr` 是 Nacos 的地址,`namespace` 是命名空间,`file-extension` 是配置文件的扩展名,`application.name` 是应用的名称。 接着在 `bootstrap.properties` 中配置应用的环境和配置来源: ```properties spring.profiles.active=dev spring.cloud.nacos.config.prefix=${spring.application.name}-${spring.profiles.active} spring.cloud.nacos.config.group=DEFAULT_GROUP spring.cloud.nacos.config.shared-dataids=${spring.application.name}-${spring.profiles.active}.properties ``` 其中 `spring.profiles.active` 是应用的环境,`prefix` 是配置文件的前缀,`group` 是配置文件所在的分组,`shared-dataids` 是配置文件的名称。 最后,在代码中使用 `@Value` 注解来获取配置项的值: ```java @RestController public class ConfigController { @Value("${config.key}") private String configValue; @GetMapping("/config") public String getConfig() { return configValue; } } ``` 其中 `config.key` 是配置项的名称。 以上就是使用 Nacos 实现 Spring Cloud配置容灾的简单示例。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

融极

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值