add PolarisCircuitBreaker feign support

pull/916/head
seanyu 3 years ago
parent f6829e726e
commit 63c641848c

@ -18,12 +18,15 @@
package com.tencent.cloud.polaris.circuitbreaker.config; package com.tencent.cloud.polaris.circuitbreaker.config;
import com.tencent.cloud.polaris.circuitbreaker.feign.PolarisCircuitBreakerNameResolver; import com.tencent.cloud.polaris.circuitbreaker.feign.PolarisCircuitBreakerNameResolver;
import com.tencent.cloud.polaris.circuitbreaker.feign.PolarisFeignCircuitBreakerTargeter;
import feign.Feign; import feign.Feign;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.cloud.openfeign.CircuitBreakerNameResolver; import org.springframework.cloud.openfeign.CircuitBreakerNameResolver;
import org.springframework.cloud.openfeign.FeignClientFactoryBean; import org.springframework.cloud.openfeign.FeignClientFactoryBean;
import org.springframework.cloud.openfeign.Targeter;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -43,4 +46,9 @@ public class PolarisCircuitBreakerFeignClientAutoConfiguration {
return new PolarisCircuitBreakerNameResolver(); return new PolarisCircuitBreakerNameResolver();
} }
@Bean
public Targeter polarisFeignCircuitBreakerTargeter(CircuitBreakerFactory circuitBreakerFactory, CircuitBreakerNameResolver circuitBreakerNameResolver) {
return new PolarisFeignCircuitBreakerTargeter(circuitBreakerFactory, circuitBreakerNameResolver);
}
} }

@ -4,18 +4,17 @@ import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreak
import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ApplicationContext; 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;
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnPolarisCircuitBreakerEnabled @ConditionalOnPolarisCircuitBreakerEnabled
@ConditionalOnClass(name = "org.springframework.web.client.RestTemplate")
@AutoConfigureAfter(PolarisCircuitBreakerAutoConfiguration.class) @AutoConfigureAfter(PolarisCircuitBreakerAutoConfiguration.class)
public class PolarisCircuitBreakerRestTemplateAutoConfiguration { public class PolarisCircuitBreakerRestTemplateAutoConfiguration {
@Bean @Bean
@ConditionalOnClass(name = "org.springframework.web.client.RestTemplate")
public static PolarisCircuitBreakerRestTemplateBeanPostProcessor polarisCircuitBreakerRestTemplateBeanPostProcessor( public static PolarisCircuitBreakerRestTemplateBeanPostProcessor polarisCircuitBreakerRestTemplateBeanPostProcessor(
ApplicationContext applicationContext) { ApplicationContext applicationContext) {
return new PolarisCircuitBreakerRestTemplateBeanPostProcessor(applicationContext); return new PolarisCircuitBreakerRestTemplateBeanPostProcessor(applicationContext);

@ -0,0 +1,80 @@
package com.tencent.cloud.polaris.circuitbreaker.feign;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import com.tencent.polaris.api.pojo.CircuitBreakerStatus;
import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;
import feign.Request;
import feign.RequestTemplate;
import feign.Response;
import feign.codec.Decoder;
import org.springframework.cloud.openfeign.FallbackFactory;
public class PolarisCircuitBreakerFallbackFactory implements FallbackFactory {
private final Decoder decoder;
public PolarisCircuitBreakerFallbackFactory(Decoder decoder) {
this.decoder = decoder;
}
@Override
public Object create(Throwable t) {
return new DefaultFallback(t, decoder);
}
public class DefaultFallback {
private final Throwable t;
private final Decoder decoder;
public DefaultFallback(Throwable t, Decoder decoder) {
this.t = t;
this.decoder = decoder;
}
public Object fallback(Method method) {
if (t instanceof CallAbortedException) {
CircuitBreakerStatus.FallbackInfo fallbackInfo = ((CallAbortedException) t).getFallbackInfo();
if (fallbackInfo != null) {
Response.Builder responseBuilder = Response.builder()
.status(fallbackInfo.getCode());
if (fallbackInfo.getHeaders() != null) {
Map<String, Collection<String>> headers = new HashMap<>();
fallbackInfo.getHeaders().forEach((k, v) -> {
if (headers.containsKey(k)) {
headers.get(k).add(v);
}
else {
headers.put(k, new HashSet<>(Collections.singleton(v)));
}
});
responseBuilder.headers(headers);
}
if (fallbackInfo.getBody() != null) {
responseBuilder.body(fallbackInfo.getBody(), StandardCharsets.UTF_8);
}
// Feign Response need a nonnull Request, which is not important in fallback response, so we create a fake one
Request request = Request.create(Request.HttpMethod.GET, "/", new HashMap<>(), Request.Body.empty(), new RequestTemplate());
responseBuilder.request(request);
try (Response response = responseBuilder.build()) {
return decoder.decode(response, method.getGenericReturnType());
}
catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
throw new IllegalStateException(t);
}
}
}

@ -0,0 +1,75 @@
package com.tencent.cloud.polaris.circuitbreaker.feign;
import feign.Feign;
import feign.Target;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.cloud.openfeign.CircuitBreakerNameResolver;
import org.springframework.cloud.openfeign.FallbackFactory;
public class PolarisFeignCircuitBreaker {
private PolarisFeignCircuitBreaker() {
throw new IllegalStateException("Don't instantiate a utility class");
}
/**
* @return builder for Feign CircuitBreaker integration
*/
public static PolarisFeignCircuitBreaker.Builder builder(Feign.Builder delegateBuilder) {
return new PolarisFeignCircuitBreaker.Builder(delegateBuilder);
}
/**
* Builder for Feign CircuitBreaker integration.
*/
public static final class Builder extends Feign.Builder {
private final Feign.Builder delegateBuilder;
public Builder(Feign.Builder delegateBuilder) {
this.delegateBuilder = delegateBuilder;
}
private CircuitBreakerFactory circuitBreakerFactory;
private String feignClientName;
private CircuitBreakerNameResolver circuitBreakerNameResolver;
public PolarisFeignCircuitBreaker.Builder circuitBreakerFactory(CircuitBreakerFactory circuitBreakerFactory) {
this.circuitBreakerFactory = circuitBreakerFactory;
return this;
}
public PolarisFeignCircuitBreaker.Builder feignClientName(String feignClientName) {
this.feignClientName = feignClientName;
return this;
}
public PolarisFeignCircuitBreaker.Builder circuitBreakerNameResolver(CircuitBreakerNameResolver circuitBreakerNameResolver) {
this.circuitBreakerNameResolver = circuitBreakerNameResolver;
return this;
}
public <T> T target(Target<T> target, T fallback) {
return build(fallback != null ? new FallbackFactory.Default<T>(fallback) : null).newInstance(target);
}
public <T> T target(Target<T> target, FallbackFactory<? extends T> fallbackFactory) {
return build(fallbackFactory).newInstance(target);
}
@Override
public <T> T target(Target<T> target) {
return build(null).newInstance(target);
}
public Feign build(final FallbackFactory<?> nullableFallbackFactory) {
delegateBuilder.invocationHandlerFactory((target, dispatch) -> new PolarisFeignCircuitBreakerInvocationHandler(
circuitBreakerFactory, feignClientName, target, dispatch, nullableFallbackFactory, circuitBreakerNameResolver, this.decoder));
return delegateBuilder.build();
}
}
}

@ -0,0 +1,178 @@
package com.tencent.cloud.polaris.circuitbreaker.feign;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import feign.InvocationHandlerFactory;
import feign.Target;
import feign.codec.Decoder;
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.cloud.client.circuitbreaker.NoFallbackAvailableException;
import org.springframework.cloud.openfeign.CircuitBreakerNameResolver;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import static feign.Util.checkNotNull;
public class PolarisFeignCircuitBreakerInvocationHandler implements InvocationHandler {
private final CircuitBreakerFactory factory;
private final String feignClientName;
private final Target<?> target;
private final Map<Method, InvocationHandlerFactory.MethodHandler> dispatch;
private final FallbackFactory<?> nullableFallbackFactory;
private final Map<Method, Method> fallbackMethodMap;
private final CircuitBreakerNameResolver circuitBreakerNameResolver;
private final Decoder decoder;
public PolarisFeignCircuitBreakerInvocationHandler(CircuitBreakerFactory factory, String feignClientName, Target<?> target,
Map<Method, InvocationHandlerFactory.MethodHandler> dispatch, FallbackFactory<?> nullableFallbackFactory,
CircuitBreakerNameResolver circuitBreakerNameResolver, Decoder decoder) {
this.factory = factory;
this.feignClientName = feignClientName;
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch");
this.fallbackMethodMap = toFallbackMethod(dispatch);
this.nullableFallbackFactory = nullableFallbackFactory;
this.circuitBreakerNameResolver = circuitBreakerNameResolver;
this.decoder = decoder;
}
@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
// code is the same as ReflectiveFeign.FeignInvocationHandler
if ("equals".equals(method.getName())) {
try {
Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
}
catch (IllegalArgumentException e) {
return false;
}
}
else if ("hashCode".equals(method.getName())) {
return hashCode();
}
else if ("toString".equals(method.getName())) {
return toString();
}
String circuitName = circuitBreakerNameResolver.resolveCircuitBreakerName(feignClientName, target, method);
CircuitBreaker circuitBreaker = factory.create(circuitName);
Supplier<Object> supplier = asSupplier(method, args);
Function<Throwable, Object> fallbackFunction;
if (this.nullableFallbackFactory != null) {
fallbackFunction = throwable -> {
Object fallback = this.nullableFallbackFactory.create(throwable);
try {
return this.fallbackMethodMap.get(method).invoke(fallback, args);
}
catch (Exception exception) {
unwrapAndRethrow(exception);
}
return null;
};
}
else {
fallbackFunction = throwable -> {
PolarisCircuitBreakerFallbackFactory.DefaultFallback fallback =
(PolarisCircuitBreakerFallbackFactory.DefaultFallback) new PolarisCircuitBreakerFallbackFactory(this.decoder).create(throwable);
return fallback.fallback(method);
};
}
return circuitBreaker.run(supplier, fallbackFunction);
}
private void unwrapAndRethrow(Exception exception) {
if (exception instanceof InvocationTargetException || exception instanceof NoFallbackAvailableException) {
Throwable underlyingException = exception.getCause();
if (underlyingException instanceof RuntimeException) {
throw (RuntimeException) underlyingException;
}
if (underlyingException != null) {
throw new IllegalStateException(underlyingException);
}
throw new IllegalStateException(exception);
}
}
private Supplier<Object> asSupplier(final Method method, final Object[] args) {
final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
final Thread caller = Thread.currentThread();
return () -> {
boolean isAsync = caller != Thread.currentThread();
try {
if (isAsync) {
RequestContextHolder.setRequestAttributes(requestAttributes);
}
return dispatch.get(method).invoke(args);
}
catch (RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
finally {
if (isAsync) {
RequestContextHolder.resetRequestAttributes();
}
}
};
}
/**
* 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) {
PolarisFeignCircuitBreakerInvocationHandler other = (PolarisFeignCircuitBreakerInvocationHandler) obj;
return this.target.equals(other.target);
}
return false;
}
@Override
public int hashCode() {
return this.target.hashCode();
}
@Override
public String toString() {
return this.target.toString();
}
}

@ -0,0 +1,82 @@
package com.tencent.cloud.polaris.circuitbreaker.feign;
import feign.Feign;
import feign.Target;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.cloud.openfeign.CircuitBreakerNameResolver;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.cloud.openfeign.FeignCircuitBreaker;
import org.springframework.cloud.openfeign.FeignClientFactoryBean;
import org.springframework.cloud.openfeign.FeignContext;
import org.springframework.cloud.openfeign.Targeter;
import org.springframework.util.StringUtils;
public class PolarisFeignCircuitBreakerTargeter implements Targeter {
private final CircuitBreakerFactory circuitBreakerFactory;
private final CircuitBreakerNameResolver circuitBreakerNameResolver;
public PolarisFeignCircuitBreakerTargeter(CircuitBreakerFactory circuitBreakerFactory, CircuitBreakerNameResolver circuitBreakerNameResolver) {
this.circuitBreakerFactory = circuitBreakerFactory;
this.circuitBreakerNameResolver = circuitBreakerNameResolver;
}
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget<T> target) {
if (!(feign instanceof FeignCircuitBreaker.Builder)) {
return feign.target(target);
}
PolarisFeignCircuitBreaker.Builder builder = PolarisFeignCircuitBreaker.builder(feign);
String name = !StringUtils.hasText(factory.getContextId()) ? factory.getName() : factory.getContextId();
Class<?> fallback = factory.getFallback();
if (fallback != void.class) {
return targetWithFallback(name, context, target, builder, fallback);
}
Class<?> fallbackFactory = factory.getFallbackFactory();
if (fallbackFactory != void.class) {
return targetWithFallbackFactory(name, context, target, builder, fallbackFactory);
}
return builder(name, builder).target(target);
}
private <T> T targetWithFallbackFactory(String feignClientName, FeignContext context,
Target.HardCodedTarget<T> target, PolarisFeignCircuitBreaker.Builder builder, Class<?> fallbackFactoryClass) {
FallbackFactory<? extends T> fallbackFactory = (FallbackFactory<? extends T>) getFromContext("fallbackFactory",
feignClientName, context, fallbackFactoryClass, FallbackFactory.class);
return builder(feignClientName, builder).target(target, fallbackFactory);
}
private <T> T targetWithFallback(String feignClientName, FeignContext context, Target.HardCodedTarget<T> target,
PolarisFeignCircuitBreaker.Builder builder, Class<?> fallback) {
T fallbackInstance = getFromContext("fallback", feignClientName, context, fallback, target.type());
return builder(feignClientName, builder).target(target, fallbackInstance);
}
private <T> T getFromContext(String fallbackMechanism, String feignClientName, FeignContext context,
Class<?> beanType, Class<T> targetType) {
Object fallbackInstance = context.getInstance(feignClientName, beanType);
if (fallbackInstance == null) {
throw new IllegalStateException(
String.format("No " + fallbackMechanism + " instance of type %s found for feign client %s",
beanType, feignClientName));
}
if (!targetType.isAssignableFrom(beanType)) {
throw new IllegalStateException(String.format("Incompatible " + fallbackMechanism
+ " instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
beanType, targetType, feignClientName));
}
return (T) fallbackInstance;
}
private PolarisFeignCircuitBreaker.Builder builder(String feignClientName, PolarisFeignCircuitBreaker.Builder builder) {
return builder
.circuitBreakerFactory(circuitBreakerFactory)
.feignClientName(feignClientName)
.circuitBreakerNameResolver(circuitBreakerNameResolver);
}
}

@ -26,8 +26,7 @@ import org.springframework.web.bind.annotation.GetMapping;
* *
* @author sean yu * @author sean yu
*/ */
@Primary @FeignClient(name = "polaris-circuitbreaker-callee-service", contextId = "use-polaris-fallback")
@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 ProviderBWithFallback {
//
// @Override @Override
// public String info() { public String info() {
// return "fallback: trigger the refuse for service b"; return "fallback: trigger the refuse for service b";
// } }
//} }

@ -0,0 +1,17 @@
package com.tencent.cloud.polaris.circuitbreaker.feign.example;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
@FeignClient(name = "polaris-circuitbreaker-callee-service", contextId = "use-code-fallback", fallback = ProviderBFallback.class)
public interface ProviderBWithFallback {
/**
* Get info of service B.
*
* @return info of service B
*/
@GetMapping("/example/service/b/info")
String info();
}

@ -35,12 +35,24 @@ public class ServiceAController {
@Autowired @Autowired
private ProviderB polarisServiceB; private ProviderB polarisServiceB;
@Autowired
private ProviderBWithFallback providerBWithFallback;
/**
* Get info of Service B by Feign.
* @return info of Service B
*/
@GetMapping("/getBServiceInfo/fallbackFromLocalCode")
public String getBServiceInfoFallbackFromLocalCode() {
return providerBWithFallback.info();
}
/** /**
* Get info of Service B by Feign. * Get info of Service B by Feign.
* @return info of Service B * @return info of Service B
*/ */
@GetMapping("/getBServiceInfo") @GetMapping("/getBServiceInfo/fallbackFromPolaris")
public String getBServiceInfo() { public String getBServiceInfoFallbackFromPolaris() {
return polarisServiceB.info(); return polarisServiceB.info();
} }

@ -1,6 +1,8 @@
package com.tencent.cloud.polaris.circuitbreaker.resttemplate.example; package com.tencent.cloud.polaris.circuitbreaker.resttemplate.example;
import java.util.HashMap;
import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreakerFallback; import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreakerFallback;
import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreakerHttpResponse; import com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreakerHttpResponse;
@ -12,6 +14,9 @@ public class CustomPolarisCircuitBreakerFallback implements PolarisCircuitBreake
public PolarisCircuitBreakerHttpResponse fallback() { public PolarisCircuitBreakerHttpResponse fallback() {
return new PolarisCircuitBreakerHttpResponse( return new PolarisCircuitBreakerHttpResponse(
200, 200,
new HashMap<String, String>(){{
put("Content-Type", "application/json");
}},
"{\"msg\": \"this is a fallback class\"}"); "{\"msg\": \"this is a fallback class\"}");
} }
} }

@ -62,13 +62,13 @@ public class ServiceAController {
} }
@GetMapping("/getBServiceInfo/fallback") @GetMapping("/getBServiceInfo/fallback")
public String getBServiceInfoFallback() { public ResponseEntity<String> getBServiceInfoFallback() {
return fallbackRestTemplate.getForObject("/example/service/b/info", String.class); return fallbackRestTemplate.getForEntity("/example/service/b/info", String.class);
} }
@GetMapping("/getBServiceInfo/fallbackClass") @GetMapping("/getBServiceInfo/fallbackClass")
public String getBServiceInfoFallbackClass() { public ResponseEntity<String> getBServiceInfoFallbackClass() {
return fallbackClassRestTemplate.getForObject("/example/service/b/info", String.class); return fallbackClassRestTemplate.getForEntity("/example/service/b/info", String.class);
} }
} }

Loading…
Cancel
Save