需求描述
spring cloud 项目中feign 整合 hystrix经常使用,但是最近发现hystrix功能强大,但是对我们来说有些大材小用。
首先我只需要他的一个熔断作用,就是说请求超时、异常了返回 FeignClient注解中配置的fallback,不需要非阻塞操作、也不需要重试,hystrix 调用feign时候做了线程池隔离处理,这样增加了项目复杂度(线程池参数配置、线程少了请求服务直接拒绝,多了线程得管理。。。)
目前feign 超时之后是直接抛异常的,这样的话虽然是及时熔断了,但是正常的程序逻辑不走了配置的fallback也没有作用,这个配置项得配合 hystrix 才行。
我需要的是这样的效果
1 2 3 4 5 |
try { feign.api(); } catch (){ return fallback(); } |
但是每个feign调用都手动加上try..catch 实在是太low了,最好能写个类似切面一样的玩意。
这时候就想到了 hystrix,既然人家框架已经做了,我直接看下代码,copy不完了么
源码学习
前两天发布了一篇文章也是关于 feign、hystrix 调用集成的
基于之前的分析关键代码
1 |
HystrixInvocationHandler (feign.hystrix) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@Override public Object invoke( final Object proxy, final Method method, final Object[] args) throws Throwable { ............. // setterMethodMap 封装 hystrixCommand 配置信息(超时时间、是否重试.....) 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(); } |
按照之前分析源码方式,直接看哪里被调用了就可以看到, hystrix 实际上自己封装了一个 feign.Builer 类名是 feign.hystrix.HystrixFeign.Builder 用的是建造者模式,生成的类是在调用服务时用到
看到 关键的 build() 方法
1 2 3 4 5 6 7 8 9 10 11 |
Feign build( final FallbackFactory<?> nullableFallbackFactory) { super .invocationHandlerFactory( new InvocationHandlerFactory() { // 重新定义一个 InvocationHandler 实现 类似 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 动态代理我这里不多说了,核心就是 InvocationHandler (如果是jdk动态代理的话),那么 feign 这里也是,我们看看feign 调用声明是个接口,实际上是spring 动态代理生成了代理类,调用方法时实际调用的是
1 |
java.lang.reflect.InvocationHandler#invoke |
方案构想
那么我们只需要借鉴下 hystrix 的方式,自己实现一个feign.build ,将 InvocationHandler 换成自己的,
然后在我们自己的 InvocationHandler 中调用feign 官方的 InvocationHandler 就行,也就是
1 |
feign.hystrix.HystrixInvocationHandler#invoke |
这个方法中的
1 |
this .dispatch.get(method).invoke(args); |
这个代码
方案具体代码实现
方案一
自己实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import feign.Feign; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Component; /** * 自定义feign 构建 * @author hgf */ public class CusFeignBuilder extends Feign.Builder{ public CusFeignBuilder() { this .invocationHandlerFactory((target, dispatch) -> { Class<?> type = target.type(); FeignClient annotation = type.getAnnotation(FeignClient. class ); // 构造 fallback 实例 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); }); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
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调用 */ @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( "{} 请求 出现异常 ==> {}" , configKey, throwable.getMessage()); try { return getFallbackReturn(method, args, throwable); } catch (Throwable e) { throw throwable; } } } /** * 反射调用 {@link FeignClient#fallback()}生成失败返回值 * @param method 当前feign方法 * @param args 参数 * @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 容器中注册这个bean就行
1 2 3 4 |
@Bean CusFeignBuilder cusFeignBuilder(){ return new CusFeignBuilder(); } |
方案二
集成 sentinel ,今天写博客再回头看源码时候才发现的
加入依赖
1 2 3 4 |
< dependency > < groupId >com.alibaba.cloud</ groupId > < artifactId >spring-cloud-starter-alibaba-sentinel</ artifactId > </ dependency > |
配置开启
1 |
feign.sentinel.enabled= true |
手动实现feign 接口,将实体类注册到 spring 中
1 2 3 4 5 6 7 |
@Component public class DeviceApiFallBack implements DeviceApi{ @Override public ServerResponse<String> login(String appId) { return ServerResponse.createByErrorMessage( "请求失败" ); } } |
其实看代码知道原理一样,无非实现方式不一样
两个方案其实都行,方案一自己实现代码量多,方案二sentinel 官方实现,但是需要引入依赖,增加复杂度,而且 接口实现需要注册到spring 中
目前我选的还是方案一,简单。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。
原文链接:https://blog.csdn.net/weixin_39660224/article/details/109497272
查看更多关于如何自定义feign调用实现hystrix超时、异常熔断的详细内容...