目录

一、前言

二、问题再现

三、原因分析

1、项目启动

2、环境信息加载

3、扫描spring.factories

4、实例化对象


一、前言

本篇主要介绍在nacos作为配置中心时,配置信息在application.properties(或yml)以及在bootstrap.properties(或yml)中的区别,以及遇到的问题,通过源码分析及debug调试跟踪,来深入理解nacos作为配置中心时,是如何加载配置信息的。

二、问题再现

在项目中创建bootstrap.properties和application.properties两个配置文件,nacos配置信息先放在application.properties中,bootstrap.properties为空文件,看下会出现什么问题

NacosTraceSubscriber 的使用_spring boot

application.properties中配置信息:

服务端口
server.port=8081
#服务名称
spring.application.name=zhufeng-web-user
#logback日志
logging.config=classpath:config/logback.xml
#nacos注册中心地址
spring.cloud.nacos.discovery.server-addr=10.211.55.9:8848,10.211.55.10:8848,10.211.55.11:8848
#nacos配置中心地址
spring.cloud.nacos.config.server-addr=10.211.55.9:8848,10.211.55.10:8848,10.211.55.11:884
#配置文件类型
spring.cloud.nacos.config.file-extension=properties

nacos的pom依赖信息:

<!--注册中心客户端 -->
<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
	<exclusions>
		<!--移除默认版本,默认为2.x版本-->
		<exclusion>
			<groupId>com.alibaba.nacos</groupId>
			<artifactId>nacos-client</artifactId>
		</exclusion>
	</exclusions>
</dependency>
<!--nacos的配置中心的依赖-->
<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
	<exclusions>
		<!--移除默认版本,默认为2.x版本-->
		<exclusion>
			<groupId>com.alibaba.nacos</groupId>
			<artifactId>nacos-client</artifactId>
		</exclusion>
	</exclusions>
</dependency>
<dependency>
	<groupId>com.alibaba.nacos</groupId>
	<artifactId>nacos-client</artifactId>
	<version>1.4.3</version>
</dependency>

springcloud版本信息

<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.7.RELEASE</spring-cloud-alibaba.version>

项目启动:

NacosTraceSubscriber 的使用_spring cloud_02

此时控制台报错:

Caused by: java.lang.IllegalArgumentException: The IPv4 or Domain address("localhost") is incorrect.
	at com.alibaba.nacos.common.utils.IPUtil.splitIPPortStr(IPUtil.java:138) ~[nacos-common-1.4.3.jar:na]
	at com.alibaba.nacos.client.config.impl.ServerListManager.<init>(ServerListManager.java:162) ~[nacos-client-1.4.3.jar:na]
	at com.alibaba.nacos.client.config.http.ServerHttpAgent.<init>(ServerHttpAgent.java:274) ~[nacos-client-1.4.3.jar:na]
	at com.alibaba.nacos.client.config.NacosConfigService.<init>(NacosConfigService.java:88) ~[nacos-client-1.4.3.jar:na]
	at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:67) ~[na:na]
	... 72 common frames omitted

本地没有启动nacos服务端,所以通过localhost链接nacos会报错,问题是为什么这里会获取localhost,而没有通过applicaiton.properties配置的nacos地址去链接?

三、原因分析

1、项目启动

/**
 * @ClassName: UserApplication
 * @Description 启动类
 * @author 月夜烛峰
 * @date 2022/7/21 13:50
 */
@EnableDiscoveryClient
@SpringBootApplication
public class UserApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class, args);
    }
}

项目通过SpringApplication.run(...)的方式启动,首先看下源码,这里做了什么

/**
 * Static helper that can be used to run a {@link SpringApplication} from the
 * specified source using default settings.
 * @param primarySource the primary source to load
 * @param args the application arguments (usually passed from a Java main method)
 * @return the running {@link ApplicationContext}
 */
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
	return run(new Class<?>[] { primarySource }, args);
}

/**
 * Static helper that can be used to run a {@link SpringApplication} from the
 * specified sources using default settings and user supplied arguments.
 * @param primarySources the primary sources to load
 * @param args the application arguments (usually passed from a Java main method)
 * @return the running {@link ApplicationContext}
 */
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
	return new SpringApplication(primarySources).run(args);
}

通过注释及方法明明规范可以看出,项目启动的时候先加载配置信息,也就是当前的系统信息、JDK信息、配置文件信息等。 

2、环境信息加载

源码见 ApplicationContextAwareProcessor.java:

NacosTraceSubscriber 的使用_spring cloud_03

springboot开始配置环境信息,如上图。

NacosTraceSubscriber 的使用_java_04

通过debug可以看出,环境信息environment中有七个环境变量,其中第七个为我们自己配置的配置文件: config/bootstrap.properties

这时候,项目还在启动阶段,尚未初始化完成,从这里可以可以看出bootstrap.properties被加载的优先级非常高。

3、扫描spring.factories

加载完环境信息,紧接着开始扫描项目以及jar包中的 META-INF/spring.factories 。

源码参见:SpringFactoriesLoader.java

/**
 * General purpose factory loading mechanism for internal use within the framework.
 *
 * <p>{@code SpringFactoriesLoader} {@linkplain #loadFactories loads} and instantiates
 * factories of a given type from {@value #FACTORIES_RESOURCE_LOCATION} files which
 * may be present in multiple JAR files in the classpath. The {@code spring.factories}
 * file must be in {@link Properties} format, where the key is the fully qualified
 * name of the interface or abstract class, and the value is a comma-separated list of
 * implementation class names. For example:
 *
 * <pre class="code">example.MyService=example.MyServiceImpl1,example.MyServiceImpl2</pre>
 *
 * where {@code example.MyService} is the name of the interface, and {@code MyServiceImpl1}
 * and {@code MyServiceImpl2} are two implementations.
 *
 * @author Arjen Poutsma
 * @author Juergen Hoeller
 * @author Sam Brannen
 * @since 3.2
 */
public final class SpringFactoriesLoader {

	/**
	 * The location to look for factories.
	 * <p>Can be present in multiple JAR files.
	 */
	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
	
	...
	
	public static <T> List<T> loadFactories(...){
		...
	}
	
	public static List<String> loadFactoryNames(...){
		...
	}
}

继续断点跟踪

NacosTraceSubscriber 的使用_java_05

 然后在loadFactoryNames(...)方法中开始扫描所有 spring.factories

NacosTraceSubscriber 的使用_spring cloud_06

naocs作为配置中心时,依赖中也配置了spring.factories:

NacosTraceSubscriber 的使用_java_07

 配置信息如下: 

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
org.springframework.boot.env.PropertySourceLoader=\
com.alibaba.cloud.nacos.parser.NacosJsonPropertySourceLoader,\
com.alibaba.cloud.nacos.parser.NacosXmlPropertySourceLoader
org.springframework.context.ApplicationListener=\
com.alibaba.cloud.nacos.logging.NacosLoggingListener

我们这里优先关注这条配置信息:

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration

循环遍历获取到 NacosConfigBootstrapConfiguration,此时还没有开始将对象实例化。

继续跟踪:

NacosTraceSubscriber 的使用_bootstrap_08

获取 NacosConfigBootstrapConfiguration 对象中的方法信息。

NacosTraceSubscriber 的使用_spring cloud_09

加载完成,列表中一共七项,然后开始加载bean定义信息,为实例化做好准备。

4、实例化对象

准备就绪后,开始实例化对象,可以看到对象的属性信息都为默认值,通过断点调试跟踪nacos配置信息的赋值过程。

NacosTraceSubscriber 的使用_spring cloud_10

上图可知,通过反射调用NacosConfigBootstrapConfiguration中的nacosConfigProperties()方法。

NacosTraceSubscriber 的使用_微服务_11

继续断点跟踪NacosConfigProperties实例化过程:

NacosTraceSubscriber 的使用_spring cloud_12

NacosConfigProperties实例化后,会执行 @Postconstruct(自动初始化) 修饰的init()方法。

在overrideFromEnv()方法中,会对nacos的serverAddr、userName、password进行初始化。

属性信息从enviroment中获取:

NacosTraceSubscriber 的使用_微服务_13

enviroment的属性信息来源于config/bootstrap.properties,此时还没有完成环境的初始化,也就是还没开始加载应用的配置信息,config/application.properties 并没有被加载。

NacosTraceSubscriber 的使用_spring cloud_14

此处创建的service对象为NacosConfigService,ErrorCode为 -400

public static ConfigService createConfigService(Properties properties) throws NacosException {
	try {
		Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.config.NacosConfigService");
		Constructor constructor = driverImplClass.getConstructor(Properties.class);
		ConfigService vendorImpl = (ConfigService)constructor.newInstance(properties);
		return vendorImpl;
	} catch (Throwable var4) {
		throw new NacosException(-400, var4);
	}
}

NacosConfigService构造方法如下:

NacosTraceSubscriber 的使用_spring cloud_15

如上图,在NacosConfigService构造方法中,会发起http请求 ,因无法链接nacos配置中心服务端,导致service创建失败。

整个过程,都没有从config/application.properties中读取配置信息,所以根据项目配置文件启动优先级,链接数据库、redis、nacos等配置信息,最好在bootstrap.properties(或yml)中配置 。