Nacos Config客户端与Spring Boot、Spring Cloud深度集成

从源码角度,解析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初始化过程中注入依赖的。

posted @ 2019-12-25 14:09  啥也不懂的新同学  阅读(...)  评论(...编辑  收藏