如何使用 Fegin
@SpringBootApplication
@EnableFeignClients
public class WebApplication {
public static void main(String[] args) {
SpringApplication.run(WebApplication.class, args);
}
@FeignClient("name")
static interface NameService {
@RequestMapping("/")
public String getName();
}
流程图
SpringCloud Fegin是如何工作的
Feign涉及了两个注解,一个是@EnableFeignClients,用来开启 Feign,另一个是@FeignClient,用来标记要用 Feign 来拦截的请求接口。
EnableFeignClients
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
String[] value() default {};
//扫描路径(生效路径)
String[] basePackages() default {};
//默认包路径
Class<?>[] basePackageClasses() default {};
//全局配置
Class<?>[] defaultConfiguration() default {};
//控制生效指定client
Class<?>[] clients() default {};
}
FeignClient
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {
//feign名称(nacos调用名称/contextId为空时使用name)
@AliasFor("name")
String value() default "";
@Deprecated
String serviceId() default "";
//上下文容器隔离id
String contextId() default "";
@AliasFor("value")
String name() default "";
String qualifier() default "";
//请求完整路径,url值存在时, 不会负载均衡
String url() default "";
//当发生http 404错误时,如果该字段位true,会调用decoder进行解码,否则抛出FeignException
boolean decode404() default false;
//当前client的配置列表(基于contextId/name隔离)
Class<?>[] configuration() default {};
//容错回调
Class<?> fallback() default void.class;
//容错回调工厂
Class<?> fallbackFactory() default void.class;
//请求路径
String path() default "";
boolean primary() default true;
}
注册Clients以及配置
先关注对EnableFeignClients的处理,可以看出他使用了@Import(FeignClientsRegistrar.class)注解, FeignClientsRegistrar是一个注册器,主要实现了配置的注入和Client的注入
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//注册全局配置
registerDefaultConfiguration(metadata, registry);
//注册Client配置以及FeignClientFactory(FeignClient对象生成)
registerFeignClients(metadata, registry);
}
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//获取EnableFeignClients注解上的配置属性
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
//获取defaultConfiguration
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
//注册FeignClientSpecification
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
//为name属性赋值
builder.addConstructorArgValue(name);
//为configurations属性赋值
builder.addConstructorArgValue(configuration);
//注册到BeanDefinition,后续由spring创建FeignClientSpecification
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
/**
1.获取FeignClient注解configuration配置,注册到FeignClientSpecification
2.为FeignClient设置FeignClientFactory
**/
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
//获取EnableFeignClients注解的clients配置
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
//clients为空
if (clients == null || clients.length == 0) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
Set<String> basePackages = getBasePackages(metadata);
//扫描所有FeignClient的注解接口
for (String basePackage : basePackages) {
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
}
else {
//获取全局client
for (Class<?> clazz : clients) {
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
//获取FeignClient注解配置集合
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
//获取configuration配置,并注入到FeignClientSpecification
registerClientConfiguration(registry, name,
attributes.get("configuration"));
//Feign赋值到FeignClientFactory,由spring创建工厂
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
//获取FeignClient配置,为FeignClientFactoryBean属性赋值
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
String contextId = getContextId(attributes);
definition.addPropertyValue("contextId", contextId);
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
// has a default, won't be null
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
//FeignClientFactoryBean注册BeanDefinition
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
FeignClientFactoryBean
实现了FactoryBean接口,FactoryBean的作用是spring在获取bean的时候,会调用getObject方法,bean的创建交由业务实现,FeignClient的builder创建就是在getObject方法中实现
public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
FeignContext
- 继承NamedContextFactory
- NamedContextFactory类的作用是为每个FeignClient创建上下文容器AnnotationConfigApplicationContext,并把client里对应的configurations的bean注册到容器里,起到隔离的作用.获取对应配置的时候,优先取的是当前容器里的配置bean
- FeignAutoConfiguration会在启动的时候,将注册的FeignClient对应的FeignClientSpecification,赋值到FeignContext的configurations属性里
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
//获取 client对应的容器(name或者contextId)
protected AnnotationConfigApplicationContext getContext(String name) {
if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
this.contexts.put(name, createContext(name));
}
}
}
return this.contexts.get(name);
}
protected AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//优先注册FeignClient里的configurations
if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name)
.getConfiguration()) {
context.register(configuration);
}
}
//再注册EnableFeignClients里的defaultConfigurations
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
context.register(PropertyPlaceholderAutoConfiguration.class,
this.defaultConfigType);
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
this.propertySourceName,
Collections.<String, Object>singletonMap(this.propertyName, name)));
if (this.parent != null) {
// Uses Environment from parent as well as beans
context.setParent(this.parent);
// jdk11 issue
// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
context.setClassLoader(this.parent.getClassLoader());
}
context.setDisplayName(generateDisplayName(name));
context.refresh();
return context;
}
类图
FeignClient创建
FeignClient的创建是依赖FeignClientFactoryBean来创建,触发条件是注入FeignClient或者getBean
FeignClientFactoryBean
@Override
public Object getObject() throws Exception {
return getTarget();
}
<T> T getTarget() {
//获取FeignContext对象
FeignContext context = applicationContext.getBean(FeignContext.class);
//通过FeignContext获取client配置,构建Builder
Feign.Builder builder = feign(context);
//判断client是否配置url,没有配置则走负载均衡
if (!StringUtils.hasText(url)) {
if (!name.startsWith("http")) {
url = "http://" + name;
}
else {
url = name;
}
url += cleanPath();
return (T) loadBalance(builder, context,
new HardCodedTarget<>(type, name, url));
}
if (StringUtils.hasText(url) && !url.startsWith("http")) {
url = "http://" + url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// not load balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient) client).getDelegate();
}
if (client instanceof FeignBlockingLoadBalancerClient) {
// not load balancing because we have a url,
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(type, name, url));
}
protected Feign.Builder feign(FeignContext context) {
//获取日志工厂
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(type);
//此处配置取的是FeignConetext的默认配置FeignClientsConfiguration
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
.contract(get(context, Contract.class));
// @formatter:on
//获取configurations配置和proper
configureFeign(context, builder);
return builder;
}
protected void configureFeign(FeignContext context, Feign.Builder builder) {
//获取feign.client开头的配置
//feign.client.default.开头的配置是针对所有client的全局配置
//feign.client.contextId.开头的是针对某个client的配置
FeignClientProperties properties = applicationContext
.getBean(FeignClientProperties.class);
//是否获取inheritParentContext父容器上下文配置
FeignClientConfigurer feignClientConfigurer = getOptional(context,
FeignClientConfigurer.class);
setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());
//properties配置不为空,并且可以获取父上下文的配置bean
if (properties != null && inheritParentContext) {
//feign.client.defaultToProperties:true时候
//覆盖顺序properties.contextId->properties.default->configurations
if (properties.isDefaultToProperties()) {
//获取configurations配置
configureUsingConfiguration(context, builder);
//获取properties.default,配置重复覆盖前一项
configureUsingProperties(
properties.getConfig().get(properties.getDefaultConfig()),
builder);
//获取properties. contextId,配置重复覆盖前一项
configureUsingProperties(properties.getConfig().get(contextId), builder);
}
else {
//feign.client.defaultToProperties:false时候
//覆盖顺序configurations->properties.contextId->properties.default->
configureUsingProperties(
properties.getConfig().get(properties.getDefaultConfig()),
builder);
configureUsingProperties(properties.getConfig().get(contextId), builder);
configureUsingConfiguration(context, builder);
}
}
else {
//只获取configurations配置
configureUsingConfiguration(context, builder);
}
}
loadBalance负载均衡
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
//获取负载均衡客户端,支持ribbon和loadBanlancer,默认选择ribbon
//FeignRibbonClientAutoConfiguration支持ribbon负载均衡配置
//FeignLoadBalancerAutoConfiguration支持loadBalancer负载均衡配置
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
Targeter targeter = get(context, Targeter.class);
//feign动态代理调用
return targeter.target(this, builder, context, target);
}
throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
FeignRibbonClientAutoConfiguration
默认选择
@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",
matchIfMissing = true)
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(FeignAutoConfiguration.class)
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
// Order is important here, last should be the default, first should be optional
// see
// https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
//导入支持HttpClient OkHttpClient和HttpUrlConnection三种配置
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
OkHttpFeignLoadBalancedConfiguration.class,
DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
@Bean
@Primary
@ConditionalOnMissingBean
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
public CachingSpringLoadBalancerFactory cachingLBClientFactory(
SpringClientFactory factory) {
return new CachingSpringLoadBalancerFactory(factory);
}
@Bean
@Primary
@ConditionalOnMissingBean
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(
SpringClientFactory factory, LoadBalancedRetryFactory retryFactory) {
return new CachingSpringLoadBalancerFactory(factory, retryFactory);
}
@Bean
@ConditionalOnMissingBean
public Request.Options feignRequestOptions() {
return LoadBalancerFeignClient.DEFAULT_OPTIONS;
}
}
OkHttpClient
注入条件:
- 类路径有OkHttpClient.class
- 配置文件有feign.okhttp.enabled
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")
@Import(OkHttpFeignConfiguration.class)
class OkHttpFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory, okhttp3.OkHttpClient okHttpClient) {
OkHttpClient delegate = new OkHttpClient(okHttpClient);
return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
}
}
HttpClient
注入条件:
- 类路径有ApacheHttpClient.class
- feign.httpclient.enabled,没有配置默认生效
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
@Import(HttpClientFeignConfiguration.class)
class HttpClientFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory, HttpClient httpClient) {
ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
}
}
HttpUrlConnection
注入条件:
以上两个client缺省默认
@Configuration(proxyBeanMethods = false)
class DefaultFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
clientFactory);
}
}
FeignLoadBalancerAutoConfiguration
注入条件
- 引入loadBalancer jar包
- @AutoConfigureAfter(FeignRibbonClientAutoConfiguration.class)由于此处加载的条件是在ribbon后面,所以默认不会生效,因为ribbon配置注入后,@ConditionalOnMissingBean(Client.class),导致loadBalancer里的client不会注入
@ConditionalOnClass(Feign.class)
@ConditionalOnBean(BlockingLoadBalancerClient.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
@AutoConfigureAfter(FeignRibbonClientAutoConfiguration.class)
@EnableConfigurationProperties(FeignHttpClientProperties.class)
@Configuration(proxyBeanMethods = false)
// Order is important here, last should be the default, first should be optional
// see
// https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({ HttpClientFeignLoadBalancerConfiguration.class,
OkHttpFeignLoadBalancerConfiguration.class,
DefaultFeignLoadBalancerConfiguration.class })
public class FeignLoadBalancerAutoConfiguration {
}
Nacos支持负载均衡
fegin动态代理为FeginClient注解的接口生成代理类,拦截每个方法,获取基于spring注解的参数,最终调用
LoadBalancerFeignClient.execute方法
@Override
public Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
//根据Request封装参数到RibbonRequest
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
//根据Options封装ribbon的配置
IClientConfig requestConfig = getClientConfig(options, clientName);
return lbClient(clientName)
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
}
catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
private FeignLoadBalancer lbClient(String clientName) {
return this.lbClientFactory.create(clientName);
}
IClientConfig getClientConfig(Request.Options options, String clientName) {
IClientConfig requestConfig;
//FeignClient没有配置Options时,获取ribbonClient的配置
if (options == DEFAULT_OPTIONS) {
requestConfig = this.clientFactory.getClientConfig(clientName);
}
else {
//配置了Options,取FeignClient配置(连接和请求超时时间)
requestConfig = new FeignOptionsClientConfig(options);
}
return requestConfig;
}
CachingSpringLoadBalancerFactory
此处由于RibbonNacosAutoConfiguration类注入了NacosRibbonClientConfiguration,并在NacosRibbonClientConfiguration中重新注入了ServerList,右NacosServerList实现从Nacos配置中心拉取服务地址,覆盖RibbonClientConfiguration中的ConfigurationBasedServerList
//创建FeignLoadBalancer
public FeignLoadBalancer create(String clientName) {
FeignLoadBalancer client = this.cache.get(clientName);
if (client != null) {
return client;
}
//加载ribbon client对应配置,此处暂不深究
//在RibbonClientConfiguration中注入
IClientConfig config = this.factory.getClientConfig(clientName);
//获取上下文的ZoneAwareLoadBalancer
ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
ServerIntrospector serverIntrospector = this.factory.getInstance(clientName,
ServerIntrospector.class);
client = this.loadBalancedRetryFactory != null
? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
this.loadBalancedRetryFactory)
: new FeignLoadBalancer(lb, config, serverIntrospector);
this.cache.put(clientName, client);
return client;
}
FeignLoadBalancer
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
try {
//此处根据前面的Nacos获取server的负载均衡方式,获取一条ip地址和端口
return command.submit(
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
//回调,获取server成功后,再调用FeignLoadBalancer.execute方法,调用原生Okhtt
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
} catch (Exception e) {
Throwable t = e.getCause();
if (t instanceof ClientException) {
throw (ClientException) t;
} else {
throw new ClientException(e);
}
}
}
@Override
public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
throws IOException {
Request.Options options;
//此处获取的是getClientConfig方法里设置的超时时间设置
if (configOverride != null) {
RibbonProperties override = RibbonProperties.from(configOverride);
options = new Request.Options(override.connectTimeout(this.connectTimeout),
override.readTimeout(this.readTimeout));
}
else {
options = new Request.Options(this.connectTimeout, this.readTimeout);
}
Response response = request.client().execute(request.toRequest(), options);
return new RibbonResponse(request.getUri(), response);
}