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

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

目录
相关文章
|
2月前
|
人工智能 Java Nacos
基于 Spring AI Alibaba + Nacos 的分布式 Multi-Agent 构建指南
本文将针对 Spring AI Alibaba + Nacos 的分布式多智能体构建方案展开介绍,同时结合 Demo 说明快速开发方法与实际效果。
2425 65
|
3月前
|
XML Java Nacos
Spring Boot 整合Nacos 版本兼容适配 史上最详细文档
本文介绍SpringBoot整合Nacos的完整流程,涵盖Nacos下载安装、配置中心与服务发现集成、版本兼容性问题及实战配置。重点解决SpringBoot 3.3.0与Nacos版本适配难题,推荐使用Spring Cloud Alibaba方案,并提供项目开源地址供参考学习。
|
5月前
|
Java Spring
Spring Boot配置的优先级?
在Spring Boot项目中,配置可通过配置文件和外部配置实现。支持的配置文件包括application.properties、application.yml和application.yaml,优先级依次降低。外部配置常用方式有Java系统属性(如-Dserver.port=9001)和命令行参数(如--server.port=10010),其中命令行参数优先级高于系统属性。整体优先级顺序为:命令行参数 &gt; Java系统属性 &gt; application.properties &gt; application.yml &gt; application.yaml。
1031 0
|
5月前
|
Dubbo 数据可视化 Java
整合SpringBoot、Dubbo与Nacos:一个快速入门教程
经过上述步骤,消费者模块成功引用了生产者提供的服务,并通过Spring Web将服务映射到了特定的URL路径上。消费者模块成功地调用并展示了生产者提供的数据,并在不移除特定依赖项的情况下确保了系统的正常运行。
|
2月前
|
JavaScript Java Maven
【SpringBoot(二)】带你认识Yaml配置文件类型、SpringMVC的资源访问路径 和 静态资源配置的原理!
SpringBoot专栏第二章,从本章开始正式进入SpringBoot的WEB阶段开发,本章先带你认识yaml配置文件和资源的路径配置原理,以方便在后面的文章中打下基础
358 3
|
3月前
|
缓存 Java 应用服务中间件
Spring Boot配置优化:Tomcat+数据库+缓存+日志,全场景教程
本文详解Spring Boot十大核心配置优化技巧,涵盖Tomcat连接池、数据库连接池、Jackson时区、日志管理、缓存策略、异步线程池等关键配置,结合代码示例与通俗解释,助你轻松掌握高并发场景下的性能调优方法,适用于实际项目落地。
661 5
|
3月前
|
传感器 Java 数据库
探索Spring Boot的@Conditional注解的上下文配置
Spring Boot 的 `@Conditional` 注解可根据不同条件动态控制 Bean 的加载,提升应用的灵活性与可配置性。本文深入解析其用法与优势,并结合实例展示如何通过自定义条件类实现环境适配的智能配置。
215 0
探索Spring Boot的@Conditional注解的上下文配置
|
4月前
|
安全 算法 Java
在Spring Boot中应用Jasypt以加密配置信息。
通过以上步骤,可以在Spring Boot应用中有效地利用Jasypt对配置信息进行加密,这样即使配置文件被泄露,其中的敏感信息也不会直接暴露给攻击者。这是一种在不牺牲操作复杂度的情况下提升应用安全性的简便方法。
1081 10
|
5月前
|
人工智能 安全 Java
Spring Boot yml 配置敏感信息加密
本文介绍了如何在 Spring Boot 项目中使用 Jasypt 实现配置文件加密,包含添加依赖、配置密钥、生成加密值、在配置中使用加密值及验证步骤,并提供了注意事项,确保敏感信息的安全管理。
1233 1