diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-config-2.4.x-example/pom.xml b/spring-cloud-alibaba-examples/nacos-example/nacos-config-2.4.x-example/pom.xml new file mode 100644 index 000000000..09b031af6 --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-config-2.4.x-example/pom.xml @@ -0,0 +1,53 @@ + + + + + com.alibaba.cloud + spring-cloud-alibaba-examples + ${revision} + ../../pom.xml + + 4.0.0 + + nacos-config-2.4.x-example + Spring Cloud Starter Alibaba Nacos Config 2.4.x Example + Example demonstrating how to use nacos config in spring boot 2.4.x + jar + + + + + org.springframework.boot + spring-boot-starter-web + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + org.springframework.cloud + spring-cloud-starter-bootstrap + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-deploy-plugin + ${maven-deploy-plugin.version} + + true + + + + + + diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-config-2.4.x-example/readme-zh.md b/spring-cloud-alibaba-examples/nacos-example/nacos-config-2.4.x-example/readme-zh.md new file mode 100644 index 000000000..363a4cb50 --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-config-2.4.x-example/readme-zh.md @@ -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 + + org.springframework.boot + spring-boot-starter-web + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + org.springframework.cloud + spring-cloud-starter-bootstrap + + + +``` + + +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 map; + private List users; + // getters and setters ... + + public static class User { + private String name; + private Integer age; + // getters and setters ... + } +} +``` + +验证动态刷新 +访问 http://localhost:8888 +再从 nacos 修改配置, 再次访问即可验证动态配置生效 diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-config-2.4.x-example/src/main/java/com/alibaba/cloud/imports/examples/Application.java b/spring-cloud-alibaba-examples/nacos-example/nacos-config-2.4.x-example/src/main/java/com/alibaba/cloud/imports/examples/Application.java new file mode 100644 index 000000000..a3e47854f --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-config-2.4.x-example/src/main/java/com/alibaba/cloud/imports/examples/Application.java @@ -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); + } + +} diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-config-2.4.x-example/src/main/java/com/alibaba/cloud/imports/examples/controller/UserController.java b/spring-cloud-alibaba-examples/nacos-example/nacos-config-2.4.x-example/src/main/java/com/alibaba/cloud/imports/examples/controller/UserController.java new file mode 100644 index 000000000..adfce66b1 --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-config-2.4.x-example/src/main/java/com/alibaba/cloud/imports/examples/controller/UserController.java @@ -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); + } + +} diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-config-2.4.x-example/src/main/java/com/alibaba/cloud/imports/examples/model/UserConfig.java b/spring-cloud-alibaba-examples/nacos-example/nacos-config-2.4.x-example/src/main/java/com/alibaba/cloud/imports/examples/model/UserConfig.java new file mode 100644 index 000000000..b6b8df93c --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-config-2.4.x-example/src/main/java/com/alibaba/cloud/imports/examples/model/UserConfig.java @@ -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 map; + private List 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 getMap() { + return map; + } + + public void setMap(Map map) { + this.map = map; + } + + public List getUsers() { + return users; + } + + public void setUsers(List 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; + } + } + +} diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-config-2.4.x-example/src/main/resources/application.yml b/spring-cloud-alibaba-examples/nacos-example/nacos-config-2.4.x-example/src/main/resources/application.yml new file mode 100644 index 000000000..6047c5ab3 --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-config-2.4.x-example/src/main/resources/application.yml @@ -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 diff --git a/spring-cloud-alibaba-examples/pom.xml b/spring-cloud-alibaba-examples/pom.xml index e210d751d..3c3c379d5 100644 --- a/spring-cloud-alibaba-examples/pom.xml +++ b/spring-cloud-alibaba-examples/pom.xml @@ -28,6 +28,7 @@ sentinel-example/sentinel-spring-cloud-gateway-example nacos-example/nacos-discovery-example nacos-example/nacos-config-example + nacos-example/nacos-config-2.4.x-example nacos-example/nacos-gateway-example seata-example/business-service seata-example/order-service diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/controller/ApiController.java b/spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/controller/ApiController.java index 0e78ee1a7..31ec17a2a 100644 --- a/spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/controller/ApiController.java +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/controller/ApiController.java @@ -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; import org.springframework.web.bind.annotation.GetMapping; @@ -12,28 +28,28 @@ import org.springframework.web.bind.annotation.RestController; @RestController public class ApiController { - @GetMapping("/default/{ok}") - public String defaultConfig(@PathVariable boolean ok) { - if (ok) { - return "ok"; - } - throw new RuntimeException("fail"); - } + @GetMapping("/default/{ok}") + public String defaultConfig(@PathVariable boolean ok) { + if (ok) { + return "ok"; + } + throw new RuntimeException("fail"); + } - @GetMapping("/feign/{ok}") - public String feignConfig(@PathVariable boolean ok) { - if (ok) { - return "ok"; - } - throw new RuntimeException("fail"); - } + @GetMapping("/feign/{ok}") + public String feignConfig(@PathVariable boolean ok) { + if (ok) { + return "ok"; + } + throw new RuntimeException("fail"); + } - @GetMapping("/feignMethod/{ok}") - public String feignMethodConfig(@PathVariable boolean ok) { - if (ok) { - return "ok"; - } - throw new RuntimeException("fail"); - } + @GetMapping("/feignMethod/{ok}") + public String feignMethodConfig(@PathVariable boolean ok) { + if (ok) { + return "ok"; + } + throw new RuntimeException("fail"); + } } diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/controller/TestController.java b/spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/controller/TestController.java index e6f4a3588..281c1c2c8 100644 --- a/spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/controller/TestController.java +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/controller/TestController.java @@ -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; import com.alibaba.cloud.examples.feign.OrderClient; import com.alibaba.cloud.examples.feign.UserClient; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -15,25 +32,24 @@ import org.springframework.web.bind.annotation.RestController; @RestController public class TestController { - @Autowired - private UserClient userClient; - @Autowired - private OrderClient orderClient; - - - @GetMapping("/test/default/{ok}") - public String testDefault(@PathVariable boolean ok) { - return orderClient.defaultConfig(ok); - } - - @GetMapping("/test/feign/{ok}") - public String testFeign(@PathVariable boolean ok) { - return userClient.feign(ok); - } - - @GetMapping("/test/feignMethod/{ok}") - public String testFeignMethod(@PathVariable boolean ok) { - return userClient.feignMethod(ok); - } + @Autowired + private UserClient userClient; + @Autowired + private OrderClient orderClient; + + @GetMapping("/test/default/{ok}") + public String testDefault(@PathVariable boolean ok) { + return orderClient.defaultConfig(ok); + } + + @GetMapping("/test/feign/{ok}") + public String testFeign(@PathVariable boolean ok) { + return userClient.feign(ok); + } + + @GetMapping("/test/feignMethod/{ok}") + public String testFeignMethod(@PathVariable boolean ok) { + return userClient.feignMethod(ok); + } } diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/feign/OrderClient.java b/spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/feign/OrderClient.java index c715b12ea..0448e4f07 100644 --- a/spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/feign/OrderClient.java +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/feign/OrderClient.java @@ -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; 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) public interface OrderClient { - @GetMapping("/default/{ok}") - String defaultConfig(@PathVariable boolean ok); + @GetMapping("/default/{ok}") + String defaultConfig(@PathVariable boolean ok); } diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/feign/OrderClientFallBack.java b/spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/feign/OrderClientFallBack.java index bb142ba2a..9ef6e797a 100644 --- a/spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/feign/OrderClientFallBack.java +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/feign/OrderClientFallBack.java @@ -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; import org.springframework.stereotype.Component; @@ -9,8 +25,8 @@ import org.springframework.stereotype.Component; */ @Component public class OrderClientFallBack implements OrderClient { - @Override - public String defaultConfig(boolean ok) { - return "order fallback"; - } + @Override + public String defaultConfig(boolean ok) { + return "order fallback"; + } } diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/feign/UserClient.java b/spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/feign/UserClient.java index 301846de0..ea5bd8b66 100644 --- a/spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/feign/UserClient.java +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/feign/UserClient.java @@ -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; 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) public interface UserClient { - @GetMapping("/feignMethod/{ok}") - String feignMethod(@PathVariable boolean ok); + @GetMapping("/feignMethod/{ok}") + String feignMethod(@PathVariable boolean ok); - @GetMapping("/feign/{ok}") - String feign(@PathVariable boolean ok); + @GetMapping("/feign/{ok}") + String feign(@PathVariable boolean ok); } diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/feign/UserClientFallBack.java b/spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/feign/UserClientFallBack.java index 933d9c281..454240cff 100644 --- a/spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/feign/UserClientFallBack.java +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-circuitbreaker-example/src/main/java/com/alibaba/cloud/examples/feign/UserClientFallBack.java @@ -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; import org.springframework.stereotype.Component; @@ -9,13 +25,13 @@ import org.springframework.stereotype.Component; */ @Component public class UserClientFallBack implements UserClient { - @Override - public String feignMethod(boolean ok) { - return "user fallback"; - } + @Override + public String feignMethod(boolean ok) { + return "user fallback"; + } - @Override - public String feign(boolean ok) { - return "user fallback"; - } + @Override + public String feign(boolean ok) { + return "user fallback"; + } } diff --git a/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/CircuitBreakerRuleChangeListener.java b/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/CircuitBreakerRuleChangeListener.java index 7b41d2257..049d6b6fe 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/CircuitBreakerRuleChangeListener.java +++ b/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/CircuitBreakerRuleChangeListener.java @@ -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; 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.csp.sentinel.EntryType; @@ -27,11 +50,13 @@ import org.springframework.core.annotation.AnnotationUtils; */ public class CircuitBreakerRuleChangeListener implements ApplicationContextAware, ApplicationListener, SmartInitializingSingleton { - private static final Logger LOGGER = LoggerFactory.getLogger(CircuitBreakerRuleChangeListener.class); + private static final Logger LOGGER = LoggerFactory + .getLogger(CircuitBreakerRuleChangeListener.class); 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 AbstractCircuitBreakerFactory circuitBreakerFactory; @@ -58,7 +83,8 @@ public class CircuitBreakerRuleChangeListener implements ApplicationContextAware } @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException { this.applicationContext = applicationContext; } @@ -73,13 +99,16 @@ public class CircuitBreakerRuleChangeListener implements ApplicationContextAware // as it will cause the bean to be initialized prematurely, // and we don't want to change the initialization order of the beans if (circuitBreakerFactory == null) { - String[] names = applicationContext.getBeanNamesForType(AbstractCircuitBreakerFactory.class); + String[] names = applicationContext + .getBeanNamesForType(AbstractCircuitBreakerFactory.class); if (names.length >= 1) { - this.circuitBreakerFactory = applicationContext.getBean(names[0], AbstractCircuitBreakerFactory.class); + this.circuitBreakerFactory = applicationContext.getBean(names[0], + AbstractCircuitBreakerFactory.class); } } 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. propertiesBackup.getRules().keySet().stream() .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 // NOTE: feign client name cannot be the same as the general resource name !!! @@ -116,7 +147,8 @@ public class CircuitBreakerRuleChangeListener implements ApplicationContextAware .map(beanName -> { try { return Class.forName(beanName); - } catch (ClassNotFoundException ignore) { + } + catch (ClassNotFoundException ignore) { // definitely not a feign client, just ignore return null; } @@ -128,7 +160,9 @@ public class CircuitBreakerRuleChangeListener implements ApplicationContextAware return; } String feignClientName = AnnotationUtils.getValue(anno).toString(); - Optional.ofNullable(DegradeRuleManager.getRulesOfResource(feignClientName)).ifPresent(Set::clear); + Optional.ofNullable( + DegradeRuleManager.getRulesOfResource(feignClientName)) + .ifPresent(Set::clear); }); } diff --git a/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/FeignClientCircuitNameResolver.java b/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/FeignClientCircuitNameResolver.java index 4d15dfbaf..368c42cc4 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/FeignClientCircuitNameResolver.java +++ b/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/FeignClientCircuitNameResolver.java @@ -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; import java.lang.reflect.Method; @@ -9,7 +25,7 @@ import feign.Target; import org.springframework.cloud.client.circuitbreaker.AbstractCircuitBreakerFactory; 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. diff --git a/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/SentinelFeignClientAutoConfiguration.java b/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/SentinelFeignClientAutoConfiguration.java index 90264b24d..f2e2d5e74 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/SentinelFeignClientAutoConfiguration.java +++ b/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/SentinelFeignClientAutoConfiguration.java @@ -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"); * 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.openfeign.CircuitBreakerNameResolver; import org.springframework.cloud.openfeign.FeignClientFactoryBean; -import org.springframework.cloud.util.ConditionalOnBootstrapEnabled; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -46,14 +45,12 @@ import static com.alibaba.cloud.circuitbreaker.sentinel.feign.CircuitBreakerRule */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ Feign.class, FeignClientFactoryBean.class }) -@ConditionalOnProperty(name = "spring.cloud.circuitbreaker.sentinel.enabled", - havingValue = "true", matchIfMissing = true) +@ConditionalOnProperty(name = "spring.cloud.circuitbreaker.sentinel.enabled", havingValue = "true", matchIfMissing = true) @EnableConfigurationProperties(SentinelFeignClientProperties.class) public class SentinelFeignClientAutoConfiguration { @Configuration(proxyBeanMethods = false) - @ConditionalOnProperty(name = "feign.sentinel.refresh-rules", - havingValue = "true", matchIfMissing = true) + @ConditionalOnProperty(name = "feign.sentinel.refresh-rules", havingValue = "true", matchIfMissing = true) public static class CircuitBreakerListenerConfiguration { @Bean diff --git a/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/SentinelFeignClientProperties.java b/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/SentinelFeignClientProperties.java index cb93eb7db..532501303 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/SentinelFeignClientProperties.java +++ b/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/main/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/SentinelFeignClientProperties.java @@ -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; + import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; + import org.springframework.boot.context.properties.ConfigurationProperties; /** @@ -79,7 +96,8 @@ public class SentinelFeignClientProperties { ObjectMapper objectMapper = new ObjectMapper(); String json = objectMapper.writeValueAsString(this); return objectMapper.readValue(json, this.getClass()); - } catch (JsonProcessingException ignored) { + } + catch (JsonProcessingException ignored) { } return new SentinelFeignClientProperties(); } diff --git a/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/test/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/FeignClientCircuitBreakerRuleIntegrationTest.java b/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/test/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/FeignClientCircuitBreakerRuleIntegrationTest.java index 202426393..8f7da1220 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/test/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/FeignClientCircuitBreakerRuleIntegrationTest.java +++ b/spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel/src/test/java/com/alibaba/cloud/circuitbreaker/sentinel/feign/FeignClientCircuitBreakerRuleIntegrationTest.java @@ -29,7 +29,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; 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.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/pom.xml b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/pom.xml index 5815b8a46..f6a2e185a 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/pom.xml +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/pom.xml @@ -36,6 +36,7 @@ org.springframework.cloud spring-cloud-starter-bootstrap + true diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/NacosConfigAutoConfiguration.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/NacosConfigAutoConfiguration.java index 60075c9c1..df24f54c6 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/NacosConfigAutoConfiguration.java +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/NacosConfigAutoConfiguration.java @@ -21,7 +21,9 @@ import com.alibaba.cloud.nacos.refresh.NacosRefreshHistory; import com.alibaba.cloud.nacos.refresh.NacosRefreshProperties; 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.SearchStrategy; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -34,6 +36,7 @@ import org.springframework.context.annotation.Configuration; public class NacosConfigAutoConfiguration { @Bean + @ConditionalOnMissingBean(value = NacosConfigProperties.class, search = SearchStrategy.CURRENT) public NacosConfigProperties nacosConfigProperties(ApplicationContext context) { if (context.getParent() != null && BeanFactoryUtils.beanNamesForTypeIncludingAncestors( diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/NacosConfigProperties.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/NacosConfigProperties.java index 4827aebf1..6ce2fdf3d 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/NacosConfigProperties.java +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/NacosConfigProperties.java @@ -98,6 +98,9 @@ public class NacosConfigProperties { } private void overrideFromEnv() { + if (environment == null) { + return; + } if (StringUtils.isEmpty(this.getServerAddr())) { String serverAddr = environment .resolvePlaceholders("${spring.cloud.nacos.config.server-addr:}"); @@ -570,6 +573,9 @@ public class NacosConfigProperties { } private void enrichNacosConfigProperties(Properties nacosConfigProperties) { + if (environment == null) { + return; + } Map properties = PropertySourcesUtils .getSubProperties((ConfigurableEnvironment) environment, PREFIX); properties.forEach((k, v) -> nacosConfigProperties.putIfAbsent(resolveKey(k), diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/client/NacosPropertySource.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/client/NacosPropertySource.java index 2abdc3499..91e7fd043 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/client/NacosPropertySource.java +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/client/NacosPropertySource.java @@ -62,7 +62,7 @@ public class NacosPropertySource extends MapPropertySource { this.isRefreshable = isRefreshable; } - NacosPropertySource(List> propertySources, String group, + public NacosPropertySource(List> propertySources, String group, String dataId, Date timestamp, boolean isRefreshable) { this(group, dataId, getSourceMap(group, dataId, propertySources), timestamp, isRefreshable); diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataLoader.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataLoader.java new file mode 100644 index 000000000..db8a105ba --- /dev/null +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataLoader.java @@ -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}. + * + *

+ * Load {@link ConfigData} via {@link NacosConfigDataResource} + * + * @author freeman + */ +public class NacosConfigDataLoader implements ConfigDataLoader { + + 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> 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> 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 getBean(ConfigDataLoaderContext context, Class type) { + if (context.getBootstrapContext().isRegistered(type)) { + return context.getBootstrapContext().get(type); + } + return null; + } + +} diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataLocationResolver.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataLocationResolver.java new file mode 100644 index 000000000..15ef3a402 --- /dev/null +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataLocationResolver.java @@ -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, 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 resolve( + ConfigDataLocationResolverContext context, ConfigDataLocation location) + throws ConfigDataLocationNotFoundException, + ConfigDataResourceNotFoundException { + return Collections.emptyList(); + } + + @Override + public List 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 loadConfigDataResources( + ConfigDataLocation location, Profiles profiles, + NacosConfigProperties properties) { + List 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 queryMap = getQueryMap(uri); + return queryMap.containsKey(GROUP) ? queryMap.get(GROUP) : properties.getGroup(); + } + + private Map getQueryMap(URI uri) { + String query = uri.getQuery(); + if (StringUtils.isBlank(query)) { + return Collections.emptyMap(); + } + Map 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 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]; + } + +} diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataResource.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataResource.java new file mode 100644 index 000000000..c0973c20b --- /dev/null +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataResource.java @@ -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 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 + '}'; + } + } + +} diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/resources/META-INF/spring.factories b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/resources/META-INF/spring.factories index aa6bf0d92..dc58fcddd 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/resources/META-INF/spring.factories @@ -7,4 +7,12 @@ org.springframework.boot.diagnostics.FailureAnalyzer=\ com.alibaba.cloud.nacos.diagnostics.analyzer.NacosConnectionFailureAnalyzer org.springframework.boot.env.PropertySourceLoader=\ com.alibaba.cloud.nacos.parser.NacosJsonPropertySourceLoader,\ -com.alibaba.cloud.nacos.parser.NacosXmlPropertySourceLoader \ No newline at end of file +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 diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/test/java/com/alibaba/cloud/nacos/NacosConfigAutoConfigurationTest.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/test/java/com/alibaba/cloud/nacos/NacosConfigAutoConfigurationTest.java new file mode 100644 index 000000000..59309e3d5 --- /dev/null +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/test/java/com/alibaba/cloud/nacos/NacosConfigAutoConfigurationTest.java @@ -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(); + } + +} diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/test/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataLocationResolverTest.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/test/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataLocationResolverTest.java new file mode 100644 index 000000000..b149c3a2e --- /dev/null +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/test/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataLocationResolverTest.java @@ -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 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 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 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 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 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 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 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 resources = this.resolver.resolveProfileSpecific( + context, ConfigDataLocation.of("nacos:test.yml"), profiles); + assertThat(resources).hasSize(1); + return resources.get(0); + } + +}