前言
在使用openFeign+sentinel的时候总是要写很多的Fallback或者FallbackFactory就想能不能写一个统一的降级,自动注入失败的返回值。也方便管理
一、项目依赖
我的项目使用的是:springboot2.6.3+springcloud2021.0.1+springcloudalibaba2021.0.1.0及相关sentinel,openfeign等依赖
1、问题关键所在
我们只需要将SentinelFeign、SentinelInvocationHandler重写就可以
2、重写SentinelFeign
import com.alibaba.cloud.sentinel.feign.SentinelContractHolder;
import feign.Contract;
import feign.Feign;
import feign.InvocationHandlerFactory;
import feign.Target;
import org.springframework.beans.BeansException;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.FeignContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
/**
* 支持自动降级注入 重写 {@link com.alibaba.cloud.sentinel.feign.SentinelFeign}
*/
public final class CloudSentinelFeign {
private CloudSentinelFeign() {
}
public static Builder builder() {
return new Builder();
}
public static final class Builder extends Feign.Builder implements ApplicationContextAware {
private Contract contract = new Contract.Default();
private ApplicationContext applicationContext;
private FeignContext feignContext;
@Override
public Feign.Builder invocationHandlerFactory(InvocationHandlerFactory invocationHandlerFactory) {
throw new UnsupportedOperationException();
}
@Override
public Builder contract(Contract contract) {
this.contract = contract;
return this;
}
@Override
public Feign build() {
super.invocationHandlerFactory(new InvocationHandlerFactory() {
@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
// 查找 FeignClient 上的 降级策略
FeignClient feignClient = AnnotationUtils.findAnnotation(target.type(), FeignClient.class);
Class<?> fallback = feignClient.fallback();
Class<?> fallbackFactory = feignClient.fallbackFactory();
String beanName = feignClient.contextId();
if (!StringUtils.hasText(beanName)) {
beanName = feignClient.name();
}
Object fallbackInstance;
FallbackFactory<?> fallbackFactoryInstance;
if (void.class != fallback) {
fallbackInstance = getFromContext(beanName, "fallback", fallback, target.type());
return new CloudSentinelInvocationHandler(target, dispatch,
new FallbackFactory.Default(fallbackInstance));
}
if (void.class != fallbackFactory) {
fallbackFactoryInstance = (FallbackFactory<?>) getFromContext(beanName, "fallbackFactory",
fallbackFactory, FallbackFactory.class);
return new CloudSentinelInvocationHandler(target, dispatch, fallbackFactoryInstance);
}
return new CloudSentinelInvocationHandler(target, dispatch);
}
private Object getFromContext(String name, String type, Class<?> fallbackType, Class<?> targetType) {
Object fallbackInstance = feignContext.getInstance(name, fallbackType);
if (fallbackInstance == null) {
throw new IllegalStateException(String.format(
"No %s instance of type %s found for feign client %s", type, fallbackType, name));
}
if (!targetType.isAssignableFrom(fallbackType)) {
throw new IllegalStateException(String.format(
"Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
type, fallbackType, targetType, name));
}
return fallbackInstance;
}
});
super.contract(new SentinelContractHolder(contract));
return super.build();
}
private Object getFieldValue(Object instance, String fieldName) {
Field field = ReflectionUtils.findField(instance.getClass(), fieldName);
field.setAccessible(true);
try {
return field.get(instance);
} catch (IllegalAccessException e) {
// ignore
}
return null;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
feignContext = this.applicationContext.getBean(FeignContext.class);
}
}
}
3、重写SentinelInvocationHandler
import cn.dys.common.core.util.Result;
import cn.dys.common.sentinel.util.SentinelExceptionUtil;
import com.alibaba.cloud.sentinel.feign.SentinelContractHolder;
import com.alibaba.cloud.sentinel.feign.SentinelInvocationHandler;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import feign.Feign;
import feign.InvocationHandlerFactory;
import feign.MethodMetadata;
import feign.Target;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
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 static feign.Util.checkNotNull;
/**
* 支持自动降级注入 重写 {@link com.alibaba.cloud.sentinel.feign.SentinelInvocationHandler}
*/
@Slf4j
public class CloudSentinelInvocationHandler implements InvocationHandler {
private final Target<?> target;
private final Map<Method, InvocationHandlerFactory.MethodHandler> dispatch;
private FallbackFactory<?> fallbackFactory;
private Map<Method, Method> fallbackMethodMap;
CloudSentinelInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch,
FallbackFactory<?> fallbackFactory) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch");
this.fallbackFactory = fallbackFactory;
this.fallbackMethodMap = toFallbackMethod(dispatch);
}
CloudSentinelInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch");
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
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();
}
Object result;
InvocationHandlerFactory.MethodHandler methodHandler = this.dispatch.get(method);
// only handle by HardCodedTarget
if (target instanceof Target.HardCodedTarget) {
Target.HardCodedTarget hardCodedTarget = (Target.HardCodedTarget) target;
MethodMetadata methodMetadata = SentinelContractHolder.METADATA_MAP
.get(hardCodedTarget.type().getName()
+ Feign.configKey(hardCodedTarget.type(), method));
// resource default is HttpMethod:protocol://url
if (methodMetadata == null) {
result = methodHandler.invoke(args);
} else {
String resourceName = methodMetadata.template().method().toUpperCase()
+ ":" + hardCodedTarget.url() + methodMetadata.template().path();
Entry entry = null;
try {
ContextUtil.enter(resourceName);
entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
result = methodHandler.invoke(args);
} catch (Throwable ex) {
// fallback handle
if (!BlockException.isBlockException(ex)) {
Tracer.traceEntry(ex, entry);
}
if (fallbackFactory != null) {
try {
Object fallbackResult = fallbackMethodMap.get(method)
.invoke(fallbackFactory.create(ex), args);
return fallbackResult;
} catch (IllegalAccessException e) {
// shouldn't happen as method is public due to being an
// interface
throw new AssertionError(e);
} catch (InvocationTargetException e) {
throw new AssertionError(e.getCause());
}
} else {
//主动降级注入的关键点
// 若是Result类型 执行自动降级返回Result
if (Result.class == method.getReturnType()) {
log.error("feign 服务间调用异常", ex);
//SentinelExceptionUtil自己封装的判断sentinel熔断类型
return SentinelExceptionUtil.getResult(ex);
} else {
throw ex;
}
}
} finally {
if (entry != null) {
entry.exit(1, args);
}
ContextUtil.exit();
}
}
} else {
// other target type using default strategy
result = methodHandler.invoke(args);
}
return result;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof SentinelInvocationHandler) {
CloudSentinelInvocationHandler other = (CloudSentinelInvocationHandler) obj;
return target.equals(other.target);
}
return false;
}
@Override
public int hashCode() {
return target.hashCode();
}
@Override
public String toString() {
return target.toString();
}
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;
}
}
4、注册Feign.Builder
/**
* sentinel 配置
*/
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(SentinelFeignAutoConfiguration.class)
public class SentinelAutoConfiguration {
@Bean
@Primary
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.sentinel.enabled")
public Feign.Builder feignSentinelBuilderPrimary() {
return CloudSentinelFeign.builder();
}
}
因为没有想到办法解决SentinelFeignAutoConfiguration中的feignSentinelBuilder,所以给bean修改名称并增加primary注解绕过@ConditionalOnMissingBean注解的限制,让程序默认使用自定义bean。