我是靠谱客的博主 坚强身影,最近开发中收集的这篇文章主要介绍Feign与Ribbon,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

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所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(55)

评论列表共有 0 条评论

立即
投稿
返回
顶部