目录
一、前言
二、问题再现
三、原因分析
1、项目启动
2、环境信息加载
3、扫描spring.factories
4、实例化对象
一、前言
本篇主要介绍在nacos作为配置中心时,配置信息在application.properties(或yml)以及在bootstrap.properties(或yml)中的区别,以及遇到的问题,通过源码分析及debug调试跟踪,来深入理解nacos作为配置中心时,是如何加载配置信息的。
二、问题再现
在项目中创建bootstrap.properties和application.properties两个配置文件,nacos配置信息先放在application.properties中,bootstrap.properties为空文件,看下会出现什么问题
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>
项目启动:
此时控制台报错:
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:
springboot开始配置环境信息,如上图。
通过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(...){
...
}
}
继续断点跟踪
然后在loadFactoryNames(...)方法中开始扫描所有 spring.factories
naocs作为配置中心时,依赖中也配置了spring.factories:
配置信息如下:
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,此时还没有开始将对象实例化。
继续跟踪:
获取 NacosConfigBootstrapConfiguration 对象中的方法信息。
加载完成,列表中一共七项,然后开始加载bean定义信息,为实例化做好准备。
4、实例化对象
准备就绪后,开始实例化对象,可以看到对象的属性信息都为默认值,通过断点调试跟踪nacos配置信息的赋值过程。
上图可知,通过反射调用NacosConfigBootstrapConfiguration中的nacosConfigProperties()方法。
继续断点跟踪NacosConfigProperties实例化过程:
NacosConfigProperties实例化后,会执行 @Postconstruct(自动初始化) 修饰的init()方法。
在overrideFromEnv()方法中,会对nacos的serverAddr、userName、password进行初始化。
属性信息从enviroment中获取:
enviroment的属性信息来源于config/bootstrap.properties,此时还没有完成环境的初始化,也就是还没开始加载应用的配置信息,config/application.properties 并没有被加载。
此处创建的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构造方法如下:
如上图,在NacosConfigService构造方法中,会发起http请求 ,因无法链接nacos配置中心服务端,导致service创建失败。
整个过程,都没有从config/application.properties中读取配置信息,所以根据项目配置文件启动优先级,链接数据库、redis、nacos等配置信息,最好在bootstrap.properties(或yml)中配置 。