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>
AI 代码解读

添加配置:项目本地配置文件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
AI 代码解读

使用案例

/**
 * 注意   注意    注意
 * 使用@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;
}
AI 代码解读

这时候我们启动项目,获取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
AI 代码解读

这里发现我导入的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);
    }
AI 代码解读

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();
    }
AI 代码解读

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;
    }
AI 代码解读

跟源码你会发现:在执行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);
        }
    }
AI 代码解读

这里就很清楚看到,把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);
        }
    }
AI 代码解读

这里执行各个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());
        }
    }
AI 代码解读

这样,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 {
   

}
AI 代码解读

这里@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) {
   

    }
}
AI 代码解读

这里将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;
    }
}
AI 代码解读

从上面源码可以看出,实现了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);
        }
    }
AI 代码解读

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

    private StandardEnvironment environment = new StandardEnvironment();
AI 代码解读

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

目录
打赏
0
0
0
0
140
分享
相关文章
高效搭建Nacos:实现微服务的服务注册与配置中心
Nacos(Dynamic Naming and Configuration Service)是阿里巴巴开源的一款动态服务发现、配置管理和服务管理平台。它旨在帮助开发者更轻松地构建、部署和管理分布式系统,特别是在微服务架构中。
615 81
高效搭建Nacos:实现微服务的服务注册与配置中心
springcloud/springboot集成NACOS 做注册和配置中心以及nacos源码分析
通过本文,我们详细介绍了如何在 Spring Cloud 和 Spring Boot 中集成 Nacos 进行服务注册和配置管理,并对 Nacos 的源码进行了初步分析。Nacos 作为一个强大的服务注册和配置管理平台,为微服务架构提供
81 14
详细介绍SpringBoot启动流程及配置类解析原理
通过对 Spring Boot 启动流程及配置类解析原理的深入分析,我们可以看到 Spring Boot 在启动时的灵活性和可扩展性。理解这些机制不仅有助于开发者更好地使用 Spring Boot 进行应用开发,还能够在面对问题时,迅速定位和解决问题。希望本文能为您在 Spring Boot 开发过程中提供有效的指导和帮助。
59 12
SpringBoot自动配置及自定义Starter
Java程序员依赖Spring框架简化开发,但复杂的配置文件增加了负担。SpringBoot以“约定大于配置”理念简化了这一过程,通过引入各种Starter并加载默认配置,几乎做到开箱即用。
131 10
SpringBoot自动配置及自定义Starter
|
2月前
|
使用Spring Boot集成Nacos
通过上述步骤,Spring Boot应用可以成功集成Nacos,利用Nacos的服务发现和配置管理功能来提升微服务架构的灵活性和可维护性。通过这种集成,开发者可以更高效地管理和部署微服务。
274 17
SpringBoot配置跨模块扫描问题解决方案
在分布式项目中,使用Maven进行多模块开发时,某些模块(如xxx-common)没有启动类。如何将这些模块中的类注册为Spring管理的Bean对象?本文通过案例分析,介绍了两种解决方案:常规方案是通过`@SpringBootApplication(scanBasePackages)`指定扫描路径;推荐方案是保持各模块包结构一致(如com.xxx),利用SpringBoot默认扫描规则自动识别其他模块中的组件,简化配置。
SpringBoot配置跨模块扫描问题解决方案
SpringCloud 应用 Nacos 配置中心注解
在 Spring Cloud 应用中可以非常低成本地集成 Nacos 实现配置动态刷新,在应用程序代码中通过 Spring 官方的注解 @Value 和 @ConfigurationProperties,引用 Spring enviroment 上下文中的属性值,这种用法的最大优点是无代码层面侵入性,但也存在诸多限制,为了解决问题,提升应用接入 Nacos 配置中心的易用性,Spring Cloud Alibaba 发布一套全新的 Nacos 配置中心的注解。
393 17
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
211 14
Nacos 配置中心变更利器:自定义标签灰度
本文是对 MSE Nacos 应用自定义标签灰度的功能介绍,欢迎大家升级版本进行试用。
643 20

热门文章

最新文章