springboot整合nacos做配置中心的一次问题排查

简介: 这里先把问题陈述一下:**springboot整合nacos做配置中心,使用@NacosConfigurationProperties注解注入bean属性,当nacos前台客户端没有配置这个bean的属性,项目

这里先把问题陈述一下:springboot整合nacos做配置中心,使用@NacosConfigurationProperties注解注入bean属性,当nacos前台客户端没有配置这个bean的属性,项目代码本地配置文件配置了,此时bean的属性并没有注入进来

我们都知道nacos既可以做注册中心,又可以做配置中心,这里我们主要讲nacos做配置中心,从而实现配置动态刷新

1.springboot整合nacos做配置中心

springboot整合nacos很简单,只需要几步:

引入依赖

      <dependency>
        <groupId>com.alibaba.boot</groupId>
        <artifactId>nacos-config-spring-boot-starter</artifactId>
        <version>0.2.10</version>
      </dependency>

添加配置:项目本地配置文件application.yml

nacos:
  config:
    server-addr: 10.10.0.14:8848
    namespace: 6de43a50-61da-426a-9b2c-df474034c13d
    type: properties
    data-id: example.properties
    bootstrap:
      enable: true
 user1:
  name: shepherd-local
  age: 18
  id: 127

使用案例

/**
 * 注意   注意    注意
 * 使用@ConfigurationProperties注解如果不走配置中心,或者走配置中心但没有配置当前配置属性信息,这时候会加载本地配置问题
 * 使用@ConfigurationProperties不会动态更新
 * 使用@NacosConfigurationProperties注解如果不走配置中心,或者走配置中心但没有配置当前配置属性信息,这时候即时本地文件配置了也不会加载注入属性
 */
@Data
@Component
@NacosConfigurationProperties(prefix = "user1", autoRefreshed = true, dataId = "example.properties")
//@ConfigurationProperties(prefix = "user1")
public class User {
   
    private String name;
    private Integer age;
    private Long id;
}

这时候我们启动项目,获取user bean对象,你会发现,假如我们没有在nacos前台客户端的添加相应的属性配置,但是在项目代码本地配置了,如上所示,却发现属性没有注入进来!!!这时候就很疑惑了,按道理,如果nacos配置中心没有配置,是不是应该读取本地配置文件的配置呢?我想大部分的人都是这么认为的,当然也有可能是我们自己使用不当,为了消除疑惑,接下来只能debug源码了

项目推荐:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装,解决业务开发时常见的非功能性需求,防止重复造轮子,方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化,做到可插拔。严格控制包依赖和统一版本管理,做到最少化依赖。注重代码规范和注释,非常适合个人学习和企业使用

Github地址https://github.com/plasticene/plasticene-boot-starter-parent

Gitee地址https://gitee.com/plasticene3/plasticene-boot-starter-parent

微信公众号Shepherd进阶笔记

交流探讨群:Shepherd_126

2.源码解析

我们都知道,按照spring boot的自动装配原理,要开启什么功能,只需要引入依赖,就会有一个xxxAutoConfiguration自动配置类,nacos也不例外,在导入的nacos依赖jar包里面可以找到自动配置的spring.factories文件:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.alibaba.boot.nacos.config.autoconfigure.NacosConfigAutoConfiguration
#org.springframework.context.ApplicationContextInitializer=\
#  com.alibaba.boot.nacos.config.autoconfigure.NacosConfigApplicationContextInitializer
org.springframework.boot.env.EnvironmentPostProcessor=\
  com.alibaba.boot.nacos.config.autoconfigure.NacosConfigEnvironmentProcessor
org.springframework.context.ApplicationListener=\
  com.alibaba.boot.nacos.config.logging.NacosLoggingListener

这里发现我导入的0.2.10版本的NacosConfigApplicationContextInitializer被注释掉了, ApplicationContextInitializer 是一个回调接口,用于 Spring ConfigurableApplicationContext 容器执行 #refresh() 方法进行初始化之前,提前走一些自定义的初始化逻辑。这里注释掉说明不会走自动装配,说明在启动时候别的地方会加载这个NacosConfigApplicationContextInitializer初始化类:

SpringApplicationrun()方法开始:

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

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

SpringApplication的构造方法:

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }

setInitializers()方法就是加载各种ApplicationContextInitializer初始化类的,但是这里是从spring.factories中查找、自动装配,而我们根据前面分享已经知道被注释掉了,自然这里是加载不到NacosConfigApplicationContextInitializer类,那说明是在后面执行的run()方法加载进来的:

public ConfigurableApplicationContext run(String... args) {
   
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
   
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            configureIgnoreBeanInfo(environment);
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                    new Class[] {
    ConfigurableApplicationContext.class }, context);
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
   
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
   
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {
   
            listeners.running(context);
        }
        catch (Throwable ex) {
   
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }

跟源码你会发现:在执行prepareEnvironment()方法会调用NacosConfigEnvironmentProcessorpostProcessEnvironment()方法:

    @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);
        }
    }

这里就很清楚看到,把NacosConfigApplicationContextInitializer添加到了初始化类集合中。接下来执行SpringApplication

prepareContext方法,其调用了applyInitializers()方法:

    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);
        }
    }

这里执行各个ApplicationContextInitializer初始化类的回调方法initialize(),显然NacosConfigApplicationContextInitializer的初始化方法也是在这里执行了

    @Override
    public void initialize(ConfigurableApplicationContext context) {
   
        singleton.setApplicationContext(context);
        environment = context.getEnvironment();
        nacosConfigProperties = NacosConfigPropertiesUtils
                .buildNacosConfigProperties(environment);
        final NacosConfigLoader configLoader = new NacosConfigLoader(
                nacosConfigProperties, environment, builder);
        if (!enable()) {
   
            logger.info("[Nacos Config Boot] : The preload configuration is not enabled");
        }
        else {
   

            // If it opens the log level loading directly will cache
            // DeferNacosPropertySource release

            if (processor.enable()) {
   
                processor.publishDeferService(context);
                configLoader
                        .addListenerIfAutoRefreshed(processor.getDeferPropertySources());
            }
            else {
   
                configLoader.loadConfig();
                configLoader.addListenerIfAutoRefreshed();
            }
        }

        final ConfigurableListableBeanFactory factory = context.getBeanFactory();
        if (!factory
                .containsSingleton(NacosBeanUtils.GLOBAL_NACOS_PROPERTIES_BEAN_NAME)) {
   
            factory.registerSingleton(NacosBeanUtils.GLOBAL_NACOS_PROPERTIES_BEAN_NAME,
                    configLoader.buildGlobalNacosProperties());
        }
    }

这样,nacos的上下文环境配置就初始化好了

自动配置类

@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 {
   

}

这里@Condition相关注解就是典型的条件装配,没什么好说的,@EnableConfigurationProperties开启NacosConfigProperties类的属性注入,最后是用@Import导入NacosConfigBootBeanDefinitionRegistrar类,这是我们需要重点关注的地方

@Configuration
public class NacosConfigBootBeanDefinitionRegistrar
        implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
   

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
   
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory;
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
                .rootBeanDefinition(NacosBootConfigurationPropertiesBinder.class);
        defaultListableBeanFactory.registerBeanDefinition(
                NacosBootConfigurationPropertiesBinder.BEAN_NAME,
                beanDefinitionBuilder.getBeanDefinition());
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
            BeanDefinitionRegistry registry) {
   

    }
}

这里将NacosBootConfigurationPropertiesBinder类以beanName为nacosConfigurationPropertiesBinder注册到deanDefinition注册中心,方便后续获取这个bean。

接下来跟源码就到NacosConfigurationPropertiesBindingPostProcessor,看类名称就知道这个类是对使用了@NacosConfigurationProperties注解的类进行属性绑定的

public class NacosConfigurationPropertiesBindingPostProcessor
        implements BeanPostProcessor, ApplicationContextAware {
   

    /**
     * The name of {@link NacosConfigurationPropertiesBindingPostProcessor} Bean
     */
    public static final String BEAN_NAME = "nacosConfigurationPropertiesBindingPostProcessor";

    private Properties globalNacosProperties;

    private NacosServiceFactory nacosServiceFactory;

    private Environment environment;

    private ApplicationEventPublisher applicationEventPublisher;

    private ConfigurableApplicationContext applicationContext;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
   

        NacosConfigurationProperties nacosConfigurationProperties = findAnnotation(
                bean.getClass(), NacosConfigurationProperties.class);

        if (nacosConfigurationProperties != null) {
   
            bind(bean, beanName, nacosConfigurationProperties);
        }

        return bean;
    }

    private void bind(Object bean, String beanName,
            NacosConfigurationProperties nacosConfigurationProperties) {
   

        NacosConfigurationPropertiesBinder binder;
        try {
   
            binder = applicationContext.getBean(
                    NacosConfigurationPropertiesBinder.BEAN_NAME,
                    NacosConfigurationPropertiesBinder.class);
            if (binder == null) {
   
                binder = new NacosConfigurationPropertiesBinder(applicationContext);
            }

        }
        catch (Exception e) {
   
            binder = new NacosConfigurationPropertiesBinder(applicationContext);
        }

        binder.bind(bean, beanName, nacosConfigurationProperties);

    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {
   
        return bean;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
   
        this.applicationContext = (ConfigurableApplicationContext) applicationContext;
    }
}

从上面源码可以看出,实现了BeanPostProcessor, 并且重写了前置处理方法postProcessBeforeInitialization,这里判断当前bean有没有使用NacosConfigurationProperties注解, 有的话接着就做属性注入绑定操作。

属性注入绑定最终执行到NacosBootConfigurationPropertiesBinderdobinder()方法:这是消除疑惑核心方法

    @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;
      // 从nacos配置中心加载属性源
            NacosPropertySource propertySource = new NacosPropertySource(name, dataId, groupId, content, configType);
            // 将属性源放到environment环境中
      environment.getPropertySources().addLast(propertySource);
      // 绑定环境
            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);
        }
    }

这里我们发现environment是在当前类new出来的,然后从nacos配置中心获取属性源加入到当前environment中,并没有从spring上下文中获取environment,所以代码本地的配置文件这里是不会被操作的,自然本地配置的属性也不能被注入进来

    private StandardEnvironment environment = new StandardEnvironment();

至于为什么这么实现不得而知。。。

目录
相关文章
|
14天前
|
Cloud Native Java Nacos
微服务时代的新宠儿!Spring Cloud Nacos实战指南,带你玩转服务发现与配置管理,拥抱云原生潮流!
【8月更文挑战第29天】Spring Cloud Nacos作为微服务架构中的新兴之星,凭借其轻量、高效的特点,迅速成为服务发现、配置管理和治理的首选方案。Nacos(命名和配置服务)由阿里巴巴开源,为云原生应用提供了动态服务发现及配置管理等功能,简化了服务间的调用与依赖管理。本文将指导你通过五个步骤在Spring Boot项目中集成Nacos,实现服务注册、发现及配置动态管理,从而轻松搭建出高效的微服务环境。
77 0
|
13天前
|
安全 Nacos 数据安全/隐私保护
升级指南:从Nacos 1.3.0 到 2.3.0,并兼容 Seata 的鉴权配置
本文详细介绍了如何在微服务环境下从 Nacos 1.3.0 升级到 2.3.0,并确保 Seata 各版本的兼容性。作者小米分享了升级过程中的关键步骤,包括备份配置、更新鉴权信息及验证测试等,并解答了常见问题。通过这些步骤,可以帮助读者顺利完成升级并提高系统的安全性与一致性。
59 8
升级指南:从Nacos 1.3.0 到 2.3.0,并兼容 Seata 的鉴权配置
|
16天前
|
Cloud Native Java Nacos
Spring Cloud Config、Apollo、Nacos和Archaius对比
这篇文章对比了Spring Cloud Config、Apollo、Nacos和Archaius这四种配置中心的适应场景、优缺点。文中讨论了它们的功能特点,例如Spring Cloud Config的集中化配置管理和动态刷新能力,Apollo的实时配置推送和权限治理,Nacos的服务发现和管理功能,以及Archaius的动态配置更新能力。文章指出选择配置中心应根据项目需求和架构来决定,并提供了一个对比图来帮助读者更直观地理解这些工具的差异。
31 1
Spring Cloud Config、Apollo、Nacos和Archaius对比
|
10天前
|
IDE Java 开发工具
还在为繁琐的配置头疼吗?一文教你如何用 Spring Boot 快速启动,让开发效率飙升,从此告别加班——打造你的首个轻量级应用!
【9月更文挑战第2天】Spring Boot 是一款基于 Spring 框架的简化开发工具包,采用“约定优于配置”的原则,帮助开发者快速创建独立的生产级应用程序。本文将指导您完成首个 Spring Boot 项目的搭建过程,包括环境配置、项目初始化、添加依赖、编写控制器及运行应用。首先需确保 JDK 版本不低于 8,并安装支持 Spring Boot 的现代 IDE,如 IntelliJ IDEA 或 Eclipse。
40 5
|
26天前
|
运维 Java Nacos
Spring Cloud应用框架:Nacos作为服务注册中心和配置中心
Spring Cloud应用框架:Nacos作为服务注册中心和配置中心
|
11天前
|
Java Spring 开发者
解锁 Spring Boot 自动化配置的黑科技:带你走进一键配置的高效开发新时代,再也不怕繁琐设置!
【8月更文挑战第31天】Spring Boot 的自动化配置机制极大简化了开发流程,使开发者能专注业务逻辑。通过 `@SpringBootApplication` 注解组合,特别是 `@EnableAutoConfiguration`,Spring Boot 可自动激活所需配置。例如,添加 JPA 依赖后,只需在 `application.properties` 配置数据库信息,即可自动完成 JPA 和数据源设置。这一机制基于多种条件注解(如 `@ConditionalOnClass`)实现智能配置。深入理解该机制有助于提升开发效率并更好地解决问题。
24 0
|
14天前
|
缓存 Java 数据库连接
Spring Boot 资源文件属性配置,紧跟技术热点,为你的应用注入灵动活力!
【8月更文挑战第29天】在Spring Boot开发中,资源文件属性配置至关重要,它让开发者能灵活定制应用行为而不改动代码,极大提升了可维护性和扩展性。Spring Boot支持多种配置文件类型,如`application.properties`和`application.yml`,分别位于项目的resources目录下。`.properties`文件采用键值对形式,而`yml`文件则具有更清晰的层次结构,适合复杂配置。此外,Spring Boot还支持占位符引用和其他外部来源的属性值,便于不同环境下覆盖默认配置。通过合理配置,应用能快速适应各种环境与需求变化。
26 0
|
27天前
|
缓存 Cloud Native Java
【紧急救援】Nacos配置上线后失效?手把手教你如何轻松搞定命名空间修改难题!
【8月更文挑战第15天】Nacos是关键的云原生服务管理平台,用于动态服务发现与配置管理。但在使用其管理微服务配置时,可能会遇到命名空间内的配置更新后不生效的问题。本文探讨此问题并提供解决方案。首先需确认Nacos服务器运行正常及客户端正确连接。接着检查客户端缓存配置,可通过禁用缓存或缩短缓存间隔来即时更新配置。例如,在Spring Cloud Alibaba Nacos配置中心中启用自动刷新功能,并设置每5秒拉取新配置。同时,对于新增配置项,需重启客户端应用。还需检查Nacos服务器日志排除异常,并考虑升级Nacos版本解决兼容性问题。通过这些步骤,通常可有效解决配置不生效的难题。
30 0
|
27天前
|
Dubbo Java Nacos
【实战攻略】破解Dubbo+Nacos+Spring Boot 3 Native打包后运行异常的终极秘籍——从零开始彻底攻克那些让你头疼不已的技术难题!
【8月更文挑战第15天】Nacos作为微服务注册与配置中心受到欢迎,但使用Dubbo+Nacos+Spring Boot 3进行GraalVM native打包后常遇运行异常。本文剖析此问题及其解决策略:确认GraalVM版本兼容性;配置反射列表以支持必要类和方法;采用静态代理替代动态代理;检查并调整配置文件;禁用不支持的功能;利用日志和GraalVM诊断工具定位问题;根据诊断结果调整GraalVM配置。通过系统排查方法,能有效解决此类问题,确保服务稳定运行。
48 0
|
2月前
|
Java Nacos 数据库
使用 nacos 搭建注册中心及配置中心
使用 nacos 搭建注册中心及配置中心
63 5