diff --git a/CHANGELOG.md b/CHANGELOG.md
index bc95b7dd8..51b815950 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,3 +3,4 @@
- [fix:fix PolarisContextProperties instantiated twice causing NPE.](https://github.com/Tencent/spring-cloud-tencent/pull/1640)
- [fix: fix ConfigChangeListener and unit test](https://github.com/Tencent/spring-cloud-tencent/pull/1655)
+- [feat: support spring-retry and feign config refresh and feign eager load support schema](https://github.com/Tencent/spring-cloud-tencent/pull/1650)
diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/beanprocessor/LoadBalancerInterceptorBeanPostProcessor.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/beanprocessor/LoadBalancerInterceptorBeanPostProcessor.java
deleted file mode 100644
index 72596b238..000000000
--- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/beanprocessor/LoadBalancerInterceptorBeanPostProcessor.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Tencent is pleased to support the open source community by making spring-cloud-tencent available.
- *
- * Copyright (C) 2021 Tencent. 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.beanprocessor;
-
-import com.tencent.cloud.polaris.circuitbreaker.instrument.resttemplate.PolarisLoadBalancerInterceptor;
-import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
-
-import org.springframework.beans.BeansException;
-import org.springframework.beans.factory.BeanFactory;
-import org.springframework.beans.factory.BeanFactoryAware;
-import org.springframework.beans.factory.config.BeanPostProcessor;
-import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
-import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
-import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory;
-import org.springframework.core.Ordered;
-import org.springframework.lang.NonNull;
-
-/**
- * LoadBalancerInterceptorBeanPostProcessor is used to wrap the default LoadBalancerInterceptor implementation and returns a custom PolarisLoadBalancerInterceptor.
- *
- * @author Shedfree Wu
- */
-public class LoadBalancerInterceptorBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware, Ordered {
- /**
- * The order of the bean post processor. if user want to wrap it(CustomLoadBalancerInterceptor -> PolarisLoadBalancerInterceptor), CustomLoadBalancerInterceptorBeanPostProcessor's order should be bigger than ${@link POLARIS_LOAD_BALANCER_INTERCEPTOR_POST_PROCESSOR_ORDER}.
- */
- public static final int POLARIS_LOAD_BALANCER_INTERCEPTOR_POST_PROCESSOR_ORDER = 0;
-
- private BeanFactory factory;
-
- @Override
- public void setBeanFactory(@NonNull BeanFactory beanFactory) throws BeansException {
- this.factory = beanFactory;
- }
-
- @Override
- public Object postProcessBeforeInitialization(@NonNull Object bean, @NonNull String beanName) throws BeansException {
- if (bean instanceof LoadBalancerInterceptor) {
- // Support rest template router.
- // Replaces the default LoadBalancerInterceptor implementation and returns a custom PolarisLoadBalancerInterceptor
- LoadBalancerRequestFactory requestFactory = this.factory.getBean(LoadBalancerRequestFactory.class);
- LoadBalancerClient loadBalancerClient = this.factory.getBean(LoadBalancerClient.class);
- EnhancedPluginRunner pluginRunner = this.factory.getBean(EnhancedPluginRunner.class);
- return new PolarisLoadBalancerInterceptor(loadBalancerClient, requestFactory, pluginRunner);
- }
- return bean;
- }
-
- @Override
- public int getOrder() {
- return POLARIS_LOAD_BALANCER_INTERCEPTOR_POST_PROCESSOR_ORDER;
- }
-}
diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerAutoConfiguration.java
index 0f01fa1bf..1bb62d804 100644
--- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerAutoConfiguration.java
+++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/config/PolarisCircuitBreakerAutoConfiguration.java
@@ -21,7 +21,6 @@ import java.util.ArrayList;
import java.util.List;
import com.tencent.cloud.polaris.circuitbreaker.PolarisCircuitBreakerFactory;
-import com.tencent.cloud.polaris.circuitbreaker.beanprocessor.LoadBalancerInterceptorBeanPostProcessor;
import com.tencent.cloud.polaris.circuitbreaker.common.CircuitBreakerConfigModifier;
import com.tencent.cloud.polaris.circuitbreaker.reporter.CircuitBreakerPlugin;
import com.tencent.cloud.polaris.circuitbreaker.reporter.ExceptionCircuitBreakerReporter;
@@ -32,7 +31,6 @@ import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
@@ -92,10 +90,4 @@ public class PolarisCircuitBreakerAutoConfiguration {
return new CircuitBreakerConfigModifier(properties, polarisCircuitBreakerProperties);
}
- @Bean
- @ConditionalOnClass(name = "org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor")
- public LoadBalancerInterceptorBeanPostProcessor loadBalancerInterceptorBeanPostProcessor() {
- return new LoadBalancerInterceptorBeanPostProcessor();
- }
-
}
diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/instrument/resttemplate/PolarisLoadBalancerInterceptor.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/instrument/resttemplate/PolarisLoadBalancerInterceptor.java
deleted file mode 100644
index fbf077f6f..000000000
--- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/instrument/resttemplate/PolarisLoadBalancerInterceptor.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Tencent is pleased to support the open source community by making spring-cloud-tencent available.
- *
- * Copyright (C) 2021 Tencent. 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.instrument.resttemplate;
-
-import java.io.IOException;
-import java.net.URI;
-
-import com.tencent.cloud.rpc.enhancement.instrument.resttemplate.EnhancedRestTemplateWrapInterceptor;
-import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
-
-import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
-import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
-import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory;
-import org.springframework.http.HttpRequest;
-import org.springframework.http.client.ClientHttpRequestExecution;
-import org.springframework.http.client.ClientHttpResponse;
-import org.springframework.util.Assert;
-
-/**
- * PolarisLoadBalancerInterceptor is a wrapper of LoadBalancerInterceptor.
- *
- * @author Shedfree Wu
- */
-public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor {
-
- private final LoadBalancerClient loadBalancer;
- private final LoadBalancerRequestFactory requestFactory;
-
- private final EnhancedPluginRunner enhancedPluginRunner;
-
- public PolarisLoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory,
- EnhancedPluginRunner enhancedPluginRunner) {
- super(loadBalancer, requestFactory);
- this.loadBalancer = loadBalancer;
- this.requestFactory = requestFactory;
- this.enhancedPluginRunner = enhancedPluginRunner;
- }
-
- @Override
- public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
- final URI originalUri = request.getURI();
- String peerServiceName = originalUri.getHost();
- Assert.state(peerServiceName != null,
- "Request URI does not contain a valid hostname: " + originalUri);
-
- if (enhancedPluginRunner != null) {
- EnhancedRestTemplateWrapInterceptor enhancedRestTemplateWrapInterceptor = new EnhancedRestTemplateWrapInterceptor(enhancedPluginRunner, loadBalancer);
- return enhancedRestTemplateWrapInterceptor.intercept(request, peerServiceName, this.requestFactory.createRequest(request, body, execution));
- }
- else {
- return super.intercept(request, body, execution);
- }
- }
-}
diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/resttemplate/PolarisLoadBalancerInterceptorTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/resttemplate/PolarisLoadBalancerInterceptorTest.java
deleted file mode 100644
index 50f7b9ae6..000000000
--- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/instrument/resttemplate/PolarisLoadBalancerInterceptorTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Tencent is pleased to support the open source community by making spring-cloud-tencent available.
- *
- * Copyright (C) 2021 Tencent. 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.instrument.resttemplate;
-
-import java.io.IOException;
-import java.net.URI;
-import java.util.Objects;
-
-import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.Mock;
-import org.mockito.junit.jupiter.MockitoExtension;
-
-import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
-import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory;
-import org.springframework.http.HttpRequest;
-import org.springframework.http.client.ClientHttpRequestExecution;
-import org.springframework.http.client.ClientHttpResponse;
-
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-/**
- * Test for ${@link PolarisLoadBalancerInterceptor}.
- *
- * @author Shedfree Wu
- */
-@ExtendWith(MockitoExtension.class)
-class PolarisLoadBalancerInterceptorTest {
-
- @Mock
- private LoadBalancerClient loadBalancer;
-
- @Mock
- private LoadBalancerRequestFactory requestFactory;
-
- @Mock
- private EnhancedPluginRunner enhancedPluginRunner;
-
- @Mock
- private HttpRequest request;
-
- @Mock
- private ClientHttpRequestExecution execution;
-
- private PolarisLoadBalancerInterceptor interceptor;
- private byte[] body;
-
- @BeforeEach
- void setUp() {
- body = "test body".getBytes();
- }
-
- @Test
- void testInterceptWithEnhancedPlugin() throws IOException {
- // Arrange
- ClientHttpResponse mockResponse = mock(ClientHttpResponse.class);
- interceptor = new PolarisLoadBalancerInterceptor(loadBalancer, requestFactory, enhancedPluginRunner);
- URI uri = URI.create("http://test-service/path");
- when(request.getURI()).thenReturn(uri);
- when(loadBalancer.execute(any(), any())).thenReturn(mockResponse);
-
- // Act
- ClientHttpResponse response = interceptor.intercept(request, body, execution);
-
- // Assert
- Assertions.assertTrue(Objects.equals(mockResponse, response) || response instanceof PolarisCircuitBreakerHttpResponse);
- }
-
- @Test
- void testInterceptWithoutEnhancedPlugin() throws IOException {
- // Arrange
- ClientHttpResponse mockResponse = mock(ClientHttpResponse.class);
- interceptor = new PolarisLoadBalancerInterceptor(loadBalancer, requestFactory, null);
- URI uri = URI.create("http://test-service/path");
- when(request.getURI()).thenReturn(uri);
- when(loadBalancer.execute(any(), any())).thenReturn(mockResponse);
-
- // Act
- ClientHttpResponse response = interceptor.intercept(request, body, execution);
-
- // Assert
- Assertions.assertEquals(mockResponse, response);
- }
-
- @Test
- void testInterceptWithInvalidUri() {
- // Arrange
- interceptor = new PolarisLoadBalancerInterceptor(loadBalancer, requestFactory, enhancedPluginRunner);
- when(request.getURI()).thenReturn(URI.create("http:///path")); // Invalid URI without host
-
- // Act & Assert
- Exception exception = Assertions.assertThrows(IllegalStateException.class, () -> {
- interceptor.intercept(request, body, execution);
- });
- Assertions.assertTrue(exception.getMessage().contains("Request URI does not contain a valid hostname"));
- }
-
- @Test
- void testConstructor() {
- // Act
- interceptor = new PolarisLoadBalancerInterceptor(loadBalancer, requestFactory, enhancedPluginRunner);
-
- // Assert
- Assertions.assertNotNull(interceptor);
- }
-}
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 2652804cd..b29e2d1cb 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
@@ -83,9 +83,16 @@ public class PolarisConfigAutoConfiguration {
springValueRegistry, configFileService, contextRefresher, polarisSDKContextManager.getSDKContext());
}
+ /**
+ * In some scenarios, configurations are not annotated with @RefreshScope but are refreshed directly by listening to events.
+ * In such cases, it is necessary to actively execute putRefreshScopePrefixKey.
+ */
@Bean
public SpringValueRegistry springValueRegistry() {
- return new SpringValueRegistry();
+ SpringValueRegistry springValueRegistry = new SpringValueRegistry();
+ // TODO: support dynamic config
+ springValueRegistry.putRefreshScopePrefixKey("spring.cloud.openfeign.client");
+ return springValueRegistry;
}
@Bean
diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/feign/FeignEagerLoadSmartLifecycle.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/feign/FeignEagerLoadSmartLifecycle.java
index da1bbbaca..0f7804cc9 100644
--- a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/feign/FeignEagerLoadSmartLifecycle.java
+++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/eager/instrument/feign/FeignEagerLoadSmartLifecycle.java
@@ -19,6 +19,7 @@ package com.tencent.cloud.polaris.eager.instrument.feign;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
+import java.net.URI;
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClient;
import com.tencent.cloud.polaris.discovery.reactive.PolarisReactiveDiscoveryClient;
@@ -60,18 +61,24 @@ public class FeignEagerLoadSmartLifecycle implements SmartLifecycle {
// if feignClient contains url, it doesn't need to eager load.
if (StringUtils.isEmpty(feignClient.url())) {
// support variables and default values.
- String feignName = hardCodedTarget.name();
- LOG.info("[{}] eager-load start", feignName);
+ String url = hardCodedTarget.name();
+ // refer to FeignClientFactoryBean, convert to URL, then take the host as the service name.
+ if (!url.startsWith("http://") && !url.startsWith("https://")) {
+ url = "http://" + url;
+ }
+ String serviceName = URI.create(url).getHost();
+
+ LOG.info("[{}] eager-load start, feign name: {}", serviceName, hardCodedTarget.name());
if (polarisDiscoveryClient != null) {
- polarisDiscoveryClient.getInstances(feignName);
+ polarisDiscoveryClient.getInstances(serviceName);
}
else if (polarisReactiveDiscoveryClient != null) {
- polarisReactiveDiscoveryClient.getInstances(feignName).subscribe();
+ polarisReactiveDiscoveryClient.getInstances(serviceName).subscribe();
}
else {
- LOG.warn("[{}] no discovery client found.", feignName);
+ LOG.warn("[{}] no discovery client found.", serviceName);
}
- LOG.info("[{}] eager-load end", feignName);
+ LOG.info("[{}] eager-load end", serviceName);
}
}
}
diff --git a/spring-cloud-tencent-examples/tsf-example/consumer-demo-retry/pom.xml b/spring-cloud-tencent-examples/tsf-example/consumer-demo-retry/pom.xml
new file mode 100644
index 000000000..b39c426ed
--- /dev/null
+++ b/spring-cloud-tencent-examples/tsf-example/consumer-demo-retry/pom.xml
@@ -0,0 +1,35 @@
+
+
+ 4.0.0
+
+ tsf-example
+ com.tencent.cloud
+ ${revision}
+ ../pom.xml
+
+ consumer-demo-retry
+
+
+
+ com.tencent.cloud
+ spring-cloud-starter-tencent-all
+
+
+
+ org.springframework.retry
+ spring-retry
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+
+
+
\ No newline at end of file
diff --git a/spring-cloud-tencent-examples/tsf-example/consumer-demo-retry/src/main/java/com/tencent/cloud/tsf/demo/consumer/ConsumerApplication.java b/spring-cloud-tencent-examples/tsf-example/consumer-demo-retry/src/main/java/com/tencent/cloud/tsf/demo/consumer/ConsumerApplication.java
new file mode 100644
index 000000000..87694e5b2
--- /dev/null
+++ b/spring-cloud-tencent-examples/tsf-example/consumer-demo-retry/src/main/java/com/tencent/cloud/tsf/demo/consumer/ConsumerApplication.java
@@ -0,0 +1,41 @@
+/*
+ * Tencent is pleased to support the open source community by making spring-cloud-tencent available.
+ *
+ * Copyright (C) 2021 Tencent. 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.tsf.demo.consumer;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.loadbalancer.LoadBalanced;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+import org.springframework.context.annotation.Bean;
+import org.springframework.tsf.annotation.EnableTsf;
+import org.springframework.web.client.RestTemplate;
+
+@SpringBootApplication
+@EnableFeignClients // 使用Feign微服务调用时请启用
+@EnableTsf
+public class ConsumerApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(ConsumerApplication.class, args);
+ }
+
+ @LoadBalanced
+ @Bean
+ public RestTemplate restTemplate() {
+ return new RestTemplate();
+ }
+}
diff --git a/spring-cloud-tencent-examples/tsf-example/consumer-demo-retry/src/main/java/com/tencent/cloud/tsf/demo/consumer/controller/ConsumerController.java b/spring-cloud-tencent-examples/tsf-example/consumer-demo-retry/src/main/java/com/tencent/cloud/tsf/demo/consumer/controller/ConsumerController.java
new file mode 100644
index 000000000..09326b06d
--- /dev/null
+++ b/spring-cloud-tencent-examples/tsf-example/consumer-demo-retry/src/main/java/com/tencent/cloud/tsf/demo/consumer/controller/ConsumerController.java
@@ -0,0 +1,170 @@
+/*
+ * Tencent is pleased to support the open source community by making spring-cloud-tencent available.
+ *
+ * Copyright (C) 2021 Tencent. 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.tsf.demo.consumer.controller;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+import com.tencent.cloud.common.util.PolarisCompletableFutureUtils;
+import com.tencent.cloud.tsf.demo.consumer.proxy.ProviderDemoService;
+import com.tencent.cloud.tsf.demo.consumer.proxy.ProviderService;
+import com.tencent.polaris.api.utils.StringUtils;
+import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.tsf.core.TsfContext;
+import org.springframework.tsf.core.entity.Tag;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.client.RestTemplate;
+
+@RestController
+public class ConsumerController {
+ @Autowired
+ private RestTemplate restTemplate;
+ @Autowired
+ private ProviderService providerService;
+ @Autowired
+ private ProviderDemoService providerDemoService;
+
+ @RequestMapping(value = "/echo-rest/{str}", method = RequestMethod.GET)
+ public String restProvider(@PathVariable String str,
+ @RequestParam(required = false) String tagName,
+ @RequestParam(required = false) String tagValue) {
+ if (StringUtils.isNotBlank(tagName)) {
+ TsfContext.putTag(tagName, tagValue);
+ }
+ TsfContext.putTag("operation", "rest");
+ Map mTags = new HashMap<>();
+ mTags.put("rest-trace-key1", "value1");
+ mTags.put("rest-trace-key2", "value2");
+ TsfContext.putTags(mTags, Tag.ControlFlag.TRANSITIVE);
+ try {
+ return restTemplate.getForObject("http://provider-demo/echo/" + str, String.class);
+ }
+ catch (CallAbortedException callAbortedException) {
+ return callAbortedException.getMessage();
+ }
+ }
+
+ @RequestMapping(value = "/echo-slow-rest/{str}", method = RequestMethod.GET)
+ public String restSlowProvider(@PathVariable String str,
+ @RequestParam(required = false) String tagName,
+ @RequestParam(required = false) String tagValue) {
+ if (StringUtils.isNotBlank(tagName)) {
+ TsfContext.putTag(tagName, tagValue);
+ }
+ TsfContext.putTag("operation", "rest");
+ Map mTags = new HashMap<>();
+ mTags.put("rest-trace-key1", "value1");
+ mTags.put("rest-trace-key2", "value2");
+ TsfContext.putTags(mTags, Tag.ControlFlag.TRANSITIVE);
+ try {
+ return restTemplate.getForObject("http://provider-demo/echo/slow/" + str, String.class);
+ }
+ catch (CallAbortedException callAbortedException) {
+ return callAbortedException.getMessage();
+ }
+ }
+
+
+ @RequestMapping(value = "/echo-rest-async/{str}", method = RequestMethod.GET)
+ public String restAsync(@PathVariable String str,
+ @RequestParam(required = false) String tagName,
+ @RequestParam(required = false) String tagValue) throws ExecutionException, InterruptedException {
+ if (StringUtils.isNotBlank(tagName)) {
+ TsfContext.putTag(tagName, tagValue);
+ }
+ TsfContext.putTag("operation", "rest");
+ Map mTags = new HashMap<>();
+ mTags.put("rest-trace-key1", "value1");
+ mTags.put("rest-trace-key2", "value2");
+ TsfContext.putTags(mTags, Tag.ControlFlag.TRANSITIVE);
+ CompletableFuture echoFuture = PolarisCompletableFutureUtils.supplyAsync(() -> {
+ try {
+ return restTemplate.getForObject("http://provider-demo/echo/" + str, String.class);
+ }
+ catch (CallAbortedException callAbortedException) {
+ return callAbortedException.getMessage();
+ }
+ });
+ return echoFuture.get();
+ }
+
+ @RequestMapping(value = "/echo-feign/{str}", method = RequestMethod.GET)
+ public String feignProvider(@PathVariable String str,
+ @RequestParam(required = false) String tagName,
+ @RequestParam(required = false) String tagValue) {
+ if (StringUtils.isNotBlank(tagName)) {
+ TsfContext.putTag(tagName, tagValue);
+ }
+ TsfContext.putTag("operation", "feign");
+ Map mTags = new HashMap<>();
+ mTags.put("feign-trace-key1", "value1");
+ mTags.put("feign-trace-key2", "value2");
+ TsfContext.putTags(mTags, Tag.ControlFlag.TRANSITIVE);
+ try {
+ return providerDemoService.echo(str);
+ }
+ catch (CallAbortedException callAbortedException) {
+ return callAbortedException.getMessage();
+ }
+ }
+
+ @RequestMapping(value = "/echo-slow-feign/{str}", method = RequestMethod.GET)
+ public String feignSlowProvider(@PathVariable String str,
+ @RequestParam(required = false) String tagName,
+ @RequestParam(required = false) String tagValue,
+ @RequestParam(required = false) String delay) {
+ if (StringUtils.isNotBlank(tagName)) {
+ TsfContext.putTag(tagName, tagValue);
+ }
+ TsfContext.putTag("operation", "feign");
+ Map mTags = new HashMap<>();
+ mTags.put("feign-trace-key1", "value1");
+ mTags.put("feign-trace-key2", "value2");
+ TsfContext.putTags(mTags, Tag.ControlFlag.TRANSITIVE);
+ int sleepTime = delay == null ? 1000 : Integer.parseInt(delay);
+ try {
+ return providerDemoService.echoSlow(str, sleepTime);
+ }
+ catch (CallAbortedException callAbortedException) {
+ return callAbortedException.getMessage();
+ }
+ }
+
+ @RequestMapping(value = "/echo-feign-url/{str}", method = RequestMethod.GET)
+ public String feignUrlProvider(@PathVariable String str,
+ @RequestParam(required = false) String tagName,
+ @RequestParam(required = false) String tagValue) {
+ if (StringUtils.isNotBlank(tagName)) {
+ TsfContext.putTag(tagName, tagValue);
+ }
+ TsfContext.putTag("operation", "feignUrl");
+ Map mTags = new HashMap<>();
+ mTags.put("feignUrl-trace-key1", "value1");
+ mTags.put("feignUrl-trace-key2", "value2");
+ TsfContext.putTags(mTags, Tag.ControlFlag.TRANSITIVE);
+ return providerService.echo(str);
+ }
+}
diff --git a/spring-cloud-tencent-examples/tsf-example/consumer-demo-retry/src/main/java/com/tencent/cloud/tsf/demo/consumer/controller/SdkBaseTest.java b/spring-cloud-tencent-examples/tsf-example/consumer-demo-retry/src/main/java/com/tencent/cloud/tsf/demo/consumer/controller/SdkBaseTest.java
new file mode 100644
index 000000000..1b68d6777
--- /dev/null
+++ b/spring-cloud-tencent-examples/tsf-example/consumer-demo-retry/src/main/java/com/tencent/cloud/tsf/demo/consumer/controller/SdkBaseTest.java
@@ -0,0 +1,54 @@
+/*
+ * Tencent is pleased to support the open source community by making spring-cloud-tencent available.
+ *
+ * Copyright (C) 2021 Tencent. 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.tsf.demo.consumer.controller;
+
+import com.tencent.polaris.api.utils.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.tsf.core.TsfContext;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.client.RestTemplate;
+
+@RestController
+public class SdkBaseTest {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SdkBaseTest.class);
+
+ @Autowired
+ private RestTemplate restTemplate;
+
+ // 调用一次provider接口
+ @RequestMapping(value = "/echo-once/{str}", method = RequestMethod.GET)
+ public String restOnceProvider(@PathVariable String str,
+ @RequestParam(required = false) String tagName,
+ @RequestParam(required = false) String tagValue) {
+ if (!StringUtils.isEmpty(tagName)) {
+ TsfContext.putTag(tagName, tagValue);
+ }
+ LOG.info("start call echo-once");
+ String result = restTemplate.getForObject("http://provider-demo/echo/" + str, String.class);
+ LOG.info("end call echo-once, the result is : " + result);
+ return result;
+ }
+}
diff --git a/spring-cloud-tencent-examples/tsf-example/consumer-demo-retry/src/main/java/com/tencent/cloud/tsf/demo/consumer/entity/CustomMetadata.java b/spring-cloud-tencent-examples/tsf-example/consumer-demo-retry/src/main/java/com/tencent/cloud/tsf/demo/consumer/entity/CustomMetadata.java
new file mode 100644
index 000000000..f8c72a055
--- /dev/null
+++ b/spring-cloud-tencent-examples/tsf-example/consumer-demo-retry/src/main/java/com/tencent/cloud/tsf/demo/consumer/entity/CustomMetadata.java
@@ -0,0 +1,32 @@
+/*
+ * Tencent is pleased to support the open source community by making spring-cloud-tencent available.
+ *
+ * Copyright (C) 2021 Tencent. 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.tsf.demo.consumer.entity;
+
+/**
+ * 用户自定义 Metadata.
+ */
+public class CustomMetadata {
+
+ private String name;
+ private String value;
+
+ public CustomMetadata(String name, String value) {
+ this.name = name;
+ this.value = value;
+ }
+}
diff --git a/spring-cloud-tencent-examples/tsf-example/consumer-demo-retry/src/main/java/com/tencent/cloud/tsf/demo/consumer/proxy/ProviderDemoService.java b/spring-cloud-tencent-examples/tsf-example/consumer-demo-retry/src/main/java/com/tencent/cloud/tsf/demo/consumer/proxy/ProviderDemoService.java
new file mode 100644
index 000000000..d615ff08a
--- /dev/null
+++ b/spring-cloud-tencent-examples/tsf-example/consumer-demo-retry/src/main/java/com/tencent/cloud/tsf/demo/consumer/proxy/ProviderDemoService.java
@@ -0,0 +1,36 @@
+/*
+ * Tencent is pleased to support the open source community by making spring-cloud-tencent available.
+ *
+ * Copyright (C) 2021 Tencent. 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.tsf.demo.consumer.proxy;
+
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+
+@FeignClient(name = "${provider.name:provider-demo}")
+public interface ProviderDemoService {
+ @RequestMapping(value = "/echo/{str}", method = RequestMethod.GET)
+ String echo(@PathVariable("str") String str);
+
+ @RequestMapping(value = "/echo/error/{str}", method = RequestMethod.GET)
+ String echoError(@PathVariable("str") String str);
+
+ @RequestMapping(value = "/echo/slow/{str}", method = RequestMethod.GET)
+ String echoSlow(@PathVariable("str") String str, @RequestParam("delay") int delay);
+}
diff --git a/spring-cloud-tencent-examples/tsf-example/consumer-demo-retry/src/main/java/com/tencent/cloud/tsf/demo/consumer/proxy/ProviderService.java b/spring-cloud-tencent-examples/tsf-example/consumer-demo-retry/src/main/java/com/tencent/cloud/tsf/demo/consumer/proxy/ProviderService.java
new file mode 100644
index 000000000..abc552faf
--- /dev/null
+++ b/spring-cloud-tencent-examples/tsf-example/consumer-demo-retry/src/main/java/com/tencent/cloud/tsf/demo/consumer/proxy/ProviderService.java
@@ -0,0 +1,44 @@
+/*
+ * Tencent is pleased to support the open source community by making spring-cloud-tencent available.
+ *
+ * Copyright (C) 2021 Tencent. 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.tsf.demo.consumer.proxy;
+
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+/**
+ * 测试通过URL配置FeignClient
+ * 使用时修改provider-ip:provider-port配置
+ */
+@FeignClient(name = "provider", url = "http://127.0.0.1:18081", fallback = FeignClientFallback.class)
+public interface ProviderService {
+
+ @RequestMapping(value = "/echo/{str}", method = RequestMethod.GET)
+ String echo(@PathVariable("str") String str);
+
+}
+
+@Component
+class FeignClientFallback implements ProviderService {
+ @Override
+ public String echo(String str) {
+ return "tsf-fault-tolerance-" + str;
+ }
+}
diff --git a/spring-cloud-tencent-examples/tsf-example/consumer-demo-retry/src/main/resources/application.yml b/spring-cloud-tencent-examples/tsf-example/consumer-demo-retry/src/main/resources/application.yml
new file mode 100644
index 000000000..eb7a01b09
--- /dev/null
+++ b/spring-cloud-tencent-examples/tsf-example/consumer-demo-retry/src/main/resources/application.yml
@@ -0,0 +1,20 @@
+server:
+ port: 18083
+spring:
+ application:
+ name: consumer-demo
+ config:
+ import: optional:polaris
+
+feign:
+ tsf:
+ enabled: true
+
+#本地测试时打开
+#tsf_namespace_id: default_namespace
+
+logging:
+ file:
+ name: /tsf-demo-logs/${spring.application.name}/root.log
+ level:
+ root: INFO
diff --git a/spring-cloud-tencent-examples/tsf-example/consumer-demo/src/main/java/com/tencent/cloud/tsf/demo/consumer/controller/ConsumerController.java b/spring-cloud-tencent-examples/tsf-example/consumer-demo/src/main/java/com/tencent/cloud/tsf/demo/consumer/controller/ConsumerController.java
index 25e100fe7..09326b06d 100644
--- a/spring-cloud-tencent-examples/tsf-example/consumer-demo/src/main/java/com/tencent/cloud/tsf/demo/consumer/controller/ConsumerController.java
+++ b/spring-cloud-tencent-examples/tsf-example/consumer-demo/src/main/java/com/tencent/cloud/tsf/demo/consumer/controller/ConsumerController.java
@@ -67,6 +67,26 @@ public class ConsumerController {
}
}
+ @RequestMapping(value = "/echo-slow-rest/{str}", method = RequestMethod.GET)
+ public String restSlowProvider(@PathVariable String str,
+ @RequestParam(required = false) String tagName,
+ @RequestParam(required = false) String tagValue) {
+ if (StringUtils.isNotBlank(tagName)) {
+ TsfContext.putTag(tagName, tagValue);
+ }
+ TsfContext.putTag("operation", "rest");
+ Map mTags = new HashMap<>();
+ mTags.put("rest-trace-key1", "value1");
+ mTags.put("rest-trace-key2", "value2");
+ TsfContext.putTags(mTags, Tag.ControlFlag.TRANSITIVE);
+ try {
+ return restTemplate.getForObject("http://provider-demo/echo/slow/" + str, String.class);
+ }
+ catch (CallAbortedException callAbortedException) {
+ return callAbortedException.getMessage();
+ }
+ }
+
@RequestMapping(value = "/echo-rest-async/{str}", method = RequestMethod.GET)
public String restAsync(@PathVariable String str,
@@ -111,6 +131,28 @@ public class ConsumerController {
}
}
+ @RequestMapping(value = "/echo-slow-feign/{str}", method = RequestMethod.GET)
+ public String feignSlowProvider(@PathVariable String str,
+ @RequestParam(required = false) String tagName,
+ @RequestParam(required = false) String tagValue,
+ @RequestParam(required = false) String delay) {
+ if (StringUtils.isNotBlank(tagName)) {
+ TsfContext.putTag(tagName, tagValue);
+ }
+ TsfContext.putTag("operation", "feign");
+ Map mTags = new HashMap<>();
+ mTags.put("feign-trace-key1", "value1");
+ mTags.put("feign-trace-key2", "value2");
+ TsfContext.putTags(mTags, Tag.ControlFlag.TRANSITIVE);
+ int sleepTime = delay == null ? 1000 : Integer.parseInt(delay);
+ try {
+ return providerDemoService.echoSlow(str, sleepTime);
+ }
+ catch (CallAbortedException callAbortedException) {
+ return callAbortedException.getMessage();
+ }
+ }
+
@RequestMapping(value = "/echo-feign-url/{str}", method = RequestMethod.GET)
public String feignUrlProvider(@PathVariable String str,
@RequestParam(required = false) String tagName,
diff --git a/spring-cloud-tencent-examples/tsf-example/consumer-demo/src/main/java/com/tencent/cloud/tsf/demo/consumer/proxy/ProviderDemoService.java b/spring-cloud-tencent-examples/tsf-example/consumer-demo/src/main/java/com/tencent/cloud/tsf/demo/consumer/proxy/ProviderDemoService.java
index d615ff08a..a47ebd428 100644
--- a/spring-cloud-tencent-examples/tsf-example/consumer-demo/src/main/java/com/tencent/cloud/tsf/demo/consumer/proxy/ProviderDemoService.java
+++ b/spring-cloud-tencent-examples/tsf-example/consumer-demo/src/main/java/com/tencent/cloud/tsf/demo/consumer/proxy/ProviderDemoService.java
@@ -23,7 +23,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
-@FeignClient(name = "${provider.name:provider-demo}")
+@FeignClient(name = "${provider.name:http://provider-demo}")
public interface ProviderDemoService {
@RequestMapping(value = "/echo/{str}", method = RequestMethod.GET)
String echo(@PathVariable("str") String str);
diff --git a/spring-cloud-tencent-examples/tsf-example/pom.xml b/spring-cloud-tencent-examples/tsf-example/pom.xml
index a6db70129..979efb5f9 100644
--- a/spring-cloud-tencent-examples/tsf-example/pom.xml
+++ b/spring-cloud-tencent-examples/tsf-example/pom.xml
@@ -15,6 +15,7 @@
provider-demo
+ consumer-demo-retry
consumer-demo
diff --git a/spring-cloud-tencent-examples/tsf-example/provider-demo/src/main/java/com/tencent/cloud/tsf/demo/provider/ProviderController.java b/spring-cloud-tencent-examples/tsf-example/provider-demo/src/main/java/com/tencent/cloud/tsf/demo/provider/ProviderController.java
index eaba3e34e..40c3f0758 100644
--- a/spring-cloud-tencent-examples/tsf-example/provider-demo/src/main/java/com/tencent/cloud/tsf/demo/provider/ProviderController.java
+++ b/spring-cloud-tencent-examples/tsf-example/provider-demo/src/main/java/com/tencent/cloud/tsf/demo/provider/ProviderController.java
@@ -29,7 +29,9 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@@ -52,6 +54,8 @@ public class ProviderController {
@Autowired
private ProviderNameConfig providerNameConfig;
+ private boolean ifBadGateway = false;
+
// 获取本机ip
public static String getInet4Address() {
Enumeration nis;
@@ -85,6 +89,11 @@ public class ProviderController {
@RequestMapping(value = "/echo/{param}", method = RequestMethod.GET)
public ResponseEntity echo(@PathVariable String param) {
+ if (ifBadGateway) {
+ LOG.info("Provider Demo is called wrong.");
+ return new ResponseEntity<>("failed for call provider demo service. Address: " + getInet4Address(), HttpStatus.BAD_GATEWAY);
+ }
+
int status;
String responseBody;
@@ -149,4 +158,17 @@ public class ProviderController {
LOG.info("provider-demo -- unit response info: [" + result + "]");
return result;
}
+
+ @GetMapping("/setBadGateway")
+ public String setBadGateway(@RequestParam boolean param) {
+ this.ifBadGateway = param;
+ if (param) {
+ LOG.info("info is set to return HttpStatus.BAD_GATEWAY.");
+ return "info is set to return HttpStatus.BAD_GATEWAY.";
+ }
+ else {
+ LOG.info("info is set to return HttpStatus.OK.");
+ return "info is set to return HttpStatus.OK.";
+ }
+ }
}
diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/beanprocessor/BlockingLoadBalancerClientBeanPostProcessor.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/beanprocessor/BlockingLoadBalancerClientBeanPostProcessor.java
new file mode 100644
index 000000000..f48144b22
--- /dev/null
+++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/beanprocessor/BlockingLoadBalancerClientBeanPostProcessor.java
@@ -0,0 +1,66 @@
+/*
+ * Tencent is pleased to support the open source community by making spring-cloud-tencent available.
+ *
+ * Copyright (C) 2021 Tencent. 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.rpc.enhancement.beanprocessor;
+
+import com.tencent.cloud.rpc.enhancement.instrument.resttemplate.PolarisBlockingLoadBalancerClient;
+import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
+import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
+import org.springframework.core.Ordered;
+import org.springframework.lang.NonNull;
+
+/**
+ * BlockingLoadBalancerClientBeanPostProcessor is used to wrap the default BlockingLoadBalancerClient implementation and returns a custom PolarisBlockingLoadBalancerClient.
+ *
+ * @author Shedfree Wu
+ */
+public class BlockingLoadBalancerClientBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware, Ordered {
+ /**
+ * The order of the bean post processor. If user wants to wrap it(CustomBlockingLoadBalancerClient -> PolarisBlockingLoadBalancerClient), CustomBlockingLoadBalancerClientBeanPostProcessor's order should be bigger than ${@link ORDER}.
+ */
+ public static final int ORDER = 0;
+
+ private BeanFactory factory;
+
+ @Override
+ public void setBeanFactory(@NonNull BeanFactory beanFactory) throws BeansException {
+ this.factory = beanFactory;
+ }
+
+ @Override
+ public Object postProcessBeforeInitialization(@NonNull Object bean, @NonNull String beanName) throws BeansException {
+ if (bean instanceof BlockingLoadBalancerClient) {
+ BlockingLoadBalancerClient delegate = (BlockingLoadBalancerClient) bean;
+ ReactiveLoadBalancer.Factory requestFactory = this.factory.getBean(ReactiveLoadBalancer.Factory.class);
+ EnhancedPluginRunner pluginRunner = this.factory.getBean(EnhancedPluginRunner.class);
+ return new PolarisBlockingLoadBalancerClient(requestFactory, delegate, pluginRunner);
+ }
+ return bean;
+ }
+
+ @Override
+ public int getOrder() {
+ return ORDER;
+ }
+}
diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java
index 96b886a91..4e45577cb 100644
--- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java
+++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java
@@ -23,6 +23,7 @@ import java.util.List;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import com.tencent.cloud.polaris.context.PolarisSDKContextManager;
import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration;
+import com.tencent.cloud.rpc.enhancement.beanprocessor.BlockingLoadBalancerClientBeanPostProcessor;
import com.tencent.cloud.rpc.enhancement.instrument.feign.EnhancedFeignBeanPostProcessor;
import com.tencent.cloud.rpc.enhancement.instrument.feign.PolarisLoadBalancerFeignRequestTransformer;
import com.tencent.cloud.rpc.enhancement.instrument.filter.EnhancedReactiveFilter;
@@ -188,6 +189,12 @@ public class RpcEnhancementAutoConfiguration {
return new PolarisLoadBalancerRequestTransformer();
}
+ @Bean
+ @ConditionalOnClass(name = "org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor")
+ public BlockingLoadBalancerClientBeanPostProcessor loadBalancerInterceptorBeanPostProcessor() {
+ return new BlockingLoadBalancerClientBeanPostProcessor();
+ }
+
}
diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateWrapInterceptor.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateWrapInterceptor.java
index 2659ce340..cb29597f3 100644
--- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateWrapInterceptor.java
+++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateWrapInterceptor.java
@@ -58,24 +58,24 @@ public class EnhancedRestTemplateWrapInterceptor {
}
- public ClientHttpResponse intercept(HttpRequest request, String serviceId,
- LoadBalancerRequest loadBalancerRequest) throws IOException {
+ public T intercept(HttpRequest httpRequest, String serviceId, ServiceInstance serviceInstance,
+ LoadBalancerRequest loadBalancerRequest) throws IOException {
EnhancedPluginContext enhancedPluginContext = new EnhancedPluginContext();
- URI serviceUrl = request.getURI();
- if (request instanceof ServiceRequestWrapper) {
- serviceUrl = ((ServiceRequestWrapper) request).getRequest().getURI();
+ URI serviceUrl = httpRequest.getURI();
+ if (httpRequest instanceof ServiceRequestWrapper) {
+ serviceUrl = ((ServiceRequestWrapper) httpRequest).getRequest().getURI();
}
EnhancedRequestContext enhancedRequestContext = EnhancedRequestContext.builder()
- .httpHeaders(request.getHeaders())
- .httpMethod(request.getMethod())
- .url(request.getURI())
+ .httpHeaders(httpRequest.getHeaders())
+ .httpMethod(httpRequest.getMethod())
+ .url(httpRequest.getURI())
.serviceUrl(serviceUrl)
.build();
enhancedPluginContext.setRequest(enhancedRequestContext);
- enhancedPluginContext.setOriginRequest(request);
+ enhancedPluginContext.setOriginRequest(httpRequest);
enhancedPluginContext.setLocalServiceInstance(pluginRunner.getLocalServiceInstance());
@@ -85,16 +85,27 @@ public class EnhancedRestTemplateWrapInterceptor {
pluginRunner.run(EnhancedPluginType.Client.PRE, enhancedPluginContext);
startMillis = System.currentTimeMillis();
- ClientHttpResponse response = delegate.execute(serviceId, loadBalancerRequest);
+ T response = null;
+ // retry rest template, serviceInstance is not null
+ if (serviceInstance != null) {
+ response = delegate.execute(serviceId, serviceInstance, loadBalancerRequest);
+ }
+ else {
+ response = delegate.execute(serviceId, loadBalancerRequest);
+ }
// get target instance after execute
enhancedPluginContext.setTargetServiceInstance((ServiceInstance) MetadataContextHolder.get()
- .getLoadbalancerMetadata().get(LOAD_BALANCER_SERVICE_INSTANCE), request.getURI());
+ .getLoadbalancerMetadata().get(LOAD_BALANCER_SERVICE_INSTANCE), httpRequest.getURI());
enhancedPluginContext.setDelay(System.currentTimeMillis() - startMillis);
- EnhancedResponseContext enhancedResponseContext = EnhancedResponseContext.builder()
- .httpStatus(response.getRawStatusCode())
- .httpHeaders(response.getHeaders())
- .build();
+
+ EnhancedResponseContext.EnhancedContextResponseBuilder enhancedResponseContextBuilder = EnhancedResponseContext.builder();
+ if (response instanceof ClientHttpResponse) {
+ enhancedResponseContextBuilder.httpStatus(((ClientHttpResponse) response).getStatusCode().value());
+ enhancedResponseContextBuilder.httpHeaders(((ClientHttpResponse) response).getHeaders());
+ }
+ EnhancedResponseContext enhancedResponseContext = enhancedResponseContextBuilder.build();
+
enhancedPluginContext.setResponse(enhancedResponseContext);
// Run post enhanced plugins.
@@ -114,7 +125,7 @@ public class EnhancedRestTemplateWrapInterceptor {
if (existFallback) {
Object fallbackResponse = fallbackResponseValue.getObjectValue().orElse(null);
if (fallbackResponse instanceof ClientHttpResponse) {
- return (ClientHttpResponse) fallbackResponse;
+ return (T) fallbackResponse;
}
}
throw callAbortedException;
diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/PolarisBlockingLoadBalancerClient.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/PolarisBlockingLoadBalancerClient.java
new file mode 100644
index 000000000..aa9a89774
--- /dev/null
+++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/PolarisBlockingLoadBalancerClient.java
@@ -0,0 +1,74 @@
+/*
+ * Tencent is pleased to support the open source community by making spring-cloud-tencent available.
+ *
+ * Copyright (C) 2021 Tencent. 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.rpc.enhancement.instrument.resttemplate;
+
+import java.io.IOException;
+
+import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
+
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest;
+import org.springframework.cloud.client.loadbalancer.LoadBalancerUtils;
+import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
+import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
+import org.springframework.http.HttpRequest;
+
+/**
+ * PolarisBlockingLoadBalancerClient is a wrapper of BlockingLoadBalancerClient.
+ *
+ * @author Shedfree Wu
+ */
+public class PolarisBlockingLoadBalancerClient extends BlockingLoadBalancerClient {
+
+ private BlockingLoadBalancerClient delegate;
+
+ private final EnhancedPluginRunner enhancedPluginRunner;
+
+ public PolarisBlockingLoadBalancerClient(ReactiveLoadBalancer.Factory loadBalancerClientFactory,
+ BlockingLoadBalancerClient delegate, EnhancedPluginRunner enhancedPluginRunner) {
+ super(loadBalancerClientFactory);
+ this.delegate = delegate;
+ this.enhancedPluginRunner = enhancedPluginRunner;
+ }
+
+ /**
+ * common rest template.
+ */
+ @Override
+ public T execute(String serviceId, LoadBalancerRequest request) throws IOException {
+ HttpRequest httpRequest = LoadBalancerUtils.getHttpRequestIfAvailable(request);
+ if (httpRequest == null || enhancedPluginRunner == null) {
+ return delegate.execute(serviceId, request);
+ }
+ EnhancedRestTemplateWrapInterceptor enhancedRestTemplateWrapInterceptor = new EnhancedRestTemplateWrapInterceptor(enhancedPluginRunner, delegate);
+ return enhancedRestTemplateWrapInterceptor.intercept(httpRequest, serviceId, null, request);
+ }
+
+ /**
+ * retry rest template.
+ */
+ @Override
+ public T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest request) throws IOException {
+ HttpRequest httpRequest = LoadBalancerUtils.getHttpRequestIfAvailable(LoadBalancerUtils.getDelegateLoadBalancerRequestIfAvailable(request));
+ if (httpRequest == null || serviceInstance == null || enhancedPluginRunner == null) {
+ return delegate.execute(serviceId, serviceInstance, request);
+ }
+ EnhancedRestTemplateWrapInterceptor enhancedRestTemplateWrapInterceptor = new EnhancedRestTemplateWrapInterceptor(enhancedPluginRunner, delegate);
+ return enhancedRestTemplateWrapInterceptor.intercept(httpRequest, serviceId, serviceInstance, request);
+ }
+}
diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerUtils.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerUtils.java
new file mode 100644
index 000000000..f9552f798
--- /dev/null
+++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerUtils.java
@@ -0,0 +1,75 @@
+/*
+ * Tencent is pleased to support the open source community by making spring-cloud-tencent available.
+ *
+ * Copyright (C) 2021 Tencent. 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 org.springframework.cloud.client.loadbalancer;
+
+import java.lang.reflect.Field;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.springframework.http.HttpRequest;
+
+public final class LoadBalancerUtils {
+
+ private static final Logger logger = LoggerFactory.getLogger(LoadBalancerUtils.class);
+
+ private LoadBalancerUtils() {
+ }
+
+ /**
+ * if request is a BlockingLoadBalancerRequest, return its HttpRequest.
+ */
+ public static HttpRequest getHttpRequestIfAvailable(LoadBalancerRequest> request) {
+ if (request instanceof BlockingLoadBalancerRequest) {
+ BlockingLoadBalancerRequest blockingRequest = (BlockingLoadBalancerRequest) request;
+ return blockingRequest.getHttpRequest();
+ }
+ else {
+ if (logger.isDebugEnabled()) {
+ logger.debug("LoadBalancerRequest is not a BlockingLoadBalancerRequest, request:{}", request);
+ }
+ return null;
+ }
+ }
+
+ /**
+ * if request is a LoadBalancerRequestAdapter(RetryLoadBalancerInterceptor), return its delegate.
+ */
+ public static LoadBalancerRequest> getDelegateLoadBalancerRequestIfAvailable(LoadBalancerRequest> request) {
+ if (!(request instanceof LoadBalancerRequestAdapter)) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("LoadBalancerRequest is not a LoadBalancerRequestAdapter, request:{}", request);
+ }
+ return request;
+ }
+
+ try {
+ Field delegateField = LoadBalancerRequestAdapter.class.getDeclaredField("delegate");
+ delegateField.setAccessible(true);
+ return (LoadBalancerRequest>) delegateField.get(request);
+ }
+ catch (Exception e) {
+ // ignore
+ if (logger.isDebugEnabled()) {
+ logger.debug("Failed to get delegate from LoadBalancerRequestAdapter, request:{}", request, e);
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/beanprocessor/LoadBalancerInterceptorBeanPostProcessorTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/beanprocessor/BlockingLoadBalancerClientBeanPostProcessorTest.java
similarity index 51%
rename from spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/beanprocessor/LoadBalancerInterceptorBeanPostProcessorTest.java
rename to spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/beanprocessor/BlockingLoadBalancerClientBeanPostProcessorTest.java
index 6e5f55d50..0d50adcfd 100644
--- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/beanprocessor/LoadBalancerInterceptorBeanPostProcessorTest.java
+++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/beanprocessor/BlockingLoadBalancerClientBeanPostProcessorTest.java
@@ -15,9 +15,8 @@
* specific language governing permissions and limitations under the License.
*/
-package com.tencent.cloud.polaris.circuitbreaker.beanprocessor;
+package com.tencent.cloud.rpc.enhancement.beanprocessor;
-import com.tencent.cloud.polaris.circuitbreaker.instrument.resttemplate.PolarisLoadBalancerInterceptor;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
@@ -27,19 +26,16 @@ import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
-import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
- * Test for ${@link LoadBalancerInterceptorBeanPostProcessor}.
+ * Test for ${@link BlockingLoadBalancerClientBeanPostProcessor}.
*
* @author Shedfree Wu
*/
-class LoadBalancerInterceptorBeanPostProcessorTest {
+class BlockingLoadBalancerClientBeanPostProcessorTest {
@Mock
private BeanFactory beanFactory;
@@ -53,12 +49,12 @@ class LoadBalancerInterceptorBeanPostProcessorTest {
@Mock
private EnhancedPluginRunner pluginRunner;
- private LoadBalancerInterceptorBeanPostProcessor processor;
+ private BlockingLoadBalancerClientBeanPostProcessor processor;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
- processor = new LoadBalancerInterceptorBeanPostProcessor();
+ processor = new BlockingLoadBalancerClientBeanPostProcessor();
processor.setBeanFactory(beanFactory);
// Setup mock behavior
@@ -67,22 +63,6 @@ class LoadBalancerInterceptorBeanPostProcessorTest {
when(beanFactory.getBean(EnhancedPluginRunner.class)).thenReturn(pluginRunner);
}
- @Test
- void testPostProcessBeforeInitializationWithLoadBalancerInterceptor() {
- // Arrange
- LoadBalancerInterceptor originalInterceptor = mock(LoadBalancerInterceptor.class);
- String beanName = "testBean";
-
- // Act
- Object result = processor.postProcessBeforeInitialization(originalInterceptor, beanName);
-
- // Assert
- Assertions.assertInstanceOf(PolarisLoadBalancerInterceptor.class, result);
- verify(beanFactory).getBean(LoadBalancerRequestFactory.class);
- verify(beanFactory).getBean(LoadBalancerClient.class);
- verify(beanFactory).getBean(EnhancedPluginRunner.class);
- }
-
@Test
void testPostProcessBeforeInitializationWithNonLoadBalancerInterceptor() {
// Arrange
@@ -102,26 +82,6 @@ class LoadBalancerInterceptorBeanPostProcessorTest {
int order = processor.getOrder();
// Assert
- Assertions.assertEquals(LoadBalancerInterceptorBeanPostProcessor.POLARIS_LOAD_BALANCER_INTERCEPTOR_POST_PROCESSOR_ORDER, order);
- }
-
- @Test
- void testSetBeanFactory() {
- // Arrange
- BeanFactory newBeanFactory = mock(BeanFactory.class);
- LoadBalancerInterceptorBeanPostProcessor newProcessor = new LoadBalancerInterceptorBeanPostProcessor();
-
- // Act
- newProcessor.setBeanFactory(newBeanFactory);
-
- // Assert
- // Verify the bean factory is set by trying to process a bean
- LoadBalancerInterceptor interceptor = mock(LoadBalancerInterceptor.class);
- when(newBeanFactory.getBean(LoadBalancerRequestFactory.class)).thenReturn(requestFactory);
- when(newBeanFactory.getBean(LoadBalancerClient.class)).thenReturn(loadBalancerClient);
- when(newBeanFactory.getBean(EnhancedPluginRunner.class)).thenReturn(pluginRunner);
-
- Object result = newProcessor.postProcessBeforeInitialization(interceptor, "testBean");
- Assertions.assertInstanceOf(PolarisLoadBalancerInterceptor.class, result);
+ Assertions.assertEquals(BlockingLoadBalancerClientBeanPostProcessor.ORDER, order);
}
}
diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateWrapInterceptorTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateWrapInterceptorTest.java
deleted file mode 100644
index fd7d99e08..000000000
--- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/instrument/resttemplate/EnhancedRestTemplateWrapInterceptorTest.java
+++ /dev/null
@@ -1,302 +0,0 @@
-/*
- * Tencent is pleased to support the open source community by making spring-cloud-tencent available.
- *
- * Copyright (C) 2021 Tencent. 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.rpc.enhancement.instrument.resttemplate;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
-
-import com.tencent.cloud.common.constant.ContextConstant;
-import com.tencent.cloud.common.metadata.MetadataContextHolder;
-import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
-import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginRunner;
-import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
-import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;
-import com.tencent.polaris.metadata.core.MetadataType;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.junit.jupiter.MockitoExtension;
-
-import org.springframework.cloud.client.ServiceInstance;
-import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
-import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest;
-import org.springframework.cloud.client.loadbalancer.ServiceRequestWrapper;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.HttpRequest;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.client.ClientHttpResponse;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-/**
- * Test for {@link EnhancedRestTemplateWrapInterceptor}.
- *
- * @author Shedfree Wu
- */
-@ExtendWith(MockitoExtension.class)
-class EnhancedRestTemplateWrapInterceptorTest {
-
- @Mock
- private EnhancedPluginRunner pluginRunner;
-
- @Mock
- private LoadBalancerClient delegate;
-
- @Mock
- private HttpRequest request;
-
- @Mock
- private ClientHttpResponse response;
-
- @Mock
- private ServiceRequestWrapper serviceRequestWrapper;
-
- @Mock
- private ServiceInstance localServiceInstance;
-
- private EnhancedRestTemplateWrapInterceptor interceptor;
-
- @BeforeEach
- void setUp() {
- MockitoAnnotations.openMocks(this);
- interceptor = new EnhancedRestTemplateWrapInterceptor(pluginRunner, delegate);
- }
-
- @Test
- void testInterceptWithNormalRequest() throws IOException {
- // Arrange
- URI uri = URI.create("http://test-service/api");
- HttpHeaders headers = new HttpHeaders();
- headers.add("test-header", "test-value");
-
- when(request.getURI()).thenReturn(uri);
- when(request.getHeaders()).thenReturn(headers);
- when(request.getMethod()).thenReturn(HttpMethod.GET);
- when(pluginRunner.getLocalServiceInstance()).thenReturn(localServiceInstance);
- when(delegate.execute(any(), any())).thenReturn(response);
- when(response.getRawStatusCode()).thenReturn(200);
- when(response.getHeaders()).thenReturn(new HttpHeaders());
-
- // Act
- interceptor.intercept(request, "test-service", mock(LoadBalancerRequest.class));
-
- // Assert
- verify(pluginRunner).run(eq(EnhancedPluginType.Client.PRE), any(EnhancedPluginContext.class));
-
- // Verify context setup
- ArgumentCaptor contextCaptor = ArgumentCaptor.forClass(EnhancedPluginContext.class);
- verify(pluginRunner).run(eq(EnhancedPluginType.Client.PRE), contextCaptor.capture());
-
- EnhancedPluginContext capturedContext = contextCaptor.getValue();
- Assertions.assertEquals(uri, capturedContext.getRequest().getUrl());
- Assertions.assertEquals(uri, capturedContext.getRequest().getServiceUrl());
- Assertions.assertEquals(headers, capturedContext.getRequest().getHttpHeaders());
- Assertions.assertEquals(HttpMethod.GET, capturedContext.getRequest().getHttpMethod());
- Assertions.assertEquals(localServiceInstance, capturedContext.getLocalServiceInstance());
- }
-
- @Test
- void testInterceptWithServiceRequestWrapper() throws IOException {
- // Arrange
- URI originalUri = URI.create("http://original-service/api");
- URI wrappedUri = URI.create("http://wrapped-service/api");
- HttpHeaders headers = new HttpHeaders();
-
- when(serviceRequestWrapper.getURI()).thenReturn(wrappedUri);
- when(serviceRequestWrapper.getRequest()).thenReturn(request);
- when(serviceRequestWrapper.getHeaders()).thenReturn(headers);
- when(serviceRequestWrapper.getMethod()).thenReturn(HttpMethod.POST);
- when(request.getURI()).thenReturn(originalUri);
- when(pluginRunner.getLocalServiceInstance()).thenReturn(localServiceInstance);
- when(delegate.execute(any(), any())).thenReturn(response);
- when(response.getRawStatusCode()).thenReturn(200);
- when(response.getHeaders()).thenReturn(new HttpHeaders());
-
- // Act
- interceptor.intercept(serviceRequestWrapper, "test-service", mock(LoadBalancerRequest.class));
-
- // Assert
- verify(pluginRunner).run(eq(EnhancedPluginType.Client.PRE), any(EnhancedPluginContext.class));
-
- ArgumentCaptor contextCaptor = ArgumentCaptor.forClass(EnhancedPluginContext.class);
- verify(pluginRunner).run(eq(EnhancedPluginType.Client.PRE), contextCaptor.capture());
-
- EnhancedPluginContext capturedContext = contextCaptor.getValue();
- Assertions.assertEquals(wrappedUri, capturedContext.getRequest().getUrl());
- Assertions.assertEquals(originalUri, capturedContext.getRequest().getServiceUrl());
- Assertions.assertEquals(serviceRequestWrapper, capturedContext.getOriginRequest());
- }
-
- @Test
- void testInterceptWithFallback() throws IOException {
- // Arrange
- URI originalUri = URI.create("http://original-service/api");
- URI wrappedUri = URI.create("http://wrapped-service/api");
- HttpHeaders headers = new HttpHeaders();
-
- CallAbortedException abortedException = new CallAbortedException("test-error", null);
-
- when(serviceRequestWrapper.getURI()).thenReturn(wrappedUri);
- when(serviceRequestWrapper.getRequest()).thenReturn(request);
- when(serviceRequestWrapper.getHeaders()).thenReturn(headers);
- when(serviceRequestWrapper.getMethod()).thenReturn(HttpMethod.POST);
- when(request.getURI()).thenReturn(originalUri);
- when(pluginRunner.getLocalServiceInstance()).thenReturn(localServiceInstance);
- doThrow(abortedException)
- .when(pluginRunner)
- .run(eq(EnhancedPluginType.Client.PRE), any(EnhancedPluginContext.class));
-
- Object fallbackResponse = new MockClientHttpResponse();
- MetadataContextHolder.get().getMetadataContainer(MetadataType.APPLICATION, true).
- putMetadataObjectValue(ContextConstant.CircuitBreaker.CIRCUIT_BREAKER_FALLBACK_HTTP_RESPONSE, fallbackResponse);
-
- // Act
- interceptor.intercept(serviceRequestWrapper, "test-service", mock(LoadBalancerRequest.class));
-
- // Assert
- verify(pluginRunner).run(eq(EnhancedPluginType.Client.PRE), any(EnhancedPluginContext.class));
-
- ArgumentCaptor contextCaptor = ArgumentCaptor.forClass(EnhancedPluginContext.class);
- verify(pluginRunner).run(eq(EnhancedPluginType.Client.PRE), contextCaptor.capture());
-
- EnhancedPluginContext capturedContext = contextCaptor.getValue();
- Assertions.assertEquals(wrappedUri, capturedContext.getRequest().getUrl());
- Assertions.assertEquals(originalUri, capturedContext.getRequest().getServiceUrl());
- Assertions.assertEquals(serviceRequestWrapper, capturedContext.getOriginRequest());
- }
-
- @Test
- void testInterceptWithNoFallback() {
- // Arrange
- URI originalUri = URI.create("http://original-service/api");
- URI wrappedUri = URI.create("http://wrapped-service/api");
- HttpHeaders headers = new HttpHeaders();
-
- CallAbortedException abortedException = new CallAbortedException("test-error", null);
-
- when(serviceRequestWrapper.getURI()).thenReturn(wrappedUri);
- when(serviceRequestWrapper.getRequest()).thenReturn(request);
- when(serviceRequestWrapper.getHeaders()).thenReturn(headers);
- when(serviceRequestWrapper.getMethod()).thenReturn(HttpMethod.POST);
- when(request.getURI()).thenReturn(originalUri);
- when(pluginRunner.getLocalServiceInstance()).thenReturn(localServiceInstance);
- doThrow(abortedException)
- .when(pluginRunner)
- .run(any(), any());
- // Act
- Assertions.assertThrows(CallAbortedException.class, () -> {
- interceptor.intercept(serviceRequestWrapper, "test-service", mock(LoadBalancerRequest.class));
- });
- }
-
- @Test
- void testInterceptWithNullLocalServiceInstance() throws IOException {
- // Arrange
- URI uri = URI.create("http://test-service/api");
- when(request.getURI()).thenReturn(uri);
- when(request.getHeaders()).thenReturn(new HttpHeaders());
- when(request.getMethod()).thenReturn(HttpMethod.GET);
- when(pluginRunner.getLocalServiceInstance()).thenReturn(null);
- when(delegate.execute(any(), any())).thenReturn(response);
- when(response.getRawStatusCode()).thenReturn(200);
- when(response.getHeaders()).thenReturn(new HttpHeaders());
-
- // Act
- interceptor.intercept(request, "test-service", mock(LoadBalancerRequest.class));
-
- // Assert
- verify(pluginRunner).run(eq(EnhancedPluginType.Client.PRE), any(EnhancedPluginContext.class));
-
- ArgumentCaptor contextCaptor = ArgumentCaptor.forClass(EnhancedPluginContext.class);
- verify(pluginRunner).run(eq(EnhancedPluginType.Client.PRE), contextCaptor.capture());
-
- EnhancedPluginContext capturedContext = contextCaptor.getValue();
- assertThat(capturedContext.getLocalServiceInstance()).isNull();
- }
-
- @Test
- void testExceptionHandling() throws IOException {
- // Arrange
- LoadBalancerRequest loadBalancerRequest = mock(LoadBalancerRequest.class);
- IOException expectedException = new IOException("Test exception");
- when(delegate.execute(anyString(), any(LoadBalancerRequest.class)))
- .thenThrow(expectedException);
-
- // Act & Assert
- Exception actualException = Assertions.assertThrows(IOException.class, () -> {
- interceptor.intercept(request, "test-service", loadBalancerRequest);
- });
-
- // Verify exception handling
- verify(pluginRunner, times(1))
- .run(eq(EnhancedPluginType.Client.EXCEPTION), any(EnhancedPluginContext.class));
-
- // Verify finally block is executed
- verify(pluginRunner, times(1))
- .run(eq(EnhancedPluginType.Client.FINALLY), any(EnhancedPluginContext.class));
-
- // Verify the thrown exception is the same
- Assertions.assertEquals(expectedException, actualException);
- }
-
- static class MockClientHttpResponse implements ClientHttpResponse {
- @Override
- public HttpStatus getStatusCode() throws IOException {
- return null;
- }
-
- @Override
- public int getRawStatusCode() throws IOException {
- return 0;
- }
-
- @Override
- public String getStatusText() throws IOException {
- return null;
- }
-
- @Override
- public void close() {
-
- }
-
- @Override
- public InputStream getBody() throws IOException {
- return null;
- }
-
- @Override
- public HttpHeaders getHeaders() {
- return null;
- }
- }
-}