add PolarisCircuitBreakerRestTemplate support

pull/917/head
seanyu 3 years ago
parent 0f35fb4811
commit 9ef157f7c1

@ -22,6 +22,7 @@ import java.util.List;
import com.tencent.cloud.polaris.circuitbreaker.PolarisCircuitBreakerFactory; import com.tencent.cloud.polaris.circuitbreaker.PolarisCircuitBreakerFactory;
import com.tencent.cloud.polaris.circuitbreaker.common.CircuitBreakerConfigModifier; import com.tencent.cloud.polaris.circuitbreaker.common.CircuitBreakerConfigModifier;
import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreakerRestTemplateBeanPostProcessor;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI; import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI;
@ -31,9 +32,12 @@ import com.tencent.polaris.client.api.SDKContext;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.cloud.client.circuitbreaker.Customizer; import org.springframework.cloud.client.circuitbreaker.Customizer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -72,4 +76,12 @@ public class PolarisCircuitBreakerAutoConfiguration {
return new CircuitBreakerConfigModifier(properties); return new CircuitBreakerConfigModifier(properties);
} }
@Bean
@ConditionalOnMissingBean
@ConditionalOnClass(name = "org.springframework.web.client.RestTemplate")
public static PolarisCircuitBreakerRestTemplateBeanPostProcessor sentinelBeanPostProcessor(
ApplicationContext applicationContext, CircuitBreakerFactory circuitBreakerFactory) {
return new PolarisCircuitBreakerRestTemplateBeanPostProcessor(applicationContext, circuitBreakerFactory);
}
} }

@ -0,0 +1,7 @@
package com.tencent.cloud.polaris.circuitbreaker.resttemplate;
public interface PolarisCircuitBreakerFallback {
PolarisCircuitBreakerHttpResponse fallback();
}

@ -0,0 +1,68 @@
package com.tencent.cloud.polaris.circuitbreaker.resttemplate;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import com.tencent.polaris.api.pojo.CircuitBreakerStatus;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.AbstractClientHttpResponse;
public class PolarisCircuitBreakerHttpResponse extends AbstractClientHttpResponse {
private final CircuitBreakerStatus.FallbackInfo fallbackInfo;
public PolarisCircuitBreakerHttpResponse(int code){
this(new CircuitBreakerStatus.FallbackInfo(code, null, null));
}
public PolarisCircuitBreakerHttpResponse(int code, String body){
this(new CircuitBreakerStatus.FallbackInfo(code, null, body));
}
public PolarisCircuitBreakerHttpResponse(int code, Map<String, String> headers, String body){
this(new CircuitBreakerStatus.FallbackInfo(code, headers, body));
}
public PolarisCircuitBreakerHttpResponse(CircuitBreakerStatus.FallbackInfo fallbackInfo){
this.fallbackInfo = fallbackInfo;
}
@Override
public int getRawStatusCode() throws IOException {
return fallbackInfo.getCode();
}
@Override
public String getStatusText() throws IOException {
HttpStatus status = HttpStatus.resolve(getRawStatusCode());
return (status != null ? status.getReasonPhrase() : "");
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
if (fallbackInfo.getBody() != null) {
return new ByteArrayInputStream(fallbackInfo.getBody().getBytes());
}
return null;
}
@Override
public HttpHeaders getHeaders() {
if (fallbackInfo.getHeaders() != null) {
HttpHeaders headers = new HttpHeaders();
fallbackInfo.getHeaders().forEach(headers::add);
return headers;
}
return null;
}
}

@ -0,0 +1,18 @@
package com.tencent.cloud.polaris.circuitbreaker.resttemplate;
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;
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PolarisCircuitBreakerRestTemplate {
String fallback() default "";
Class<PolarisCircuitBreakerFallback> fallbackClass() default PolarisCircuitBreakerFallback.class;
}

@ -0,0 +1,98 @@
package com.tencent.cloud.polaris.circuitbreaker.resttemplate;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
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.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;
public class PolarisCircuitBreakerRestTemplateBeanPostProcessor implements MergedBeanDefinitionPostProcessor {
private final ApplicationContext applicationContext;
private final CircuitBreakerFactory circuitBreakerFactory;
public PolarisCircuitBreakerRestTemplateBeanPostProcessor(ApplicationContext applicationContext, CircuitBreakerFactory circuitBreakerFactory) {
this.applicationContext = applicationContext;
this.circuitBreakerFactory = circuitBreakerFactory;
}
private ConcurrentHashMap<String, PolarisCircuitBreakerRestTemplate> cache = new ConcurrentHashMap<>();
private void checkPolarisCircuitBreakerRestTemplate(PolarisCircuitBreakerRestTemplate polarisCircuitBreakerRestTemplate,
String beanName) {
}
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
if (checkAnnotated(beanDefinition, beanType, beanName)) {
PolarisCircuitBreakerRestTemplate polarisCircuitBreakerRestTemplate;
if (beanDefinition.getSource() instanceof StandardMethodMetadata) {
polarisCircuitBreakerRestTemplate = ((StandardMethodMetadata) beanDefinition.getSource()).getIntrospectedMethod()
.getAnnotation(PolarisCircuitBreakerRestTemplate.class);
}
else {
polarisCircuitBreakerRestTemplate = beanDefinition.getResolvedFactoryMethod()
.getAnnotation(PolarisCircuitBreakerRestTemplate.class);
}
checkPolarisCircuitBreakerRestTemplate(polarisCircuitBreakerRestTemplate, beanName);
cache.put(beanName, polarisCircuitBreakerRestTemplate);
}
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (cache.containsKey(beanName)) {
// add interceptor for each RestTemplate with @PolarisCircuitBreakerRestTemplate annotation
StringBuilder interceptorBeanNamePrefix = new StringBuilder();
PolarisCircuitBreakerRestTemplate polarisCircuitBreakerRestTemplate = cache.get(beanName);
interceptorBeanNamePrefix
.append(StringUtils.uncapitalize(
PolarisCircuitBreakerRestTemplate.class.getSimpleName()))
.append("_")
.append(polarisCircuitBreakerRestTemplate.fallbackClass().getSimpleName());
RestTemplate restTemplate = (RestTemplate) bean;
String interceptorBeanName = interceptorBeanNamePrefix + "@" + bean;
registerBean(interceptorBeanName, polarisCircuitBreakerRestTemplate, applicationContext, circuitBreakerFactory);
PolarisCircuitBreakerRestTemplateInterceptor polarisCircuitBreakerRestTemplateInterceptor = applicationContext
.getBean(interceptorBeanName, PolarisCircuitBreakerRestTemplateInterceptor.class);
restTemplate.getInterceptors().add(0, polarisCircuitBreakerRestTemplateInterceptor);
}
return bean;
}
private boolean checkAnnotated(RootBeanDefinition beanDefinition,
Class<?> beanType, String beanName) {
return beanName != null && beanType == RestTemplate.class
&& beanDefinition.getSource() instanceof MethodMetadata
&& ((MethodMetadata) beanDefinition.getSource())
.isAnnotated(PolarisCircuitBreakerRestTemplate.class.getName());
}
private void registerBean(String interceptorBeanName,
PolarisCircuitBreakerRestTemplate polarisCircuitBreakerRestTemplate, ApplicationContext applicationContext, CircuitBreakerFactory circuitBreakerFactory) {
// register PolarisCircuitBreakerRestTemplateInterceptor bean
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext
.getAutowireCapableBeanFactory();
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
.genericBeanDefinition(PolarisCircuitBreakerRestTemplateInterceptor.class);
beanDefinitionBuilder.addConstructorArgValue(polarisCircuitBreakerRestTemplate);
beanDefinitionBuilder.addConstructorArgValue(applicationContext);
beanDefinitionBuilder.addConstructorArgValue(circuitBreakerFactory);
BeanDefinition interceptorBeanDefinition = beanDefinitionBuilder
.getRawBeanDefinition();
beanFactory.registerBeanDefinition(interceptorBeanName,
interceptorBeanDefinition);
}
}

@ -0,0 +1,68 @@
package com.tencent.cloud.polaris.circuitbreaker.resttemplate;
import java.io.IOException;
import java.lang.reflect.Method;
import com.tencent.polaris.api.pojo.CircuitBreakerStatus;
import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;
import org.apache.commons.lang.StringUtils;
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;
public class PolarisCircuitBreakerRestTemplateInterceptor implements ClientHttpRequestInterceptor {
private final PolarisCircuitBreakerRestTemplate polarisCircuitBreakerRestTemplate;
private final ApplicationContext applicationContext;
private final CircuitBreakerFactory circuitBreakerFactory;
public PolarisCircuitBreakerRestTemplateInterceptor(
PolarisCircuitBreakerRestTemplate polarisCircuitBreakerRestTemplate,
ApplicationContext applicationContext,
CircuitBreakerFactory circuitBreakerFactory
) {
this.polarisCircuitBreakerRestTemplate = polarisCircuitBreakerRestTemplate;
this.applicationContext = applicationContext;
this.circuitBreakerFactory = circuitBreakerFactory;
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
return circuitBreakerFactory.create(request.getURI().getHost() + "#" + request.getURI().getPath()).run(
() -> {
try {
return execution.execute(request, body);
}
catch (IOException e) {
throw new IllegalStateException(e);
}
},
t -> {
if (!StringUtils.isEmpty(polarisCircuitBreakerRestTemplate.fallback())) {
CircuitBreakerStatus.FallbackInfo fallbackInfo = new CircuitBreakerStatus.FallbackInfo(200, null, polarisCircuitBreakerRestTemplate.fallback());
return new PolarisCircuitBreakerHttpResponse(fallbackInfo);
}
if (polarisCircuitBreakerRestTemplate.fallbackClass() != null && polarisCircuitBreakerRestTemplate.fallbackClass().getSuperclass() != null) {
Method method = ReflectionUtils.findMethod(PolarisCircuitBreakerFallback.class, "fallback");
PolarisCircuitBreakerFallback polarisCircuitBreakerFallback = applicationContext.getBean(polarisCircuitBreakerRestTemplate.fallbackClass());
return (PolarisCircuitBreakerHttpResponse) ReflectionUtils.invokeMethod(method, polarisCircuitBreakerFallback);
}
if (t instanceof CallAbortedException) {
CircuitBreakerStatus.FallbackInfo fallbackInfo = ((CallAbortedException) t).getFallbackInfo();
if (fallbackInfo != null) {
return new PolarisCircuitBreakerHttpResponse(fallbackInfo);
}
}
throw new IllegalStateException(t);
}
);
}
}

@ -27,7 +27,7 @@ import org.springframework.web.bind.annotation.GetMapping;
* @author sean yu * @author sean yu
*/ */
@Primary @Primary
@FeignClient(name = "polaris-circuitbreaker-callee-service", fallback = ProviderBFallback.class) @FeignClient(name = "polaris-circuitbreaker-callee-service")
public interface ProviderB { public interface ProviderB {
/** /**

@ -1,34 +1,34 @@
/* ///*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available. // * Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
* // *
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. // * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
* // *
* Licensed under the BSD 3-Clause License (the "License"); // * Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License. // * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at // * You may obtain a copy of the License at
* // *
* https://opensource.org/licenses/BSD-3-Clause // * https://opensource.org/licenses/BSD-3-Clause
* // *
* Unless required by applicable law or agreed to in writing, software distributed // * 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 // * 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 // * CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License. // * specific language governing permissions and limitations under the License.
*/ // */
//
package com.tencent.cloud.polaris.circuitbreaker.feign.example; //package com.tencent.cloud.polaris.circuitbreaker.feign.example;
//
import org.springframework.stereotype.Component; //import org.springframework.stereotype.Component;
//
/** ///**
* Circuit breaker example callee fallback. // * Circuit breaker example callee fallback.
* // *
* @author sean yu // * @author sean yu
*/ // */
@Component //@Component
public class ProviderBFallback implements ProviderB { //public class ProviderBFallback implements ProviderB {
//
@Override // @Override
public String info() { // public String info() {
return "fallback: trigger the refuse for service b"; // return "fallback: trigger the refuse for service b";
} // }
} //}

@ -40,14 +40,19 @@ public class ServiceAController {
@Autowired @Autowired
private CircuitBreakerFactory circuitBreakerFactory; private CircuitBreakerFactory circuitBreakerFactory;
// @GetMapping("/getBServiceInfo")
// public String getBServiceInfo() {
// return circuitBreakerFactory
// .create("polaris-circuitbreaker-callee-service#/example/service/b/info")
// .run(() ->
// restTemplate.getForObject("/example/service/b/info", String.class),
// throwable -> "trigger the refuse for service b"
// );
// }
@GetMapping("/getBServiceInfo") @GetMapping("/getBServiceInfo")
public String getBServiceInfo() { public String getBServiceInfo() {
return circuitBreakerFactory return restTemplate.getForObject("/example/service/b/info", String.class);
.create("polaris-circuitbreaker-callee-service#/example/service/b/info")
.run(() ->
restTemplate.getForObject("/example/service/b/info", String.class),
throwable -> "trigger the refuse for service b"
);
} }
} }

@ -18,6 +18,9 @@
package com.tencent.cloud.polaris.circuitbreaker.resttemplate.example; package com.tencent.cloud.polaris.circuitbreaker.resttemplate.example;
import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreakerFallback;
import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreakerRestTemplate;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.client.loadbalancer.LoadBalanced;
@ -39,6 +42,7 @@ public class ServiceAResTemplate {
@Bean @Bean
@LoadBalanced @LoadBalanced
@PolarisCircuitBreakerRestTemplate
public RestTemplate restTemplate() { public RestTemplate restTemplate() {
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("http://polaris-circuitbreaker-callee-service"); DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("http://polaris-circuitbreaker-callee-service");
RestTemplate restTemplate = new RestTemplate(); RestTemplate restTemplate = new RestTemplate();

Loading…
Cancel
Save