Nacos is an open source project for discovering, configuring and managing cloud-native applications. Key features of Nacos include:
- Service Discovery and Service Health Check
- Dynamic Configuration Management
- Dynamic DNS Service
- Service and Metadata Management
Nacos Spring Project, which is based on Nacos, fully embraces the Spring ecosystem and is designed to help you build Spring applications rapidly.
The project contains a core module named nacos-spring-context
. It enables you to expand modern Java programming models in the following ways:
These features strongly depend on Spring Framework 3.2+ API, and can be seamlessly integrated with any Spring Stack, such as Spring Boot and Spring Cloud.
Note: We recommend that you use annotation-driven programming, even though XML-based features also work.
- 1. Samples
- 2. Dependencies & Compatibility
- 3. Quickstart
- 4. Core Features
- 4.1. Annotation-Driven
- 4.1.1. Enable Nacos
- 4.1.2. Configure Change Listener method
- 4.1.2.1. Type Conversion
- [4.1.2.1.1. Type Conversion during YAML Parsing](#41211-Type Conversion during YAML Parsing)
- 4.1.2.2. Timeout of Execution
- 4.1.2.1. Type Conversion
- 4.1.3. Global and Special Nacos Properties
- 4.1.4.
@NacosProperties
- 4.2. Dependency Injection
- 4.3. Externalized Configuration
- 4.4. Event/Listener Driven
- 4.1. Annotation-Driven
- 5. Modules
- 6. Relative Projects
Included in this section are some samples for you to get a quick start with Nacos Spring.
Take the Spring Web MVC project for example:
-
Check out the source code of
nacos-spring-project
:$ git clone git@github.com:nacos-group/nacos-spring-project.git
-
Build your source code with Maven:
$ mvn clean package
-
Run Spring Web MVC Samples:
$ java -jar target/nacos-spring-webmvc-sample.war
The following table shows the dependencies and compatabilities of Nacos Spring Project.
Dependencies | Compatibility |
---|---|
Java | 1.8+ |
Spring Context | 3.2+ |
Alibaba Spring Context Support | 1.0.1+ |
Alibaba Nacos | 1.1.1+ |
This quickstart shows you how to enable Nacos and its service discovery and configuration management features in your Spring project.
Before you configure your Spring project to use Nacos, you need to start a Nacos server in the backend. Refer to Nacos Quick Start for instructions on how to start a Nacos server.
Complete the following steps to enable Nacos for your Spring project.
-
Add
nacos-spring-context
in your Spring application's dependencies:<dependencies> ... <dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-spring-context</artifactId> <version>1.1.0</version> </dependency> ... </dependencies>
Note: Support Spring 5 from version 0.2.3-RC1, support Spring 6 from version 2.1.0.
-
Add the
@EnableNacos
annotation in the@Configuration
class of Spring and specify "${host}:${port}" of your Nacos server in theserverAddr
attribute:@Configuration @EnableNacos( globalProperties = @NacosProperties(serverAddr = "${nacos.server-addr:localhost:12345}") ) public class NacosConfiguration { ... }
Now you would like to use the confguration service of Nacos. Simply use Dependency Injection to inject ConfigService
instance in your Spring Beans when @EnableNacos
is annotated, as shown below:
@Service
public class ConfigServiceDemo {
@NacosInjected
private ConfigService configService;
public void demoGetConfig() {
try {
String dataId = "{dataId}";
String group = "{group}";
String content = configService.getConfig(dataId, groupId, 5000);
System.out.println(content);
} catch (NacosException e) {
e.printStackTrace();
}
}
...
}
ConfigService
is the key interface of Nacos which helps you to get or publish configurations.
The following code achieves the same effect:
try {
// Initialize the configuration service, and the console automatically obtains the following parameters through the sample code.
String serverAddr = "{serverAddr}";
String dataId = "{dataId}";
String group = "{group}";
Properties properties = new Properties();
properties.put("serverAddr", serverAddr);
ConfigService configService = NacosFactory.createConfigService(properties);
// Actively get the configuration.
String content = configService.getConfig(dataId, group, 5000);
System.out.println(content);
} catch (NacosException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
If you would also like to use the service discovery feature of Nacos, you can inject a NamingService
instance for service discovery:
@NacosInjected
private NamingService namingService;
For details about the usages of ConfigService
and NamingService
, please refer to Nacos SDK.
This section provides a detailed description of the key features of nacos-spring-context
:
@EnableNacos
is a modular-driven annotation that enables all features of Nacos Spring, including Service Discovery and Distributed Configuration. It equals to @EnableNacosDiscovery
and
@EnableNacosConfig
, which can be configured separately and used in different scenarios.
Suppose there was a config in Nacos Server whose dataId
is "testDataId" and groupId
is default group("DEFAULT_GROUP"). Now you would like to change its content by using the ConfigService#publishConfig
method:
@NacosInjected
private ConfigService configService;
@Test
public void testPublishConfig() throws NacosException {
configService.publishConfig(DATA_ID, DEFAULT_GROUP, "9527");
}
Then you would like to add a listener, which will be listening for the config changes. You can do this by adding a config change listener method into your Spring Beans:
@NacosConfigListener(dataId = DATA_ID)
public void onMessage(String config) {
assertEquals("mercyblitz", config); // asserts true
}
The code below has the same effect:
configService.addListener(DATA_ID, DEFAULT_GROUP, new AbstractListener() {
@Override
public void receiveConfigInfo(String config) {
assertEquals("9527", config); // asserts true
}
});
Note: @NacosConfigListener
supports richer type conversions.
The type conversion of @NacosConfigListener
includes both build-in and customized implementations. By default, build-in type conversion is based on Spring DefaultFormattingConversionService
, which means it covers most of the general cases as well as the rich features of the higher Spring framework.
For example, the content "9527" in the preceding example can also be listened by a method with integer or the Integer
argument:
@NacosConfigListener(dataId = DATA_ID)
public void onInteger(Integer value) {
assertEquals(Integer.valueOf(9527), value); // asserts true
}
@NacosConfigListener(dataId = DATA_ID)
public void onInt(int value) {
assertEquals(9527, value); // asserts true
}
Of course, nacos-spring-context
provides elastic extension for developers. If you define a named nacosConfigConversionService
Spring Bean whose type is ConversionService
, the DefaultFormattingConversionService
will be ignored. In addition, you can customize the implementation of the NacosConfigConverter
interface to specify a listener method for type conversion:
public class UserNacosConfigConverter implements NacosConfigConverter<User> {
@Override
public boolean canConvert(Class<User> targetType) {
return true;
}
@Override
public User convert(String source) {
return JSON.parseObject(source, User.class);
}
}
The UserNacosConfigConverter
class binds the @NacosConfigListener.converter()
attribute:
@NacosInjected
private ConfigService configService;
@Test
public void testPublishUser() throws NacosException {
configService.publishConfig("user", DEFAULT_GROUP, "{\"id\":1,\"name\":\"mercyblitz\"}");
}
@NacosConfigListener(dataId = "user", converter = UserNacosConfigConverter.class)
public void onUser(User user) {
assertEquals(Long.valueOf(1L), user.getId());
assertEquals("mercyblitz", user.getName());
}
By default, starting from version 1.1.2, this library uses SafeConstructor
for type conversion during YAML parsing. This is done to ensure that potentially unsafe code is not executed during the parsing process. SafeConstructor
provides a secure construction logic for mapping YAML structures to Java objects.
System Property Toggle
To maintain compatibility with versions prior to 1.1.2, we have introduced a system property toggle named yamlAllowComplexObject
. Prior to version 1.1.2, the library defaulted to using Constructor
, another constructor in the SnakeYAML library that supports more complex object mapping. Starting from version 1.1.2, the default is switched to SafeConstructor
.
Potential Risks
It's important to note that enabling Constructor
introduces some potential risks, particularly the risk of Remote Code Execution (RCE
). This is because Constructor
allows more flexible object construction, but it also increases the risk of handling malicious YAML input.
Recommendations
- We recommend using the
NacosConfigConverter
for custom conversions.
If You Must Use Constructor
- Ensure that the source of the YAML data is secure.
How to Disable SafeConstructor
You can set the toggle by adding a JVM system property when starting your application. For example, in the command line:
java -DyamlAllowComplexObject=true -jar your-application.jar
As it might cost some time to run customized NacosConfigConverter
, you can set max execution time in the @NacosConfigListener.timeout()
attribute to prevent it from blocking other listeners:
@Configuration
public class Listeners {
private Integer integerValue;
private Double doubleValue;
@NacosConfigListener(dataId = DATA_ID, timeout = 50)
public void onInteger(Integer value) throws Exception {
Thread.sleep(100); // timeout of execution
this.integerValue = value;
}
@NacosConfigListener(dataId = DATA_ID, timeout = 200)
public void onDouble(Double value) throws Exception {
Thread.sleep(100); // normal execution
this.doubleValue = value;
}
public Integer getIntegerValue() {
return integerValue;
}
public Double getDoubleValue() {
return doubleValue;
}
}
The integerValue
of Listeners
Bean is always null
and will not be changed. Therefore, those asserts will be true
:
@Autowired
private Listeners listeners;
@Test
public void testPublishConfig() throws NacosException {
configService.publishConfig(DATA_ID, DEFAULT_GROUP, "9527");
assertNull(listeners.getIntegerValue()); // asserts true
assertEquals(Double.valueOf(9527), listeners.getDoubleValue()); // asserts true
}
The globalProperties
is a required attribute in any @EnableNacos
, @EnableNacosDiscovery
or @EnableNacosConfig
, and its type is @NacosProperties
. globalProperties
initializes "Global Nacos Properties" that will be used by other annotations
and components, e,g @NacosInjected
. In other words, Global Nacos Properties" defines the global and default properties. It is set with the lowest priority and can be overridden if needed. The precedence of overiding rules is shown in the following table:
Precedence Order | Nacos Annotation | Required |
---|---|---|
1 | *.properties() |
N |
2 | @EnableNacosConfig.globalProperties() or @EnableNacosDiscovery.globalProperties() |
Y |
3 | @EnableNacos.globalProperties() |
Y |
*.properties()
defines special Nacos properties which come from one of the following:
@NacosInjected.properties()
@NacosConfigListener.properties()
@NacosPropertySource.properties()
@NacosConfigurationProperties.properties()
Special Nacos properties are also configured by @NacosProperties
. However, they are optional and are used to override Global Nacos Properties in special scenarios. If not defined, the Nacos Properties will
try to retrieve properities from @EnableNacosConfig.globalProperties()
or @EnableNacosDiscovery.globalProperties()
, or
@EnableNacos.globalProperties()
.
@NacosProperties
is a uniform annotation for global and special Nacos properties. It serves as a mediator between Java Properties
and NacosFactory
class. NacosFactory
is responsible for creating ConfigService
or NamingService
instances.
The attributes of @NacosProperties
completely support placeholders whose source is all kinds of PropertySource
in Spring Environment
abstraction, typically Java System Properties
and OS environment variables. The prefix of all placeholders are nacos.
. The mapping between the attributes of @NacosProperties
and Nacos properties are shown below:
Attribute | Property | Placeholder | Description | Required |
---|---|---|---|---|
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 |
Note that there are some differences in the placeholders of globalProperties()
between @EnableNacosDiscovery
and @EnableNacosConfig
:
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}} |
These placeholders of @EnableNacosDiscovery
and @EnableNacosConfig
are designed to isolate different Nacos servers, and are unnecessary in most scenarios. By default, general placeholders will be reused.
@NacosInjected
is a core annotation which is used to inject ConfigService
or NamingService
instance in your Spring Beans and make these instances cacheable. This means the instances will be the same if their @NacosProperties
are equal, regargless of whether the properties come from global or special Nacos properties:
@NacosInjected
private ConfigService configService;
@NacosInjected(properties = @NacosProperties(encode = "UTF-8"))
private ConfigService configService2;
@NacosInjected(properties = @NacosProperties(encode = "GBK"))
private ConfigService configService3;
@NacosInjected
private NamingService namingService;
@NacosInjected(properties = @NacosProperties(encode = "UTF-8"))
private NamingService namingService2;
@NacosInjected(properties = @NacosProperties(encode = "GBK"))
private NamingService namingService3;
@Test
public void testInjection() {
Assert.assertEquals(configService, configService2);
Assert.assertNotEquals(configService2, configService3);
Assert.assertEquals(namingService, namingService2);
Assert.assertNotEquals(namingService2, namingService3);
}
The property configService
uses @EnableNacos#globalProperties()
or @EnableNacosConfig#globalProperties()
, and because the default value of the encode
attribute is “UTF-8”, therefore the configService
instance and the configService2
instance which is annotated by @NacosProperties(encode = "UTF-8")
are the same. The same is true for namingService
and namingService2
.
More importantly, unlike the ConfigService
instances created by the NacosFactory.createConfigService()
method, the ConfigService
instances created by the @NacosInjected
annotation support Nacos Spring events. For instance, there will be an NacosConfigPublishedEvent
after an enhanced ConfigService
invokes the publishConfig()
method. Refer to the Event/Listener Driven section for more details.
Externalized configuration is a concept introduced by Spring Boot, which allows applications to receive external property sources to control runtime behavior. Nacos Server runs an isolation process outside the application to maintain the application configurations. nacos-spring-context
provides properties features including object binding, dynamic configuration(auto-refreshed) and so on, and dependence on Spring Boot or Spring Cloud framework is required.
Here is a simple comparison between nacos-spring-context
and Spring stack:
Spring Stack | Nacos Spring | Highlight |
---|---|---|
@Value |
@NacosValue |
auto-refreshed |
@ConfigurationProperties |
@NacosConfigurationProperties |
auto-refreshed,@NacosProperty ,@NacosIgnore |
@PropertySource |
@NacosPropertySource |
auto-refreshed, precedence order control |
@PropertySources |
@NacosPropertySources |
Nacos Event/Listener Driven is based on the standard Spring Event/Listener mechanism. The ApplicationEvent
of Spring is an abstract super class for all Nacos Spring events:
Nacos Spring Event | Trigger |
---|---|
NacosConfigPublishedEvent |
After ConfigService.publishConfig() |
NacosConfigReceivedEvent |
AfterListener.receiveConfigInfo() |
NacosConfigRemovedEvent |
After configService.removeConfig() |
NacosConfigTimeoutEvent |
ConfigService.getConfig() on timeout |
NacosConfigListenerRegisteredEvent |
After ConfigService.addListner() or ConfigService.removeListener() |
NacosConfigurationPropertiesBeanBoundEvent |
After @NacosConfigurationProperties binding |
NacosConfigMetadataEvent |
After Nacos Config operations |