如何自定義feign調(diào)用實(shí)現(xiàn)hystrix超時(shí)、異常熔斷
需求描述
spring cloud 項(xiàng)目中feign 整合 hystrix經(jīng)常使用,但是最近發(fā)現(xiàn)hystrix功能強(qiáng)大,但是對我們來說有些大材小用。
首先我只需要他的一個(gè)熔斷作用,就是說請求超時(shí)、異常了返回 FeignClient注解中配置的fallback,不需要非阻塞操作、也不需要重試,hystrix 調(diào)用feign時(shí)候做了線程池隔離處理,這樣增加了項(xiàng)目復(fù)雜度(線程池參數(shù)配置、線程少了請求服務(wù)直接拒絕,多了線程得管理。。。)
目前feign 超時(shí)之后是直接拋異常的,這樣的話雖然是及時(shí)熔斷了,但是正常的程序邏輯不走了配置的fallback也沒有作用,這個(gè)配置項(xiàng)得配合 hystrix 才行。
我需要的是這樣的效果
try{
feign.api();
}catch(){
return fallback();
}
但是每個(gè)feign調(diào)用都手動加上try..catch 實(shí)在是太low了,最好能寫個(gè)類似切面一樣的玩意。
這時(shí)候就想到了 hystrix,既然人家框架已經(jīng)做了,我直接看下代碼,copy不完了么
源碼學(xué)習(xí)
前兩天發(fā)布了一篇文章也是關(guān)于feign、hystrix 調(diào)用集成的
基于之前的分析關(guān)鍵代碼
HystrixInvocationHandler (feign.hystrix)
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
.............
// setterMethodMap 封裝 hystrixCommand 配置信息(超時(shí)時(shí)間、是否重試.....)
HystrixCommand<Object> hystrixCommand = new HystrixCommand<Object>(setterMethodMap.get(method)) {
@Override
protected Object run() throws Exception {
....
HystrixInvocationHandler.this.dispatch.get(method).invoke(args);
....
}
@Override
protected Object getFallback() {
.........
}
};
......
return hystrixCommand.execute();
}
按照之前分析源碼方式,直接看哪里被調(diào)用了就可以看到, hystrix 實(shí)際上自己封裝了一個(gè) feign.Builer 類名是 feign.hystrix.HystrixFeign.Builder 用的是建造者模式,生成的類是在調(diào)用服務(wù)時(shí)用到
看到 關(guān)鍵的 build() 方法
Feign build(final FallbackFactory<?> nullableFallbackFactory) {
super.invocationHandlerFactory(new InvocationHandlerFactory() {
// 重新定義一個(gè) InvocationHandler 實(shí)現(xiàn) 類似 aop效果
@Override public InvocationHandler create(Target target,
Map<Method, MethodHandler> dispatch) {
return new HystrixInvocationHandler(target, dispatch, setterFactory, nullableFallbackFactory);
}
});
super.contract(new HystrixDelegatingContract(contract));
return super.build();
}
spring 動態(tài)代理我這里不多說了,核心就是 InvocationHandler (如果是jdk動態(tài)代理的話),那么 feign 這里也是,我們看看feign 調(diào)用聲明是個(gè)接口,實(shí)際上是spring 動態(tài)代理生成了代理類,調(diào)用方法時(shí)實(shí)際調(diào)用的是
java.lang.reflect.InvocationHandler#invoke
方案構(gòu)想
那么我們只需要借鑒下 hystrix 的方式,自己實(shí)現(xiàn)一個(gè)feign.build ,將 InvocationHandler 換成自己的,
然后在我們自己的 InvocationHandler 中調(diào)用feign 官方的 InvocationHandler 就行,也就是
feign.hystrix.HystrixInvocationHandler#invoke
這個(gè)方法中的
this.dispatch.get(method).invoke(args);
這個(gè)代碼
方案具體代碼實(shí)現(xiàn)
方案一
自己實(shí)現(xiàn)
import feign.Feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
/**
* 自定義feign 構(gòu)建
* @author hgf
*/
public class CusFeignBuilder extends Feign.Builder{
public CusFeignBuilder() {
this.invocationHandlerFactory((target, dispatch) -> {
Class<?> type = target.type();
FeignClient annotation = type.getAnnotation(FeignClient.class);
// 構(gòu)造 fallback 實(shí)例
Object fallBackObj = null;
if (annotation != null && !annotation.fallback().equals(void.class)) {
try {
fallBackObj = annotation.fallback().newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
return new CusFeignInvocationHandler(target, dispatch, fallBackObj);
});
}
}
import feign.Feign;
import feign.InvocationHandlerFactory;
import feign.Target;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FeignClient;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static com.eco.common.utils.Md5Util.logger;
import static feign.Util.checkNotNull;
/**
* 自定義的feign調(diào)用
*/
@Slf4j
public class CusFeignInvocationHandler implements InvocationHandler {
private final Target target;
private final Map<Method, InvocationHandlerFactory.MethodHandler> dispatch;
private final Object fallbackObj;
private final Map<String, Method> fallbackMethodMap = new ConcurrentHashMap<>();
CusFeignInvocationHandler(Target target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch, Object fallbackObj) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
this.fallbackObj = fallbackObj;
}
public Object feignInvoke(Object proxy, Method method, 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();
}
return dispatch.get(method).invoke(args);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof CusFeignInvocationHandler) {
CusFeignInvocationHandler other = (CusFeignInvocationHandler) obj;
return target.equals(other.target);
}
return false;
}
@Override
public int hashCode() {
return target.hashCode();
}
@Override
public String toString() {
return target.toString();
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
return feignInvoke(proxy, method, args);
} catch (Throwable throwable) {
String configKey = Feign.configKey(target.type(), method);
logger.error("{} 請求 出現(xiàn)異常 ==> {}", configKey, throwable.getMessage());
try {
return getFallbackReturn(method, args, throwable);
} catch (Throwable e) {
throw throwable;
}
}
}
/**
* 反射調(diào)用 {@link FeignClient#fallback()}生成失敗返回值
* @param method 當(dāng)前feign方法
* @param args 參數(shù)
* @param throwable 異常
*/
public Object getFallbackReturn(Method method, Object[] args, Throwable throwable) throws Throwable {
if (fallbackObj == null) {
throw new RuntimeException("fallbackObj is null");
}
String configKey = Feign.configKey(target.type(), method);
Method fallbackMethod = fallbackMethodMap.get(configKey);
if (fallbackMethod == null) {
Class<?> declaringClass = method.getDeclaringClass();
FeignClient annotation = declaringClass.getAnnotation(FeignClient.class);
if (annotation == null) {
throw new RuntimeException("FeignClient annotation not found");
}
// 失敗返回
Class<?> fallback = annotation.fallback();
fallbackMethod = fallback.getMethod(method.getName(), method.getParameterTypes());
fallbackMethodMap.put(configKey, fallbackMethod);
}
if (fallbackMethod == null) {
throw new RuntimeException("fallbackMethodMap not found");
}
return fallbackMethod.invoke(fallbackObj, args);
}
}
然后在 spring 容器中注冊這個(gè)bean就行
@Bean
CusFeignBuilder cusFeignBuilder(){
return new CusFeignBuilder();
}
方案二
集成 sentinel ,今天寫博客再回頭看源碼時(shí)候才發(fā)現(xiàn)的
加入依賴
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
配置開啟
feign.sentinel.enabled=true
手動實(shí)現(xiàn)feign 接口,將實(shí)體類注冊到 spring 中
@Component
public class DeviceApiFallBack implements DeviceApi{
@Override
public ServerResponse<String> login(String appId) {
return ServerResponse.createByErrorMessage("請求失敗");
}
}
其實(shí)看代碼知道原理一樣,無非實(shí)現(xiàn)方式不一樣
兩個(gè)方案其實(shí)都行,方案一自己實(shí)現(xiàn)代碼量多,方案二sentinel 官方實(shí)現(xiàn),但是需要引入依賴,增加復(fù)雜度,而且 接口實(shí)現(xiàn)需要注冊到spring 中
目前我選的還是方案一,簡單。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
springboot cloud使用eureka整合分布式事務(wù)組件Seata 的方法
這篇文章主要介紹了springboot cloud使用eureka整合分布式事務(wù)組件Seata 的方法 ,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-05-05
Java ProcessBuilder執(zhí)行多次CMD命令的使用
本文介紹了Java的ProcessBuilder類,該類用于執(zhí)行外部命令,通過ProcessBuilder,我們可以在Java程序中靈活地執(zhí)行多次CMD命令,并控制輸入輸出流以及工作目錄等,感興趣的可以了解一下2024-11-11
非常適合新手學(xué)生的Java線程池優(yōu)化升級版
作者是一個(gè)來自河源的大三在校生,以下筆記都是作者自學(xué)之路的一些淺薄經(jīng)驗(yàn),如有錯誤請指正,將來會不斷的完善筆記,幫助更多的Java愛好者入門2022-03-03
Java中JUC包(java.util.concurrent)下的常用子類
相信大家已經(jīng)對并發(fā)機(jī)制中出現(xiàn)的很多的常見知識點(diǎn)進(jìn)行了總結(jié),下面這篇文章主要給大家介紹了關(guān)于Java中JUC包(java.util.concurrent)下的常用子類的相關(guān)資料,文中通過圖文以及示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-12-12
如何使用Spring+redis實(shí)現(xiàn)對session的分布式管理
Java Swing JRadioButton單選按鈕具體使用
SpringCloud Gateway中斷言路由和過濾器的使用詳解

