Nacos Spring

本文将介绍 nacos-spring-context 中的一些关键的特性:

  • 注解驱动
  • 依赖注入
  • 外部化配置
  • 事件驱动

1. 注解驱动

1.1. 启用 Nacos

@EnableNacos是一个模块驱动的注解,它支持 Nacos Spring 的所有功能,包括服务发现配置管理。它等于 @EnableNacosDiscovery 加上 @EnableNacosConfig,可以单独配置并在不同场景中使用。

1.2. 配置监听

假设在 Nacos 服务中有一个配置,其 dataId 是 “testDataId” 而 groupId 是默认组(”DEFAULT_GROUP”)。 现在,您可以使用 ConfigService#publishConfig 方法更改其内容:

  1. @NacosInjected
  2. private ConfigService configService;
  3. @Test
  4. public void testPublishConfig() throws NacosException {
  5. configService.publishConfig(DATA_ID, DEFAULT_GROUP, "9527");
  6. }

然后您可以添加一个监听器,它将监听配置的变化。 您可以通过在 Spring Bean 中添加配置变更监听器方法来执行此操作:

  1. @NacosConfigListener(dataId = DATA_ID)
  2. public void onMessage(String config) {
  3. assertEquals("mercyblitz", config); // asserts true
  4. }

下面的代码具有相同的效果:

  1. configService.addListener(DATA_ID, DEFAULT_GROUP, new AbstractListener() {
  2. @Override
  3. public void receiveConfigInfo(String config) {
  4. assertEquals("9527", config); // asserts true
  5. }
  6. });

另外,@NacosConfigListener 支持更丰富的类型转换。

1.2.1. 类型

@NacosConfigListener 的类型转换包括内置和自定义实现。 默认情况下,内置类型转换基于 Spring DefaultFormattingConversionService,这意味着它包好了大多数情况以及 Spring 框架更高级版本的丰富功能。

例如,前面示例中的内容 “9527” 也可以通过带 “int” 或 “Integer” 参数的方法进行监听::

  1. @NacosConfigListener(dataId = DATA_ID)
  2. public void onInteger(Integer value) {
  3. assertEquals(Integer.valueOf(9527), value); // asserts true
  4. }
  5. @NacosConfigListener(dataId = DATA_ID)
  6. public void onInt(int value) {
  7. assertEquals(9527, value); // asserts true
  8. }

当然, nacos-spring-context 为开发人员提供弹性扩展。 如果定义名为nacosConfigConversionService的Spring Bean,其类型为ConversionService,则将忽略DefaultFormattingConversionService。 此外,您可以自定义NacosConfigConverter接口的实现,以指定类型转换的侦听器方法:

  1. public class UserNacosConfigConverter implements NacosConfigConverter<User> {
  2. @Override
  3. public boolean canConvert(Class<User> targetType) {
  4. return true;
  5. }
  6. @Override
  7. public User convert(String source) {
  8. return JSON.parseObject(source, User.class);
  9. }
  10. }

UserNacosConfigConverter 类绑定在 @NacosConfigListener.converter() 属性上,如下:

  1. @NacosInjected
  2. private ConfigService configService;
  3. @Test
  4. public void testPublishUser() throws NacosException {
  5. configService.publishConfig("user", DEFAULT_GROUP, "{\"id\":1,\"name\":\"mercyblitz\"}");
  6. }
  7. @NacosConfigListener(dataId = "user", converter = UserNacosConfigConverter.class)
  8. public void onUser(User user) {
  9. assertEquals(Long.valueOf(1L), user.getId());
  10. assertEquals("mercyblitz", user.getName());
  11. }

1.2.2. 超时时间

由于运行自定义的 NacosConfigConverter 可能需要一些时间,因此您可以在 @NacosConfigListener.timeout() 属性中设置最大执行时间,以防止它阻塞其他侦听器:

  1. @Configuration
  2. public class Listeners {
  3. private Integer integerValue;
  4. private Double doubleValue;
  5. @NacosConfigListener(dataId = DATA_ID, timeout = 50)
  6. public void onInteger(Integer value) throws Exception {
  7. Thread.sleep(100); // timeout of execution
  8. this.integerValue = value;
  9. }
  10. @NacosConfigListener(dataId = DATA_ID, timeout = 200)
  11. public void onDouble(Double value) throws Exception {
  12. Thread.sleep(100); // normal execution
  13. this.doubleValue = value;
  14. }
  15. public Integer getIntegerValue() {
  16. return integerValue;
  17. }
  18. public Double getDoubleValue() {
  19. return doubleValue;
  20. }
  21. }

Listeners Bean 的 integerValue 总是为null,不会改变。 因此,以下断言都将是 true

  1. @Autowired
  2. private Listeners listeners;
  3. @Test
  4. public void testPublishConfig() throws NacosException {
  5. configService.publishConfig(DATA_ID, DEFAULT_GROUP, "9527");
  6. assertNull(listeners.getIntegerValue()); // asserts true
  7. assertEquals(Double.valueOf(9527), listeners.getDoubleValue()); // asserts true
  8. }

1.3. 全局和自定义 Nacos 属性

globalProperties 是任何 @EnableNacos@EnableNacosDiscovery@EnableNacosConfig 中的必选属性,其类型为 @NacosProperties

globalProperties 将初始化为其他注解或组件的 “全局 Nacos 属性“,例如:@NacosInjected

换句话说,全局 Nacos 属性 定义全局和默认属性。它设置为具有最低优先级,并且也可以被覆盖。覆盖优先级如下表所示:

Precedence OrderNacos AnnotationRequired
1*.properties()N
2@EnableNacosConfig.globalProperties() or @EnableNacosDiscovery.globalProperties()Y
3@EnableNacos.globalProperties()Y

*.properties() 定义来自以下之一的自定义 Nacos 属性:

  • @NacosInjected.properties()
  • @NacosConfigListener.properties()
  • @NacosPropertySource.properties()
  • @NacosConfigurationProperties.properties()

自定义的 Nacos 属性也由 @NacosProperties 配置。 不过,它们是可选的,用于在特殊情况下覆盖全局 Nacos 属性。 如果没有定义,Nacos 属性将尝试从 @EnableNacosConfig.globalProperties()@EnableNacosDiscovery.globalProperties()@EnableNacos.globalProperties() 中查找属性。

1.4. @NacosProperties

@NacosProperties 是全局和自定义 Nacos 属性的统一注解。 它充当Java PropertiesNacosFactory 类之间的中介。NacosFactory 负责创建 ConfigServiceNamingService 实例。

@NacosProperties 的属性完全支持占位符,它的源是Spring Environment 抽象中的各种 PropertySource,通常是Java System Properties 和操作系统环境变量。 所有占位符的前缀都是 nacos.@NacosProperties 和 Nacos 属性的属性之间的映射如下所示:

AttributePropertyPlaceholderDescriptionRequired
endpoint()endpoint${nacos.endpoint:}N
namespace()namespace${nacos.namespace:}N
accessKey()access-key${nacos.access-key:}N
secretKey()secret-key${nacos.secret-key:}N
serverAddr()server-addr${nacos.server-addr:}Y
contextPath()context-path${nacos.context-path:}N
clusterName()cluster-name${nacos.cluster-name:}N
encode()encode${nacos.encode:UTF-8}N

请注意,@EnableNacosDiscovery@EnableNacosConfig 之间 globalProperties() 的占位符存在一些差异:

Attribute@EnableNacosDiscovery‘s Placeholder@EnableNacosConfig‘s Placeholder
endpoint()${nacos.discovery.endpoint:${nacos.endpoint:}}${nacos.config.endpoint:${nacos.endpoint:}}
namespace()${nacos.discovery.namespace:${nacos.namespace:}}${nacos.config.namespace:${nacos.namespace:}}
accessKey()${nacos.discovery.access-key:${nacos.access-key:}}${nacos.config.access-key:${nacos.access-key:}}
secretKey()${nacos.discovery.secret-key:${nacos.secret-key:}}${nacos.config.secret-key:${nacos.secret-key:}}
serverAddr()${nacos.discovery.server-addr:${nacos.server-addr:}}${nacos.config.server-addr:${nacos.server-addr:}}
contextPath()${nacos.discovery.context-path:${nacos.context-path:}}${nacos.config.context-path:${nacos.context-path:}}
clusterName()${nacos.discovery.cluster-name:${nacos.cluster-name:}}${nacos.config.cluster-name:${nacos.cluster-name:}}
encode()${nacos.discovery.encode:${nacos.encode:UTF-8}}${nacos.config.encode:${nacos.encode:UTF-8}}

这些 @EnableNacosDiscovery@EnableNacosConfig 的占位符用于隔离不同的 Nacos 服务,在大多数情况下都是不必要的。默认情况下,将使用常规占位符。

2. 依赖注入

@NacosInjected 是一个核心注解,用于在Spring Beans 中注入 ConfigServiceNamingService 实例,并使这些实例可缓存。 这意味着如果它们的 @NacosProperties 相等,则实例将是相同的,无论属性是来自全局还是自定义的 Nacos 属性:

  1. @NacosInjected
  2. private ConfigService configService;
  3. @NacosInjected(properties = @NacosProperties(encode = "UTF-8"))
  4. private ConfigService configService2;
  5. @NacosInjected(properties = @NacosProperties(encode = "GBK"))
  6. private ConfigService configService3;
  7. @NacosInjected
  8. private NamingService namingService;
  9. @NacosInjected(properties = @NacosProperties(encode = "UTF-8"))
  10. private NamingService namingService2;
  11. @NacosInjected(properties = @NacosProperties(encode = "GBK"))
  12. private NamingService namingService3;
  13. @Test
  14. public void testInjection() {
  15. Assert.assertEquals(configService, configService2);
  16. Assert.assertNotEquals(configService2, configService3);
  17. Assert.assertEquals(namingService, namingService2);
  18. Assert.assertNotEquals(namingService2, namingService3);
  19. }

属性 configService 使用 @EnableNacos#globalProperties()@EnableNacosConfig#globalProperties(),因为 encode 属性的默认值是 “UTF-8”,因此 configService 实例和由 @NacosProperties(encode ="UTF-8") 注解的 configService2 实例是相同的。 namingServicenamingService2 也是如此。

值得注意的是,与 NacosFactory.createConfigService() 方法创建的 ConfigService 实例不同,@NacosInjected 注解创建的 ConfigService 实例支持 Nacos Spring 事件。 例如,在增强的 ConfigService 调用 publishConfig() 方法之后会有一个 NacosConfigPublishedEvent。 有关更多详细信息,请参阅”事件驱动”部分。

3. 外部化配置

外部化配置是 Spring Boot 引入的概念,它允许应用程序接收外部属性源以控制运行时行为。 Nacos Server 在应用程序外部运行单独的进程以维护应用程序配置。 nacos-spring-context 提供了对象绑定,动态配置(自动刷新)等功能。

这里有 nacos-spring-context 和 Spring Stack 之间的简单比较:

Spring StackNacos SpringHighlight
@Value@NacosValueauto-refreshed
@ConfigurationProperties@NacosConfigurationPropertiesauto-refreshed,@NacosProperty,@NacosIgnore
@PropertySource@NacosPropertySourceauto-refreshed, precedence order control
@PropertySources@NacosPropertySources

4. 事件驱动

Nacos 事件驱动 基于标准的 Spring Event / Listener 机制。 Spring 的 ApplicationEvent 是所有 Nacos Spring 事件的抽象超类:

Nacos Spring EventTrigger
NacosConfigPublishedEventAfter ConfigService.publishConfig()
NacosConfigReceivedEventAfterListener.receiveConfigInfo()
NacosConfigRemovedEventAfter configService.removeConfig()
NacosConfigTimeoutEventConfigService.getConfig() on timeout
NacosConfigListenerRegisteredEventAfter ConfigService.addListner() or ConfigService.removeListener()
NacosConfigurationPropertiesBeanBoundEventAfter @NacosConfigurationProperties binding
NacosConfigMetadataEventAfter Nacos Config operations

相关项目