本文的思想建立在已经能初步使用Feign的基础之上,如果你还是零基础,建议先去补充一些Spring Cloud Feign +Hystrix的知识。Spring Cloud Feign默认使用Hystrix作为熔断器,随着Spring Cloud Alibaba家族日益壮大,Sentinel-0.9.0版本也支持Gateway了。今天我们来看看如何简化代码,使用Feign,如何用Sentinel代替Hystrix并简化代码,统一管理Fallback。
-
问题来源:
Feign的使用场景如下:
@FeignClient(value = "user-service", fallbackFactory = UserFeignFallbackFactory.class)
public interface RemoteUserFeign {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") int id);
}
@Component
public class UserFeignFallbackFactory implements FallbackFactory<RemoteUserFeign> {
@Override
public RemoteUserFeign create(Throwable cause) {
RemoteUserFeignImpl feign = new RemoteUserFeignImpl();
feign.setCause(cause);
return feign;
}
}
@Slf4j
public class RemoteUserFeignImpl implements RemoteUserFeign {
@Setter
private Throwable cause;
@Override
public User findById(int id) {
log.error("feign 查询用户信息失败 id:{},throw:{}", id, cause);
return null;
}
}
我们使用Feign时,使用的@FeignClient注解每次都要为其设置fallbackFactory参数。导致项目中会多出很多冗余代码。那我们能不能有一个自己定制化的默认Fallback去处理这些相同的事情呢。
-
问题定位
首先我们使用Sentinel,引入Alibaba-Sentinel相关包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>0.9.0.RELEASE</version>
</dependency>
直接打开源码来看,spring-cloud-starter-alibaba-sentinel这个包中与Feign相关的代码并不多,主要还是使用的Hystrix的思路去实现的SentinelFeign
接着经过Feign断点和源码的调试,定位核心代码部分,相关类和行数我都截图出来
从SentinelFeign源码中可以看出,当未设置fallbackFactory时,并未向SentinelInvocationHandler构造方法中传入FallbackFactory。那我们解决的思路就出来了:
- 编写公共FallbackFactory
- 改写SentinelFeign使得fallbackFactory为void.class时,我们传入自己的公共FallbackFactory实例
-
问题解决
- 首先编写真正的Fallback处理器
@Slf4j @AllArgsConstructor public class FunFeignFallback<T> implements MethodInterceptor { private final Class<T> targetType; private final String targetName; private final Throwable cause; @Nullable @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { String errorMessage = cause.getMessage(); log.error("FunFeignFallback:[{}.{}] serviceId:[{}] message:[{}]", targetType.getName(), method.getName(), targetName, errorMessage); // 非 FeignException,直接返回 if (!(cause instanceof FeignException)) { //此处只是示例,具体可以返回带有业务错误数据的对象 return null; } FeignException exception = (FeignException) cause; //此处只是示例,具体可以返回带有业务错误数据的对象 return JsonUtils.toJson(exception.content()); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } FunFeignFallback<?> that = (FunFeignFallback<?>) o; return targetType.equals(that.targetType); } @Override public int hashCode() { return Objects.hash(targetType); } }
- 然后编写FallbackFactory
@AllArgsConstructor public class FunFallbackFactory<T> implements FallbackFactory<T> { private final Target<T> target; @Override @SuppressWarnings("unchecked") public T create(Throwable cause) { final Class<T> targetType = target.type(); final String targetName = target.name(); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(targetType); enhancer.setUseCache(true); enhancer.setCallback(new FunFeignFallback<>(targetType, targetName, cause)); return (T) enhancer.create(); } }
- 重新实现SentinelFeign,由于没有对SentinelInvocationHandler的访问权限,我们得在自己项目里建立
org.springframework.cloud.alibaba.sentinel.feign包并将新的SentinelFeign放在此包下。public class FunSentinelFeign { public static FunSentinelFeign.Builder builder() { return new FunSentinelFeign.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 FunSentinelFeign.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) { Object feignClientFactoryBean = FunSentinelFeign.Builder.this.applicationContext .getBean("&" + target.type().getName()); Class fallback = (Class) getFieldValue(feignClientFactoryBean, "fallback"); Class fallbackFactory = (Class) getFieldValue(feignClientFactoryBean, "fallbackFactory"); String name = (String) getFieldValue(feignClientFactoryBean, "name"); Object fallbackInstance; FallbackFactory fallbackFactoryInstance; // check fallback and fallbackFactory properties if (void.class != fallback) { fallbackInstance = getFromContext(name, "fallback", fallback, target.type()); return new SentinelInvocationHandler(target, dispatch, new FallbackFactory.Default(fallbackInstance)); } if (void.class != fallbackFactory) { fallbackFactoryInstance = (FallbackFactory) getFromContext(name, "fallbackFactory", fallbackFactory, FallbackFactory.class); return new SentinelInvocationHandler(target, dispatch, fallbackFactoryInstance); } // 默认的 fallbackFactory FunFallbackFactory funFallbackFactory = new FunFallbackFactory(target); return new SentinelInvocationHandler(target, dispatch, funFallbackFactory); } 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); } } }
- 最后注入我们的SentinelFeign Bean
@Configuration public class FunFeignFallbackConfiguration { @Bean @Scope("prototype") @ConditionalOnClass({SphU.class, Feign.class}) @ConditionalOnProperty(name = "feign.sentinel.enabled") @Primary public Feign.Builder feignSentinelBuilder() { return FunSentinelFeign.builder(); } }
记得使用sentinel的时候,需要在配置文件中配置feign.sentinel.enabled=true,注释掉feign.hystrix.enabled=true。
@FeignClient(value = "user-service")
public interface RemoteUserFeign {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") int id);
}
到这里就大功告成了。后面写Feign只用写下面代码就可以了,至于一些特殊的,也可以自己定制fallbackFactory加在注解里。