Merge pull request #2349 from DanielLiu1123/support_spring_config_import

Support spring.config.import
pull/2364/head
余黄彬 3 years ago committed by GitHub
commit 14ecf7870b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<version>${revision}</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>nacos-config-2.4.x-example</artifactId>
<name>Spring Cloud Starter Alibaba Nacos Config 2.4.x Example</name>
<description>Example demonstrating how to use nacos config in spring boot 2.4.x</description>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>${maven-deploy-plugin.version}</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,105 @@
# Nacos Config 2.4.x Example
## 项目说明
Spring Boot 2.4.0 版本开始默认不启动 bootstrap 容器
本项目演示如何在 Spring boot >= 2.4.0 版本不启用 bootstrap 容器情况下如何使用 nacos
[Nacos](https://github.com/alibaba/Nacos) 是阿里巴巴开源的一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
## 示例
### 如何接入
1. 首先,修改 pom.xml 文件,引入 Nacos Config Starter
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<!-- 不再需要 bootstrap 容器 -->
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</exclusion>
</exclusions>
</dependency>
```
2. 在应用的 /src/main/resources/***application.yml*** 配置文件中配置 Nacos Config 元数据
```yaml
server:
port: 8888
spring:
application:
name: nacos-config-import-example
cloud:
nacos:
config:
name: test.yml
file-extension: yml
# 2.4.0 新增配置 spring.config.import
config:
import:
- optional:nacos:localhost:8848
```
3. 在 nacos 创建 test.yml
```yaml
configdata:
user:
age: 21
name: freeman
map:
hobbies:
- art
- programming
intro: Hello, I'm freeman
users:
- name: dad
age: 20
- name: mom
age: 18
```
4. 完成上述操作后,应用会从 Nacos Config 中获取相应的配置,并添加在 Spring Environment 的 PropertySources 中
```java
// controller
@RestController
public class UserController {
@Autowired
private UserConfig userConfig;
@GetMapping
public String get() throws JsonProcessingException {
return new ObjectMapper().writeValueAsString(userConfig);
}
}
// ConfigurationProperties
@ConfigurationProperties(prefix = "configdata.user")
public class UserConfig {
private String name;
private Integer age;
private Map<String, Object> map;
private List<User> users;
// getters and setters ...
public static class User {
private String name;
private Integer age;
// getters and setters ...
}
}
```
验证动态刷新
访问 http://localhost:8888
再从 nacos 修改配置, 再次访问即可验证动态配置生效

@ -0,0 +1,36 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.imports.examples;
import com.alibaba.cloud.imports.examples.model.UserConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
/**
* @author freeman
*/
@SpringBootApplication
@EnableConfigurationProperties(UserConfig.class)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

@ -0,0 +1,43 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.imports.examples.controller;
import com.alibaba.cloud.imports.examples.model.UserConfig;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
*
*
* @author freeman
*/
@RestController
public class UserController {
@Autowired
private UserConfig userConfig;
@GetMapping
public String get() throws JsonProcessingException {
return new ObjectMapper().writeValueAsString(userConfig);
}
}

@ -0,0 +1,89 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.imports.examples.model;
import java.util.List;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
*
*
* @author freeman
*/
@ConfigurationProperties(prefix = "configdata.user")
public class UserConfig {
private String name;
private Integer age;
private Map<String, Object> map;
private List<User> users;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Map<String, Object> getMap() {
return map;
}
public void setMap(Map<String, Object> map) {
this.map = map;
}
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
public static class User {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
}

@ -0,0 +1,15 @@
server:
port: 8888
spring:
application:
name: nacos-config-import-example
cloud:
nacos:
config:
group: DEFAULT_GROUP
server-addr: localhost:8848
config:
import:
- optional:nacos:test.yml
- optional:nacos:test01.yml?group=group_02
- optional:nacos:test02.yml?group=group_03&refreshEnabled=false

@ -28,6 +28,7 @@
<module>sentinel-example/sentinel-spring-cloud-gateway-example</module> <module>sentinel-example/sentinel-spring-cloud-gateway-example</module>
<module>nacos-example/nacos-discovery-example</module> <module>nacos-example/nacos-discovery-example</module>
<module>nacos-example/nacos-config-example</module> <module>nacos-example/nacos-config-example</module>
<module>nacos-example/nacos-config-2.4.x-example</module>
<module>nacos-example/nacos-gateway-example</module> <module>nacos-example/nacos-gateway-example</module>
<module>seata-example/business-service</module> <module>seata-example/business-service</module>
<module>seata-example/order-service</module> <module>seata-example/order-service</module>

@ -1,3 +1,19 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.examples.controller; package com.alibaba.cloud.examples.controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@ -12,28 +28,28 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
public class ApiController { public class ApiController {
@GetMapping("/default/{ok}") @GetMapping("/default/{ok}")
public String defaultConfig(@PathVariable boolean ok) { public String defaultConfig(@PathVariable boolean ok) {
if (ok) { if (ok) {
return "ok"; return "ok";
} }
throw new RuntimeException("fail"); throw new RuntimeException("fail");
} }
@GetMapping("/feign/{ok}") @GetMapping("/feign/{ok}")
public String feignConfig(@PathVariable boolean ok) { public String feignConfig(@PathVariable boolean ok) {
if (ok) { if (ok) {
return "ok"; return "ok";
} }
throw new RuntimeException("fail"); throw new RuntimeException("fail");
} }
@GetMapping("/feignMethod/{ok}") @GetMapping("/feignMethod/{ok}")
public String feignMethodConfig(@PathVariable boolean ok) { public String feignMethodConfig(@PathVariable boolean ok) {
if (ok) { if (ok) {
return "ok"; return "ok";
} }
throw new RuntimeException("fail"); throw new RuntimeException("fail");
} }
} }

@ -1,7 +1,24 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.examples.controller; package com.alibaba.cloud.examples.controller;
import com.alibaba.cloud.examples.feign.OrderClient; import com.alibaba.cloud.examples.feign.OrderClient;
import com.alibaba.cloud.examples.feign.UserClient; import com.alibaba.cloud.examples.feign.UserClient;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
@ -15,25 +32,24 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
public class TestController { public class TestController {
@Autowired @Autowired
private UserClient userClient; private UserClient userClient;
@Autowired @Autowired
private OrderClient orderClient; private OrderClient orderClient;
@GetMapping("/test/default/{ok}")
@GetMapping("/test/default/{ok}") public String testDefault(@PathVariable boolean ok) {
public String testDefault(@PathVariable boolean ok) { return orderClient.defaultConfig(ok);
return orderClient.defaultConfig(ok); }
}
@GetMapping("/test/feign/{ok}")
@GetMapping("/test/feign/{ok}") public String testFeign(@PathVariable boolean ok) {
public String testFeign(@PathVariable boolean ok) { return userClient.feign(ok);
return userClient.feign(ok); }
}
@GetMapping("/test/feignMethod/{ok}")
@GetMapping("/test/feignMethod/{ok}") public String testFeignMethod(@PathVariable boolean ok) {
public String testFeignMethod(@PathVariable boolean ok) { return userClient.feignMethod(ok);
return userClient.feignMethod(ok); }
}
} }

@ -1,3 +1,19 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.examples.feign; package com.alibaba.cloud.examples.feign;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;
@ -12,7 +28,7 @@ import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(value = "order", url = "http://localhost:${server.port}", fallback = OrderClientFallBack.class) @FeignClient(value = "order", url = "http://localhost:${server.port}", fallback = OrderClientFallBack.class)
public interface OrderClient { public interface OrderClient {
@GetMapping("/default/{ok}") @GetMapping("/default/{ok}")
String defaultConfig(@PathVariable boolean ok); String defaultConfig(@PathVariable boolean ok);
} }

@ -1,3 +1,19 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.examples.feign; package com.alibaba.cloud.examples.feign;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -9,8 +25,8 @@ import org.springframework.stereotype.Component;
*/ */
@Component @Component
public class OrderClientFallBack implements OrderClient { public class OrderClientFallBack implements OrderClient {
@Override @Override
public String defaultConfig(boolean ok) { public String defaultConfig(boolean ok) {
return "order fallback"; return "order fallback";
} }
} }

@ -1,3 +1,19 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.examples.feign; package com.alibaba.cloud.examples.feign;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;
@ -12,10 +28,10 @@ import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(value = "user", url = "http://localhost:${server.port}", fallback = UserClientFallBack.class) @FeignClient(value = "user", url = "http://localhost:${server.port}", fallback = UserClientFallBack.class)
public interface UserClient { public interface UserClient {
@GetMapping("/feignMethod/{ok}") @GetMapping("/feignMethod/{ok}")
String feignMethod(@PathVariable boolean ok); String feignMethod(@PathVariable boolean ok);
@GetMapping("/feign/{ok}") @GetMapping("/feign/{ok}")
String feign(@PathVariable boolean ok); String feign(@PathVariable boolean ok);
} }

@ -1,3 +1,19 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.examples.feign; package com.alibaba.cloud.examples.feign;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -9,13 +25,13 @@ import org.springframework.stereotype.Component;
*/ */
@Component @Component
public class UserClientFallBack implements UserClient { public class UserClientFallBack implements UserClient {
@Override @Override
public String feignMethod(boolean ok) { public String feignMethod(boolean ok) {
return "user fallback"; return "user fallback";
} }
@Override @Override
public String feign(boolean ok) { public String feign(boolean ok) {
return "user fallback"; return "user fallback";
} }
} }

@ -1,7 +1,30 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.circuitbreaker.sentinel.feign; package com.alibaba.cloud.circuitbreaker.sentinel.feign;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.*; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import com.alibaba.cloud.circuitbreaker.sentinel.SentinelConfigBuilder; import com.alibaba.cloud.circuitbreaker.sentinel.SentinelConfigBuilder;
import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.EntryType;
@ -27,11 +50,13 @@ import org.springframework.core.annotation.AnnotationUtils;
*/ */
public class CircuitBreakerRuleChangeListener implements ApplicationContextAware, public class CircuitBreakerRuleChangeListener implements ApplicationContextAware,
ApplicationListener<RefreshScopeRefreshedEvent>, SmartInitializingSingleton { ApplicationListener<RefreshScopeRefreshedEvent>, SmartInitializingSingleton {
private static final Logger LOGGER = LoggerFactory.getLogger(CircuitBreakerRuleChangeListener.class); private static final Logger LOGGER = LoggerFactory
.getLogger(CircuitBreakerRuleChangeListener.class);
private SentinelFeignClientProperties properties; private SentinelFeignClientProperties properties;
/** /**
* properties backup, prevent rules from being updated every time the container is refreshed * properties backup, prevent rules from being updated every time the container is
* refreshed.
*/ */
private SentinelFeignClientProperties propertiesBackup; private SentinelFeignClientProperties propertiesBackup;
private AbstractCircuitBreakerFactory circuitBreakerFactory; private AbstractCircuitBreakerFactory circuitBreakerFactory;
@ -58,7 +83,8 @@ public class CircuitBreakerRuleChangeListener implements ApplicationContextAware
} }
@Override @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext; this.applicationContext = applicationContext;
} }
@ -73,13 +99,16 @@ public class CircuitBreakerRuleChangeListener implements ApplicationContextAware
// as it will cause the bean to be initialized prematurely, // as it will cause the bean to be initialized prematurely,
// and we don't want to change the initialization order of the beans // and we don't want to change the initialization order of the beans
if (circuitBreakerFactory == null) { if (circuitBreakerFactory == null) {
String[] names = applicationContext.getBeanNamesForType(AbstractCircuitBreakerFactory.class); String[] names = applicationContext
.getBeanNamesForType(AbstractCircuitBreakerFactory.class);
if (names.length >= 1) { if (names.length >= 1) {
this.circuitBreakerFactory = applicationContext.getBean(names[0], AbstractCircuitBreakerFactory.class); this.circuitBreakerFactory = applicationContext.getBean(names[0],
AbstractCircuitBreakerFactory.class);
} }
} }
if (properties == null) { if (properties == null) {
this.properties = applicationContext.getBean(SentinelFeignClientProperties.class); this.properties = applicationContext
.getBean(SentinelFeignClientProperties.class);
} }
} }
@ -105,7 +134,9 @@ public class CircuitBreakerRuleChangeListener implements ApplicationContextAware
// first, clear all manually configured feign clients and methods. // first, clear all manually configured feign clients and methods.
propertiesBackup.getRules().keySet().stream() propertiesBackup.getRules().keySet().stream()
.filter(key -> !Objects.equals(key, propertiesBackup.getDefaultRule())) .filter(key -> !Objects.equals(key, propertiesBackup.getDefaultRule()))
.forEach(resource -> Optional.ofNullable(DegradeRuleManager.getRulesOfResource(resource)).ifPresent(Set::clear)); .forEach(resource -> Optional
.ofNullable(DegradeRuleManager.getRulesOfResource(resource))
.ifPresent(Set::clear));
// Find all feign clients, clear the corresponding rules // Find all feign clients, clear the corresponding rules
// NOTE: feign client name cannot be the same as the general resource name !!! // NOTE: feign client name cannot be the same as the general resource name !!!
@ -116,7 +147,8 @@ public class CircuitBreakerRuleChangeListener implements ApplicationContextAware
.map(beanName -> { .map(beanName -> {
try { try {
return Class.forName(beanName); return Class.forName(beanName);
} catch (ClassNotFoundException ignore) { }
catch (ClassNotFoundException ignore) {
// definitely not a feign client, just ignore // definitely not a feign client, just ignore
return null; return null;
} }
@ -128,7 +160,9 @@ public class CircuitBreakerRuleChangeListener implements ApplicationContextAware
return; return;
} }
String feignClientName = AnnotationUtils.getValue(anno).toString(); String feignClientName = AnnotationUtils.getValue(anno).toString();
Optional.ofNullable(DegradeRuleManager.getRulesOfResource(feignClientName)).ifPresent(Set::clear); Optional.ofNullable(
DegradeRuleManager.getRulesOfResource(feignClientName))
.ifPresent(Set::clear);
}); });
} }

@ -1,3 +1,19 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.circuitbreaker.sentinel.feign; package com.alibaba.cloud.circuitbreaker.sentinel.feign;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -9,7 +25,7 @@ import feign.Target;
import org.springframework.cloud.client.circuitbreaker.AbstractCircuitBreakerFactory; import org.springframework.cloud.client.circuitbreaker.AbstractCircuitBreakerFactory;
import org.springframework.cloud.openfeign.CircuitBreakerNameResolver; import org.springframework.cloud.openfeign.CircuitBreakerNameResolver;
import static com.alibaba.cloud.circuitbreaker.sentinel.feign.CircuitBreakerRuleChangeListener.*; import static com.alibaba.cloud.circuitbreaker.sentinel.feign.CircuitBreakerRuleChangeListener.getConfigurations;
/** /**
* Feign client circuit breaker name resolver. * Feign client circuit breaker name resolver.

@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -32,7 +32,6 @@ import org.springframework.cloud.client.circuitbreaker.AbstractCircuitBreakerFac
import org.springframework.cloud.client.circuitbreaker.Customizer; import org.springframework.cloud.client.circuitbreaker.Customizer;
import org.springframework.cloud.openfeign.CircuitBreakerNameResolver; import org.springframework.cloud.openfeign.CircuitBreakerNameResolver;
import org.springframework.cloud.openfeign.FeignClientFactoryBean; import org.springframework.cloud.openfeign.FeignClientFactoryBean;
import org.springframework.cloud.util.ConditionalOnBootstrapEnabled;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -46,14 +45,12 @@ import static com.alibaba.cloud.circuitbreaker.sentinel.feign.CircuitBreakerRule
*/ */
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Feign.class, FeignClientFactoryBean.class }) @ConditionalOnClass({ Feign.class, FeignClientFactoryBean.class })
@ConditionalOnProperty(name = "spring.cloud.circuitbreaker.sentinel.enabled", @ConditionalOnProperty(name = "spring.cloud.circuitbreaker.sentinel.enabled", havingValue = "true", matchIfMissing = true)
havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(SentinelFeignClientProperties.class) @EnableConfigurationProperties(SentinelFeignClientProperties.class)
public class SentinelFeignClientAutoConfiguration { public class SentinelFeignClientAutoConfiguration {
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "feign.sentinel.refresh-rules", @ConditionalOnProperty(name = "feign.sentinel.refresh-rules", havingValue = "true", matchIfMissing = true)
havingValue = "true", matchIfMissing = true)
public static class CircuitBreakerListenerConfiguration { public static class CircuitBreakerListenerConfiguration {
@Bean @Bean

@ -1,14 +1,31 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.circuitbreaker.sentinel.feign; package com.alibaba.cloud.circuitbreaker.sentinel.feign;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
/** /**
@ -79,7 +96,8 @@ public class SentinelFeignClientProperties {
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(this); String json = objectMapper.writeValueAsString(this);
return objectMapper.readValue(json, this.getClass()); return objectMapper.readValue(json, this.getClass());
} catch (JsonProcessingException ignored) { }
catch (JsonProcessingException ignored) {
} }
return new SentinelFeignClientProperties(); return new SentinelFeignClientProperties();
} }

@ -29,7 +29,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import static com.alibaba.cloud.circuitbreaker.sentinel.feign.FeignClientCircuitBreakerRuleIntegrationTest.*; import static com.alibaba.cloud.circuitbreaker.sentinel.feign.FeignClientCircuitBreakerRuleIntegrationTest.Application;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT;

@ -36,6 +36,7 @@
<dependency> <dependency>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId> <artifactId>spring-cloud-starter-bootstrap</artifactId>
<optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>

@ -21,7 +21,9 @@ import com.alibaba.cloud.nacos.refresh.NacosRefreshHistory;
import com.alibaba.cloud.nacos.refresh.NacosRefreshProperties; import com.alibaba.cloud.nacos.refresh.NacosRefreshProperties;
import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -34,6 +36,7 @@ import org.springframework.context.annotation.Configuration;
public class NacosConfigAutoConfiguration { public class NacosConfigAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean(value = NacosConfigProperties.class, search = SearchStrategy.CURRENT)
public NacosConfigProperties nacosConfigProperties(ApplicationContext context) { public NacosConfigProperties nacosConfigProperties(ApplicationContext context) {
if (context.getParent() != null if (context.getParent() != null
&& BeanFactoryUtils.beanNamesForTypeIncludingAncestors( && BeanFactoryUtils.beanNamesForTypeIncludingAncestors(

@ -98,6 +98,9 @@ public class NacosConfigProperties {
} }
private void overrideFromEnv() { private void overrideFromEnv() {
if (environment == null) {
return;
}
if (StringUtils.isEmpty(this.getServerAddr())) { if (StringUtils.isEmpty(this.getServerAddr())) {
String serverAddr = environment String serverAddr = environment
.resolvePlaceholders("${spring.cloud.nacos.config.server-addr:}"); .resolvePlaceholders("${spring.cloud.nacos.config.server-addr:}");
@ -570,6 +573,9 @@ public class NacosConfigProperties {
} }
private void enrichNacosConfigProperties(Properties nacosConfigProperties) { private void enrichNacosConfigProperties(Properties nacosConfigProperties) {
if (environment == null) {
return;
}
Map<String, Object> properties = PropertySourcesUtils Map<String, Object> properties = PropertySourcesUtils
.getSubProperties((ConfigurableEnvironment) environment, PREFIX); .getSubProperties((ConfigurableEnvironment) environment, PREFIX);
properties.forEach((k, v) -> nacosConfigProperties.putIfAbsent(resolveKey(k), properties.forEach((k, v) -> nacosConfigProperties.putIfAbsent(resolveKey(k),

@ -62,7 +62,7 @@ public class NacosPropertySource extends MapPropertySource {
this.isRefreshable = isRefreshable; this.isRefreshable = isRefreshable;
} }
NacosPropertySource(List<PropertySource<?>> propertySources, String group, public NacosPropertySource(List<PropertySource<?>> propertySources, String group,
String dataId, Date timestamp, boolean isRefreshable) { String dataId, Date timestamp, boolean isRefreshable) {
this(group, dataId, getSourceMap(group, dataId, propertySources), timestamp, this(group, dataId, getSourceMap(group, dataId, propertySources), timestamp,
isRefreshable); isRefreshable);

@ -0,0 +1,112 @@
/*
* Copyright 2015-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.nacos.configdata;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.cloud.nacos.NacosPropertySourceRepository;
import com.alibaba.cloud.nacos.client.NacosPropertySource;
import com.alibaba.cloud.nacos.parser.NacosDataParserHandler;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.exception.NacosException;
import org.apache.commons.logging.Log;
import org.springframework.boot.context.config.ConfigData;
import org.springframework.boot.context.config.ConfigDataLoader;
import org.springframework.boot.context.config.ConfigDataLoaderContext;
import org.springframework.boot.context.config.ConfigDataResourceNotFoundException;
import org.springframework.core.env.PropertySource;
import static com.alibaba.cloud.nacos.configdata.NacosConfigDataResource.NacosItemConfig;
/**
* Implementation of {@link ConfigDataLoader}.
*
* <p>
* Load {@link ConfigData} via {@link NacosConfigDataResource}
*
* @author freeman
*/
public class NacosConfigDataLoader implements ConfigDataLoader<NacosConfigDataResource> {
private final Log log;
public NacosConfigDataLoader(Log log) {
this.log = log;
}
@Override
public ConfigData load(ConfigDataLoaderContext context,
NacosConfigDataResource resource) {
return doLoad(context, resource);
}
public ConfigData doLoad(ConfigDataLoaderContext context,
NacosConfigDataResource resource) {
try {
ConfigService configService = getBean(context, NacosConfigManager.class)
.getConfigService();
NacosConfigProperties properties = getBean(context,
NacosConfigProperties.class);
NacosItemConfig config = resource.getConfig();
// pull config from nacos
List<PropertySource<?>> propertySources = pullConfig(configService,
config.getGroup(), config.getDataId(), config.getSuffix(),
properties.getTimeout());
NacosPropertySource propertySource = new NacosPropertySource(propertySources,
config.getGroup(), config.getDataId(), new Date(),
config.isRefreshEnabled());
NacosPropertySourceRepository.collectNacosPropertySource(propertySource);
// TODO Currently based on 2.4.2,
// compatibility needs to be done when upgrading to boot version 2.4.5
return new ConfigData(propertySources);
}
catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug("Error getting properties from nacos: " + resource, e);
}
if (!resource.isOptional()) {
throw new ConfigDataResourceNotFoundException(resource, e);
}
}
return null;
}
private List<PropertySource<?>> pullConfig(ConfigService configService, String group,
String dataId, String suffix, long timeout)
throws NacosException, IOException {
String config = configService.getConfig(dataId, group, timeout);
return NacosDataParserHandler.getInstance().parseNacosData(dataId, config,
suffix);
}
protected <T> T getBean(ConfigDataLoaderContext context, Class<T> type) {
if (context.getBootstrapContext().isRegistered(type)) {
return context.getBootstrapContext().get(type);
}
return null;
}
}

@ -0,0 +1,256 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.nacos.configdata;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.nacos.common.utils.StringUtils;
import org.apache.commons.logging.Log;
import org.springframework.boot.BootstrapRegistry.InstanceSupplier;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.context.config.ConfigDataLocation;
import org.springframework.boot.context.config.ConfigDataLocationNotFoundException;
import org.springframework.boot.context.config.ConfigDataLocationResolver;
import org.springframework.boot.context.config.ConfigDataLocationResolverContext;
import org.springframework.boot.context.config.ConfigDataResource;
import org.springframework.boot.context.config.ConfigDataResourceNotFoundException;
import org.springframework.boot.context.config.Profiles;
import org.springframework.boot.context.properties.bind.BindHandler;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.core.Ordered;
import static com.alibaba.cloud.nacos.configdata.NacosConfigDataResource.NacosItemConfig;
/**
* Implementation of {@link ConfigDataLocationResolver}, load Nacos
* {@link ConfigDataResource}.
*
* @author freeman
*/
public class NacosConfigDataLocationResolver
implements ConfigDataLocationResolver<NacosConfigDataResource>, Ordered {
/**
* Prefix for Config Server imports.
*/
public static final String PREFIX = "nacos:";
private final Log log;
// support params
private static final String GROUP = "group";
private static final String REFRESH_ENABLED = "refreshEnabled";
public NacosConfigDataLocationResolver(Log log) {
this.log = log;
}
@Override
public int getOrder() {
return -1;
}
protected NacosConfigProperties loadProperties(
ConfigDataLocationResolverContext context) {
Binder binder = context.getBinder();
BindHandler bindHandler = getBindHandler(context);
NacosConfigProperties nacosConfigProperties;
if (context.getBootstrapContext().isRegistered(NacosConfigProperties.class)) {
nacosConfigProperties = context.getBootstrapContext()
.get(NacosConfigProperties.class);
}
else {
nacosConfigProperties = binder
.bind("spring.cloud.nacos", Bindable.of(NacosConfigProperties.class),
bindHandler)
.map(properties -> binder
.bind(NacosConfigProperties.PREFIX,
Bindable.ofInstance(properties), bindHandler)
.orElse(properties))
.orElseGet(() -> binder
.bind(NacosConfigProperties.PREFIX,
Bindable.of(NacosConfigProperties.class), bindHandler)
.orElseGet(NacosConfigProperties::new));
}
return nacosConfigProperties;
}
private BindHandler getBindHandler(ConfigDataLocationResolverContext context) {
return context.getBootstrapContext().getOrElse(BindHandler.class, null);
}
protected Log getLog() {
return this.log;
}
@Override
public boolean isResolvable(ConfigDataLocationResolverContext context,
ConfigDataLocation location) {
if (!location.hasPrefix(getPrefix())) {
return false;
}
return context.getBinder()
.bind(NacosConfigProperties.PREFIX + ".enabled", Boolean.class)
.orElse(true);
}
protected String getPrefix() {
return PREFIX;
}
@Override
public List<NacosConfigDataResource> resolve(
ConfigDataLocationResolverContext context, ConfigDataLocation location)
throws ConfigDataLocationNotFoundException,
ConfigDataResourceNotFoundException {
return Collections.emptyList();
}
@Override
public List<NacosConfigDataResource> resolveProfileSpecific(
ConfigDataLocationResolverContext resolverContext,
ConfigDataLocation location, Profiles profiles)
throws ConfigDataLocationNotFoundException {
NacosConfigProperties properties = loadProperties(resolverContext);
ConfigurableBootstrapContext bootstrapContext = resolverContext
.getBootstrapContext();
bootstrapContext.registerIfAbsent(NacosConfigProperties.class,
InstanceSupplier.of(properties));
registerConfigManager(properties, bootstrapContext);
return loadConfigDataResources(location, profiles, properties);
}
private List<NacosConfigDataResource> loadConfigDataResources(
ConfigDataLocation location, Profiles profiles,
NacosConfigProperties properties) {
List<NacosConfigDataResource> result = new ArrayList<>();
URI uri = getUri(location, properties);
if (StringUtils.isBlank(dataIdFor(uri))) {
throw new IllegalArgumentException("dataId must be specified");
}
NacosConfigDataResource resource = new NacosConfigDataResource(properties,
location.isOptional(), profiles, log,
new NacosItemConfig().setGroup(groupFor(uri, properties))
.setDataId(dataIdFor(uri)).setSuffix(suffixFor(uri, properties))
.setRefreshEnabled(refreshEnabledFor(uri, properties)));
result.add(resource);
return result;
}
private URI getUri(ConfigDataLocation location, NacosConfigProperties properties) {
String path = location.getNonPrefixedValue(getPrefix());
if (StringUtils.isBlank(path)) {
path = "/";
}
if (!path.startsWith("/")) {
path = "/" + path;
}
String uri = properties.getServerAddr() + path;
return getUri(uri);
}
private void registerConfigManager(NacosConfigProperties properties,
ConfigurableBootstrapContext bootstrapContext) {
if (!bootstrapContext.isRegistered(NacosConfigManager.class)) {
bootstrapContext.register(NacosConfigManager.class,
InstanceSupplier.of(new NacosConfigManager(properties)));
}
}
private URI getUri(String uris) {
if (!uris.startsWith("http://") && !uris.startsWith("https://")) {
uris = "http://" + uris;
}
URI uri;
try {
uri = new URI(uris);
}
catch (URISyntaxException e) {
throw new IllegalArgumentException("illegal URI: " + uris);
}
return uri;
}
private String groupFor(URI uri, NacosConfigProperties properties) {
Map<String, String> queryMap = getQueryMap(uri);
return queryMap.containsKey(GROUP) ? queryMap.get(GROUP) : properties.getGroup();
}
private Map<String, String> getQueryMap(URI uri) {
String query = uri.getQuery();
if (StringUtils.isBlank(query)) {
return Collections.emptyMap();
}
Map<String, String> result = new HashMap<>(4);
for (String entry : query.split("&")) {
String[] kv = entry.split("=");
if (kv.length == 2) {
result.put(kv[0], kv[1]);
}
}
return result;
}
private String suffixFor(URI uri, NacosConfigProperties properties) {
String dataId = dataIdFor(uri);
if (dataId != null && dataId.contains(".")) {
return dataId.substring(dataId.lastIndexOf('.') + 1);
}
return properties.getFileExtension();
}
private boolean refreshEnabledFor(URI uri, NacosConfigProperties properties) {
Map<String, String> queryMap = getQueryMap(uri);
return queryMap.containsKey(REFRESH_ENABLED)
? Boolean.parseBoolean(queryMap.get(REFRESH_ENABLED))
: properties.isRefreshEnabled();
}
private String dataIdFor(URI uri) {
String path = uri.getPath();
// notice '/'
if (path == null || path.length() <= 1) {
return StringUtils.EMPTY;
}
String[] parts = path.substring(1).split("/");
if (parts.length != 1) {
throw new IllegalArgumentException("illegal dataId");
}
return parts[0];
}
}

@ -0,0 +1,183 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.nacos.configdata;
import java.util.List;
import java.util.Objects;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import org.apache.commons.logging.Log;
import org.springframework.boot.context.config.ConfigDataResource;
import org.springframework.boot.context.config.Profiles;
import org.springframework.util.StringUtils;
/**
* @author freeman
*/
public class NacosConfigDataResource extends ConfigDataResource {
private final NacosConfigProperties properties;
private final boolean optional;
private final Profiles profiles;
private final Log log;
private final NacosItemConfig config;
public NacosConfigDataResource(NacosConfigProperties properties, boolean optional,
Profiles profiles, Log log, NacosItemConfig config) {
this.properties = properties;
this.optional = optional;
this.profiles = profiles;
this.log = log;
this.config = config;
}
public NacosConfigProperties getProperties() {
return this.properties;
}
public boolean isOptional() {
return this.optional;
}
public String getProfiles() {
return StringUtils.collectionToCommaDelimitedString(getAcceptedProfiles());
}
List<String> getAcceptedProfiles() {
return this.profiles.getAccepted();
}
public Log getLog() {
return this.log;
}
public NacosItemConfig getConfig() {
return config;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
NacosConfigDataResource that = (NacosConfigDataResource) o;
return optional == that.optional && Objects.equals(properties, that.properties)
&& Objects.equals(profiles, that.profiles)
&& Objects.equals(log, that.log) && Objects.equals(config, that.config);
}
@Override
public int hashCode() {
return Objects.hash(properties, optional, profiles, log, config);
}
@Override
public String toString() {
return "NacosConfigDataResource{" + "properties=" + properties + ", optional="
+ optional + ", profiles=" + profiles + ", config=" + config + '}';
}
public static class NacosItemConfig {
private String group;
private String dataId;
private String suffix;
private boolean refreshEnabled;
public NacosItemConfig() {
}
public NacosItemConfig(String group, String dataId, String suffix,
boolean refreshEnabled) {
this.group = group;
this.dataId = dataId;
this.suffix = suffix;
this.refreshEnabled = refreshEnabled;
}
public NacosItemConfig setGroup(String group) {
this.group = group;
return this;
}
public NacosItemConfig setDataId(String dataId) {
this.dataId = dataId;
return this;
}
public NacosItemConfig setSuffix(String suffix) {
this.suffix = suffix;
return this;
}
public NacosItemConfig setRefreshEnabled(boolean refreshEnabled) {
this.refreshEnabled = refreshEnabled;
return this;
}
public String getGroup() {
return group;
}
public String getDataId() {
return dataId;
}
public String getSuffix() {
return suffix;
}
public boolean isRefreshEnabled() {
return refreshEnabled;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
NacosItemConfig that = (NacosItemConfig) o;
return refreshEnabled == that.refreshEnabled
&& Objects.equals(group, that.group)
&& Objects.equals(dataId, that.dataId)
&& Objects.equals(suffix, that.suffix);
}
@Override
public int hashCode() {
return Objects.hash(group, dataId, suffix, refreshEnabled);
}
@Override
public String toString() {
return "NacosItemConfig{" + "group='" + group + '\'' + ", dataId='" + dataId
+ '\'' + ", suffix='" + suffix + '\'' + ", refreshEnabled="
+ refreshEnabled + '}';
}
}
}

@ -7,4 +7,12 @@ org.springframework.boot.diagnostics.FailureAnalyzer=\
com.alibaba.cloud.nacos.diagnostics.analyzer.NacosConnectionFailureAnalyzer com.alibaba.cloud.nacos.diagnostics.analyzer.NacosConnectionFailureAnalyzer
org.springframework.boot.env.PropertySourceLoader=\ org.springframework.boot.env.PropertySourceLoader=\
com.alibaba.cloud.nacos.parser.NacosJsonPropertySourceLoader,\ com.alibaba.cloud.nacos.parser.NacosJsonPropertySourceLoader,\
com.alibaba.cloud.nacos.parser.NacosXmlPropertySourceLoader com.alibaba.cloud.nacos.parser.NacosXmlPropertySourceLoader
# ConfigData Location Resolvers
org.springframework.boot.context.config.ConfigDataLocationResolver=\
com.alibaba.cloud.nacos.configdata.NacosConfigDataLocationResolver
# ConfigData Loaders
org.springframework.boot.context.config.ConfigDataLoader=\
com.alibaba.cloud.nacos.configdata.NacosConfigDataLoader

@ -0,0 +1,63 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.nacos;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* NacosConfigAutoConfiguration Tester.
*
* @author freeman
*/
public class NacosConfigAutoConfigurationTest {
@Test
public void noImports_thenCreateProperties() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
NacosConfigAutoConfiguration.class);
assertThat(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
NacosConfigProperties.class).length).isEqualTo(1);
assertThat(context.getBean(NacosConfigProperties.class).getServerAddr())
.isEqualTo("localhost:8848");
context.close();
}
@Test
public void imports_thenNoCreateProperties() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
NacosConfigAutoConfiguration.class);
// mock import
context.registerBean(NacosConfigProperties.class, () -> {
NacosConfigProperties properties = new NacosConfigProperties();
properties.setServerAddr("localhost");
return properties;
});
assertThat(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
NacosConfigProperties.class).length).isEqualTo(1);
assertThat(context.getBean(NacosConfigProperties.class).getServerAddr())
.isEqualTo("localhost");
context.close();
}
}

@ -0,0 +1,240 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cloud.nacos.configdata;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.nacos.api.config.ConfigService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.context.config.ConfigDataLocation;
import org.springframework.boot.context.config.ConfigDataLocationResolverContext;
import org.springframework.boot.context.config.Profiles;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.logging.DeferredLog;
import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* NacosConfigDataLocationResolver Tester.
*
* @author freeman
*/
public class NacosConfigDataLocationResolverTest {
private NacosConfigDataLocationResolver resolver;
private ConfigDataLocationResolverContext context = mock(
ConfigDataLocationResolverContext.class);
private MockEnvironment environment;
private Binder environmentBinder;
private ConfigurableBootstrapContext bootstrapContext = mock(
ConfigurableBootstrapContext.class);
@BeforeEach
void setup() {
this.environment = new MockEnvironment();
this.environmentBinder = Binder.get(this.environment);
this.resolver = new NacosConfigDataLocationResolver(new DeferredLog());
when(bootstrapContext.isRegistered(eq(ConfigService.class))).thenReturn(true);
when(context.getBinder()).thenReturn(environmentBinder);
when(context.getBootstrapContext()).thenReturn(bootstrapContext);
}
@Test
void testIsResolvable_givenIncorrectPrefix_thenReturnFalse() {
assertThat(
this.resolver.isResolvable(this.context, ConfigDataLocation.of("test:")))
.isFalse();
}
@Test
void testIsResolvable_givenCorrectPrefix_thenReturnTure() {
assertThat(
this.resolver.isResolvable(this.context, ConfigDataLocation.of("nacos:")))
.isTrue();
assertThat(this.resolver.isResolvable(this.context,
ConfigDataLocation.of("optional:nacos:"))).isTrue();
}
@Test
void testIsResolvable_givenDisable_thenReturnFalse() {
this.environment.setProperty(NacosConfigProperties.PREFIX + ".enabled", "false");
assertThat(
this.resolver.isResolvable(this.context, ConfigDataLocation.of("nacos:")))
.isFalse();
}
@Test
void testResolveProfileSpecific_givenNothing_thenReturnDefaultProfile() {
NacosConfigDataResource resource = testResolveProfileSpecific();
assertThat(resource.getProfiles()).isEqualTo("default");
}
@Test
void testStartWithASlashIsOK() {
String locationUri = "nacos:/app";
List<NacosConfigDataResource> resources = testUri(locationUri);
assertThat(resources).hasSize(1);
assertThat(resources.get(0).getConfig().getDataId()).isEqualTo("app");
locationUri = "nacos:app";
resources = testUri(locationUri);
assertThat(resources).hasSize(1);
assertThat(resources.get(0).getConfig().getDataId()).isEqualTo("app");
}
@Test
void testDataIdMustBeSpecified() {
String locationUri = "nacos:";
assertThatThrownBy(() -> testUri(locationUri))
.hasMessage("dataId must be specified");
}
@Test
void testInvalidDataId() {
String locationUri = "nacos:test/test.yml";
assertThatThrownBy(() -> testUri(locationUri)).hasMessage("illegal dataId");
}
@Test
void whenCustomizeSuffix_thenOverrideDefault() {
String locationUri = "nacos:app";
List<NacosConfigDataResource> resources = testUri(locationUri);
assertThat(resources).hasSize(1);
assertThat(resources.get(0).getConfig().getDataId()).isEqualTo("app");
assertThat(resources.get(0).getConfig().getSuffix()).isEqualTo("properties");
environment.setProperty("spring.cloud.nacos.config.file-extension", "yml");
locationUri = "nacos:app";
resources = testUri(locationUri);
assertThat(resources).hasSize(1);
assertThat(resources.get(0).getConfig().getDataId()).isEqualTo("app");
assertThat(resources.get(0).getConfig().getSuffix()).isEqualTo("yml");
locationUri = "nacos:app.json";
resources = testUri(locationUri);
assertThat(resources).hasSize(1);
assertThat(resources.get(0).getConfig().getDataId()).isEqualTo("app.json");
assertThat(resources.get(0).getConfig().getSuffix()).isEqualTo("json");
}
@Test
void testUrisInLocationShouldOverridesProperty() {
environment.setProperty("spring.cloud.nacos.config.group", "default");
environment.setProperty("spring.cloud.nacos.config.refreshEnabled", "true");
String locationUri = "nacos:test.yml?group=not_default&refreshEnabled=false";
List<NacosConfigDataResource> resources = testUri(locationUri);
assertThat(resources).hasSize(1);
NacosConfigDataResource resource = resources.get(0);
assertThat(resource.getConfig().getGroup()).isEqualTo("not_default");
assertThat(resource.getConfig().getSuffix()).isEqualTo("yml");
assertThat(resource.getConfig().isRefreshEnabled()).isFalse();
assertThat(resource.getConfig().getDataId()).isEqualTo("test.yml");
}
@Test
void testSetCommonPropertiesIsOK() {
environment.setProperty("spring.cloud.nacos.username", "root");
environment.setProperty("spring.cloud.nacos.password", "root");
environment.setProperty("spring.cloud.nacos.server-addr", "localhost:8888");
String locationUri = "nacos:test.yml";
List<NacosConfigDataResource> resources = testUri(locationUri);
assertThat(resources).hasSize(1);
NacosConfigDataResource resource = resources.get(0);
assertThat(resource.getProperties().getUsername()).isEqualTo("root");
assertThat(resource.getProperties().getPassword()).isEqualTo("root");
assertThat(resource.getProperties().getServerAddr()).isEqualTo("localhost:8888");
}
@Test
void testCommonPropertiesHasLowerPriority() {
environment.setProperty("spring.cloud.nacos.username", "root");
environment.setProperty("spring.cloud.nacos.password", "root");
environment.setProperty("spring.cloud.nacos.config.password", "not_root");
environment.setProperty("spring.cloud.nacos.server-addr", "localhost:8888");
environment.setProperty("spring.cloud.nacos.config.server-addr",
"localhost:9999");
String locationUri = "nacos:test.yml";
List<NacosConfigDataResource> resources = testUri(locationUri);
assertThat(resources).hasSize(1);
NacosConfigDataResource resource = resources.get(0);
assertThat(resource.getProperties().getUsername()).isEqualTo("root");
assertThat(resource.getProperties().getPassword()).isEqualTo("not_root");
assertThat(resource.getProperties().getServerAddr()).isEqualTo("localhost:9999");
}
private List<NacosConfigDataResource> testUri(String locationUri,
String... activeProfiles) {
Profiles profiles = mock(Profiles.class);
when(profiles.getActive()).thenReturn(Arrays.asList(activeProfiles));
return this.resolver.resolveProfileSpecific(context,
ConfigDataLocation.of(locationUri), profiles);
}
@Test
void whenNoneInBootstrapContext_thenCreateNewConfigClientProperties() {
when(bootstrapContext.isRegistered(eq(NacosConfigProperties.class)))
.thenReturn(false);
when(bootstrapContext.get(eq(NacosConfigProperties.class)))
.thenReturn(new NacosConfigProperties());
List<NacosConfigDataResource> resources = this.resolver.resolveProfileSpecific(
context, ConfigDataLocation.of("nacos:test.yml"), mock(Profiles.class));
assertThat(resources).hasSize(1);
verify(bootstrapContext, times(0)).get(eq(NacosConfigProperties.class));
NacosConfigDataResource resource = resources.get(0);
assertThat(resource.getConfig().getGroup()).isEqualTo("DEFAULT_GROUP");
assertThat(resource.getConfig().getDataId()).isEqualTo("test.yml");
}
private NacosConfigDataResource testResolveProfileSpecific() {
return testResolveProfileSpecific("default");
}
private NacosConfigDataResource testResolveProfileSpecific(String activeProfile) {
Profiles profiles = mock(Profiles.class);
if (activeProfile != null) {
when(profiles.getActive())
.thenReturn(Collections.singletonList(activeProfile));
when(profiles.getAccepted())
.thenReturn(Collections.singletonList(activeProfile));
}
List<NacosConfigDataResource> resources = this.resolver.resolveProfileSpecific(
context, ConfigDataLocation.of("nacos:test.yml"), profiles);
assertThat(resources).hasSize(1);
return resources.get(0);
}
}
Loading…
Cancel
Save