feat:support smooth upgrade from tsf. (#1482)

pull/1483/head
Haotian Zhang 1 week ago committed by GitHub
parent 01d24732af
commit 6992011ed5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -41,3 +41,4 @@
- [feat:support concurrency rate limit.](https://github.com/Tencent/spring-cloud-tencent/pull/1455)
- [feat:support auth.](https://github.com/Tencent/spring-cloud-tencent/pull/1479)
- [feat:upgrade trace plugin.](https://github.com/Tencent/spring-cloud-tencent/pull/1480)
- [feat:support smooth upgrade from tsf.](https://github.com/Tencent/spring-cloud-tencent/pull/1482)

@ -59,6 +59,17 @@
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-auth</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-fault-tolerance</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>

@ -15,11 +15,6 @@
<dependencies>
<!-- Spring Cloud Tencent dependencies start -->
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-commons</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-rpc-enhancement</artifactId>
@ -56,7 +51,7 @@
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>polaris-test-mock-discovery</artifactId>

@ -23,8 +23,8 @@ import java.util.Map;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.rpc.enhancement.instrument.resttemplate.EnhancedRestTemplateInterceptor;
import com.tencent.cloud.rpc.enhancement.plugin.DefaultEnhancedPluginRunner;
import com.tencent.cloud.rpc.enhancement.resttemplate.EnhancedRestTemplateInterceptor;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

@ -27,8 +27,8 @@ import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.metadata.StaticMetadataManager;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.rpc.enhancement.instrument.scg.EnhancedGatewayGlobalFilter;
import com.tencent.cloud.rpc.enhancement.plugin.DefaultEnhancedPluginRunner;
import com.tencent.cloud.rpc.enhancement.scg.EnhancedGatewayGlobalFilter;
import org.assertj.core.util.Maps;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;

@ -0,0 +1,40 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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 org.springframework.tsf.auth.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Empty annotation. Compatible with old versions TSF SDK.
*
* @author Haotian Zhang
*/
@Deprecated(since = "2.0.0.0")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface EnableTsfAuth {
}

@ -0,0 +1,45 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.auth.config;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link PolarisAuthProperties}.
*
* @author Haotian Zhang
*/
public class PolarisAuthPropertiesTest {
private final ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(PolarisAuthPropertiesAutoConfiguration.class))
.withPropertyValues("spring.cloud.polaris.auth.enabled=false");
@Test
public void testGetAndSet() {
this.applicationContextRunner.run(context -> {
PolarisAuthProperties properties = context.getBean(PolarisAuthProperties.class);
assertThat(properties.isEnabled()).isFalse();
});
}
}

@ -21,7 +21,7 @@ import java.util.function.Function;
import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder;
import com.tencent.cloud.polaris.circuitbreaker.common.PolarisResultToErrorCode;
import com.tencent.cloud.polaris.circuitbreaker.reactor.PolarisCircuitBreakerReactorTransformer;
import com.tencent.cloud.polaris.circuitbreaker.instrument.reactor.PolarisCircuitBreakerReactorTransformer;
import com.tencent.cloud.polaris.circuitbreaker.util.PolarisCircuitBreakerUtils;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.pojo.ServiceKey;

@ -18,7 +18,7 @@
package com.tencent.cloud.polaris.circuitbreaker.config;
import com.tencent.cloud.polaris.circuitbreaker.ReactivePolarisCircuitBreakerFactory;
import com.tencent.cloud.polaris.circuitbreaker.gateway.PolarisCircuitBreakerFilterFactory;
import com.tencent.cloud.polaris.circuitbreaker.instrument.gateway.PolarisCircuitBreakerFilterFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
@ -44,8 +44,8 @@ import org.springframework.web.reactive.DispatcherHandler;
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
@AutoConfigureAfter({ReactivePolarisCircuitBreakerAutoConfiguration.class })
@ConditionalOnClass({ DispatcherHandler.class, ReactivePolarisCircuitBreakerAutoConfiguration.class,
@AutoConfigureAfter({ReactivePolarisCircuitBreakerAutoConfiguration.class})
@ConditionalOnClass({DispatcherHandler.class, ReactivePolarisCircuitBreakerAutoConfiguration.class,
ReactiveCircuitBreakerFactory.class, ReactivePolarisCircuitBreakerFactory.class, GatewayAutoConfiguration.class})
public class GatewayPolarisCircuitBreakerAutoConfiguration {

@ -22,9 +22,9 @@ import java.util.List;
import com.tencent.cloud.polaris.circuitbreaker.PolarisCircuitBreakerFactory;
import com.tencent.cloud.polaris.circuitbreaker.common.CircuitBreakerConfigModifier;
import com.tencent.cloud.polaris.circuitbreaker.instrument.resttemplate.PolarisCircuitBreakerRestTemplateBeanPostProcessor;
import com.tencent.cloud.polaris.circuitbreaker.reporter.ExceptionCircuitBreakerReporter;
import com.tencent.cloud.polaris.circuitbreaker.reporter.SuccessCircuitBreakerReporter;
import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreakerRestTemplateBeanPostProcessor;
import com.tencent.cloud.polaris.context.PolarisSDKContextManager;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;

@ -17,9 +17,9 @@
package com.tencent.cloud.polaris.circuitbreaker.config;
import com.tencent.cloud.polaris.circuitbreaker.feign.PolarisCircuitBreakerNameResolver;
import com.tencent.cloud.polaris.circuitbreaker.feign.PolarisFeignCircuitBreaker;
import com.tencent.cloud.polaris.circuitbreaker.feign.PolarisFeignCircuitBreakerTargeter;
import com.tencent.cloud.polaris.circuitbreaker.instrument.feign.PolarisCircuitBreakerNameResolver;
import com.tencent.cloud.polaris.circuitbreaker.instrument.feign.PolarisFeignCircuitBreaker;
import com.tencent.cloud.polaris.circuitbreaker.instrument.feign.PolarisFeignCircuitBreakerTargeter;
import feign.Feign;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@ -39,7 +39,7 @@ import org.springframework.context.annotation.Scope;
* @author seansyyu 2023-02-28
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Feign.class, FeignClientFactoryBean.class })
@ConditionalOnClass({Feign.class, FeignClientFactoryBean.class})
@ConditionalOnPolarisCircuitBreakerEnabled
public class PolarisCircuitBreakerFeignClientAutoConfiguration {

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.feign;
package com.tencent.cloud.polaris.circuitbreaker.instrument.feign;
import java.io.IOException;
import java.lang.reflect.Method;

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.feign;
package com.tencent.cloud.polaris.circuitbreaker.instrument.feign;
import java.lang.reflect.Method;
import java.net.URI;

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.feign;
package com.tencent.cloud.polaris.circuitbreaker.instrument.feign;
import feign.Feign;
import feign.Target;
@ -47,15 +47,13 @@ public final class PolarisFeignCircuitBreaker {
*/
public static final class Builder extends Feign.Builder {
public Builder() {
}
private CircuitBreakerFactory circuitBreakerFactory;
private String feignClientName;
private CircuitBreakerNameResolver circuitBreakerNameResolver;
public Builder() {
}
public PolarisFeignCircuitBreaker.Builder circuitBreakerFactory(CircuitBreakerFactory circuitBreakerFactory) {
this.circuitBreakerFactory = circuitBreakerFactory;
return this;

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.feign;
package com.tencent.cloud.polaris.circuitbreaker.instrument.feign;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
@ -77,6 +77,24 @@ public class PolarisFeignCircuitBreakerInvocationHandler implements InvocationHa
this.decoder = decoder;
}
/**
* If the method param of {@link InvocationHandler#invoke(Object, Method, Object[])}
* is not accessible, i.e in a package-private interface, the fallback call will cause
* of access restrictions. But methods in dispatch are copied methods. So setting
* access to dispatch method doesn't take effect to the method in
* InvocationHandler.invoke. Use map to store a copy of method to invoke the fallback
* to bypass this and reducing the count of reflection calls.
* @return cached methods map for fallback invoking
*/
static Map<Method, Method> toFallbackMethod(Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
Map<Method, Method> result = new LinkedHashMap<>();
for (Method method : dispatch.keySet()) {
method.setAccessible(true);
result.put(method, method);
}
return result;
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
// early exit if the invoked method is from java.lang.Object
@ -167,24 +185,6 @@ public class PolarisFeignCircuitBreakerInvocationHandler implements InvocationHa
};
}
/**
* If the method param of {@link InvocationHandler#invoke(Object, Method, Object[])}
* is not accessible, i.e in a package-private interface, the fallback call will cause
* of access restrictions. But methods in dispatch are copied methods. So setting
* access to dispatch method doesn't take effect to the method in
* InvocationHandler.invoke. Use map to store a copy of method to invoke the fallback
* to bypass this and reducing the count of reflection calls.
* @return cached methods map for fallback invoking
*/
static Map<Method, Method> toFallbackMethod(Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
Map<Method, Method> result = new LinkedHashMap<>();
for (Method method : dispatch.keySet()) {
method.setAccessible(true);
result.put(method, method);
}
return result;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof PolarisFeignCircuitBreakerInvocationHandler) {

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.feign;
package com.tencent.cloud.polaris.circuitbreaker.instrument.feign;
import feign.Feign;
import feign.Target;

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.gateway;
package com.tencent.cloud.polaris.circuitbreaker.instrument.gateway;
import java.net.URI;

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.reactor;
package com.tencent.cloud.polaris.circuitbreaker.instrument.reactor;
import com.tencent.polaris.circuitbreak.api.InvokeHandler;
import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.reactor;
package com.tencent.cloud.polaris.circuitbreaker.instrument.reactor;
import com.tencent.polaris.circuitbreak.api.InvokeHandler;
import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.reactor;
package com.tencent.cloud.polaris.circuitbreaker.instrument.reactor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.reactor;
package com.tencent.cloud.polaris.circuitbreaker.instrument.reactor;
import java.util.function.Function;
@ -46,7 +46,8 @@ public class PolarisCircuitBreakerReactorTransformer<T> implements Function<Publ
return new PolarisCircuitBreakerFluxOperator<>((Flux<? extends T>) publisher, invokeHandler);
}
else {
throw new IllegalStateException("Publisher type is not supported: " + publisher.getClass().getCanonicalName());
throw new IllegalStateException("Publisher type is not supported: " + publisher.getClass()
.getCanonicalName());
}
}

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.resttemplate;
package com.tencent.cloud.polaris.circuitbreaker.instrument.resttemplate;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
@ -31,9 +31,10 @@ import java.lang.annotation.Target;
*
* @author sean yu
*/
@Target({ ElementType.METHOD })
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Deprecated(since = "2.0.0.0")
public @interface PolarisCircuitBreaker {
/**

@ -15,13 +15,14 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.resttemplate;
package com.tencent.cloud.polaris.circuitbreaker.instrument.resttemplate;
/**
* PolarisCircuitBreakerFallback.
*
* @author sean yu
*/
@Deprecated(since = "2.0.0.0")
public interface PolarisCircuitBreakerFallback {
PolarisCircuitBreakerHttpResponse fallback();

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.resttemplate;
package com.tencent.cloud.polaris.circuitbreaker.instrument.resttemplate;
import java.io.ByteArrayInputStream;
import java.io.IOException;

@ -15,9 +15,11 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.resttemplate;
package com.tencent.cloud.polaris.circuitbreaker.instrument.resttemplate;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
@ -26,9 +28,9 @@ import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.ApplicationContext;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.StandardMethodMetadata;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
@ -40,56 +42,27 @@ import org.springframework.web.client.RestTemplate;
public class PolarisCircuitBreakerRestTemplateBeanPostProcessor implements MergedBeanDefinitionPostProcessor {
private final ApplicationContext applicationContext;
private final Set<String> cache = Collections.synchronizedSet(new HashSet<>());
public PolarisCircuitBreakerRestTemplateBeanPostProcessor(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
private final ConcurrentHashMap<String, PolarisCircuitBreaker> cache = new ConcurrentHashMap<>();
private void checkPolarisCircuitBreakerRestTemplate(PolarisCircuitBreaker polarisCircuitBreaker) {
if (
StringUtils.hasText(polarisCircuitBreaker.fallback()) &&
!PolarisCircuitBreakerFallback.class.toGenericString().equals(polarisCircuitBreaker.fallbackClass().toGenericString())
) {
throw new IllegalArgumentException("PolarisCircuitBreaker's fallback and fallbackClass could not set at sametime !");
}
}
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
if (checkAnnotated(beanDefinition, beanType, beanName)) {
PolarisCircuitBreaker polarisCircuitBreaker;
if (beanDefinition.getSource() instanceof StandardMethodMetadata) {
polarisCircuitBreaker = ((StandardMethodMetadata) beanDefinition.getSource()).getIntrospectedMethod()
.getAnnotation(PolarisCircuitBreaker.class);
}
else {
polarisCircuitBreaker = beanDefinition.getResolvedFactoryMethod()
.getAnnotation(PolarisCircuitBreaker.class);
}
checkPolarisCircuitBreakerRestTemplate(polarisCircuitBreaker);
cache.put(beanName, polarisCircuitBreaker);
cache.add(beanName);
}
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (cache.containsKey(beanName)) {
// add interceptor for each RestTemplate with @PolarisCircuitBreaker annotation
StringBuilder interceptorBeanNamePrefix = new StringBuilder();
PolarisCircuitBreaker polarisCircuitBreaker = cache.get(beanName);
interceptorBeanNamePrefix
.append(StringUtils.uncapitalize(
PolarisCircuitBreaker.class.getSimpleName()))
.append("_")
.append(polarisCircuitBreaker.fallback())
.append("_")
.append(polarisCircuitBreaker.fallbackClass().getSimpleName());
if (cache.contains(beanName)) {
String interceptorBeanNamePrefix = StringUtils.uncapitalize("PolarisCircuitBreaker");
RestTemplate restTemplate = (RestTemplate) bean;
String interceptorBeanName = interceptorBeanNamePrefix + "@" + bean;
CircuitBreakerFactory circuitBreakerFactory = this.applicationContext.getBean(CircuitBreakerFactory.class);
registerBean(interceptorBeanName, polarisCircuitBreaker, applicationContext, circuitBreakerFactory, restTemplate);
registerBean(interceptorBeanName, applicationContext, circuitBreakerFactory, restTemplate);
PolarisCircuitBreakerRestTemplateInterceptor polarisCircuitBreakerRestTemplateInterceptor = applicationContext
.getBean(interceptorBeanName, PolarisCircuitBreakerRestTemplateInterceptor.class);
restTemplate.getInterceptors().add(0, polarisCircuitBreakerRestTemplateInterceptor);
@ -102,18 +75,16 @@ public class PolarisCircuitBreakerRestTemplateBeanPostProcessor implements Merge
return beanName != null && beanType == RestTemplate.class
&& beanDefinition.getSource() instanceof MethodMetadata
&& ((MethodMetadata) beanDefinition.getSource())
.isAnnotated(PolarisCircuitBreaker.class.getName());
.isAnnotated(LoadBalanced.class.getName());
}
private void registerBean(String interceptorBeanName, PolarisCircuitBreaker polarisCircuitBreaker,
ApplicationContext applicationContext, CircuitBreakerFactory circuitBreakerFactory, RestTemplate restTemplate) {
private void registerBean(String interceptorBeanName, ApplicationContext applicationContext,
CircuitBreakerFactory circuitBreakerFactory, RestTemplate restTemplate) {
// register PolarisCircuitBreakerRestTemplateInterceptor bean
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext
.getAutowireCapableBeanFactory();
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
.genericBeanDefinition(PolarisCircuitBreakerRestTemplateInterceptor.class);
beanDefinitionBuilder.addConstructorArgValue(polarisCircuitBreaker);
beanDefinitionBuilder.addConstructorArgValue(applicationContext);
beanDefinitionBuilder.addConstructorArgValue(circuitBreakerFactory);
beanDefinitionBuilder.addConstructorArgValue(restTemplate);
BeanDefinition interceptorBeanDefinition = beanDefinitionBuilder

@ -15,10 +15,9 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.resttemplate;
package com.tencent.cloud.polaris.circuitbreaker.instrument.resttemplate;
import java.io.IOException;
import java.lang.reflect.Method;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.polaris.circuitbreaker.exception.FallbackWrapperException;
@ -26,13 +25,10 @@ import com.tencent.polaris.api.pojo.CircuitBreakerStatus;
import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
@ -43,22 +39,14 @@ import org.springframework.web.client.RestTemplate;
*/
public class PolarisCircuitBreakerRestTemplateInterceptor implements ClientHttpRequestInterceptor {
private final PolarisCircuitBreaker polarisCircuitBreaker;
private final ApplicationContext applicationContext;
private final CircuitBreakerFactory circuitBreakerFactory;
private final RestTemplate restTemplate;
public PolarisCircuitBreakerRestTemplateInterceptor(
PolarisCircuitBreaker polarisCircuitBreaker,
ApplicationContext applicationContext,
CircuitBreakerFactory circuitBreakerFactory,
RestTemplate restTemplate
) {
this.polarisCircuitBreaker = polarisCircuitBreaker;
this.applicationContext = applicationContext;
this.circuitBreakerFactory = circuitBreakerFactory;
this.restTemplate = restTemplate;
}
@ -82,16 +70,6 @@ public class PolarisCircuitBreakerRestTemplateInterceptor implements ClientHttpR
}
},
t -> {
if (StringUtils.hasText(polarisCircuitBreaker.fallback())) {
CircuitBreakerStatus.FallbackInfo fallbackInfo = new CircuitBreakerStatus.FallbackInfo(200, null, polarisCircuitBreaker.fallback());
return new PolarisCircuitBreakerHttpResponse(fallbackInfo);
}
if (!PolarisCircuitBreakerFallback.class.toGenericString()
.equals(polarisCircuitBreaker.fallbackClass().toGenericString())) {
Method method = ReflectionUtils.findMethod(PolarisCircuitBreakerFallback.class, "fallback");
PolarisCircuitBreakerFallback polarisCircuitBreakerFallback = applicationContext.getBean(polarisCircuitBreaker.fallbackClass());
return (PolarisCircuitBreakerHttpResponse) ReflectionUtils.invokeMethod(method, polarisCircuitBreakerFallback);
}
if (t instanceof CallAbortedException) {
CircuitBreakerStatus.FallbackInfo fallbackInfo = ((CallAbortedException) t).getFallbackInfo();
if (fallbackInfo != null) {

@ -0,0 +1,38 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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 org.springframework.cloud.tsf.circuitbreaker.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Empty annotation. Compatible with old versions TSF SDK.
*
* @author Haotian Zhang
*/
@Deprecated(since = "2.0.0.0")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface EnableTsfCircuitBreaker {
}

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.feign;
package com.tencent.cloud.polaris.circuitbreaker.instrument.feign;
import java.io.BufferedReader;

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.feign;
package com.tencent.cloud.polaris.circuitbreaker.instrument.feign;
import java.lang.reflect.Method;

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.feign;
package com.tencent.cloud.polaris.circuitbreaker.instrument.feign;
import feign.Feign;
import feign.RequestLine;

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.gateway;
package com.tencent.cloud.polaris.circuitbreaker.instrument.gateway;
import java.io.BufferedReader;

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.circuitbreaker.resttemplate;
package com.tencent.cloud.polaris.circuitbreaker.instrument.resttemplate;
import java.io.BufferedReader;
import java.io.InputStream;
@ -23,14 +23,12 @@ import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.stream.Collectors;
import com.google.protobuf.util.JsonFormat;
import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerFeignClientAutoConfiguration;
import com.tencent.cloud.polaris.context.PolarisSDKContextManager;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.client.util.Utils;
import com.tencent.polaris.specification.api.v1.fault.tolerance.CircuitBreakerProto;
import com.tencent.polaris.test.mock.discovery.NamingServer;
import org.junit.jupiter.api.AfterAll;
@ -45,7 +43,6 @@ import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
@ -57,6 +54,7 @@ import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.DefaultUriBuilderFactory;
@ -95,25 +93,6 @@ public class PolarisCircuitBreakerRestTemplateIntegrationTest {
@Qualifier("restTemplateFallbackFromPolaris")
private RestTemplate restTemplateFallbackFromPolaris;
@Autowired
@Qualifier("restTemplateFallbackFromCode")
private RestTemplate restTemplateFallbackFromCode;
@Autowired
@Qualifier("restTemplateFallbackFromCode2")
private RestTemplate restTemplateFallbackFromCode2;
@Autowired
@Qualifier("restTemplateFallbackFromCode3")
private RestTemplate restTemplateFallbackFromCode3;
@Autowired
@Qualifier("restTemplateFallbackFromCode4")
private RestTemplate restTemplateFallbackFromCode4;
@Autowired
private ApplicationContext applicationContext;
@BeforeAll
static void beforeAll() throws Exception {
PolarisSDKContextManager.innerDestroy();
@ -153,26 +132,15 @@ public class PolarisCircuitBreakerRestTemplateIntegrationTest {
.expect(ExpectedCount.once(), requestTo(new URI("http://localhost:18001/example/service/b/info")))
.andExpect(method(HttpMethod.GET))
.andRespond(withStatus(HttpStatus.BAD_GATEWAY).headers(headers).body("BAD_GATEWAY"));
assertThat(defaultRestTemplate.getForObject("http://localhost:18001/example/service/b/info", String.class)).isEqualTo("fallback");
assertThatThrownBy(() -> {
defaultRestTemplate.getForObject("http://localhost:18001/example/service/b/info", String.class);
}).isInstanceOf(HttpServerErrorException.class);
mockServer.verify();
mockServer.reset();
assertThatThrownBy(() -> {
restTemplateFallbackFromPolaris.getForObject("/example/service/b/info", String.class);
}).isInstanceOf(IllegalStateException.class);
assertThat(restTemplateFallbackFromCode.getForObject("/example/service/b/info", String.class)).isEqualTo("\"this is a fallback class\"");
Utils.sleepUninterrupted(2000);
assertThat(restTemplateFallbackFromCode2.getForObject("/example/service/b/info", String.class)).isEqualTo("\"this is a fallback class\"");
Utils.sleepUninterrupted(2000);
assertThat(restTemplateFallbackFromCode3.getForEntity("/example/service/b/info", String.class)
.getStatusCode()).isEqualTo(HttpStatus.OK);
Utils.sleepUninterrupted(2000);
assertThat(restTemplateFallbackFromCode4.getForObject("/example/service/b/info", String.class)).isEqualTo("fallback");
Utils.sleepUninterrupted(2000);
assertThat(restTemplateFallbackFromPolaris.getForObject("/example/service/b/info", String.class)).isEqualTo("\"fallback from polaris server\"");
// just for code coverage
PolarisCircuitBreakerHttpResponse response = ((CustomPolarisCircuitBreakerFallback) applicationContext.getBean("customPolarisCircuitBreakerFallback")).fallback();
assertThat(response.getStatusText()).isEqualTo("OK");
assertThat(response.getFallbackInfo().getCode()).isEqualTo(200);
}
@Configuration
@ -182,14 +150,12 @@ public class PolarisCircuitBreakerRestTemplateIntegrationTest {
public static class TestConfig {
@Bean
@com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreaker(fallback = "fallback")
public RestTemplate defaultRestTemplate() {
return new RestTemplate();
}
@Bean
@LoadBalanced
@com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreaker
public RestTemplate restTemplateFallbackFromPolaris() {
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("http://" + TEST_SERVICE_NAME);
RestTemplate restTemplate = new RestTemplate();
@ -197,61 +163,6 @@ public class PolarisCircuitBreakerRestTemplateIntegrationTest {
return restTemplate;
}
@Bean
@LoadBalanced
@com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreaker(fallbackClass = CustomPolarisCircuitBreakerFallback.class)
public RestTemplate restTemplateFallbackFromCode() {
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("http://" + TEST_SERVICE_NAME);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(uriBuilderFactory);
return restTemplate;
}
@Bean
@LoadBalanced
@com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreaker(fallbackClass = CustomPolarisCircuitBreakerFallback2.class)
public RestTemplate restTemplateFallbackFromCode2() {
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("http://" + TEST_SERVICE_NAME);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(uriBuilderFactory);
return restTemplate;
}
@Bean
@LoadBalanced
@com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreaker(fallbackClass = CustomPolarisCircuitBreakerFallback3.class)
public RestTemplate restTemplateFallbackFromCode3() {
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("http://" + TEST_SERVICE_NAME);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(uriBuilderFactory);
return restTemplate;
}
@Bean
@LoadBalanced
@PolarisCircuitBreaker(fallback = "fallback")
public RestTemplate restTemplateFallbackFromCode4() {
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("http://" + TEST_SERVICE_NAME);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(uriBuilderFactory);
return restTemplate;
}
@Bean
public CustomPolarisCircuitBreakerFallback customPolarisCircuitBreakerFallback() {
return new CustomPolarisCircuitBreakerFallback();
}
@Bean
public CustomPolarisCircuitBreakerFallback2 customPolarisCircuitBreakerFallback2() {
return new CustomPolarisCircuitBreakerFallback2();
}
@Bean
public CustomPolarisCircuitBreakerFallback3 customPolarisCircuitBreakerFallback3() {
return new CustomPolarisCircuitBreakerFallback3();
}
@RestController
@RequestMapping("/example/service/b")
public class ServiceBController {
@ -269,35 +180,4 @@ public class PolarisCircuitBreakerRestTemplateIntegrationTest {
}
}
public static class CustomPolarisCircuitBreakerFallback implements PolarisCircuitBreakerFallback {
@Override
public PolarisCircuitBreakerHttpResponse fallback() {
return new PolarisCircuitBreakerHttpResponse(
200,
new HashMap<String, String>() {{
put("xxx", "xxx");
}},
"\"this is a fallback class\"");
}
}
public static class CustomPolarisCircuitBreakerFallback2 implements PolarisCircuitBreakerFallback {
@Override
public PolarisCircuitBreakerHttpResponse fallback() {
return new PolarisCircuitBreakerHttpResponse(
200,
"\"this is a fallback class\""
);
}
}
public static class CustomPolarisCircuitBreakerFallback3 implements PolarisCircuitBreakerFallback {
@Override
public PolarisCircuitBreakerHttpResponse fallback() {
return new PolarisCircuitBreakerHttpResponse(
200
);
}
}
}

@ -57,7 +57,7 @@ import org.springframework.util.CollectionUtils;
*/
public class PolarisContractReporter implements ApplicationListener<ApplicationReadyEvent> {
private final Logger LOG = LoggerFactory.getLogger(PolarisContractReporter.class);
private static final Logger LOG = LoggerFactory.getLogger(PolarisContractReporter.class);
private final org.springdoc.webmvc.api.MultipleOpenApiResource multipleOpenApiWebMvcResource;
private final org.springdoc.webflux.api.MultipleOpenApiResource multipleOpenApiWebFluxResource;

@ -38,13 +38,13 @@ import org.springframework.util.StringUtils;
public class TsfApiMetadataGrapher implements SmartLifecycle {
private static final Logger logger = LoggerFactory.getLogger(TsfApiMetadataGrapher.class);
private final AtomicBoolean isRunning = new AtomicBoolean(false);
private final org.springdoc.webmvc.api.MultipleOpenApiResource multipleOpenApiWebMvcResource;
private final org.springdoc.webflux.api.MultipleOpenApiResource multipleOpenApiWebFluxResource;
private final ObjectMapperProvider springdocObjectMapperProvider;
private Logger logger = LoggerFactory.getLogger(TsfApiMetadataGrapher.class);
private ApplicationContext applicationContext;
private String groupName;
private final ApplicationContext applicationContext;
private final String groupName;
public TsfApiMetadataGrapher(org.springdoc.webmvc.api.MultipleOpenApiResource multipleOpenApiWebMvcResource,
org.springdoc.webflux.api.MultipleOpenApiResource multipleOpenApiWebFluxResource,

@ -17,8 +17,8 @@
package com.tencent.cloud.polaris.discovery.refresh;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import com.tencent.cloud.polaris.context.PolarisSDKContextManager;
import com.tencent.cloud.polaris.discovery.ConditionalOnPolarisDiscoveryEnabled;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
@ -30,13 +30,19 @@ import org.springframework.context.annotation.Configuration;
* @author Haotian Zhang
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnPolarisEnabled
@ConditionalOnPolarisDiscoveryEnabled
public class PolarisRefreshConfiguration {
@Bean
@ConditionalOnMissingBean
public PolarisServiceStatusChangeListener polarisServiceChangeListener() {
return new PolarisServiceStatusChangeListener();
public PolarisServiceStatusChangeListener polarisServiceChangeListener(ServiceInstanceChangeCallbackManager serviceInstanceChangeCallbackManager) {
return new PolarisServiceStatusChangeListener(serviceInstanceChangeCallbackManager);
}
@Bean
@ConditionalOnMissingBean
public ServiceInstanceChangeCallbackManager serviceInstanceChangeCallbackManager() {
return new ServiceInstanceChangeCallbackManager();
}
@Bean

@ -17,14 +17,18 @@
package com.tencent.cloud.polaris.discovery.refresh;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
import com.google.common.collect.Sets;
import com.tencent.polaris.api.plugin.registry.AbstractResourceEventListener;
import com.tencent.polaris.api.pojo.Instance;
import com.tencent.polaris.api.pojo.RegistryCacheValue;
import com.tencent.polaris.api.pojo.ServiceEventKey;
import com.tencent.polaris.api.utils.CollectionUtils;
import com.tencent.polaris.client.pojo.ServiceInstancesByProto;
import com.tencent.polaris.client.pojo.ServicesByProto;
import org.slf4j.Logger;
@ -33,7 +37,6 @@ import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.util.CollectionUtils;
/**
* Change listener of Polaris service info. When service info is created or deleted, or, instance of service is from 0 to
@ -49,9 +52,13 @@ public class PolarisServiceStatusChangeListener extends AbstractResourceEventLis
public static final AtomicLong INDEX = new AtomicLong(0);
private static final Logger LOG = LoggerFactory.getLogger(PolarisServiceStatusChangeListener.class);
private final ServiceInstanceChangeCallbackManager serviceInstanceChangeCallbackManager;
private ApplicationEventPublisher publisher;
public PolarisServiceStatusChangeListener(ServiceInstanceChangeCallbackManager serviceInstanceChangeCallbackManager) {
this.serviceInstanceChangeCallbackManager = serviceInstanceChangeCallbackManager;
}
@Override
public void onResourceUpdated(ServiceEventKey svcEventKey, RegistryCacheValue oldValue,
RegistryCacheValue newValue) {
@ -87,6 +94,22 @@ public class PolarisServiceStatusChangeListener extends AbstractResourceEventLis
// Trigger reload of gateway route cache.
this.publisher.publishEvent(new HeartbeatEvent(this, INDEX.getAndIncrement()));
}
List<Instance> oldInstances = new ArrayList<>();
List<Instance> newInstances = new ArrayList<>();
if (CollectionUtils.isNotEmpty(oldIns.getInstances())) {
oldInstances.addAll(oldIns.getInstances());
}
if (CollectionUtils.isNotEmpty(newIns.getInstances())) {
newInstances.addAll(newIns.getInstances());
}
try {
this.serviceInstanceChangeCallbackManager.handle(svcEventKey.getService(), oldInstances, newInstances);
}
catch (Throwable throwable) {
LOG.error("Service[{}] instance status change callback failed.", svcEventKey.getService(), throwable);
}
}
}
}

@ -0,0 +1,32 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.discovery.refresh;
import java.util.List;
import com.tencent.polaris.api.pojo.Instance;
/**
* Callback for service instance change.
*
* @author Haotian Zhang
*/
public interface ServiceInstanceChangeCallback {
void callback(List<Instance> currentServiceInstances, List<Instance> addServiceInstances, List<Instance> deleteServiceInstances);
}

@ -0,0 +1,145 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.discovery.refresh;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClient;
import com.tencent.cloud.polaris.discovery.reactive.PolarisReactiveDiscoveryClient;
import com.tencent.polaris.api.pojo.Instance;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.polaris.client.util.NamedThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.util.annotation.NonNull;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.util.CollectionUtils;
/**
* Call back manager for service instance change.
*
* @author Haotian Zhang
*/
public class ServiceInstanceChangeCallbackManager implements ApplicationListener<ApplicationReadyEvent>, BeanPostProcessor {
private static final Logger LOG = LoggerFactory.getLogger(ServiceInstanceChangeCallbackManager.class);
private final ConcurrentHashMap<String, List<ServiceInstanceChangeCallback>> callbackMap = new ConcurrentHashMap<>();
private final ScheduledThreadPoolExecutor serviceChangeListenerExecutor;
public ServiceInstanceChangeCallbackManager() {
this.serviceChangeListenerExecutor = new ScheduledThreadPoolExecutor(4, new NamedThreadFactory("service-change-listener"));
}
public void handle(String serviceName, List<Instance> oldInstances, List<Instance> newInstances) {
List<Instance> addInstances = new ArrayList<>();
List<Instance> deleteInstances = new ArrayList<>();
// calculate add instances.
for (Instance instance : newInstances) {
if (!oldInstances.contains(instance)) {
addInstances.add(instance);
}
}
// calculate delete instances.
for (Instance instance : oldInstances) {
if (!newInstances.contains(instance)) {
deleteInstances.add(instance);
}
}
if ((!CollectionUtils.isEmpty(addInstances) || !CollectionUtils.isEmpty(deleteInstances))
&& callbackMap.containsKey(serviceName)) {
List<ServiceInstanceChangeCallback> callbacks = callbackMap.get(serviceName);
for (ServiceInstanceChangeCallback callback : callbacks) {
serviceChangeListenerExecutor.execute(() -> {
try {
callback.callback(newInstances, addInstances, deleteInstances);
}
catch (Exception e) {
LOG.error("exception in callback, service name:{}, ", serviceName, e);
}
});
}
}
}
@Override
public synchronized Object postProcessAfterInitialization(Object bean, String beanName) {
Class<?> clz = bean.getClass();
if (!ServiceInstanceChangeCallback.class.isAssignableFrom(clz)) {
return bean;
}
String serviceName = null;
if (clz.isAnnotationPresent(ServiceInstanceChangeListener.class)) {
ServiceInstanceChangeListener serviceInstanceChangeListener = clz.getAnnotation(ServiceInstanceChangeListener.class);
serviceName = serviceInstanceChangeListener.serviceName();
}
if (StringUtils.isBlank(serviceName)) {
return bean;
}
// process callback
if (callbackMap.containsKey(serviceName)) {
List<ServiceInstanceChangeCallback> callbacks = callbackMap.get(serviceName);
callbacks.add((ServiceInstanceChangeCallback) bean);
}
else {
List<ServiceInstanceChangeCallback> callbacks = new ArrayList<>();
callbacks.add((ServiceInstanceChangeCallback) bean);
callbackMap.put(serviceName, callbacks);
}
return bean;
}
@Override
public void onApplicationEvent(@NonNull ApplicationReadyEvent event) {
PolarisDiscoveryClient polarisDiscoveryClient = ApplicationContextAwareUtils.getBeanIfExists(PolarisDiscoveryClient.class);
PolarisReactiveDiscoveryClient polarisReactiveDiscoveryClient = ApplicationContextAwareUtils.getBeanIfExists(PolarisReactiveDiscoveryClient.class);
for (String serviceName : callbackMap.keySet()) {
try {
if (polarisDiscoveryClient != null) {
polarisDiscoveryClient.getInstances(serviceName);
}
else if (polarisReactiveDiscoveryClient != null) {
polarisReactiveDiscoveryClient.getInstances(serviceName).subscribe();
}
else {
LOG.warn("[{}] no discovery client found.", serviceName);
}
}
catch (Throwable throwable) {
LOG.error("Get instances of service [{}] failed.", serviceName, throwable);
}
}
}
}

@ -0,0 +1,41 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.discovery.refresh;
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;
import org.springframework.stereotype.Component;
/**
* @author Haotian Zhang
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ServiceInstanceChangeListener {
/**
* listen service name.
*/
String serviceName();
}

@ -0,0 +1,44 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.eager.config;
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClient;
import com.tencent.cloud.polaris.discovery.reactive.PolarisReactiveDiscoveryClient;
import com.tencent.cloud.polaris.eager.instrument.feign.FeignEagerLoadSmartLifecycle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnProperty(name = "spring.cloud.polaris.discovery.eager-load.enabled", havingValue = "true")
public class PolarisEagerLoadAutoConfiguration {
@Bean
@ConditionalOnClass(name = "feign.Feign")
@ConditionalOnProperty(name = "spring.cloud.polaris.discovery.eager-load.feign.enabled", havingValue = "true", matchIfMissing = true)
public FeignEagerLoadSmartLifecycle feignEagerLoadSmartLifecycle(
ApplicationContext applicationContext, @Autowired(required = false) PolarisDiscoveryClient polarisDiscoveryClient,
@Autowired(required = false) PolarisReactiveDiscoveryClient polarisReactiveDiscoveryClient) {
return new FeignEagerLoadSmartLifecycle(applicationContext, polarisDiscoveryClient, polarisReactiveDiscoveryClient);
}
}

@ -0,0 +1,94 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.eager.instrument.feign;
import com.tencent.cloud.common.util.FeignUtil;
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClient;
import com.tencent.cloud.polaris.discovery.reactive.PolarisReactiveDiscoveryClient;
import com.tencent.polaris.api.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.SmartLifecycle;
public class FeignEagerLoadSmartLifecycle implements SmartLifecycle {
private static final Logger LOG = LoggerFactory.getLogger(FeignEagerLoadSmartLifecycle.class);
private final ApplicationContext applicationContext;
private final PolarisDiscoveryClient polarisDiscoveryClient;
private final PolarisReactiveDiscoveryClient polarisReactiveDiscoveryClient;
public FeignEagerLoadSmartLifecycle(ApplicationContext applicationContext, PolarisDiscoveryClient polarisDiscoveryClient,
PolarisReactiveDiscoveryClient polarisReactiveDiscoveryClient) {
this.applicationContext = applicationContext;
this.polarisDiscoveryClient = polarisDiscoveryClient;
this.polarisReactiveDiscoveryClient = polarisReactiveDiscoveryClient;
}
@Override
public void start() {
LOG.info("feign eager-load start");
for (String name : applicationContext.getBeanDefinitionNames()) {
try {
if (name.contains(FeignUtil.FEIGN_CLIENT_SPECIF) && !name.startsWith(FeignUtil.FEIGN_CLIENT_DEFAULT)) {
String feignName = FeignUtil.analysisFeignName(name, applicationContext);
if (StringUtils.isNotBlank(feignName)) {
LOG.info("[{}] eager-load start", feignName);
if (polarisDiscoveryClient != null) {
polarisDiscoveryClient.getInstances(feignName);
}
else if (polarisReactiveDiscoveryClient != null) {
polarisReactiveDiscoveryClient.getInstances(feignName).subscribe();
}
else {
LOG.warn("[{}] no discovery client found.", feignName);
}
LOG.info("[{}] eager-load end", feignName);
}
else {
LOG.warn("feign name is blank.");
}
}
}
catch (Exception e) {
LOG.error("[{}] eager-load failed.", name, e);
}
}
LOG.info("feign eager-load end");
}
@Override
public void stop() {
}
@Override
public boolean isRunning() {
return false;
}
@Override
public int getPhase() {
return 10;
}
}

@ -21,7 +21,7 @@ import java.util.ArrayList;
import java.util.List;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import com.tencent.cloud.rpc.enhancement.resttemplate.EnhancedRestTemplateInterceptor;
import com.tencent.cloud.rpc.enhancement.instrument.resttemplate.EnhancedRestTemplateInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.tsf.registry;
package com.tencent.cloud.polaris.registry.tsf;
import java.util.HashMap;
import java.util.Map;

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.tsf.registry;
package com.tencent.cloud.polaris.registry.tsf;
import com.tencent.cloud.common.tsf.ConditionalOnTsfConsulEnabled;
import com.tencent.cloud.polaris.context.PolarisSDKContextManager;

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.tsf.registry;
package com.tencent.cloud.polaris.registry.tsf;
import com.tencent.cloud.polaris.context.config.extend.tsf.TsfCoreProperties;
import com.tencent.cloud.polaris.extend.consul.ConsulDiscoveryProperties;

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.tsf.registry;
package com.tencent.cloud.polaris.registry.tsf;
import java.util.ArrayList;
import java.util.Arrays;

@ -0,0 +1,38 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.tsf.serviceregistry.watch;
import java.util.List;
import com.ecwid.consul.v1.health.model.HealthService;
/**
* Not working anymore. Compatible with old versions TSF SDK.
* <p>
* Deprecated since 2.0.0.0.
*
* @author Haotian Zhang
*/
@Deprecated
public interface ConsulServiceChangeCallback {
/**
* .
*/
void callback(List<HealthService> currentServices, List<HealthService> addServices, List<HealthService> deleteServices);
}

@ -0,0 +1,51 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.tsf.serviceregistry.watch;
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;
import org.springframework.stereotype.Component;
/**
* Empty annotation. Compatible with old versions TSF SDK.
* <p>
* Deprecated since 2.0.0.0.
*
* @author Haotian Zhang
*/
@Deprecated
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ConsulServiceChangeListener {
/**
* .
*/
String serviceName();
/**
* true false .
*/
boolean global() default false;
}

@ -15,29 +15,32 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.quickstart.caller.circuitbreaker;
package com.tencent.tsf.serviceregistry.watch;
import java.util.HashMap;
import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreakerFallback;
import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreakerHttpResponse;
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;
import org.springframework.stereotype.Component;
/**
* CustomFallback.
* Empty annotation. Compatible with old versions TSF SDK.
* <p>
* Deprecated since 2.0.0.0.
*
* @author sean yu
* @author Haotian Zhang
*/
@Deprecated
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public class CustomFallback implements PolarisCircuitBreakerFallback {
@Override
public PolarisCircuitBreakerHttpResponse fallback() {
return new PolarisCircuitBreakerHttpResponse(
200,
new HashMap<String, String>() {{
put("Content-Type", "application/json");
}},
"{\"msg\": \"this is a fallback class\"}");
}
public @interface LocalServiceChangeListener {
/**
* currentServices .
*/
boolean excludeLocalInstance() default false;
}

@ -78,6 +78,18 @@
"defaultValue": false,
"description": "Zero protection test connectivity switch. Default: false."
},
{
"name": "spring.cloud.polaris.discovery.eager-load.enabled",
"type": "java.lang.Boolean",
"defaultValue": false,
"description": "Eager load switch. Default: false."
},
{
"name": "spring.cloud.polaris.discovery.eager-load.feign.enabled",
"type": "java.lang.Boolean",
"defaultValue": true,
"description": "Feign eager load switch. Default: true."
},
{
"name": "spring.cloud.nacos.discovery.enabled",
"type": "java.lang.Boolean",

@ -3,4 +3,5 @@ com.tencent.cloud.polaris.discovery.PolarisDiscoveryAutoConfiguration
com.tencent.cloud.polaris.registry.PolarisServiceRegistryAutoConfiguration
com.tencent.cloud.polaris.endpoint.PolarisDiscoveryEndpointAutoConfiguration
com.tencent.cloud.polaris.loadbalancer.PolarisLoadBalancerAutoConfiguration
com.tencent.cloud.polaris.tsf.registry.TsfDiscoveryRegistryAutoConfiguration
com.tencent.cloud.polaris.registry.tsf.TsfDiscoveryRegistryAutoConfiguration
com.tencent.cloud.polaris.eager.config.PolarisEagerLoadAutoConfiguration

@ -61,7 +61,7 @@ public class PolarisServiceStatusChangeListenerTest {
@Test
public void testOnResourceUpdated() {
PolarisServiceStatusChangeListener polarisServiceStatusChangeListener = new PolarisServiceStatusChangeListener();
PolarisServiceStatusChangeListener polarisServiceStatusChangeListener = new PolarisServiceStatusChangeListener(mock(ServiceInstanceChangeCallbackManager.class));
polarisServiceStatusChangeListener.setApplicationEventPublisher(publisher);
// Service update event

@ -0,0 +1,38 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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 org.springframework.tsf.ratelimit.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Empty annotation. Compatible with old versions TSF SDK.
*
* @author Haotian Zhang
*/
@Deprecated(since = "2.0.0.0")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface EnableTsfRateLimit {
}

@ -17,7 +17,7 @@
package com.tencent.cloud.polaris.router.config;
import com.tencent.cloud.polaris.router.feign.RouterLabelFeignInterceptor;
import com.tencent.cloud.polaris.router.instrument.feign.RouterLabelFeignInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;

@ -25,13 +25,13 @@ import com.tencent.cloud.polaris.router.config.properties.PolarisMetadataRouterP
import com.tencent.cloud.polaris.router.config.properties.PolarisNamespaceRouterProperties;
import com.tencent.cloud.polaris.router.config.properties.PolarisNearByRouterProperties;
import com.tencent.cloud.polaris.router.config.properties.PolarisRuleBasedRouterProperties;
import com.tencent.cloud.polaris.router.instrument.resttemplate.RouterLabelRestTemplateInterceptor;
import com.tencent.cloud.polaris.router.instrument.scg.RouterLabelGlobalFilter;
import com.tencent.cloud.polaris.router.interceptor.MetadataRouterRequestInterceptor;
import com.tencent.cloud.polaris.router.interceptor.NamespaceRouterRequestInterceptor;
import com.tencent.cloud.polaris.router.interceptor.NearbyRouterRequestInterceptor;
import com.tencent.cloud.polaris.router.interceptor.RuleBasedRouterRequestInterceptor;
import com.tencent.cloud.polaris.router.resttemplate.RouterLabelRestTemplateInterceptor;
import com.tencent.cloud.polaris.router.scg.RouterLabelGlobalFilter;
import com.tencent.cloud.rpc.enhancement.resttemplate.EnhancedRestTemplateInterceptor;
import com.tencent.cloud.rpc.enhancement.instrument.resttemplate.EnhancedRestTemplateInterceptor;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.router.feign;
package com.tencent.cloud.polaris.router.instrument.feign;
import com.tencent.cloud.common.constant.OrderConstant;
import com.tencent.cloud.common.metadata.MetadataContextHolder;

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.router.resttemplate;
package com.tencent.cloud.polaris.router.instrument.resttemplate;
import java.io.IOException;

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.router.scg;
package com.tencent.cloud.polaris.router.instrument.scg;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.metadata.provider.ReactiveMetadataProvider;

@ -0,0 +1,39 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.tsf.lane.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Empty annotation. Compatible with old versions TSF SDK.
*
* @author Haotian Zhang
*/
@Deprecated(since = "2.0.0.0")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface EnableTsfLane {
}

@ -0,0 +1,39 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.tsf.femas.adaptor.tsf.governance.zonefilter;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Empty annotation. Compatible with old versions TSF SDK.
*
* @author Haotian Zhang
*/
@Deprecated(since = "2.0.0.0")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface EnableTsfZoneFilter {
}

@ -0,0 +1,39 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.tsf.unit.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Empty annotation. Compatible with old versions TSF SDK.
*
* @author Haotian Zhang
*/
@Deprecated(since = "2.0.0.0")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface EnableTsfUnit {
}

@ -0,0 +1,44 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.tsf.unit.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Empty annotation. Compatible with old versions TSF SDK.
*
* @author Haotian Zhang
*/
@Deprecated(since = "2.0.0.0")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface TsfUnitCall {
// 单元化下的业务系统名
String systemName() default "";
// 单元化下的是否调用GDU服务
boolean global() default false;
}

@ -0,0 +1,38 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.tsf.unit.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Empty annotation. Compatible with old versions TSF SDK.
*
* @author Haotian Zhang
*/
@Deprecated(since = "2.0.0.0")
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface TsfUnitCustomerIdentifier {
}

@ -0,0 +1,42 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.tsf.unit.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Empty annotation. Compatible with old versions TSF SDK.
*
* @author Haotian Zhang
*/
@Deprecated(since = "2.0.0.0")
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface TsfUnitLocalCall {
String className() default "";
String methodName() default "";
}

@ -0,0 +1,39 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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 org.springframework.cloud.tsf.route.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Empty annotation. Compatible with old versions TSF SDK.
*
* @author Haotian Zhang
*/
@Deprecated(since = "2.0.0.0")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface EnableTsfRoute {
}

@ -20,7 +20,7 @@ package com.tencent.cloud.polaris.router.config;
import com.tencent.cloud.common.metadata.config.MetadataAutoConfiguration;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration;
import com.tencent.cloud.polaris.router.feign.RouterLabelFeignInterceptor;
import com.tencent.cloud.polaris.router.instrument.feign.RouterLabelFeignInterceptor;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.router.feign;
package com.tencent.cloud.polaris.router.instrument.feign;
import java.util.Collections;
import java.util.HashMap;

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.router.resttemplate;
package com.tencent.cloud.polaris.router.instrument.resttemplate;
import java.net.URI;

@ -15,7 +15,7 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.router.scg;
package com.tencent.cloud.polaris.router.instrument.scg;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.polaris.metadata.core.MessageMetadataContainer;

@ -0,0 +1,83 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.common.async;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import com.tencent.cloud.plugin.threadlocal.TaskExecutorWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Role;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import static com.tencent.cloud.common.metadata.CrossThreadMetadataContext.CROSS_THREAD_METADATA_CONTEXT_CONSUMER;
import static com.tencent.cloud.common.metadata.CrossThreadMetadataContext.CROSS_THREAD_METADATA_CONTEXT_SUPPLIER;
/**
* polaris async executor for @Async .
*
* @author Haotian Zhang
*/
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@ConditionalOnProperty(name = "spring.cloud.tencent.async.enabled")
public class PolarisAsyncConfiguration implements AsyncConfigurer {
private static final Logger logger = LoggerFactory.getLogger(PolarisAsyncConfiguration.class);
@Primary
@Bean("polarisAsyncExecutor")
public TaskExecutor polarisAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
int corePoolSize = 10;
executor.setCorePoolSize(corePoolSize);
int maxPoolSize = 50;
executor.setMaxPoolSize(maxPoolSize);
int queueCapacity = 10;
executor.setQueueCapacity(queueCapacity);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
String threadNamePrefix = "polaris-async-executor-";
executor.setThreadNamePrefix(threadNamePrefix);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(5);
executor.initialize();
TaskExecutor executorWrapper = new TaskExecutorWrapper<>(executor, CROSS_THREAD_METADATA_CONTEXT_SUPPLIER, CROSS_THREAD_METADATA_CONTEXT_CONSUMER);
logger.info("Created async executor with corePoolSize:{}, maxPoolSize:{}, queueCapacity:{}", corePoolSize, maxPoolSize, queueCapacity);
return executorWrapper;
}
@Override
public Executor getAsyncExecutor() {
return polarisAsyncExecutor();
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) -> logger.error("Execute asynchronous tasks '{}' failed.", method, ex);
}
}

@ -0,0 +1,68 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.common.metadata;
import java.util.function.Consumer;
import java.util.function.Supplier;
import com.tencent.cloud.common.util.JacksonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Metadata Context for cross thread.
*
* @author Haotian Zhang
*/
public class CrossThreadMetadataContext {
private static final Logger LOG = LoggerFactory.getLogger(CrossThreadMetadataContext.class);
/**
* Get metadata context from previous thread.
*/
public static final Supplier<CrossThreadMetadataContext> CROSS_THREAD_METADATA_CONTEXT_SUPPLIER = () -> {
CrossThreadMetadataContext crossThreadMetadataContext = new CrossThreadMetadataContext();
crossThreadMetadataContext.setMetadataContext(MetadataContextHolder.get());
if (LOG.isDebugEnabled()) {
LOG.debug("Context map is got: {}", JacksonUtils.serialize2Json(crossThreadMetadataContext));
}
return crossThreadMetadataContext;
};
/**
* Set metadata context to current thread.
*/
public static final Consumer<CrossThreadMetadataContext> CROSS_THREAD_METADATA_CONTEXT_CONSUMER = crossThreadMetadataContext -> {
MetadataContextHolder.set(crossThreadMetadataContext.getMetadataContext());
if (LOG.isDebugEnabled()) {
LOG.debug("Context map is set: {}", JacksonUtils.serialize2Json(crossThreadMetadataContext));
}
};
private MetadataContext metadataContext;
public MetadataContext getMetadataContext() {
return metadataContext;
}
public void setMetadataContext(MetadataContext metadataContext) {
this.metadataContext = metadataContext;
}
}

@ -142,8 +142,8 @@ public class MetadataContext extends com.tencent.polaris.metadata.core.manager.M
return values;
}
public void putMetadataAsMap(MetadataType metadataType, TransitiveType transitiveType, boolean downstream, Map<String, String> values) {
MetadataContainer metadataContainer = getMetadataContainer(metadataType, downstream);
public void putMetadataAsMap(MetadataType metadataType, TransitiveType transitiveType, boolean caller, Map<String, String> values) {
MetadataContainer metadataContainer = getMetadataContainer(metadataType, caller);
for (Map.Entry<String, String> entry : values.entrySet()) {
metadataContainer.putMetadataStringValue(entry.getKey(), entry.getValue(), transitiveType);
}

@ -84,4 +84,18 @@ public class ApplicationContextAwareUtils implements ApplicationContextAware {
}
return property;
}
public static <T> T getBean(Class<T> requiredType) {
return applicationContext.getBean(requiredType);
}
public static <T> T getBeanIfExists(Class<T> requiredType) {
try {
return applicationContext.getBean(requiredType);
}
catch (Throwable e) {
LOGGER.warn("get bean failed, bean type: {}", requiredType.getName());
return null;
}
}
}

@ -0,0 +1,83 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.common.util;
import org.springframework.context.ApplicationContext;
import org.springframework.util.StringUtils;
/**
* @author heihuliliu
*/
public final class FeignUtil {
/**
* Feign client spec.
*/
public static final String FEIGN_CLIENT_SPECIF = ".FeignClientSpecification";
/**
* Default Feign client spec.
*/
public static final String FEIGN_CLIENT_DEFAULT = "default.";
/**
* regular expression that parses ${xxx} .
*/
public static final String REGEX = "^[$][{](.*)[}]$";
/**
* replacement of ${xxx}.
*/
public static final String REPLACEMENT = "$1";
private FeignUtil() {
}
/**
* TODO If @FeignClient specifies contextId, the service name will not be obtained correctly, but the contextId will be obtained.
*
* @param name feign name.
* @param context application context.
* @return service name.
*/
public static String analysisFeignName(String name, ApplicationContext context) {
String feignName = "";
String feignPath = name.substring(0, name.indexOf(FEIGN_CLIENT_SPECIF));
// Handle the case where the service name is a variable
if (feignPath.matches(REGEX)) {
feignPath = context.getEnvironment().getProperty(feignPath.replaceAll(REGEX, REPLACEMENT));
}
if (StringUtils.hasText(feignPath)) {
// The case of multi-level paths
String[] feignNames = feignPath.split("/");
if (feignNames.length > 1) {
for (int i = 0; i < feignNames.length; i++) {
if (StringUtils.hasText(feignNames[i])) {
feignName = feignNames[i];
break;
}
}
}
else {
feignName = feignNames[0];
}
}
return feignName;
}
}

@ -0,0 +1,45 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.common.util;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import com.tencent.polaris.threadlocal.cross.CompletableFutureUtils;
import static com.tencent.cloud.common.metadata.CrossThreadMetadataContext.CROSS_THREAD_METADATA_CONTEXT_CONSUMER;
import static com.tencent.cloud.common.metadata.CrossThreadMetadataContext.CROSS_THREAD_METADATA_CONTEXT_SUPPLIER;
/**
* Polaris CompletableFuture Utils.
*
* @author Haotian Zhang
*/
public final class PolarisCompletableFutureUtils {
private PolarisCompletableFutureUtils() {
}
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
return CompletableFutureUtils.supplyAsync(supplier, CROSS_THREAD_METADATA_CONTEXT_SUPPLIER, CROSS_THREAD_METADATA_CONTEXT_CONSUMER);
}
public static CompletableFuture<Void> runAsync(Runnable runnable) {
return CompletableFutureUtils.runAsync(runnable, CROSS_THREAD_METADATA_CONTEXT_SUPPLIER, CROSS_THREAD_METADATA_CONTEXT_CONSUMER);
}
}

@ -15,10 +15,11 @@
* specific language governing permissions and limitations under the License.
*/
package org.springframework.tsf.core.context;
package org.springframework.tsf.core;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import com.tencent.cloud.common.metadata.MetadataContext;
@ -32,6 +33,7 @@ public final class TsfContext {
static final int MAX_KEY_LENGTH = 32;
static final int MAX_VALUE_LENGTH = 128;
private static final String CUSTOM_METADATA = "tsf-custom-metadata";
private TsfContext() {
@ -74,4 +76,19 @@ public final class TsfContext {
MAX_VALUE_LENGTH));
}
}
public static void putCustomMetadata(Object customMetadata) {
if (customMetadata == null) {
return;
}
MetadataContext tsfCoreContext = MetadataContextHolder.get();
Map<String, String> tagMap = new HashMap<>();
try {
tagMap.put(CUSTOM_METADATA, customMetadata.toString());
}
catch (Throwable throwable) {
throw new RuntimeException("Failed to parse custom metadata", throwable);
}
tsfCoreContext.putMetadataAsMap(MetadataType.CUSTOM, TransitiveType.NONE, false, tagMap);
}
}

@ -0,0 +1,69 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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 org.springframework.tsf.core.util;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
/**
* Spring context utils.
* <p>
* Deprecated since 2.0.0.0.
*
* @author hongweizhu
*/
@Deprecated
public class TsfSpringContextAware {
/**
* Get application context.
* @return application context
*/
public static ApplicationContext getApplicationContext() {
return ApplicationContextAwareUtils.getApplicationContext();
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// do nothing.
}
/**
* Get application property.
* @param key property name
* @return property value
*/
public static String getProperties(String key) {
return ApplicationContextAwareUtils.getProperties(key);
}
/**
* Get application property. If null, return default.
* @param key property name
* @param defaultValue default value
* @return property value
*/
public static String getProperties(String key, String defaultValue) {
return ApplicationContextAwareUtils.getProperties(key, defaultValue);
}
public static <T> T getBean(Class<T> requiredType) {
return ApplicationContextAwareUtils.getBean(requiredType);
}
}

@ -19,6 +19,12 @@
"name": "spring.cloud.tencent.metadata.headers",
"type": "java.util.List",
"description": "Custom transitive http header key list."
},
{
"name": "spring.cloud.tencent.async.enabled",
"type": "java.lang.Boolean",
"defaultValue": false,
"description": "Async support switch. Default: false."
}
]
}

@ -211,6 +211,12 @@
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-fault-tolerance</artifactId>
<version>${revision}</version>
</dependency>
<!-- third part framework dependencies -->
<dependency>
<groupId>com.google.guava</groupId>

@ -22,6 +22,7 @@ import java.net.URLDecoder;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.quickstart.callee.config.DataSourceProperties;
import com.tencent.cloud.quickstart.callee.service.FaultToleranceService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -59,6 +60,9 @@ public class QuickstartCalleeController {
@Autowired
private DataSourceProperties dataSourceProperties;
@Autowired
private FaultToleranceService faultToleranceService;
/**
* Get sum of two value.
* @param value1 value 1
@ -126,7 +130,22 @@ public class QuickstartCalleeController {
@GetMapping("/test/{num}/echo")
public String test(@PathVariable int num) {
LOG.info("Quickstart Callee Service [%s] is detected right.", num);
LOG.info("Quickstart Callee Service [{}] is detected right.", num);
return String.format("Quickstart Callee Service [%s] is detected right.", num);
}
@GetMapping("/faultTolerance/failFast")
public String faultToleranceFailFast() {
return faultToleranceService.failFast();
}
@GetMapping("/faultTolerance/failOver")
public String faultToleranceFailOver() {
return faultToleranceService.failOver();
}
@GetMapping("/faultTolerance/forking")
public String faultToleranceForking() {
return faultToleranceService.forking();
}
}

@ -0,0 +1,60 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.quickstart.callee.service;
import java.util.List;
import com.tencent.cloud.polaris.discovery.refresh.ServiceInstanceChangeCallback;
import com.tencent.cloud.polaris.discovery.refresh.ServiceInstanceChangeListener;
import com.tencent.polaris.api.pojo.Instance;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* Call back for QuickstartCalleeService.
*
* @author Haotian Zhang
*/
@Component
@ServiceInstanceChangeListener(serviceName = "QuickstartCalleeService")
public class CalleeServiceChangeCallback implements ServiceInstanceChangeCallback {
private static final Logger LOG = LoggerFactory.getLogger(CalleeServiceChangeCallback.class);
@Override
public void callback(List<Instance> currentServiceInstances, List<Instance> addServiceInstances, List<Instance> deleteServiceInstances) {
String current = generateNodeList(currentServiceInstances);
String add = generateNodeList(addServiceInstances);
String delete = generateNodeList(deleteServiceInstances);
LOG.info("current: {}, add: {}, delete: {}", current, add, delete);
}
private String generateNodeList(List<Instance> deleteServiceInstances) {
StringBuilder nodeListStr = new StringBuilder("[");
for (Instance instance : deleteServiceInstances) {
if (nodeListStr.length() > 1) {
nodeListStr.append(", ");
}
nodeListStr.append(instance.getHost()).append(":").append(instance.getPort());
}
nodeListStr.append("]");
return nodeListStr.toString();
}
}

@ -0,0 +1,62 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.quickstart.callee.service;
import java.util.concurrent.atomic.AtomicInteger;
import com.tencent.cloud.plugin.faulttolerance.annotation.FaultTolerance;
import com.tencent.cloud.plugin.faulttolerance.model.FaultToleranceStrategy;
import org.springframework.stereotype.Service;
/**
* Service for fault tolerance.
*
* @author Haotian Zhang
*/
@Service
public class FaultToleranceService {
private final AtomicInteger failOverCount = new AtomicInteger(0);
private final AtomicInteger forkingCount = new AtomicInteger(0);
@FaultTolerance(strategy = FaultToleranceStrategy.FAIL_FAST, fallbackMethod = "fallback")
public String failFast() {
throw new RuntimeException("NO");
}
public String fallback() {
return "fallback";
}
@FaultTolerance(strategy = FaultToleranceStrategy.FAIL_OVER, maxAttempts = 3)
public String failOver() {
if (failOverCount.incrementAndGet() % 4 == 0) {
return "OK";
}
throw new RuntimeException("NO");
}
@FaultTolerance(strategy = FaultToleranceStrategy.FORKING, parallelism = 4)
public String forking() {
if (forkingCount.incrementAndGet() % 4 == 0) {
return "OK";
}
throw new RuntimeException("NO");
}
}

@ -17,9 +17,6 @@
package com.tencent.cloud.quickstart.caller;
import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreaker;
import com.tencent.cloud.quickstart.caller.circuitbreaker.CustomFallback;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
@ -50,27 +47,7 @@ public class QuickstartCallerApplication {
@Bean
@LoadBalanced
public RestTemplate defaultRestTemplate() {
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("http://QuickstartCalleeService");
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(uriBuilderFactory);
return restTemplate;
}
@Bean
@LoadBalanced
@PolarisCircuitBreaker
public RestTemplate restTemplateFallbackFromPolaris() {
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("http://QuickstartCalleeService");
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(uriBuilderFactory);
return restTemplate;
}
@Bean
@LoadBalanced
@PolarisCircuitBreaker(fallbackClass = CustomFallback.class)
public RestTemplate restTemplateFallbackFromCode() {
public RestTemplate calleeRestTemplate() {
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("http://QuickstartCalleeService");
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(uriBuilderFactory);

@ -50,16 +50,8 @@ public class CircuitBreakerController {
private CircuitBreakerQuickstartCalleeServiceWithFallback circuitBreakerQuickstartCalleeServiceWithFallback;
@Autowired
@Qualifier("defaultRestTemplate")
private RestTemplate defaultRestTemplate;
@Autowired
@Qualifier("restTemplateFallbackFromPolaris")
private RestTemplate restTemplateFallbackFromPolaris;
@Autowired
@Qualifier("restTemplateFallbackFromCode")
private RestTemplate restTemplateFallbackFromCode;
@Qualifier("calleeRestTemplate")
private RestTemplate calleeRestTemplate;
@Autowired
private CircuitBreakerFactory circuitBreakerFactory;
@ -114,7 +106,7 @@ public class CircuitBreakerController {
public String circuitBreakRestTemplate() {
return circuitBreakerFactory
.create(MetadataContext.LOCAL_NAMESPACE + "#QuickstartCalleeService#/quickstart/callee/circuitBreak#http#GET")
.run(() -> defaultRestTemplate.getForObject("/quickstart/callee/circuitBreak", String.class),
.run(() -> calleeRestTemplate.getForObject("/quickstart/callee/circuitBreak", String.class),
throwable -> "trigger the refuse for service callee."
);
}
@ -126,17 +118,7 @@ public class CircuitBreakerController {
@GetMapping("/rest/fallbackFromPolaris/wildcard/{uid}")
public ResponseEntity<String> circuitBreakRestTemplateFallbackFromPolarisWildcard(@PathVariable String uid) {
String path = String.format("/quickstart/callee/circuitBreak/wildcard/%s", uid);
return restTemplateFallbackFromPolaris.getForEntity(path, String.class);
}
/**
* RestTemplate wildcard circuit breaker with fallback from code.
* @return circuit breaker information of callee
*/
@GetMapping("/rest/fallbackFromCode/wildcard/{uid}")
public ResponseEntity<String> circuitBreakRestTemplateFallbackFromCodeWildcard(@PathVariable String uid) {
String path = String.format("/quickstart/callee/circuitBreak/wildcard/%s", uid);
return restTemplateFallbackFromCode.getForEntity(path, String.class);
return calleeRestTemplate.getForEntity(path, String.class);
}
/**
@ -146,21 +128,7 @@ public class CircuitBreakerController {
@GetMapping("/rest/fallbackFromPolaris")
public ResponseEntity<String> circuitBreakRestTemplateFallbackFromPolaris() {
try {
return restTemplateFallbackFromPolaris.getForEntity("/quickstart/callee/circuitBreak", String.class);
}
catch (HttpClientErrorException | HttpServerErrorException httpClientErrorException) {
return new ResponseEntity<>(httpClientErrorException.getResponseBodyAsString(), httpClientErrorException.getStatusCode());
}
}
/**
* RestTemplate circuit breaker with fallback from code.
* @return circuit breaker information of callee
*/
@GetMapping("/rest/fallbackFromCode")
public ResponseEntity<String> circuitBreakRestTemplateFallbackFromCode() {
try {
return restTemplateFallbackFromCode.getForEntity("/quickstart/callee/circuitBreak", String.class);
return calleeRestTemplate.getForEntity("/quickstart/callee/circuitBreak", String.class);
}
catch (HttpClientErrorException | HttpServerErrorException httpClientErrorException) {
return new ResponseEntity<>(httpClientErrorException.getResponseBodyAsString(), httpClientErrorException.getStatusCode());

@ -14,27 +14,7 @@
<dependencies>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-config</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-contract</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-router</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-circuitbreaker</artifactId>
<artifactId>spring-cloud-starter-tencent-all</artifactId>
</dependency>
<dependency>
@ -46,16 +26,6 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-metadata-transfer</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-trace-plugin</artifactId>
</dependency>
</dependencies>
<build>

@ -17,8 +17,6 @@
package com.tencent.cloud.tsf.demo.consumer;
import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreaker;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
@ -37,7 +35,6 @@ public class ConsumerApplication {
@LoadBalanced
@Bean
@PolarisCircuitBreaker
public RestTemplate restTemplate() {
return new RestTemplate();
}

@ -19,14 +19,17 @@ 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.context.TsfContext;
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;
@ -62,7 +65,30 @@ public class ConsumerController {
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<String, String> mTags = new HashMap<>();
mTags.put("rest-trace-key1", "value1");
mTags.put("rest-trace-key2", "value2");
TsfContext.putTags(mTags, Tag.ControlFlag.TRANSITIVE);
CompletableFuture<String> 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)

@ -0,0 +1,54 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.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;
}
}

@ -14,27 +14,7 @@
<dependencies>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-config</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-contract</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-ratelimit</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-auth</artifactId>
<artifactId>spring-cloud-starter-tencent-all</artifactId>
</dependency>
<dependency>
@ -49,14 +29,6 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-metadata-transfer</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-trace-plugin</artifactId>
</dependency>
</dependencies>
<build>

@ -21,6 +21,7 @@
<module>spring-cloud-tencent-lossless-plugin</module>
<module>spring-cloud-starter-tencent-threadlocal-plugin</module>
<module>spring-cloud-starter-tencent-trace-plugin</module>
<module>spring-cloud-starter-tencent-fault-tolerance</module>
</modules>
</project>

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-tencent-plugin-starters</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-starter-tencent-fault-tolerance</artifactId>
<name>Spring Cloud Starter Tencent Fault Tolerance Plugin</name>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

@ -0,0 +1,91 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.plugin.faulttolerance.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.tencent.cloud.plugin.faulttolerance.model.FaultToleranceStrategy;
/**
* Annotation for Fault Tolerance.
*
* @author Haotian Zhang
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface FaultTolerance {
/**
* Specifies a method to process fallback logic.
* A fallback method should be defined in the same class where is &#064;FaultTolerance.
* Also a fallback method should have same signature to a method.
* for example:
* <p>
* <pre>
* &#064;FaultTolerance(fallbackMethod = "getByIdFallback")
* public String getById(String id) {
* // original method implementation
* }
*
* public String getByIdFallback(String id) {
* // fallback method implementation
* }
* </pre>
* </p>
* @return method name
*/
String fallbackMethod() default "";
/**
* Defines exceptions which should be ignored.
*
* @return exceptions to ignore
*/
Class<? extends Throwable>[] ignoreExceptions() default {};
/**
* Defines exceptions which should be retried.
* Default is all exceptions.
*
* @return exceptions to wrap
*/
Class<? extends Throwable>[] raisedExceptions() default {};
/**
* Defines the fault tolerance strategy, the default is fast fail strategy.
* @see FaultToleranceStrategy
*/
FaultToleranceStrategy strategy() default FaultToleranceStrategy.FAIL_FAST;
/**
* Number of retries, only used under {@link FaultToleranceStrategy}.FAIL_OVER strategy. Default is 0.
*/
int maxAttempts() default 0;
/**
* The parallelism of forking is only used under the {@link FaultToleranceStrategy}.FORKING strategy.
*/
int parallelism() default 1;
}

@ -0,0 +1,43 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.plugin.faulttolerance.common;
import java.lang.reflect.Method;
/**
* Fallback method for fault-tolerance.
*
* @author zhixinzxliu
*/
public class FallbackMethod {
/**
* Absent fallback method.
*/
public static final FallbackMethod ABSENT = new FallbackMethod(null);
private final Method method;
public FallbackMethod(Method method) {
this.method = method;
}
public Method getMethod() {
return method;
}
}

@ -0,0 +1,42 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.plugin.faulttolerance.config;
import com.tencent.cloud.plugin.faulttolerance.instrument.FaultToleranceAspect;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
/**
* FaultTolerance Auto-Configuration.
*
* @author Haotian Zhang
*/
@Configuration(proxyBeanMethods = false)
@Role(RootBeanDefinition.ROLE_INFRASTRUCTURE)
public class FaultToleranceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public FaultToleranceAspect faultToleranceAspect() {
return new FaultToleranceAspect();
}
}

@ -0,0 +1,248 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.plugin.faulttolerance.instrument;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.tencent.cloud.plugin.faulttolerance.annotation.FaultTolerance;
import com.tencent.cloud.plugin.faulttolerance.common.FallbackMethod;
import com.tencent.cloud.plugin.faulttolerance.model.FaultToleranceStrategy;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.tsf.faulttolerance.annotation.TsfFaultTolerance;
import org.springframework.cloud.tsf.faulttolerance.model.TsfFaultToleranceStragety;
/**
* Fault tolerance.
*
* @author zhixinzxliu, Haotian Zhang
*/
@Aspect
public class FaultToleranceAspect {
private static final Logger logger = LoggerFactory.getLogger(FaultToleranceAspect.class);
private final Map<Method, FallbackMethod> fallbackMethodMap = new ConcurrentHashMap<>();
private final ExecutorService executorService = Executors.newCachedThreadPool();
@Pointcut("@annotation(org.springframework.cloud.tsf.faulttolerance.annotation.TsfFaultTolerance)"
+ " || @annotation(com.tencent.cloud.plugin.faulttolerance.annotation.FaultTolerance)")
public void faultToleranceAnnotationPointcut() {
}
@Around("faultToleranceAnnotationPointcut()")
public Object methodsAnnotatedWithFaultTolerance(final ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
FaultTolerance faultTolerance = signature.getMethod().getAnnotation(FaultTolerance.class);
TsfFaultTolerance tsfFaultTolerance = signature.getMethod().getAnnotation(TsfFaultTolerance.class);
Object result;
try {
// Invoke job in parallel. Whoever returns the result first will use it.
// The latter result will be ignored directly.
// If all exceptions occur, an exception will be thrown.
if (faultTolerance != null
&& faultTolerance.strategy() == FaultToleranceStrategy.FORKING
&& faultTolerance.parallelism() > 1) {
List<Callable<Object>> jobs = generateJobs(faultTolerance.parallelism(), joinPoint);
result = executorService.invokeAny(jobs);
}
else if (tsfFaultTolerance != null &&
tsfFaultTolerance.strategy() == TsfFaultToleranceStragety.FORKING &&
tsfFaultTolerance.parallelism() > 1) {
List<Callable<Object>> jobs = generateJobs(tsfFaultTolerance.parallelism(), joinPoint);
result = executorService.invokeAny(jobs);
}
else {
result = joinPoint.proceed();
}
}
catch (Throwable throwable) {
return execFaultToleranceLogic(joinPoint, faultTolerance, tsfFaultTolerance, throwable);
}
return result;
}
private Object execFaultToleranceLogic(ProceedingJoinPoint joinPoint, FaultTolerance faultTolerance, TsfFaultTolerance tsfFaultTolerance, Throwable throwable) throws Throwable {
if (!needExecuteFaultTolerance(faultTolerance, tsfFaultTolerance, throwable)) {
throw throwable;
}
// 重试逻辑
if ((faultTolerance != null && faultTolerance.strategy() == FaultToleranceStrategy.FAIL_OVER)
|| (tsfFaultTolerance != null && tsfFaultTolerance.strategy() == TsfFaultToleranceStragety.FAIL_OVER)) {
int maxAttempts = faultTolerance != null ? faultTolerance.maxAttempts() : tsfFaultTolerance.maxAttempts();
while (maxAttempts > 0) {
try {
return joinPoint.proceed();
}
catch (Throwable throwable1) {
if (!needExecuteFaultTolerance(faultTolerance, tsfFaultTolerance, throwable1)) {
throw throwable1;
}
logger.error("The {} time retry error, will continue retry {} times.", maxAttempts, maxAttempts - 1, throwable1);
}
finally {
maxAttempts--;
}
}
}
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
if (method == null) {
throw throwable;
}
FallbackMethod fallbackMethod = fallbackMethodMap.get(method);
if (fallbackMethod == null) {
fallbackMethod = resolveFallbackMethod(joinPoint);
fallbackMethodMap.putIfAbsent(method, fallbackMethod);
}
if (fallbackMethod.getMethod() != null) {
return fallbackMethod.getMethod().invoke(joinPoint.getTarget(), joinPoint.getArgs());
}
throw throwable;
}
private List<Callable<Object>> generateJobs(int parallelism, final ProceedingJoinPoint joinPoint) {
List<Callable<Object>> jobs = new ArrayList<>();
for (int i = 0; i < parallelism; i++) {
jobs.add(() -> {
try {
return joinPoint.proceed();
}
catch (Throwable t) {
throw new RuntimeException(t);
}
});
}
return jobs;
}
/**
* 1. If the user sets ignoreExceptions and the current exception is a subclass of one of them,
* the fault-tolerant logic will not be executed.<br>
* 2. If the user does not set ignoreExceptions or the current exception is not a subclass of ignoreExceptions
* and meets the following conditions, fault-tolerant logic will be executed:<br>
* 2.1. If the user does not set raisedExceptions, fault tolerance logic will be executed.<br>
* 2.2. The user has set raisedExceptions, and the current exception is a subclass of one of the raisedExceptions set by the user.
*/
private boolean needExecuteFaultTolerance(FaultTolerance faultTolerance, TsfFaultTolerance tsfFaultTolerance, Throwable throwable) {
if (faultTolerance == null && tsfFaultTolerance == null) {
return false;
}
Class<? extends Throwable>[] ignoreExceptions = faultTolerance != null ? faultTolerance.ignoreExceptions() : null;
if (ignoreExceptions == null || ignoreExceptions.length == 0) {
ignoreExceptions = tsfFaultTolerance != null ? tsfFaultTolerance.ignoreExceptions() : null;
}
Class<? extends Throwable>[] raisedExceptions = faultTolerance != null ? faultTolerance.raisedExceptions() : null;
if (raisedExceptions == null || raisedExceptions.length == 0) {
raisedExceptions = tsfFaultTolerance != null ? tsfFaultTolerance.raisedExceptions() : null;
}
try {
if (ignoreExceptions != null) {
for (Class<? extends Throwable> ignoreException : ignoreExceptions) {
if (ignoreException.isAssignableFrom(throwable.getClass())) {
logger.debug("The exception {} is ignored.", throwable.getClass().getName());
return false;
}
}
}
if (raisedExceptions == null || raisedExceptions.length == 0) {
return true;
}
else {
for (Class<? extends Throwable> raisedException : raisedExceptions) {
if (raisedException.isAssignableFrom(throwable.getClass())) {
return true;
}
}
}
}
catch (Throwable throwable1) {
logger.warn("Check exception {} failed.", throwable.getClass().getName(), throwable1);
return false;
}
return false;
}
protected FallbackMethod resolveFallbackMethod(ProceedingJoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
FaultTolerance faultTolerance = signature.getMethod().getAnnotation(FaultTolerance.class);
TsfFaultTolerance tsfFaultTolerance = signature.getMethod().getAnnotation(TsfFaultTolerance.class);
String fallbackMethodName = faultTolerance != null ? faultTolerance.fallbackMethod() : tsfFaultTolerance.fallbackMethod();
Class<?> targetClass = joinPoint.getTarget().getClass();
Class<?>[] parameterTypes = signature.getMethod().getParameterTypes();
Method method = getDeclaredMethodFor(targetClass, fallbackMethodName, false, parameterTypes);
if (method == null) {
return FallbackMethod.ABSENT;
}
else {
if (signature.getMethod().getReturnType().isAssignableFrom(method.getReturnType())) {
return new FallbackMethod(method);
}
else {
return FallbackMethod.ABSENT;
}
}
}
private Method getDeclaredMethodFor(Class<?> clazz, String name, boolean inRecursive, Class<?>... parameterTypes) {
try {
return clazz.getDeclaredMethod(name, parameterTypes);
}
catch (NoSuchMethodException e) {
if (!inRecursive) {
logger.warn("Fallback method not found! Classname = {}, methodName = {}, parameterTypes = {}",
clazz.getName(), name, Arrays.toString(parameterTypes));
}
Class<?> superClass = clazz.getSuperclass();
if (superClass != null) {
return getDeclaredMethodFor(superClass, name, true, parameterTypes);
}
}
return null;
}
}

@ -0,0 +1,37 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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.plugin.faulttolerance.model;
public enum FaultToleranceStrategy {
/**
* Fails directly. For downstream services without idempotence, fail fast is recommended.
*/
FAIL_FAST,
/**
* If the request is wrong, it will be retried.
*/
FAIL_OVER,
/**
* Sending multiple requests at the same time requires the user to configure the degree of parallelism.
* For example, if two requests are sent at the same time, whichever one returns first will return the result.
* If the first request is an exception, it will wait for another request, and if all are exceptions, an exception will be returned.
*/
FORKING
}

@ -0,0 +1,38 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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 org.springframework.cloud.tsf.faulttolerance.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Empty annotation. Compatible with old versions TSF SDK.
*
* @author Haotian Zhang
*/
@Deprecated(since = "2.0.0.0")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface EnableTsfFaultTolerance {
}

@ -0,0 +1,90 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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 org.springframework.cloud.tsf.faulttolerance.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.cloud.tsf.faulttolerance.model.TsfFaultToleranceStragety;
/**
* @author zhixinzxiliu
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Deprecated(since = "2.0.0.0")
public @interface TsfFaultTolerance {
/**
* Specifies a method to process fallback logic.
* A fallback method should be defined in the same class where is TsfFaultTolerance.
* Also a fallback method should have same signature to a method.
* for example:
* <code>
* &#064;TsfFaultTolerance(fallbackMethod = "getByIdFallback")
* public String getById(String id) {...}
*
* public String getByIdFallback(String id) {...}
* </code>
*
* @return method name
*/
String fallbackMethod() default "";
/**
* Specifies command properties.
*
* @return command properties
*/
TsfFaultToleranceProperty[] properties() default {};
/**
* Defines exceptions which should be ignored.
*
* @return exceptions to ignore
*/
Class<? extends Throwable>[] ignoreExceptions() default {};
/**
* Defines exceptions which should be retry.
* Default is all exceptions.
*
* @return exceptions to wrap
*/
Class<? extends Throwable>[] raisedExceptions() default {};
/**
* .
*/
TsfFaultToleranceStragety strategy() default TsfFaultToleranceStragety.FAIL_FAST;
/**
* Failover.
*/
int maxAttempts() default 0;
/**
* forking Forking.
*/
int parallelism() default 1;
}

@ -0,0 +1,52 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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 org.springframework.cloud.tsf.faulttolerance.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;
/**
* This annotation allows specify TsfFaultTolerance properties in the following format:
* property name = property value.
*
* @author zhixinzxliu
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Deprecated(since = "2.0.0.0")
public @interface TsfFaultToleranceProperty {
/**
* Property name.
*
* @return name
*/
String name();
/**
* Property value.
*
* @return value
*/
String value();
}

@ -0,0 +1,38 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 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 org.springframework.cloud.tsf.faulttolerance.model;
@Deprecated(since = "2.0.0.0")
public enum TsfFaultToleranceStragety {
/**
* Fails directly. For downstream services without idempotence, fail fast is recommended.
*/
FAIL_FAST,
/**
* If the request is wrong, it will be retried.
*/
FAIL_OVER,
/**
* Sending multiple requests at the same time requires the user to configure the degree of parallelism.
* For example, if two requests are sent at the same time, whichever one returns first will return the result.
* If the first request is an exception, it will wait for another request, and if all are exceptions, an exception will be returned.
*/
FORKING
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save