diff --git a/.github/workflows/junit_test.yml b/.github/workflows/junit_test.yml index 35fed41eb..51e9cce15 100644 --- a/.github/workflows/junit_test.yml +++ b/.github/workflows/junit_test.yml @@ -5,24 +5,47 @@ name: Test with Junit on: push: - branches: [ 2021.0 ] + branches: + - main + - 2021.0 + - 2020.0 + - greenwich pull_request: - branches: [ 2021.0 ] + branches: + - main + - 2021.0 + - 2020.0 + - greenwich jobs: build: + strategy: + matrix: + java: [ 8, 11, 17 ] + os: [ 'windows-latest', 'macos-latest', 'ubuntu-latest' ] - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} steps: - name: Checkout codes - uses: actions/checkout@v2 - - name: Set up JDK 8 - uses: actions/setup-java@v2 + uses: actions/checkout@v3 + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v3 with: - java-version: '8' - distribution: 'adopt' + distribution: 'temurin' + java-version: ${{ matrix.java }} + - name: Cache local Maven repository + uses: actions/cache@v3 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- # - name: Build with Maven # run: mvn -B package --file pom.xml - name: Test with Maven run: mvn -B test --file pom.xml + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + file: '**/target/site/jacoco/jacoco.xml' diff --git a/CHANGELOG.md b/CHANGELOG.md index d4a65e9b3..425d0da06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,4 +4,10 @@ - [Upgrade: fix third-party lib CVEs & upgrade core spring libs version](https://github.com/Tencent/spring-cloud-tencent/pull/263) - [feat:support reading configuration from application.yml or application.properties.](https://github.com/Tencent/spring-cloud-tencent/pull/262) - [fix:fix ClassNotFoundException while not importing openfeign when using circuit-breaker module.](https://github.com/Tencent/spring-cloud-tencent/pull/271) -- [fix:solve the chaos code problem on rejectTips](https://github.com/Tencent/spring-cloud-tencent/pull/283) +- [fix:solve ratelimit-callee-service UnknownHostException.](https://github.com/Tencent/spring-cloud-tencent/pull/292) +- [Feature: Add config change listener feature support](https://github.com/Tencent/spring-cloud-tencent/pull/299) +- [Feature: Add config module unit test](https://github.com/Tencent/spring-cloud-tencent/pull/301) +- [Feature:add restTemplate Report Polaris](https://github.com/Tencent/spring-cloud-tencent/pull/304) +- [Update GitHub Actions workflow](https://github.com/Tencent/spring-cloud-tencent/pull/305) +- [fix: 将blocking call改为non-blocking call](https://github.com/Tencent/spring-cloud-tencent/pull/309) +- [fix:solve the chaos code problem on rejectTips](https://github.com/Tencent/spring-cloud-tencent/pull/283) \ No newline at end of file diff --git a/pom.xml b/pom.xml index b16ae8082..2561f4430 100644 --- a/pom.xml +++ b/pom.xml @@ -38,9 +38,11 @@ - spring-cloud-tencent-polaris-context spring-cloud-tencent-commons + spring-cloud-tencent-polaris-context + spring-cloud-tencent-polaris-loadbalancer spring-cloud-starter-tencent-metadata-transfer + spring-cloud-starter-tencent-polaris-config spring-cloud-starter-tencent-polaris-discovery spring-cloud-starter-tencent-polaris-ratelimit spring-cloud-starter-tencent-polaris-circuitbreaker @@ -48,8 +50,6 @@ spring-cloud-tencent-dependencies spring-cloud-tencent-examples spring-cloud-tencent-coverage - spring-cloud-starter-tencent-polaris-config - spring-cloud-tencent-polaris-loadbalancer @@ -95,7 +95,7 @@ 5.3.21 - 0.8.3 + 0.8.8 3.2.0 1.2.7 3.0.1 diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/intercepter/EncodeTransferMedataFeignInterceptorTest.java b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/intercepter/EncodeTransferMedataFeignInterceptorTest.java index 2ad7fdc66..c4a7338df 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/intercepter/EncodeTransferMedataFeignInterceptorTest.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/intercepter/EncodeTransferMedataFeignInterceptorTest.java @@ -19,9 +19,10 @@ package com.tencent.cloud.metadata.core.intercepter; import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; import com.tencent.cloud.common.constant.MetadataConstant; +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; import com.tencent.cloud.metadata.core.EncodeTransferMedataFeignInterceptor; import feign.RequestInterceptor; @@ -61,11 +62,13 @@ public class EncodeTransferMedataFeignInterceptorTest { private TestApplication.TestFeign testFeign; @Test - public void test1() { + public void testTransitiveMetadataFromApplicationConfig() { String metadata = testFeign.test(); - Assertions.assertThat(metadata).isEqualTo("{\"b\":\"2\"}"); - Assertions.assertThat(metadataLocalProperties.getContent().get("a")).isEqualTo("1"); - Assertions.assertThat(metadataLocalProperties.getContent().get("b")).isEqualTo("2"); + Assertions.assertThat(metadata).isEqualTo("2"); + Assertions.assertThat(metadataLocalProperties.getContent().get("a")) + .isEqualTo("1"); + Assertions.assertThat(metadataLocalProperties.getContent().get("b")) + .isEqualTo("2"); } @SpringBootApplication @@ -77,16 +80,13 @@ public class EncodeTransferMedataFeignInterceptorTest { public String test( @RequestHeader(MetadataConstant.HeaderName.CUSTOM_METADATA) String customMetadataStr) throws UnsupportedEncodingException { - return URLDecoder.decode(customMetadataStr, "UTF-8"); + return MetadataContextHolder.get().getContext(MetadataContext.FRAGMENT_TRANSITIVE, "b"); } @FeignClient(name = "test-feign", url = "http://localhost:8081") public interface TestFeign { - @RequestMapping(value = "/test", - headers = {"X-SCT-Metadata-Transitive-a=11", - "X-SCT-Metadata-Transitive-b=22", - "X-SCT-Metadata-Transitive-c=33"}) + @RequestMapping("/test") String test(); } diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/intercepter/EncodeTransferMedataRestTemplateInterceptorTest.java b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/intercepter/EncodeTransferMedataRestTemplateInterceptorTest.java index ba5524c9a..78abb33ab 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/intercepter/EncodeTransferMedataRestTemplateInterceptorTest.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/intercepter/EncodeTransferMedataRestTemplateInterceptorTest.java @@ -19,11 +19,12 @@ package com.tencent.cloud.metadata.core.intercepter; import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; import com.tencent.cloud.common.constant.MetadataConstant; -import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateInterceptor; +import org.assertj.core.api.Assertions; import org.junit.Test; import org.junit.runner.RunWith; @@ -32,6 +33,9 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.web.server.LocalServerPort; import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; @@ -51,9 +55,6 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen properties = { "spring.config.location = classpath:application-test.yml" }) public class EncodeTransferMedataRestTemplateInterceptorTest { - @Autowired - private MetadataLocalProperties metadataLocalProperties; - @Autowired private RestTemplate restTemplate; @@ -61,30 +62,14 @@ public class EncodeTransferMedataRestTemplateInterceptorTest { private int localServerPort; @Test - public void test1() { -// HttpHeaders httpHeaders = new HttpHeaders(); -// httpHeaders.set(MetadataConstant.HeaderName.CUSTOM_METADATA, -// "{\"a\":\"11\",\"b\":\"22\",\"c\":\"33\"}"); -// HttpEntity httpEntity = new HttpEntity<>(httpHeaders); -// String metadata = restTemplate -// .exchange("http://localhost:" + localServerPort + "/test", HttpMethod.GET, -// httpEntity, String.class) -// .getBody(); -// Assertions.assertThat(metadata) -// .isEqualTo("{\"a\":\"11\",\"b\":\"22\",\"c\":\"33\"}"); -// Assertions.assertThat(metadataLocalProperties.getContent().get("a")) -// .isEqualTo("1"); -// Assertions.assertThat(metadataLocalProperties.getContent().get("b")) -// .isEqualTo("2"); -// Assertions -// .assertThat(MetadataContextHolder.get().getContext(MetadataContext.FRAGMENT_TRANSITIVE, "a")) -// .isEqualTo("11"); -// Assertions -// .assertThat(MetadataContextHolder.get().getContext(MetadataContext.FRAGMENT_TRANSITIVE, "b")) -// .isEqualTo("22"); -// Assertions -// .assertThat(MetadataContextHolder.get().getContext(MetadataContext.FRAGMENT_TRANSITIVE, "c")) -// .isEqualTo("33"); + public void testTransitiveMetadataFromApplicationConfig() { + HttpHeaders httpHeaders = new HttpHeaders(); + HttpEntity httpEntity = new HttpEntity<>(httpHeaders); + String metadata = restTemplate + .exchange("http://localhost:" + localServerPort + "/test", HttpMethod.GET, + httpEntity, String.class) + .getBody(); + Assertions.assertThat(metadata).isEqualTo("2"); } @SpringBootApplication @@ -100,7 +85,7 @@ public class EncodeTransferMedataRestTemplateInterceptorTest { public String test( @RequestHeader(MetadataConstant.HeaderName.CUSTOM_METADATA) String customMetadataStr) throws UnsupportedEncodingException { - return URLDecoder.decode(customMetadataStr, "UTF-8"); + return MetadataContextHolder.get().getContext(MetadataContext.FRAGMENT_TRANSITIVE, "b"); } } diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerBootstrapConfiguration.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerBootstrapConfiguration.java similarity index 97% rename from spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerBootstrapConfiguration.java rename to spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerBootstrapConfiguration.java index 121ffcf6d..064bb081e 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerBootstrapConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerBootstrapConfiguration.java @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package com.tencent.cloud.polaris.circuitbreaker; +package com.tencent.cloud.polaris.circuitbreaker.config; import com.tencent.cloud.common.constant.ContextConstant; import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled; diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisFeignClientAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisFeignClientAutoConfiguration.java similarity index 95% rename from spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisFeignClientAutoConfiguration.java rename to spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisFeignClientAutoConfiguration.java index 5b98172bb..ae699dead 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisFeignClientAutoConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisFeignClientAutoConfiguration.java @@ -15,7 +15,7 @@ * specific language governing permissions and limitations under the License. */ -package com.tencent.cloud.polaris.circuitbreaker; +package com.tencent.cloud.polaris.circuitbreaker.config; import com.tencent.cloud.polaris.circuitbreaker.feign.PolarisFeignBeanPostProcessor; import com.tencent.cloud.polaris.context.PolarisContextAutoConfiguration; @@ -40,8 +40,7 @@ import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE; * * @author Haotian Zhang */ -@ConditionalOnProperty(value = "spring.cloud.polaris.circuitbreaker.enabled", havingValue = "true", - matchIfMissing = true) +@ConditionalOnProperty(value = "spring.cloud.polaris.circuitbreaker.enabled", havingValue = "true", matchIfMissing = true) @Configuration(proxyBeanMethods = false) @ConditionalOnClass(name = "org.springframework.cloud.openfeign.FeignAutoConfiguration") @AutoConfigureAfter(PolarisContextAutoConfiguration.class) diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisRestTemplateAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisRestTemplateAutoConfiguration.java new file mode 100644 index 000000000..ea99fce36 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisRestTemplateAutoConfiguration.java @@ -0,0 +1,56 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.circuitbreaker.config; + +import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisResponseErrorHandler; +import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisRestTemplateModifier; +import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisRestTemplateResponseErrorHandler; +import com.tencent.cloud.polaris.context.PolarisContextAutoConfiguration; +import com.tencent.polaris.api.core.ConsumerAPI; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +/** + * @author : wh + * @date : 2022/6/21 21:34 + * @description: Auto configuration PolarisRestTemplateAutoConfiguration + */ +@ConditionalOnProperty(value = "spring.cloud.polaris.circuitbreaker.enabled", + havingValue = "true", matchIfMissing = true) +@Configuration(proxyBeanMethods = false) +@AutoConfigureAfter(PolarisContextAutoConfiguration.class) +public class PolarisRestTemplateAutoConfiguration { + + @Bean + @ConditionalOnBean(RestTemplate.class) + public PolarisRestTemplateResponseErrorHandler polarisRestTemplateResponseErrorHandler(ConsumerAPI consumerAPI, @Autowired(required = false) PolarisResponseErrorHandler polarisResponseErrorHandler) { + return new PolarisRestTemplateResponseErrorHandler(consumerAPI, polarisResponseErrorHandler); + } + + @Bean + @ConditionalOnBean(RestTemplate.class) + public PolarisRestTemplateModifier polarisRestTemplateBeanPostProcessor(PolarisRestTemplateResponseErrorHandler restTemplateResponseErrorHandler) { + return new PolarisRestTemplateModifier(restTemplateResponseErrorHandler); + } +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/PolarisResponseErrorHandler.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/PolarisResponseErrorHandler.java new file mode 100644 index 000000000..5ddd1e6aa --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/PolarisResponseErrorHandler.java @@ -0,0 +1,29 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.circuitbreaker.resttemplate; + +import org.springframework.web.client.ResponseErrorHandler; + +/** + * @author : wh + * @date : 2022/6/21 19:12 + * @description: errorHandler {@link ResponseErrorHandler} + */ +public interface PolarisResponseErrorHandler extends ResponseErrorHandler { + +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/PolarisRestTemplateModifier.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/PolarisRestTemplateModifier.java new file mode 100644 index 000000000..bd43913f4 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/PolarisRestTemplateModifier.java @@ -0,0 +1,65 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.circuitbreaker.resttemplate; + +import java.util.Map; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.cloud.client.loadbalancer.LoadBalanced; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.util.ObjectUtils; +import org.springframework.web.client.RestTemplate; + +/** + * @author : wh + * @date : 2022/6/21 21:20 + * @description: auto configuration RestTemplate Find the RestTemplate bean annotated with {@link LoadBalanced} and replace {@link org.springframework.web.client.ResponseErrorHandler} + * with {@link PolarisRestTemplateResponseErrorHandler} + */ +public class PolarisRestTemplateModifier implements ApplicationContextAware, SmartInitializingSingleton { + + private ApplicationContext applicationContext; + + private final PolarisRestTemplateResponseErrorHandler polarisRestTemplateResponseErrorHandler; + + public PolarisRestTemplateModifier(PolarisRestTemplateResponseErrorHandler polarisRestTemplateResponseErrorHandler) { + this.polarisRestTemplateResponseErrorHandler = polarisRestTemplateResponseErrorHandler; + } + + @Override + public void afterSingletonsInstantiated() { + Map beans = this.applicationContext.getBeansWithAnnotation(LoadBalanced.class); + if (!ObjectUtils.isEmpty(beans)) { + beans.forEach(this::initRestTemplate); + } + } + + private void initRestTemplate(String beanName, Object bean) { + if (bean instanceof RestTemplate) { + RestTemplate restTemplate = (RestTemplate) bean; + restTemplate.setErrorHandler(polarisRestTemplateResponseErrorHandler); + } + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/PolarisRestTemplateResponseErrorHandler.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/PolarisRestTemplateResponseErrorHandler.java new file mode 100644 index 000000000..e6e003d90 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/resttemplate/PolarisRestTemplateResponseErrorHandler.java @@ -0,0 +1,111 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.circuitbreaker.resttemplate; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.util.Objects; + +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.util.ReflectionUtils; +import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.api.pojo.RetStatus; +import com.tencent.polaris.api.pojo.ServiceKey; +import com.tencent.polaris.api.rpc.ServiceCallResult; +import com.tencent.polaris.api.utils.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.http.HttpMethod; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.web.client.ResponseErrorHandler; + +/** + * @author : wh + * @date : 2022/6/21 17:25 + * @description: Extend ResponseErrorHandler to get request information + */ +public class PolarisRestTemplateResponseErrorHandler implements ResponseErrorHandler { + + private static final Logger LOG = LoggerFactory.getLogger(PolarisRestTemplateResponseErrorHandler.class); + + private static final String FileName = "connection"; + + private final ConsumerAPI consumerAPI; + + private final PolarisResponseErrorHandler polarisResponseErrorHandler; + + + public PolarisRestTemplateResponseErrorHandler(ConsumerAPI consumerAPI, PolarisResponseErrorHandler polarisResponseErrorHandler) { + this.consumerAPI = consumerAPI; + this.polarisResponseErrorHandler = polarisResponseErrorHandler; + } + + @Override + public boolean hasError(ClientHttpResponse response) { + return true; + } + + @Override + public void handleError(ClientHttpResponse response) throws IOException { + if (Objects.nonNull(polarisResponseErrorHandler)) { + if (polarisResponseErrorHandler.hasError(response)) { + polarisResponseErrorHandler.handleError(response); + } + } + } + + public void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException { + ServiceCallResult resultRequest = null; + try { + resultRequest = builderServiceCallResult(url, response); + } + catch (IOException e) { + LOG.error("Will report response of {} url {}", response, url, e); + throw e; + } + finally { + consumerAPI.updateServiceCallResult(resultRequest); + } + } + + private ServiceCallResult builderServiceCallResult(URI uri, ClientHttpResponse response) throws IOException { + ServiceCallResult resultRequest = new ServiceCallResult(); + String serviceName = uri.getHost(); + resultRequest.setService(serviceName); + resultRequest.setNamespace(MetadataContext.LOCAL_NAMESPACE); + resultRequest.setMethod(uri.getPath()); + resultRequest.setRetStatus(RetStatus.RetSuccess); + String sourceNamespace = MetadataContext.LOCAL_NAMESPACE; + String sourceService = MetadataContext.LOCAL_SERVICE; + if (StringUtils.isNotBlank(sourceNamespace) && StringUtils.isNotBlank(sourceService)) { + resultRequest.setCallerService(new ServiceKey(sourceNamespace, sourceService)); + } + HttpURLConnection connection = (HttpURLConnection) ReflectionUtils.getFieldValue(response, FileName); + URL url = connection.getURL(); + resultRequest.setHost(url.getHost()); + resultRequest.setPort(url.getPort()); + if (response.getStatusCode().value() > 500) { + resultRequest.setRetStatus(RetStatus.RetFail); + } + return resultRequest; + } + +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/resources/META-INF/spring.factories b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/resources/META-INF/spring.factories index 04fa47a13..229cc2af0 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/resources/META-INF/spring.factories @@ -1,4 +1,6 @@ org.springframework.cloud.bootstrap.BootstrapConfiguration=\ - com.tencent.cloud.polaris.circuitbreaker.PolarisCircuitBreakerBootstrapConfiguration + com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerBootstrapConfiguration org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ - com.tencent.cloud.polaris.circuitbreaker.PolarisFeignClientAutoConfiguration + com.tencent.cloud.polaris.circuitbreaker.config.PolarisFeignClientAutoConfiguration,\ + com.tencent.cloud.polaris.circuitbreaker.config.PolarisRestTemplateAutoConfiguration + diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerBootstrapConfigurationTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerBootstrapConfigurationTest.java index 42777a3b6..a5bd4f2e6 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerBootstrapConfigurationTest.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerBootstrapConfigurationTest.java @@ -17,6 +17,7 @@ package com.tencent.cloud.polaris.circuitbreaker; +import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerBootstrapConfiguration; import org.junit.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisFeignClientAutoConfigurationTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisFeignClientAutoConfigurationTest.java index c83608478..2443949c9 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisFeignClientAutoConfigurationTest.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisFeignClientAutoConfigurationTest.java @@ -17,6 +17,7 @@ package com.tencent.cloud.polaris.circuitbreaker; +import com.tencent.cloud.polaris.circuitbreaker.config.PolarisFeignClientAutoConfiguration; import com.tencent.cloud.polaris.circuitbreaker.feign.PolarisFeignBeanPostProcessor; import com.tencent.cloud.polaris.context.PolarisContextAutoConfiguration; import com.tencent.polaris.api.core.ConsumerAPI; diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisRestTemplateResponseErrorHandlerTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisRestTemplateResponseErrorHandlerTest.java new file mode 100644 index 000000000..5daf30bc6 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/PolarisRestTemplateResponseErrorHandlerTest.java @@ -0,0 +1,70 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.circuitbreaker; + + +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; + +import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisRestTemplateResponseErrorHandler; +import com.tencent.polaris.api.core.ConsumerAPI; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpMethod; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author : wh + * @date : 2022/6/22 09:00 + * @description: Test for {@link PolarisRestTemplateResponseErrorHandler}. + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = PolarisRestTemplateResponseErrorHandlerTest.TestApplication.class, + properties = {"spring.cloud.polaris.namespace=Test", "spring.cloud.polaris.service=TestApp"}) +public class PolarisRestTemplateResponseErrorHandlerTest { + + @Test + public void handleError() throws Exception { + ConsumerAPI consumerAPI = mock(ConsumerAPI.class); + PolarisRestTemplateResponseErrorHandler polarisRestTemplateResponseErrorHandler = new PolarisRestTemplateResponseErrorHandler(consumerAPI, null); + URI uri = mock(URI.class); + when(uri.getPath()).thenReturn("/test"); + when(uri.getHost()).thenReturn("host"); + HttpURLConnection httpURLConnection = mock(HttpURLConnection.class); + URL url = mock(URL.class); + when(httpURLConnection.getURL()).thenReturn(url); + when(url.getHost()).thenReturn("127.0.0.1"); + when(url.getPort()).thenReturn(8080); + when(httpURLConnection.getResponseCode()).thenReturn(200); + SimpleClientHttpResponseTest clientHttpResponse = new SimpleClientHttpResponseTest(httpURLConnection); + polarisRestTemplateResponseErrorHandler.handleError(uri, HttpMethod.GET, clientHttpResponse); + when(consumerAPI.unWatchService(null)).thenReturn(true); + } + + @SpringBootApplication + protected static class TestApplication { + + } +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/SimpleClientHttpResponseTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/SimpleClientHttpResponseTest.java new file mode 100644 index 000000000..ed76da17b --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/SimpleClientHttpResponseTest.java @@ -0,0 +1,106 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.circuitbreaker; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.client.AbstractClientHttpResponse; +import org.springframework.lang.Nullable; +import org.springframework.util.StreamUtils; +import org.springframework.util.StringUtils; + + +/** + * @author : wh + * @date : 2022/6/22 09:00 + * @description: mock {@link org.springframework.http.client.SimpleClientHttpResponse} + */ +public class SimpleClientHttpResponseTest extends AbstractClientHttpResponse { + + private final HttpURLConnection connection; + + @Nullable + private HttpHeaders headers; + + @Nullable + private InputStream responseStream; + + + SimpleClientHttpResponseTest(HttpURLConnection connection) { + this.connection = connection; + } + + + @Override + public int getRawStatusCode() throws IOException { + return this.connection.getResponseCode(); + } + + @Override + public String getStatusText() throws IOException { + String result = this.connection.getResponseMessage(); + return (result != null) ? result : ""; + } + + @Override + public HttpHeaders getHeaders() { + if (this.headers == null) { + this.headers = new HttpHeaders(); + // Header field 0 is the status line for most HttpURLConnections, but not on GAE + String name = this.connection.getHeaderFieldKey(0); + if (StringUtils.hasLength(name)) { + this.headers.add(name, this.connection.getHeaderField(0)); + } + int i = 1; + while (true) { + name = this.connection.getHeaderFieldKey(i); + if (!StringUtils.hasLength(name)) { + break; + } + this.headers.add(name, this.connection.getHeaderField(i)); + i++; + } + } + return this.headers; + } + + @Override + public InputStream getBody() throws IOException { + InputStream errorStream = this.connection.getErrorStream(); + this.responseStream = (errorStream != null ? errorStream : this.connection.getInputStream()); + return this.responseStream; + } + + @Override + public void close() { + try { + if (this.responseStream == null) { + getBody(); + } + StreamUtils.drain(this.responseStream); + this.responseStream.close(); + } + catch (Exception ex) { + // ignore + } + } + +} diff --git a/spring-cloud-starter-tencent-polaris-config/pom.xml b/spring-cloud-starter-tencent-polaris-config/pom.xml index 10b362a67..a7f0e5894 100644 --- a/spring-cloud-starter-tencent-polaris-config/pom.xml +++ b/spring-cloud-starter-tencent-polaris-config/pom.xml @@ -11,6 +11,7 @@ 4.0.0 spring-cloud-starter-tencent-polaris-config + Spring Cloud Starter Tencent Polaris Config @@ -64,5 +65,28 @@ + + org.springframework.boot + spring-boot-starter-test + test + + + + org.mockito + mockito-inline + test + + + + org.mockito + mockito-core + test + + + + net.bytebuddy + byte-buddy + test + diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/ConditionalOnConnectRemoteServerEnabled.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/ConditionalOnConnectRemoteServerEnabled.java new file mode 100644 index 000000000..89283ae18 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/ConditionalOnConnectRemoteServerEnabled.java @@ -0,0 +1,37 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.config; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; + +/** + * Whether to connect to a remote server, suitable for local development mode. + * + * @author lepdou 2022-06-11 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@ConditionalOnProperty(value = "spring.cloud.polaris.config.connect-remote-server", matchIfMissing = true) +public @interface ConditionalOnConnectRemoteServerEnabled { + +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigAutoConfiguration.java index 79255de5f..2b61b69be 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigAutoConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigAutoConfiguration.java @@ -20,7 +20,9 @@ package com.tencent.cloud.polaris.config; import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceAutoRefresher; import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager; +import com.tencent.cloud.polaris.config.annotation.PolarisConfigAnnotationProcessor; import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; +import com.tencent.cloud.polaris.config.listener.PolarisConfigChangeEventListener; import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -46,4 +48,14 @@ public class PolarisConfigAutoConfiguration { contextRefresher); } + @Bean + public PolarisConfigAnnotationProcessor polarisConfigAnnotationProcessor() { + return new PolarisConfigAnnotationProcessor(); + } + + @Bean + public PolarisConfigChangeEventListener polarisConfigChangeEventListener() { + return new PolarisConfigChangeEventListener(); + } + } diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigBootstrapAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigBootstrapAutoConfiguration.java index d7e0eea0e..0a87c0e65 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigBootstrapAutoConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigBootstrapAutoConfiguration.java @@ -50,24 +50,31 @@ public class PolarisConfigBootstrapAutoConfiguration { } @Bean - public ConfigFileService configFileService(SDKContext sdkContext) { - return ConfigFileServiceFactory.createConfigFileService(sdkContext); + public PolarisPropertySourceManager polarisPropertySourceManager() { + return new PolarisPropertySourceManager(); } @Bean - public PolarisPropertySourceManager polarisPropertySourceManager() { - return new PolarisPropertySourceManager(); + @ConditionalOnConnectRemoteServerEnabled + public ConfigFileService configFileService(SDKContext sdkContext) { + return ConfigFileServiceFactory.createConfigFileService(sdkContext); } @Bean - public PolarisConfigFileLocator polarisConfigFileLocator(PolarisConfigProperties polarisConfigProperties, - PolarisContextProperties polarisContextProperties, ConfigFileService configFileService, - PolarisPropertySourceManager polarisPropertySourceManager, Environment environment) { - return new PolarisConfigFileLocator(polarisConfigProperties, polarisContextProperties, configFileService, + @ConditionalOnConnectRemoteServerEnabled + public PolarisConfigFileLocator polarisConfigFileLocator( + PolarisConfigProperties polarisConfigProperties, + PolarisContextProperties polarisContextProperties, + ConfigFileService configFileService, + PolarisPropertySourceManager polarisPropertySourceManager, + Environment environment) { + return new PolarisConfigFileLocator(polarisConfigProperties, + polarisContextProperties, configFileService, polarisPropertySourceManager, environment); } @Bean + @ConditionalOnConnectRemoteServerEnabled public ConfigurationModifier configurationModifier(PolarisConfigProperties polarisConfigProperties, PolarisContextProperties polarisContextProperties) { return new ConfigurationModifier(polarisConfigProperties, polarisContextProperties); diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFileLocator.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFileLocator.java index a8182ec09..b23d6a132 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFileLocator.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFileLocator.java @@ -80,13 +80,14 @@ public class PolarisConfigFileLocator implements PropertySourceLocator { CompositePropertySource compositePropertySource = new CompositePropertySource( POLARIS_CONFIG_PROPERTY_SOURCE_NAME); + // load spring boot default config files + initInternalConfigFiles(compositePropertySource); + + // load custom config files List configFileGroups = polarisConfigProperties.getGroups(); if (CollectionUtils.isEmpty(configFileGroups)) { return compositePropertySource; } - - initInternalConfigFiles(compositePropertySource); - initCustomPolarisConfigFiles(compositePropertySource, configFileGroups); return compositePropertySource; @@ -181,7 +182,8 @@ public class PolarisConfigFileLocator implements PropertySourceLocator { private PolarisPropertySource loadPolarisPropertySource(String namespace, String group, String fileName) { ConfigKVFile configKVFile; // unknown extension is resolved as properties file - if (ConfigFileFormat.isPropertyFile(fileName) || ConfigFileFormat.isUnknownFile(fileName)) { + if (ConfigFileFormat.isPropertyFile(fileName) + || ConfigFileFormat.isUnknownFile(fileName)) { configKVFile = configFileService.getConfigPropertiesFile(namespace, group, fileName); } else if (ConfigFileFormat.isYamlFile(fileName)) { diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigAnnotationProcessor.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigAnnotationProcessor.java new file mode 100644 index 000000000..349799f69 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigAnnotationProcessor.java @@ -0,0 +1,105 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.config.annotation; + +import java.lang.reflect.Method; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Sets; +import com.tencent.cloud.polaris.config.listener.ConfigChangeEvent; +import com.tencent.cloud.polaris.config.listener.ConfigChangeListener; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.core.Ordered; +import org.springframework.core.PriorityOrdered; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.lang.NonNull; +import org.springframework.util.ReflectionUtils; + +import static com.tencent.cloud.polaris.config.listener.PolarisConfigListenerContext.addChangeListener; + +/** + * {@link PolarisConfigAnnotationProcessor} implementation for spring . + *

Refer to the Apollo project implementation: + * + * ApolloAnnotationProcessor + * @author Palmer Xu 2022-06-07 + */ +public class PolarisConfigAnnotationProcessor implements BeanPostProcessor, PriorityOrdered { + + @Override + public Object postProcessBeforeInitialization(@NonNull Object bean, @NonNull String beanName) + throws BeansException { + Class clazz = bean.getClass(); + for (Method method : findAllMethod(clazz)) { + this.processPolarisConfigChangeListener(bean, method); + } + return bean; + } + + @Override + public Object postProcessAfterInitialization(@NonNull Object bean, @NonNull String beanName) throws BeansException { + return bean; + } + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } + + private List findAllMethod(Class clazz) { + final List res = new LinkedList<>(); + ReflectionUtils.doWithMethods(clazz, res::add); + return res; + } + + private void processPolarisConfigChangeListener(final Object bean, final Method method) { + PolarisConfigKVFileChangeListener annotation = AnnotationUtils + .findAnnotation(method, PolarisConfigKVFileChangeListener.class); + if (annotation == null) { + return; + } + Class[] parameterTypes = method.getParameterTypes(); + Preconditions.checkArgument(parameterTypes.length == 1, + "Invalid number of parameters: %s for method: %s, should be 1", parameterTypes.length, + method); + Preconditions.checkArgument(ConfigChangeEvent.class.isAssignableFrom(parameterTypes[0]), + "Invalid parameter type: %s for method: %s, should be ConfigChangeEvent", parameterTypes[0], + method); + + ReflectionUtils.makeAccessible(method); + String[] annotatedInterestedKeys = annotation.interestedKeys(); + String[] annotatedInterestedKeyPrefixes = annotation.interestedKeyPrefixes(); + + ConfigChangeListener configChangeListener = changeEvent -> ReflectionUtils.invokeMethod(method, bean, changeEvent); + + Set interestedKeys = + annotatedInterestedKeys.length > 0 ? Sets.newHashSet(annotatedInterestedKeys) : null; + Set interestedKeyPrefixes = + annotatedInterestedKeyPrefixes.length > 0 ? Sets.newHashSet(annotatedInterestedKeyPrefixes) + : null; + + addChangeListener(configChangeListener, interestedKeys, interestedKeyPrefixes); + } + +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigKVFileChangeListener.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigKVFileChangeListener.java new file mode 100644 index 000000000..6acbf2336 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigKVFileChangeListener.java @@ -0,0 +1,58 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.config.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Configuring the change listener annotation. + *

Refer to the Apollo project implementation: + * + * ApolloAnnotationProcessor + * @author Palmer Xu 2022-05-31 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@Documented +public @interface PolarisConfigKVFileChangeListener { + + /** + * The keys interested in the listener, will only be notified if any of the interested keys is changed. + *
+ * If neither of {@code interestedKeys} and {@code interestedKeyPrefixes} is specified then the {@code listener} will be notified when any key is changed. + * @return interested keys in the listener + */ + String[] interestedKeys() default {}; + + /** + * The key prefixes that the listener is interested in, will be notified if and only if the changed keys start with anyone of the prefixes. + * The prefixes will simply be used to determine whether the {@code listener} should be notified or not using {@code changedKey.startsWith(prefix)}. + * e.g. "spring." means that {@code listener} is interested in keys that starts with "spring.", such as "spring.banner", "spring.jpa", etc. + * and "application" means that {@code listener} is interested in keys that starts with "application", such as "applicationName", "application.port", etc. + *
+ * If neither of {@code interestedKeys} and {@code interestedKeyPrefixes} is specified then the {@code listener} will be notified when whatever key is changed. + * @return interested key-prefixed in the listener + */ + String[] interestedKeyPrefixes() default {}; + +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/ConfigChangeEvent.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/ConfigChangeEvent.java new file mode 100644 index 000000000..119a5ce4c --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/ConfigChangeEvent.java @@ -0,0 +1,88 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.config.listener; + +import java.util.Map; +import java.util.Set; + +import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo; + +/** + * A change event when config is changed . + * + * @author Palmer Xu 2022-06-07 + */ +public final class ConfigChangeEvent { + + /** + * all changes keys map. + */ + private final Map changes; + + /** + * all interested changed keys. + */ + private final Set interestedChangedKeys; + + /** + * Config Change Event Constructor. + * @param changes all changes keys map + * @param interestedChangedKeys all interested changed keys + */ + public ConfigChangeEvent(Map changes, Set interestedChangedKeys) { + this.changes = changes; + this.interestedChangedKeys = interestedChangedKeys; + } + + /** + * Get the keys changed. + * @return the list of the keys + */ + public Set changedKeys() { + return changes.keySet(); + } + + /** + * Get a specific change instance for the key specified. + * @param key the changed key + * @return the change instance + */ + public ConfigPropertyChangeInfo getChange(String key) { + return changes.get(key); + } + + /** + * Check whether the specified key is changed . + * @param key the key + * @return true if the key is changed, false otherwise. + */ + public boolean isChanged(String key) { + return changes.containsKey(key); + } + + /** + * Maybe subclass override this method. + * + * @return interested and changed keys + */ + public Set interestedChangedKeys() { + return interestedChangedKeys; + } + +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/ConfigChangeListener.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/ConfigChangeListener.java new file mode 100644 index 000000000..14ab64a32 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/ConfigChangeListener.java @@ -0,0 +1,35 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.config.listener; + +/** + * Configuring the change listener interface. + * + * @author Palmer Xu 2022-05-31 + */ +public interface ConfigChangeListener { + + /** + * Invoked when there is any config change for the namespace. + * + * @param changeEvent the event for this change + */ + void onChange(ConfigChangeEvent changeEvent); + +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/PolarisConfigChangeEventListener.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/PolarisConfigChangeEventListener.java new file mode 100644 index 000000000..0a55255d5 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/PolarisConfigChangeEventListener.java @@ -0,0 +1,111 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.config.listener; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.google.common.collect.Maps; +import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo; + +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.cloud.context.environment.EnvironmentChangeEvent; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.lang.NonNull; + +import static com.tencent.cloud.polaris.config.listener.PolarisConfigListenerContext.fireConfigChange; +import static com.tencent.cloud.polaris.config.listener.PolarisConfigListenerContext.initialize; +import static com.tencent.cloud.polaris.config.listener.PolarisConfigListenerContext.merge; + +/** + * Polaris Config Change Event Listener . + * + * @author Elve.Xu 2022-06-08 + */ +public final class PolarisConfigChangeEventListener implements ApplicationListener { + + private static final AtomicBoolean started = new AtomicBoolean(); + + /** + * Handle an application event. + * + * @param event the event to respond to + */ + @Override + public void onApplicationEvent(@NonNull ApplicationEvent event) { + + // Initialize application all environment properties . + if (event instanceof ApplicationStartedEvent && started.compareAndSet(false, true)) { + ApplicationStartedEvent applicationStartedEvent = (ApplicationStartedEvent) event; + ConfigurableEnvironment environment = applicationStartedEvent.getApplicationContext().getEnvironment(); + Map ret = loadEnvironmentProperties(environment); + if (!ret.isEmpty()) { + initialize(ret); + } + } + + // Process Environment Change Event . + if (event instanceof EnvironmentChangeEvent) { + EnvironmentChangeEvent environmentChangeEvent = (EnvironmentChangeEvent) event; + ConfigurableApplicationContext context = (ConfigurableApplicationContext) environmentChangeEvent.getSource(); + ConfigurableEnvironment environment = context.getEnvironment(); + Map ret = loadEnvironmentProperties(environment); + Map changes = merge(ret); + fireConfigChange(changes.keySet(), Maps.newHashMap(changes)); + changes.clear(); + } + } + + /** + * Try load all application environment config properties . + * @param environment application environment instance of {@link Environment} + * @return properties + */ + @SuppressWarnings("unchecked") + private Map loadEnvironmentProperties(ConfigurableEnvironment environment) { + Map ret = Maps.newHashMap(); + MutablePropertySources sources = environment.getPropertySources(); + sources.iterator().forEachRemaining(propertySource -> { + Object o = propertySource.getSource(); + if (o instanceof Map) { + for (Map.Entry entry : ((Map) o).entrySet()) { + String key = entry.getKey(); + String value = environment.getProperty(key); + ret.put(key, value); + } + } + else if (o instanceof Collection) { + int count = 0; + Collection collection = (Collection) o; + for (Object object : collection) { + String key = "[" + (count++) + "]"; + ret.put(key, object); + } + } + }); + return ret; + } + +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/PolarisConfigListenerContext.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/PolarisConfigListenerContext.java new file mode 100644 index 000000000..05ac41756 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/PolarisConfigListenerContext.java @@ -0,0 +1,277 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.config.listener; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeListener; +import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; +import org.springframework.scheduling.concurrent.CustomizableThreadFactory; + +import static com.tencent.polaris.configuration.api.core.ChangeType.ADDED; +import static com.tencent.polaris.configuration.api.core.ChangeType.DELETED; +import static com.tencent.polaris.configuration.api.core.ChangeType.MODIFIED; + +/** + * Polaris Config Listener Context Defined . + *

Refer to the Apollo project implementation: + * + * AbstractConfig + * @author Palmer Xu 2022-06-06 + */ +public final class PolarisConfigListenerContext { + + /** + * Logger instance. + */ + private static final Logger LOG = LoggerFactory.getLogger(PolarisConfigListenerContext.class); + + /** + * Execute service Atomic Reference Cache . + */ + private static final AtomicReference EAR = new AtomicReference<>(); + + /** + * All custom {@link ConfigChangeListener} instance defined in application . + */ + private static final List listeners = Lists.newCopyOnWriteArrayList(); + + /** + * All custom interested keys defined in application . + */ + private static final Map> interestedKeys = Maps.newHashMap(); + + /** + * All custom interested key prefixes defined in application . + */ + private static final Map> interestedKeyPrefixes = Maps.newHashMap(); + + /** + * Cache all latest configuration information for users in the application environment . + */ + private static final Cache properties = CacheBuilder.newBuilder().build(); + + /** + * Get or Created new execute server . + * @return execute service instance of {@link ExecutorService} + */ + private static ExecutorService executor() { + if (EAR.get() == null) { + synchronized (PolarisConfigListenerContext.class) { + int coreThreadSize = Runtime.getRuntime().availableProcessors(); + final ExecutorService service = new ThreadPoolExecutor(coreThreadSize, coreThreadSize, + 0, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>(64), + new CustomizableThreadFactory("Config-Change-Notify-Thread-Pool-")); + + // Register Jvm Shutdown Hook + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + try { + LOG.info("Shutting down config change notify thread pool"); + service.shutdown(); + } + catch (Exception ignore) { + } + })); + EAR.set(service); + } + } + return EAR.get(); + } + + /** + * Initialize Environment Properties cache after listen ApplicationStartedEvent event . + * @param ret origin properties map + */ + static void initialize(Map ret) { + properties.putAll(ret); + } + + /** + * Merge Changed Properties . + * @param ret current environment properties map + * @return merged properties result map + */ + static Map merge(Map ret) { + Map changes = Maps.newHashMap(); + if (!ret.isEmpty()) { + + Map origin = Maps.newHashMap(properties.asMap()); + Map deleted = Maps.newHashMap(); + + origin.keySet().parallelStream().forEach(key -> { + if (!ret.containsKey(key)) { + deleted.put(key, new ConfigPropertyChangeInfo(key, String.valueOf(origin.get(key)), null, DELETED)); + properties.invalidate(key); + } + }); + changes.putAll(deleted); + + ret.keySet().parallelStream().forEach(key -> { + Object oldValue = properties.getIfPresent(key); + Object newValue = ret.get(key); + if (oldValue != null) { + if (!newValue.equals(oldValue)) { + properties.put(key, newValue); + changes.put(key, new ConfigPropertyChangeInfo(key, String.valueOf(oldValue), String.valueOf(newValue), MODIFIED)); + } + } + else { + properties.put(key, newValue); + changes.put(key, new ConfigPropertyChangeInfo(key, null, String.valueOf(newValue), ADDED)); + } + }); + } + return changes; + } + + /** + * Adding a config file change listener, will trigger a callback when the config file is published . + * @param listener the listener will be added + * @param interestedKeys the keys interested in the listener, will only be notified if any of the interested keys is changed. + * @param interestedKeyPrefixes the key prefixes that the listener is interested in, + * will be notified if and only if the changed keys start with anyone of the prefixes. + */ + public static void addChangeListener(@NonNull ConfigChangeListener listener, + @Nullable Set interestedKeys, @Nullable Set interestedKeyPrefixes) { + if (!listeners.contains(listener)) { + listeners.add(listener); + PolarisConfigListenerContext.interestedKeys.put(listener, interestedKeys == null ? Sets.newHashSet() : interestedKeys); + PolarisConfigListenerContext.interestedKeyPrefixes.put(listener, interestedKeyPrefixes == null ? Sets.newHashSet() : interestedKeyPrefixes); + } + } + + /** + * Fire config change event with {@link ConfigKVFileChangeListener} . + * @param changedKeys changed keys in listener + * @param changes target config file changes info + */ + public static void fireConfigChange(Set changedKeys, Map changes) { + final List listeners = findMatchedConfigChangeListeners(changedKeys); + for (ConfigChangeListener listener : listeners) { + Set interestedChangedKeys = resolveInterestedChangedKeys(listener, changedKeys); + Map modifiedChanges = new HashMap<>(interestedChangedKeys.size()); + interestedChangedKeys.parallelStream().forEach(key -> modifiedChanges.put(key, changes.get(key))); + ConfigChangeEvent event = new ConfigChangeEvent(modifiedChanges, interestedChangedKeys); + PolarisConfigListenerContext.executor().execute(() -> listener.onChange(event)); + } + } + + /** + * Try to find all matched config change listeners . + * @param changedKeys received changed keys + * @return list of matched {@link ConfigChangeListener} + */ + private static List findMatchedConfigChangeListeners(Set changedKeys) { + final List configChangeListeners = Lists.newArrayList(); + for (ConfigChangeListener listener : listeners) { + if (isConfigChangeListenerInterested(listener, changedKeys)) { + configChangeListeners.add(listener); + } + } + return configChangeListeners; + } + + /** + * Check {@link ConfigChangeListener} is interested in custom keys. + * @param listener instance of {@link ConfigChangeListener} + * @param changedKeys received changed keys + * @return true is interested in custom keys + */ + private static boolean isConfigChangeListenerInterested(ConfigChangeListener listener, Set changedKeys) { + Set interestedKeys = PolarisConfigListenerContext.interestedKeys.get(listener); + Set interestedKeyPrefixes = PolarisConfigListenerContext.interestedKeyPrefixes.get(listener); + + if ((interestedKeys == null || interestedKeys.isEmpty()) + && (interestedKeyPrefixes == null || interestedKeyPrefixes.isEmpty())) { + return true; + } + + if (interestedKeys != null) { + for (String interestedKey : interestedKeys) { + if (changedKeys.contains(interestedKey)) { + return true; + } + } + } + + if (interestedKeyPrefixes != null) { + for (String prefix : interestedKeyPrefixes) { + for (final String changedKey : changedKeys) { + if (changedKey.startsWith(prefix)) { + return true; + } + } + } + } + return false; + } + + /** + * Resolve all interested keys . + * @param listener instance of {@link ConfigChangeListener} + * @param changedKeys received changed keys + * @return set of all interested keys in listener + */ + private static Set resolveInterestedChangedKeys(ConfigChangeListener listener, Set changedKeys) { + Set interestedChangedKeys = Sets.newHashSet(); + + if (interestedKeys.containsKey(listener)) { + Set interestedKeys = PolarisConfigListenerContext.interestedKeys.get(listener); + for (String interestedKey : interestedKeys) { + if (changedKeys.contains(interestedKey)) { + interestedChangedKeys.add(interestedKey); + } + } + } + + if (interestedKeyPrefixes.containsKey(listener)) { + Set interestedKeyPrefixes = PolarisConfigListenerContext.interestedKeyPrefixes.get(listener); + for (String interestedKeyPrefix : interestedKeyPrefixes) { + for (String changedKey : changedKeys) { + if (changedKey.startsWith(interestedKeyPrefix)) { + interestedChangedKeys.add(changedKey); + } + } + } + } + + return Collections.unmodifiableSet(interestedChangedKeys); + } + +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-starter-tencent-polaris-config/src/main/resources/META-INF/additional-spring-configuration-metadata.json index ee84cbd47..9954a62ce 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-cloud-starter-tencent-polaris-config/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -34,6 +34,13 @@ "defaultValue": "", "description": "List of imported config files.", "sourceType": "com.tencent.cloud.polaris.config.config.PolarisConfigProperties" + }, + { + "name": "spring.cloud.polaris.config.connect-remote-server", + "type": "java.lang.Boolean", + "defaultValue": "true", + "description": "Whether to connect to a remote server, suitable for local development mode.", + "sourceType": "com.tencent.cloud.polaris.config.config.PolarisConfigProperties" } ] } diff --git a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/MockedConfigKVFile.java b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/MockedConfigKVFile.java new file mode 100644 index 000000000..026fe4d52 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/MockedConfigKVFile.java @@ -0,0 +1,170 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.config.adapter; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.tencent.polaris.configuration.api.core.ConfigFileChangeListener; +import com.tencent.polaris.configuration.api.core.ConfigKVFile; +import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeEvent; +import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeListener; + +/** + * Mock config kv file for test. + *@author lepdou 2022-06-11 + */ +public class MockedConfigKVFile implements ConfigKVFile { + + private final Map properties; + private final List listeners = new ArrayList<>(); + + public MockedConfigKVFile(Map properties) { + this.properties = properties; + } + + @Override + public String getProperty(String s, String s1) { + return String.valueOf(properties.get(s)); + } + + @Override + public Integer getIntProperty(String s, Integer integer) { + return null; + } + + @Override + public Long getLongProperty(String s, Long aLong) { + return null; + } + + @Override + public Short getShortProperty(String s, Short aShort) { + return null; + } + + @Override + public Float getFloatProperty(String s, Float aFloat) { + return null; + } + + @Override + public Double getDoubleProperty(String s, Double aDouble) { + return null; + } + + @Override + public Byte getByteProperty(String s, Byte aByte) { + return null; + } + + @Override + public Boolean getBooleanProperty(String s, Boolean aBoolean) { + return null; + } + + @Override + public String[] getArrayProperty(String s, String s1, String[] strings) { + return new String[0]; + } + + @Override + public > T getEnumProperty(String s, Class aClass, T t) { + return null; + } + + @Override + public T getJsonProperty(String s, Class aClass, T t) { + return null; + } + + @Override + public T getJsonProperty(String s, Type type, T t) { + return null; + } + + @Override + public Set getPropertyNames() { + return properties.keySet(); + } + + @Override + public void addChangeListener(ConfigKVFileChangeListener configKVFileChangeListener) { + listeners.add(configKVFileChangeListener); + } + + @Override + public void removeChangeListener(ConfigKVFileChangeListener configKVFileChangeListener) { + + } + + @Override + public String getContent() { + return null; + } + + @Override + public T asJson(Class aClass, T t) { + return null; + } + + @Override + public T asJson(Type type, T t) { + return null; + } + + @Override + public boolean hasContent() { + return false; + } + + @Override + public void addChangeListener(ConfigFileChangeListener configFileChangeListener) { + + } + + @Override + public void removeChangeListener(ConfigFileChangeListener configFileChangeListener) { + + } + + public void fireChangeListener(ConfigKVFileChangeEvent event) { + for (ConfigKVFileChangeListener listener : listeners) { + listener.onChange(event); + } + } + + @Override + public String getNamespace() { + return null; + } + + @Override + public String getFileGroup() { + return null; + } + + @Override + public String getFileName() { + return null; + } +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFileLocatorTest.java b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFileLocatorTest.java new file mode 100644 index 000000000..6fe0bfa2a --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFileLocatorTest.java @@ -0,0 +1,188 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.config.adapter; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.Lists; +import com.tencent.cloud.polaris.config.config.ConfigFileGroup; +import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; +import com.tencent.cloud.polaris.context.PolarisContextProperties; +import com.tencent.polaris.configuration.api.core.ConfigFileService; +import com.tencent.polaris.configuration.api.core.ConfigKVFile; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import org.springframework.core.env.Environment; +import org.springframework.core.env.PropertySource; + +import static org.mockito.Mockito.when; + +/** + * test for {@link PolarisConfigFileLocator} + *@author lepdou 2022-06-11 + */ +@RunWith(MockitoJUnitRunner.class) +public class PolarisConfigFileLocatorTest { + + @Mock + private PolarisConfigProperties polarisConfigProperties; + @Mock + private PolarisContextProperties polarisContextProperties; + @Mock + private ConfigFileService configFileService; + @Mock + private PolarisPropertySourceManager polarisPropertySourceManager; + @Mock + private Environment environment; + + private final String testNamespace = "testNamespace"; + private final String testServiceName = "testServiceName"; + + @Test + public void testLoadApplicationPropertiesFile() { + PolarisConfigFileLocator locator = new PolarisConfigFileLocator(polarisConfigProperties, polarisContextProperties, + configFileService, polarisPropertySourceManager, environment); + + when(polarisContextProperties.getNamespace()).thenReturn(testNamespace); + when(polarisContextProperties.getService()).thenReturn(testServiceName); + + // application.properties + Map applicationProperties = new HashMap<>(); + applicationProperties.put("k1", "v1"); + applicationProperties.put("k2", "v2"); + applicationProperties.put("k3", "v3"); + ConfigKVFile propertiesFile = new MockedConfigKVFile(applicationProperties); + when(configFileService.getConfigPropertiesFile(testNamespace, testServiceName, "application.properties")) + .thenReturn(propertiesFile); + + Map emptyMap = new HashMap<>(); + ConfigKVFile emptyConfigFile = new MockedConfigKVFile(emptyMap); + when(configFileService.getConfigYamlFile(testNamespace, testServiceName, "application.yml")).thenReturn(emptyConfigFile); + when(configFileService.getConfigPropertiesFile(testNamespace, testServiceName, "bootstrap.properties")).thenReturn(emptyConfigFile); + when(configFileService.getConfigYamlFile(testNamespace, testServiceName, "bootstrap.yml")).thenReturn(emptyConfigFile); + + when(polarisConfigProperties.getGroups()).thenReturn(null); + when(environment.getActiveProfiles()).thenReturn(new String[] {}); + + PropertySource propertySource = locator.locate(environment); + + Assert.assertEquals("v1", propertySource.getProperty("k1")); + Assert.assertEquals("v2", propertySource.getProperty("k2")); + Assert.assertEquals("v3", propertySource.getProperty("k3")); + } + + @Test + public void testActiveProfileFilesPriorityBiggerThanDefault() { + PolarisConfigFileLocator locator = new PolarisConfigFileLocator(polarisConfigProperties, polarisContextProperties, + configFileService, polarisPropertySourceManager, environment); + + when(polarisContextProperties.getNamespace()).thenReturn(testNamespace); + when(polarisContextProperties.getService()).thenReturn(testServiceName); + + // application.properties + Map applicationProperties = new HashMap<>(); + applicationProperties.put("k1", "v1"); + applicationProperties.put("k2", "v2"); + applicationProperties.put("k3", "v3"); + ConfigKVFile propertiesFile = new MockedConfigKVFile(applicationProperties); + when(configFileService.getConfigPropertiesFile(testNamespace, testServiceName, "application.properties")) + .thenReturn(propertiesFile); + + // application-dev.properties + Map devProperties = new HashMap<>(); + devProperties.put("k1", "v11"); + ConfigKVFile devFile = new MockedConfigKVFile(devProperties); + when(configFileService.getConfigPropertiesFile(testNamespace, testServiceName, "application-dev.properties")) + .thenReturn(devFile); + + Map emptyMap = new HashMap<>(); + ConfigKVFile emptyConfigFile = new MockedConfigKVFile(emptyMap); + when(configFileService.getConfigYamlFile(testNamespace, testServiceName, "application.yml")).thenReturn(emptyConfigFile); + when(configFileService.getConfigYamlFile(testNamespace, testServiceName, "application-dev.yml")).thenReturn(emptyConfigFile); + when(configFileService.getConfigPropertiesFile(testNamespace, testServiceName, "bootstrap.properties")).thenReturn(emptyConfigFile); + when(configFileService.getConfigPropertiesFile(testNamespace, testServiceName, "bootstrap-dev.properties")).thenReturn(emptyConfigFile); + when(configFileService.getConfigYamlFile(testNamespace, testServiceName, "bootstrap.yml")).thenReturn(emptyConfigFile); + when(configFileService.getConfigYamlFile(testNamespace, testServiceName, "bootstrap-dev.yml")).thenReturn(emptyConfigFile); + + when(polarisConfigProperties.getGroups()).thenReturn(null); + when(environment.getActiveProfiles()).thenReturn(new String[] {"dev"}); + + PropertySource propertySource = locator.locate(environment); + + Assert.assertEquals("v11", propertySource.getProperty("k1")); + Assert.assertEquals("v2", propertySource.getProperty("k2")); + Assert.assertEquals("v3", propertySource.getProperty("k3")); + } + + @Test + public void testGetCustomFiles() { + PolarisConfigFileLocator locator = new PolarisConfigFileLocator(polarisConfigProperties, polarisContextProperties, + configFileService, polarisPropertySourceManager, environment); + + when(polarisContextProperties.getNamespace()).thenReturn(testNamespace); + when(polarisContextProperties.getService()).thenReturn(testServiceName); + + Map emptyMap = new HashMap<>(); + ConfigKVFile emptyConfigFile = new MockedConfigKVFile(emptyMap); + + when(configFileService.getConfigPropertiesFile(testNamespace, testServiceName, "application.properties")).thenReturn(emptyConfigFile); + when(configFileService.getConfigYamlFile(testNamespace, testServiceName, "application.yml")).thenReturn(emptyConfigFile); + when(configFileService.getConfigPropertiesFile(testNamespace, testServiceName, "bootstrap.properties")).thenReturn(emptyConfigFile); + when(configFileService.getConfigYamlFile(testNamespace, testServiceName, "bootstrap.yml")).thenReturn(emptyConfigFile); + + List customFiles = new LinkedList<>(); + ConfigFileGroup configFileGroup = new ConfigFileGroup(); + String customGroup = "group1"; + configFileGroup.setName(customGroup); + String customFile1 = "file1.properties"; + String customFile2 = "file2.properties"; + configFileGroup.setFiles(Lists.newArrayList(customFile1, customFile2)); + customFiles.add(configFileGroup); + + when(polarisConfigProperties.getGroups()).thenReturn(customFiles); + when(environment.getActiveProfiles()).thenReturn(new String[] {}); + + // file1.properties + Map file1Map = new HashMap<>(); + file1Map.put("k1", "v1"); + file1Map.put("k2", "v2"); + ConfigKVFile file1 = new MockedConfigKVFile(file1Map); + when(configFileService.getConfigPropertiesFile(testNamespace, customGroup, customFile1)).thenReturn(file1); + + // file2.properties + Map file2Map = new HashMap<>(); + file2Map.put("k1", "v11"); + file2Map.put("k3", "v3"); + ConfigKVFile file2 = new MockedConfigKVFile(file2Map); + when(configFileService.getConfigPropertiesFile(testNamespace, customGroup, customFile2)).thenReturn(file2); + + PropertySource propertySource = locator.locate(environment); + + Assert.assertEquals("v1", propertySource.getProperty("k1")); + Assert.assertEquals("v2", propertySource.getProperty("k2")); + Assert.assertEquals("v3", propertySource.getProperty("k3")); + } +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertiesSourceAutoRefresherTest.java b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertiesSourceAutoRefresherTest.java new file mode 100644 index 000000000..648860f35 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertiesSourceAutoRefresherTest.java @@ -0,0 +1,122 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.config.adapter; + +import java.util.HashMap; +import java.util.Map; + +import com.google.common.collect.Lists; +import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; +import com.tencent.polaris.configuration.api.core.ChangeType; +import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeEvent; +import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import org.springframework.cloud.context.refresh.ContextRefresher; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * test for {@link PolarisPropertySourceAutoRefresher} + *@author lepdou 2022-06-11 + */ +@RunWith(MockitoJUnitRunner.class) +public class PolarisPropertiesSourceAutoRefresherTest { + + @Mock + private PolarisConfigProperties polarisConfigProperties; + @Mock + private PolarisPropertySourceManager polarisPropertySourceManager; + @Mock + private ContextRefresher contextRefresher; + + private final String testNamespace = "testNamespace"; + private final String testServiceName = "testServiceName"; + private final String testFileName = "application.properties"; + + @Test + public void testConfigFileChanged() { + PolarisPropertySourceAutoRefresher refresher = new PolarisPropertySourceAutoRefresher(polarisConfigProperties, + polarisPropertySourceManager, contextRefresher); + + when(polarisConfigProperties.isAutoRefresh()).thenReturn(true); + + Map content = new HashMap<>(); + content.put("k1", "v1"); + content.put("k2", "v2"); + content.put("k3", "v3"); + MockedConfigKVFile file = new MockedConfigKVFile(content); + PolarisPropertySource polarisPropertySource = new PolarisPropertySource(testNamespace, testServiceName, testFileName, + file, content); + + when(polarisPropertySourceManager.getAllPropertySources()).thenReturn(Lists.newArrayList(polarisPropertySource)); + + ConfigPropertyChangeInfo changeInfo = new ConfigPropertyChangeInfo("k1", "v1", "v11", ChangeType.MODIFIED); + ConfigPropertyChangeInfo changeInfo2 = new ConfigPropertyChangeInfo("k4", null, "v4", ChangeType.ADDED); + ConfigPropertyChangeInfo changeInfo3 = new ConfigPropertyChangeInfo("k2", "v2", null, ChangeType.DELETED); + Map changeInfos = new HashMap<>(); + changeInfos.put("k1", changeInfo); + changeInfos.put("k2", changeInfo3); + changeInfos.put("k4", changeInfo2); + + ConfigKVFileChangeEvent event = new ConfigKVFileChangeEvent(changeInfos); + refresher.onApplicationEvent(null); + + file.fireChangeListener(event); + + Assert.assertEquals("v11", polarisPropertySource.getProperty("k1")); + Assert.assertEquals("v3", polarisPropertySource.getProperty("k3")); + Assert.assertNull(polarisPropertySource.getProperty("k2")); + Assert.assertEquals("v4", polarisPropertySource.getProperty("k4")); + verify(contextRefresher).refresh(); + } + + @Test + public void testNewConfigFile() { + PolarisPropertySourceAutoRefresher refresher = new PolarisPropertySourceAutoRefresher(polarisConfigProperties, + polarisPropertySourceManager, contextRefresher); + + when(polarisConfigProperties.isAutoRefresh()).thenReturn(true); + + Map emptyContent = new HashMap<>(); + MockedConfigKVFile file = new MockedConfigKVFile(emptyContent); + PolarisPropertySource polarisPropertySource = new PolarisPropertySource(testNamespace, testServiceName, testFileName, + file, emptyContent); + + when(polarisPropertySourceManager.getAllPropertySources()).thenReturn(Lists.newArrayList(polarisPropertySource)); + + ConfigPropertyChangeInfo changeInfo = new ConfigPropertyChangeInfo("k1", null, "v1", ChangeType.ADDED); + Map changeInfos = new HashMap<>(); + changeInfos.put("k1", changeInfo); + + ConfigKVFileChangeEvent event = new ConfigKVFileChangeEvent(changeInfos); + refresher.onApplicationEvent(null); + + file.fireChangeListener(event); + + Assert.assertEquals("v1", polarisPropertySource.getProperty("k1")); + verify(contextRefresher).refresh(); + } + +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/listener/ConfigChangeListenerTest.java b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/listener/ConfigChangeListenerTest.java new file mode 100644 index 000000000..c408f4f54 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/listener/ConfigChangeListenerTest.java @@ -0,0 +1,124 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.config.listener; + +import com.google.common.collect.Sets; +import com.tencent.cloud.polaris.config.annotation.PolarisConfigKVFileChangeListener; +import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.context.environment.EnvironmentChangeEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.stereotype.Component; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; + +/** + * Integration testing for change listener. + *@author lepdou 2022-06-11 + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = DEFINED_PORT, + classes = ConfigChangeListenerTest.TestApplication.class, + properties = {"server.port=8081", + "spring.config.location = classpath:application-test.yml"}) +public class ConfigChangeListenerTest { + + @Autowired + private ApplicationEventPublisher applicationEventPublisher; + @Autowired + private ConfigurableApplicationContext applicationContext; + @Autowired + private TestApplication.TestConfig testConfig; + + @Test + public void test() throws InterruptedException { + //before change + Assert.assertEquals(1000, testConfig.getTimeout()); + + //submit change event + System.setProperty("timeout", "2000"); + EnvironmentChangeEvent event = new EnvironmentChangeEvent(applicationContext, + Sets.newHashSet("timeout")); + + applicationEventPublisher.publishEvent(event); + Thread.sleep(200); + //after change + Assert.assertEquals(2, testConfig.getChangeCnt()); + Assert.assertEquals(2000, testConfig.getTimeout()); + } + + + @SpringBootApplication + protected static class TestApplication { + + @Component + protected static class TestConfig { + + @Value("${timeout:1000}") + private int timeout; + + private int changeCnt; + + public int getTimeout() { + return timeout; + } + + public void setTimeout(int timeout) { + this.timeout = timeout; + } + + public int getChangeCnt() { + return changeCnt; + } + + @PolarisConfigKVFileChangeListener(interestedKeys = {"timeout"}) + public void configChangedListener(ConfigChangeEvent event) { + ConfigPropertyChangeInfo changeInfo = event.getChange("timeout"); + timeout = Integer.parseInt(changeInfo.getNewValue()); + changeCnt++; + } + + @PolarisConfigKVFileChangeListener(interestedKeyPrefixes = {"timeout"}) + public void configChangedListener2(ConfigChangeEvent event) { + ConfigPropertyChangeInfo changeInfo = event.getChange("timeout"); + timeout = Integer.parseInt(changeInfo.getNewValue()); + changeCnt++; + } + } + + @Component + protected static class EventPublisher implements ApplicationEventPublisher { + + @Override + public void publishEvent(Object o) { + + } + } + } + +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/test/resources/application-test.yml b/spring-cloud-starter-tencent-polaris-config/src/test/resources/application-test.yml new file mode 100644 index 000000000..c90d1340b --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/test/resources/application-test.yml @@ -0,0 +1,9 @@ +spring: + application: + name: test + cloud: + polaris: + address: grpc://127.0.0.1:8091 + namespace: default + config: + connect-remote-server: false diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java index 8796fc1ab..b4fff038e 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java @@ -19,6 +19,7 @@ package com.tencent.cloud.polaris.ratelimit.filter; import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -55,7 +56,7 @@ import static com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant.LAB /** * Reactive filter to check quota. * - * @author Haotian Zhang, lepdou, cheese8 + * @author Haotian Zhang, lepdou, kaiy, cheese8 */ public class QuotaCheckReactiveFilter implements WebFilter, Ordered { @@ -113,7 +114,7 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered { } // Unirate if (quotaResponse.getCode() == QuotaResultCode.QuotaResultOk && quotaResponse.getWaitMs() > 0) { - Thread.sleep(quotaResponse.getWaitMs()); + return Mono.delay(Duration.ofMillis(quotaResponse.getWaitMs())).flatMap(e -> chain.filter(exchange)); } } catch (Throwable t) { diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilterTest.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilterTest.java index 0e602e6e7..27bcb4087 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilterTest.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilterTest.java @@ -22,6 +22,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collections; import java.util.Map; +import java.util.concurrent.CountDownLatch; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.util.ApplicationContextAwareUtils; @@ -65,7 +66,7 @@ import static org.mockito.Mockito.when; /** * Test for {@link QuotaCheckReactiveFilter}. * - * @author Haotian Zhang + * @author Haotian Zhang, kaiy */ @RunWith(MockitoJUnitRunner.class) @SpringBootTest(classes = QuotaCheckReactiveFilterTest.TestApplication.class, properties = { @@ -201,7 +202,14 @@ public class QuotaCheckReactiveFilterTest { // Unirate waiting 1000ms MetadataContext.LOCAL_SERVICE = "TestApp2"; long startTimestamp = System.currentTimeMillis(); - quotaCheckReactiveFilter.filter(exchange, webFilterChain); + CountDownLatch countDownLatch = new CountDownLatch(1); + quotaCheckReactiveFilter.filter(exchange, webFilterChain).subscribe(e -> { }, t -> { }, countDownLatch::countDown); + try { + countDownLatch.await(); + } + catch (InterruptedException e) { + fail("Exception encountered.", e); + } assertThat(System.currentTimeMillis() - startTimestamp).isGreaterThanOrEqualTo(1000L); // Rate limited diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ResourceFileUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ResourceFileUtils.java index d79bfce8b..b5ea45d5d 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ResourceFileUtils.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ResourceFileUtils.java @@ -23,6 +23,7 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; import org.springframework.core.io.ClassPathResource; +import org.springframework.util.StreamUtils; /** * Read file content from classpath resource. @@ -35,20 +36,15 @@ public final class ResourceFileUtils { } public static String readFile(String path) throws IOException { - StringBuilder sb = new StringBuilder(); ClassPathResource classPathResource = new ClassPathResource(path); if (classPathResource.exists() && classPathResource.isReadable()) { try (InputStream inputStream = classPathResource.getInputStream()) { - byte[] buffer = new byte[1024 * 10]; - int len; - while ((len = inputStream.read(buffer)) != -1) { - sb.append(new String(buffer, 0, len, StandardCharsets.UTF_8)); - } + return StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8); } } - return sb.toString(); + return ""; } } diff --git a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/ResourceFileUtilsTest.java b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/ResourceFileUtilsTest.java index 334cd4e20..769d99ce7 100644 --- a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/ResourceFileUtilsTest.java +++ b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/ResourceFileUtilsTest.java @@ -35,7 +35,7 @@ public class ResourceFileUtilsTest { @Test public void testReadExistedFile() throws IOException { String content = ResourceFileUtils.readFile("test.txt"); - Assert.assertEquals("just for test\n", content); + Assert.assertEquals("just for test", content); } @Test diff --git a/spring-cloud-tencent-commons/src/test/resources/test.txt b/spring-cloud-tencent-commons/src/test/resources/test.txt index 63d3c2d75..e18c37483 100644 --- a/spring-cloud-tencent-commons/src/test/resources/test.txt +++ b/spring-cloud-tencent-commons/src/test/resources/test.txt @@ -1 +1 @@ -just for test +just for test \ No newline at end of file diff --git a/spring-cloud-tencent-coverage/pom.xml b/spring-cloud-tencent-coverage/pom.xml index 1c8ba0ed4..37903c143 100644 --- a/spring-cloud-tencent-coverage/pom.xml +++ b/spring-cloud-tencent-coverage/pom.xml @@ -24,6 +24,16 @@ spring-cloud-tencent-commons + + com.tencent.cloud + spring-cloud-tencent-polaris-context + + + + com.tencent.cloud + spring-cloud-tencent-polaris-loadbalancer + + com.tencent.cloud spring-cloud-starter-tencent-polaris-discovery @@ -49,10 +59,10 @@ spring-cloud-starter-tencent-polaris-router - - com.tencent.cloud - spring-cloud-tencent-polaris-context - + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-config + @@ -75,4 +85,4 @@ - \ No newline at end of file + diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceAController.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceAController.java index ebfea6716..3ba9901a6 100644 --- a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceAController.java +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceAController.java @@ -48,6 +48,11 @@ public class ServiceAController { return polarisServiceB.info(); } + @GetMapping("/getBServiceInfoByRestTemplate") + public String getBServiceInfoByRestTemplate() { + return restTemplate.getForObject("http://polaris-circuitbreaker-example-b/example/service/b/info", String.class); + } + /** * Get info of Service B by RestTemplate. * @return info of Service B diff --git a/spring-cloud-tencent-examples/polaris-config-example/src/main/java/com/tencent/cloud/polaris/config/example/PersonConfigChangeListener.java b/spring-cloud-tencent-examples/polaris-config-example/src/main/java/com/tencent/cloud/polaris/config/example/PersonConfigChangeListener.java new file mode 100644 index 000000000..b0efd8b36 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-config-example/src/main/java/com/tencent/cloud/polaris/config/example/PersonConfigChangeListener.java @@ -0,0 +1,49 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.config.example; + +import java.util.Set; + +import com.tencent.cloud.polaris.config.annotation.PolarisConfigKVFileChangeListener; +import com.tencent.cloud.polaris.config.listener.ConfigChangeEvent; + +import org.springframework.stereotype.Component; + +/** + * Custom Config Listener Example . + * + * @author Palmer Xu 2022-06-06 + */ +@Component +public final class PersonConfigChangeListener { + + /** + * PolarisConfigKVFileChangeListener Example . + * @param event instance of {@link ConfigChangeEvent} + */ + @PolarisConfigKVFileChangeListener(interestedKeyPrefixes = "teacher") + public void onChange(ConfigChangeEvent event) { + Set changedKeys = event.changedKeys(); + + for (String changedKey : changedKeys) { + System.out.printf("%s = %s \n", changedKey, event.getChange(changedKey)); + } + } + +} diff --git a/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/pom.xml b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/pom.xml index dd28040b5..bf89b34d6 100644 --- a/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/pom.xml +++ b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/pom.xml @@ -18,6 +18,11 @@ spring-boot-starter-web + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-discovery + + com.tencent.cloud spring-cloud-starter-tencent-polaris-ratelimit