从源码角度,解析Nacos Config客户端与Spring Boot、Spring Cloud的深度集成

原创博文,转载请注明来源

Nacos与Spring Boot集成

@NacosPropertySource和@NacosValue

@PropertySource的用法并不陌生,它是spring原生的注解,我们可以这么用:

@Configuration
@PropertySource(value = "classpath:demo.properties",ignoreResourceNotFound = false)
public class SpringPropertysourceApplication {
//...
}

意思是:把在classpath路径下,名为demo.properties的配置文件注入到spring容器中,这样,我们就可以直接在类的属性上通过@Value注解获取到demo.properties属性值了。

Nacos为了达到以上目的,提供了一个叫@NacosPropertySource的注解,和@PropertySource目的一样:把配置注入到spring容器;使用方式一样,用于任意被spring管理的类上。当然,Nacos提供了更高级的功能,比如Property变更,自动刷新的功能,下面来分析一下,Nacos是怎么集成的

com.alibaba.nacos.spring.core.env.NacosPropertySourcePostProcessor

这个类实现了org.springframework.beans.factory.config.BeanFactoryPostProcessor(Spring钩子,它在所有spring bean定义生成后,实例化之前调用,允许覆盖或添加其属性)等接口,主要作用是,扫描由spring所有的bean,查看其类上,是否有@NacosPropertySource注解,如果有的话,则生成com.alibaba.nacos.spring.core.env.NacosPropertySource实例对象(@NacosPropertySource注解标注了当前PropertySource指定的DataId,也就是一个完成的配置文件,生成实例其实就是调用Nacos原生API获取配置构造NacosPropertySource对象),再把实例添加到spring env.PropertySources中去,其实完成这几步,我们就可以通过使用@Value或者ENV.getProperty()这种方式获取到由Nacos管理的配置项了。

NacosPropertySourcePostProcessor代码片段:



上面的功能仅仅是把Nacos的配置注入到spring中,那动态刷新的功能怎么做的呢。

回顾一下,原生的Nacos sdk是怎么样监听配置变化的

ConfigService configService = NacosFactory.createConfigService(properties);
String content = configService.getConfig(dataId, group, 5000);
System.out.println(content);
configService.addListener(dataId, group, new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
System.out.println("recieve:" + configInfo);
}
@Override
public Executor getExecutor() {
return null;
}
});

可以看到,监听器与配置文件(DataId)是一对一的,所以,对于一个NacosPropertySource来说,应该有一个对应的监听器,在上诉NacosPropertySourcePostProcessor的代码片段截图中可以看到对应的代码:



这个添加listener的逻辑可以根据上面Nacos sdk的用法得出:

可以看到,在listener回调的逻辑里面,当有配置变更时会重新生成NacosPropertySource并替换掉ENV中过时的NacosPropertySource,完成这个部分的逻辑,我们通过ENV.getProperty()就可以动态获取到属性值了,但是通过@Value方式注入的到Bean对象的配置项,由于Bean已经生成,还是没办法动态更新,那Nacos是怎么做的?

com.alibaba.nacos.spring.context.annotation.config.NacosValueAnnotationBeanPostProcessor

上面说到如果通过ENV获取配置项的话,已经可以做到动态的目的了,但是如果此时持有配置项的Bean已经生成,则需要通过反射的机制,去动态更新了,从功能设计角度举个例子来讲清原理:

有类TestController,通过@Value的方式把demo.properties中app.config.threshold的配置项注入到属性threshold,那应该是这样的:

@RestController
@PropertySource(value = "classpath:demo.properties")
public class TestController {
@Value("${app.config.threshold}")
private String threshold; }

那如果要通过反射设置属性的话那就需要这么一个映射关系:

app.config.threshold -> TestController.threshold

所以如果把这个映射关系保存在内存,当listener回调通知的时候,找到配置中的对应属性,反射设置进去就好了。

Nacos也是这么做的:

NacosValueAnnotationBeanPostProcessor实现了org.springframework.beans.factory.config.BeanPostProcessor(spring钩子函数,当bean对象实例化完成,注入容器之前调用 ),在其Object postProcessBeforeInitialization(Object bean, String beanName) 方法中,我们可以解析bean中所有注有@Value的注解,并将上诉映射关系,保存在内存中:



其中doWithFields实现如下:



其中有一点需要注意的地方,Nacos并没有使用原生的@Value注解去达到动态刷新的目的,因为违背了spring使用@Value的初衷,nacos自己实现了@NacosValue的注解

综上所诉,@NacosPropertySource和@NacosValue组合使用达到动态配置的效果是这样实现的:

@NacosValue

前面解析了@NacosPropertySource和@NacosValue组合使用达到动态配置原理,遗漏了一个细节点就是使用自定义注解@NacosValue是怎么在bean初始化的过程中注入属性的(前面说的动态刷新,是通过反射设置的,是建立在bean已经初始化完毕的基础上)。

还是com.alibaba.nacos.spring.context.annotation.config.NacosValueAnnotationBeanPostProcessor这个类,除了上面说的postProcessBeforeInitialization建立配置项和属性的映射关系这个方法外,还有两个方法,就是用于@NacosValue在bean初始过程中注入属性的:



简单理解一下这两个方法的目的:

  • doGetInjectedBean首先获取了@NacosValue中的配置项比如app.config.threshold,通过beanFactory解析出配置项对应的值(在ENV中),Member是一个队Field和Method的抽象类,如果Mem是Field则把取出的值进行转换和Field保持一致,如果是方法,则取出方法参数的Field进行转换

  • buildInjectedObjectCacheKey用于对doGetInjectedBean方法中已经转换过的值生一个cacheKey,这样就不用做多次转换的无用功

这两个方法都不是spring的钩子函数的方法,是在alibaba的spring-context-support包下,抽象类com.alibaba.spring.beans.factory.annotation.AnnotationInjectedBeanPostProcessor提供的,这个类的作用是:解析被子类泛型指定的注解(public class NacosValueAnnotationBeanPostProcessor extends ValueAnnotationBeanPostProcessor)标记的属性或方法(抽象成了Member),并注入由getInjectedObject返回的值,这个方法只做了一层缓存(buildInjectedObjectCacheKey),并调用由子类扩展的方法doGetInjectedBean,完成注入

AnnotationInjectedBeanPostProcessor这个类的实现,参照了org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor的实现,而这个类就是用于Spring原生注解@Autowired 和@Value在bean初始化过程中注入依赖的。

Nacos Config客户端与Spring Boot、Spring Cloud深度集成的更多相关文章

  1. 新书上线:《Spring Boot+Spring Cloud+Vue+Element项目实战:手把手教你开发权限管理系统》,欢迎大家买回去垫椅子垫桌脚

    新书上线 大家好,笔者的新书<Spring Boot+Spring Cloud+Vue+Element项目实战:手把手教你开发权限管理系统>已上线,此书内容充实.材质优良,乃家中必备垫桌脚 ...

  2. Java面试题(Spring Boot/Spring Cloud篇)

    Spring Boot/Spring Cloud 104.什么是 spring boot? SpringBoot是一个框架,一种全新的编程规范,他的产生简化了框架的使用,所谓简化是指简化了Spring ...

  3. spring Boot+spring Cloud实现微服务详细教程第二篇

    上一篇文章已经说明了一下,关于spring boot创建maven项目的简单步骤,相信很多熟悉Maven+Eclipse作为开发常用工具的朋友们都一目了然,这篇文章主要讲解一下,构建spring bo ...

  4. spring Boot+spring Cloud实现微服务详细教程第一篇

    前些天项目组的大佬跟我聊,说项目组想从之前的架构上剥离出来公用的模块做微服务的开发,恰好去年的5/6月份在上家公司学习了国内开源的dubbo+zookeeper实现的微服务的架构.自己平时对微服务的设 ...

  5. Cola Cloud 基于 Spring Boot, Spring Cloud 构建微服务架构企业级开发平台

    Cola Cloud 基于 Spring Boot, Spring Cloud 构建微服务架构企业级开发平台: https://gitee.com/leecho/cola-cloud

  6. spring boot、cloud v2.1.0.RELEASE 使用及技术整理

    2018年10月30日 springboot v2.1.0.RELEASE 发布: https://github.com/spring-projects/spring-boot/releases/ta ...

  7. Spring Boot + Spring Cloud 实现权限管理系统 后端篇(七):集成 Druid 数据源

    数据库连接池负责分配.管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个:释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏 ...

  8. Spring Boot/Spring Cloud、ESB、Dubbo

    如何使用Spring Boot/Spring Cloud 实现微服务应用spring Cloud是一个基于Spring Boot实现的云应用开发工具,它为基于JVM的云应用开发中的配置管理.服务发现. ...

  9. 使用Spring Boot,Spring Cloud和Docker实现微服务架构

    https://github.com/sqshq/PiggyMetrics     Microservice Architecture with Spring Boot, Spring Cloud a ...

随机推荐

  1. ARM架构授权和IP核授权有什么不一样啊?

    比如,华为分别拿到这2个授权,能做的有什么区别啊? 匿名 | 浏览 2976 次 推荐于2016-06-09 02:43:35   最佳答案   一个公司若想使用ARM的内核来做自己的处理器,比如苹果 ...

  2. Docker容器数据卷-Volume详解

    Docker中的数据可以存储在类似于虚拟机磁盘的介质中,在Docker中称为数据卷(Data Volume).数据卷可以用来存储Docker应用的数据,也可以用来在Docker容器间进行数据共享.数据 ...

  3. xml解析用正则解决没有标签的文本的解析不出异常

    如  <q>sasas<w>eqwe</w>ddas</q> package com.people.xmlToSql; import java.io.F ...

  4. APP运营怎么利用留存率等数据分析用户减少的原因?

    APP运营怎么利用留存率等数据分析用户减少的原因? 数据分析最核心的方法是作比较,因为绝对的数值在大多数场合下是没有意义的,通过在不同维度之间做数据的比较分析,能帮助开发者找到数据变化的原因.举一个典 ...

  5. docker安装部署命令

    一.安装工具包 $ sudo yum install -y yum-utils #安装工具包,缺少这些依赖将无法完成 二.设置远程仓库 $sudo yum-config-manager --add-r ...

  6. oo_project_1

    Project 1题目要求分析: 实现多项式的加减运算,主要问题是解决输入格式的判断问题. 输入实例: {(3,0), (2,2), (12,3)} + {(3,1), (-5,3)} – {(-19 ...

  7. Font and PDF

    1. 独立存在的Font文件 有三类: Type 1 Font TrueType Font OpenType Font Type 1 是由Adobe开发的,它是基于PostScript的Font,它通 ...

  8. CSL 的密码(后缀数组)

    CSL 的密码 题目传送门 解题思路 后缀数组.对于每一个后缀\(k\)都有\(n - k + 1\)个前缀,把所有不和前一个(排序后的)公共且长度大于\(m\)的前缀个数加起来. 代码如下 #inc ...

  9. CommonJS规范 by ranyifeng

    1,概述 CommonJS是服务器端模块的规范,Node.js采用了这个规范. 根据CommonJS规范,一个单独的文件就是一个模块.加载模块使用require方法,该方法读取一个文件并执行,最后返回 ...

  10. 40.Unique Binary Search Trees(不同的二叉搜索树)

    Level:   Medium 题目描述: Given n, how many structurally unique BST's (binary search trees) that store v ...