背景
openfeign降级常规操作如下:
此种方式太过于麻烦,每一个方法都要写一个降级逻辑,并且降级逻辑大多是雷同的。
目标
提供默认的降级方式,若openfeign未指定FallbackFactory则走默认降级方式,否则就走自定义的FallbackFactory降级。文末附实现代码下载
版本
- springcloud
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
- sentinel
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2021.0.1.0</version>
</dependency>
实现
- 查看spring-cloud-starter-alibaba-sentinel降级源码
从上述源码可以看到若不存在自定义的fallbackFactory,则直接抛异常,我们需要做的就是在抛异常之前插入我们的默认降级逻辑。这里有如下两种插入代码块方式:
1:Java ASM字节码修改技术插入,过往文章:https://blog.csdn.net/qq_41633199/article/details/117160642
2:参照SentinelInvocationHandler源码新建一个java文件手动写入代码块,再替换sentinel的默认实现
这里我选择方法2. - 添加默认降级逻辑
- 参照SentinelInvocationHandler.java源码新建一个java文件DegradeSentinelInvocationHandler.java
- 手动写入默认降级方法
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 {
//没有自定义降级处理,并且返回格式是R,则进行统一降级处理
if (method.getReturnType().equals(R.class)) {
log.warn(ex.getMessage(), ex);
String failMsg = ofNullable(ex).map(Throwable::getMessage).filter(CharSequenceUtil::isNotEmpty).orElse("系统异常,请联系管理员");
return R.error(failMsg);
}
throw ex;
}
- 参照com.alibaba.cloud.sentinel.feign.SentinelFeign.java新建DegradeSentinelFeign.java构建Feign对象
核心代码:
@Override
public Feign build() {
super.invocationHandlerFactory(new InvocationHandlerFactory() {
@Override
public InvocationHandler create(Target target,
Map<Method, MethodHandler> dispatch) {
GenericApplicationContext gctx = (GenericApplicationContext) DegradeSentinelFeign.Builder.this.applicationContext;
BeanDefinition def = gctx.getBeanDefinition(target.type().getName());
/*
* Due to the change of the initialization sequence,
* BeanFactory.getBean will cause a circular dependency. So
* FeignClientFactoryBean can only be obtained from BeanDefinition
*/
FeignClientFactoryBean feignClientFactoryBean = (FeignClientFactoryBean) def
.getAttribute("feignClientsRegistrarFactoryBean");
Class fallback = feignClientFactoryBean.getFallback();
Class fallbackFactory = feignClientFactoryBean.getFallbackFactory();
String beanName = feignClientFactoryBean.getContextId();
if (!StringUtils.hasText(beanName)) {
beanName = (String) getFieldValue(feignClientFactoryBean, "name");
}
Object fallbackInstance;
FallbackFactory fallbackFactoryInstance;
// check fallback and fallbackFactory properties
if (void.class != fallback) {
fallbackInstance = getFromContext(beanName, "fallback", fallback,
target.type());
return new DegradeSentinelInvocationHandler(target, dispatch,
new FallbackFactory.Default(fallbackInstance));
}
if (void.class != fallbackFactory) {
fallbackFactoryInstance = (FallbackFactory) getFromContext(
beanName, "fallbackFactory", fallbackFactory,
FallbackFactory.class);
return new DegradeSentinelInvocationHandler(target, dispatch,
fallbackFactoryInstance);
}
return new DegradeSentinelInvocationHandler(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();
}
- 参照com.alibaba.cloud.sentinel.feign.SentinelFeignAutoConfiguration.java新建DegradeSentinelFeignAutoConfiguration.java替换Sentinel自带降级处理逻辑
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({SphU.class, Feign.class, SentinelFeignAutoConfiguration.class})
@AutoConfigureBefore(SentinelFeignAutoConfiguration.class)
public class DegradeSentinelFeignAutoConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.sentinel.enabled")
public Feign.Builder feignSentinelBuilder() {
return DegradeSentinelFeign.builder();
}
}
- 在spring.factories文件指定自定义降级配置类
完整代码下载
springboot openfeign Sentinel统一降级处理实现代码