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