概述
feign是一个声明式的HTTP客户端,spring-cloud-openfeign将feign集成到spring boot中,在接口上通过注解声明Rest协议,将http调用转换为接口方法的调用,使得客户端调用http服务更加简单。
Fegin
1.fegin的默认超时时间为1s,默认不开启hystrix,默认开启ribbon启用轮询规则(RoundRobinRule);
2.对于开启啦hystrix和ribbon的feign ,ribbon的超时不会抛出异常,hystrix与feign谁小听谁的;
原理分析
@EnableFeignClients 注解声明客户端接口
(@EnableFeignClients的参数声明客户端接口的位置和默认的配置类。)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
//basePackages的别名
String[] value() default {};
//声明基础包,spring boot启动后,会扫描该包下被@FeignClient注解的接口
String[] basePackages() default {};
//声明基础包的类,通过该类声明基础包
Class<?>[] basePackageClasses() default {};
//默认配置类
Class<?>[] defaultConfiguration() default {};
//直接声明的客户端接口类
Class<?>[] clients() default {};
}
@FeignClient注解,将接口声明为Feign客户端
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {
@AliasFor("name")
String value() default "";
//名称,对应与eureka上注册的应用名
@AliasFor("value")
String name() default "";
//生成spring bean的qualifier
String qualifier() default "";
//http服务的url
String url() default "";
boolean decode404() default false;
//配置类,这里设置的配置类是Spring Configuration,将会在FeignContext中创建内部声明的Bean,用于不同的客户端进行隔离
Class<?>[] configuration() default {};
//声明hystrix调用失败后的方法
Class<?> fallback() default void.class;
Class<?> fallbackFactory() default void.class;
String path() default "";
}
FeignClientsRegistrar 注册客户端
@EnableFeignClients注解上被注解了@Import(FeignClientsRegistrar.class),@Import注解的作用是将指定的类作为Bean注入到Spring Context中,我们再来看被引入的FeignClientsRegistrar
FeignClientsRegistrar类实现了3个接口:
- 接口ResourceLoaderAware用于注入ResourceLoader
- 接口BeanClassLoaderAware用于注入ClassLoader
- 接口ImportBeanDefinitionRegistrar用于动态向Spring Context中注册bean
ImportBeanDefinitionRegistrar接口方法registerBeanDefinitions有两个参数:
- AnnotationMetadata 包含被@Import注解类的信息
- BeanDefinitionRegistry bean定义注册中心
这里 @Import注解在@EnableFeignClients上,@EnableFeignClients注解在spring boot启动类上,AnnotationMetadata拿到的是spring boot启动类的相关信息
registerDefaultConfiguration方法,注册默认配置
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//获取@EnableFeignClients注解参数
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
//如果参数中包含defaultConfiguration
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
//注册客户端的配置Bean
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}
取出@EnableFeignClients注解参数defaultConfiguration,注册到spring Context中。registerClientConfiguration方法代码如下:
这里使用spring 动态注册bean的方式,注册了一个FeignClientSpecification的bean。
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
// 创建一个BeanDefinitionBuilder,注册bean的类为FeignClientSpecification
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
//增加构造函数参数
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
//调用BeanDefinitionRegistry.registerBeanDefinition方法动态注册Bean
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
FeignClientSpecification 客户端定义
一个简单的pojo,继承了NamedContextFactory.Specification,两个属性String name 和 Class<?>[] configuration,用于FeignContext命名空间独立配置,后面会用到。
扫描注解声明的客户端,调用registerFeignClient方法注册到registry中。这里是一个典型的spring动态注册bean的例子,可以参考这段代码在spring中轻松的实现类路径下class扫描,动态注册bean到spring中。想了解spring类的扫描机制,可以断点到ClassPathScanningCandidateComponentProvider.findCandidateComponents方法中,一步步调试。
FeignClientFactoryBean 创建feign客户端的工厂
@Data
@EqualsAndHashCode(callSuper = false)
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
ApplicationContextAware {
//feign客户端接口类
private Class<?> type;
private String name;
private String url;
private String path;
private boolean decode404;
private ApplicationContext applicationContext;
//hystrix集成,调用失败的执行方法
private Class<?> fallback = void.class;
//同上
private Class<?> fallbackFactory = void.class;
。。。
@Override
public Object getObject() throws Exception {
//FeignContext在FeignAutoConfiguration中自动注册,FeignContext用于客户端配置类独立注册,后面具体分析
FeignContext context = applicationContext.getBean(FeignContext.class);
//创建Feign.Builder
Feign.Builder builder = feign(context);
//如果@FeignClient注解没有设置url参数
if (!StringUtils.hasText(this.url)) {
String url;
//url为@FeignClient注解的name参数
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
//加上path
url += cleanPath();
//返回loadBlance客户端,也就是ribbon+eureka的客户端
return loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, url));
}
//@FeignClient设置了url参数,不做负载均衡
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
//加上path
String url = this.url + cleanPath();
//从FeignContext中获取client
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// 有url参数,不做负载均衡,但是客户端是ribbon,或者实际的客户端
client = ((LoadBalancerFeignClient)client).getDelegate();
}
builder.client(client);
}
//从FeignContext中获取Targeter
Targeter targeter = get(context, Targeter.class);
//生成客户端代理
return targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
}
}
这段代码有个比较重要的逻辑,如果在@FeignClient注解中设置了url参数,就不走Ribbon,直接url调用,否则通过Ribbon调用,实现客户端负载均衡。
可以看到,生成Feign客户端所需要的各种配置对象,都是通过FeignContex中获取的。
FeignClientsConfiguration 客户端默认配置
@Configuration
public class FeignClientsConfiguration {
//注入springMVC的HttpMessageConverters
@Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
//注解参数处理器,处理SpringMVC注解,生成http元数据
@Autowired(required = false)
private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>();
//Decoder bean,默认通过HttpMessageConverters进行处理
@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() {
return new ResponseEntityDecoder(new SpringDecoder(this.messageConverters));
}
//Encoder bean,默认通过HttpMessageConverters进行处理
@Bean
@ConditionalOnMissingBean
public Encoder feignEncoder() {
return new SpringEncoder(this.messageConverters);
}
//Contract bean,通过SpringMvcContract进行处理接口
@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
return new SpringMvcContract(this.parameterProcessors, feignConversionService);
}
//hystrix自动注入
@Configuration
@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
protected static class HystrixFeignConfiguration {
//HystrixFeign的builder,全局关掉Hystrix配置feign.hystrix.enabled=false
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = true)
public Feign.Builder feignHystrixBuilder() {
return HystrixFeign.builder();
}
}
//默认不重试
@Bean
@ConditionalOnMissingBean
public Retryer feignRetryer() {
return Retryer.NEVER_RETRY;
}
//默认的builder
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
return Feign.builder().retryer(retryer);
}
}
使用Feign调用接口分两层,ribbon的调用和hystrix的调用,所以ribbon的超时时间和Hystrix的超时时间的结合就是Feign的超时时间。
一般情况下 都是 ++ribbon 的超时时间小于hystrix的超时时间++(因为涉及到ribbon的重试机制)
因为ribbon的重试机制和Feign的重试机制有冲突,所以源码中默认关闭Feign的重试机制。
要开启Feign的重试机制如下:(Feign默认重试五次 )
public interface Retryer extends Cloneable {
//不进行重试
Retryer NEVER_RETRY = new Retryer() {
public void continueOrPropagate(RetryableException e) {
throw e;
}
public Retryer clone() {
return this;
}
};
void continueOrPropagate(RetryableException var1);
Retryer clone();
public static class Default implements Retryer {
private final int maxAttempts;
private final long period;
private final long maxPeriod;
int attempt;
long sleptForMillis;
public Default() {
this(100L, TimeUnit.SECONDS.toMillis(1L), 5);
}
public Default(long period, long maxPeriod, int maxAttempts) {
this.period = period;
this.maxPeriod = maxPeriod;
this.maxAttempts = maxAttempts;
this.attempt = 1;
}
protected long currentTimeMillis() {
public void continueOrPropagate(RetryableException e) {
}
long nextMaxInterval() {}
public Retryer clone() {}
}
}
最后
以上就是坚强身影为你收集整理的Feign与Ribbon的全部内容,希望文章能够帮你解决Feign与Ribbon所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复