文章摘要(AI生成)
Open Feign是一个用于在Spring Boot应用中使用的轻量级HTTP客户端工具。在使用Open Feign时,我们需要在启动类上加上@EnableFeignClients注解,并导入FeignClientsRegistrar类。FeignClientsRegistrar类实现了ImportBeanDefinitionRegistrar、ResourceLoaderAware和EnvironmentAware这三个接口。ImportBeanDefinitionRegistrar接口用于将Feign客户端的bean定义添加到Spring容器的bean定义注册表中,ResourceLoaderAware接口用于加载Feign客户端,EnvironmentAware接口用于获取Feign客户端的相关配置。整个注册过程可以分为三个步骤:获取Feign相关的配置、使用加载器加载Feign客户端,并将其注册到容器中。在注册过程中,需要注册两个bean定义:我们自定义的Feign全局配置类和所有的Feign客户端的自定义配置类。通过这样的注册方式,我们可以方便地在Spring Boot应用中使用Open Feign来进行HTTP请求发送。
Open Feign如何运行
注册feign客户端
spring boot使用openfeign时,需要在启动类上加入@EnableFeignClients
注解,如下所示:
@EnableFeignClients
@SpringBootApplication
public class Application {
public static void main(String[] args) {
// Run application
SpringApplication.run(Application.class, args);
}
}
而我们查看@EnableFeignClients
注解,会发现该注解导入了一个bean注册器FeignClientsRegistrar
.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
....
}
而FeignClientsRegistrar
则实现了对feign client的注册,该类分别实现了ImportBeanDefinitionRegistrar
,ResourceLoaderAware
,ResourceLoaderAware
三个接口。其中ImportBeanDefinitionRegistrar
负责添加bean定义到spring容器的bean定义注册表中,而ResourceLoaderAware
则负责将进行feign client加载,最后EnvironmentAware
获取feign client的相关配置。这也对应了我们注册的三个步骤:
- 获取feign相关的配置
- 获取feign client加载器
- 使用加载器加载client,并将其注册到容器中
注册BeanDefinition
有两个要注册:
- 我们自定义的Feign全局配置类
- feign客户端所有的自定义配置类以及feign客户端
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//注册自定义配置类
registerDefaultConfiguration(metadata, registry);
//注册feign客户端
registerFeignClients(metadata, registry);
}
Feign全局配置类注册
@EnableFeignClients
中自定义配置加载:
-
获取EnableFeignClients中的默认配置defaultConfiguration中定义的配置类,包括自定义的编码、解码器
- 配置类bean名称前缀获取:
default.
+EnableFeignClients注解所在类名称+配置类名称
- 配置类bean名称前缀获取:
-
将自定义的编码、解码器作为BeanDefinition注册到容器中
private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
//获取EnableFeignClients中的默认配置defaultConfiguration中定义的配置类,包括自定义的编码、解码器
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));
}
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
//将自定义的编码、解码器的bean定义注册到容器中
registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
自定义配置类以及feign客户端注册
feign客户端注册:
-
判断EnableFeignClients注解中clients是否配置了feign客户端
-
如果客户端没有在注解中指定,则创建一个注解扫描器,对FeignClient注解进行扫描,将扫描到的客户端添加到BeanDefinition结果集中
扫描路径获取:
- EnableFeignClients注解中的value值
- EnableFeignClients注解中的basePackages值
- EnableFeignClients注解中的basePackageClasses值
- 如果以上三个属性都为空,则扫描EnableFeignClients注解所在包路径
源码如下:
//获取扫描路径 protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) { Map<String, Object> attributes = importingClassMetadata .getAnnotationAttributes(EnableFeignClients.class.getCanonicalName()); //EnableFeignClients注解中的value值 Set<String> basePackages = new HashSet<>(); for (String pkg : (String[]) attributes.get("value")) { if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } } //EnableFeignClients注解中的basePackages值 for (String pkg : (String[]) attributes.get("basePackages")) { if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } } //EnableFeignClients注解中的basePackageClasses值 for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) { basePackages.add(ClassUtils.getPackageName(clazz)); } //如果以上三个属性都为空,则扫描EnableFeignClients注解所在路径 if (basePackages.isEmpty()) { basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName())); } return basePackages; }
-
如果客户端在注解中指定,则直接添加到BeanDefinition结果集中
-
对BeanDefinition结果集进行遍历
-
如果FeignClient注解所属对象是否为接口,如果不为接口则报错
-
获取FeignClient注解中的属性
-
注册FeignClient注解中的configuration中定义的配置类,包括自定义的编码、解码器
-
FeignClient配置类名称,BeanName前缀获取:
- 先获取FeignClient注解中的contextId值
- 如果名称为空,再获取value值
- 如果名称为空,再获取name值
- 如果名称为空,再获取serviceId值
- 如果名称不为空,则返回;为空则为异常
源码如下:
//获取feign的bean名称 private String getClientName(Map<String, Object> client) { if (client == null) { return null; } //先获取注解中的contextId值 String value = (String) client.get("contextId"); //如果名称为空,再获取value值 if (!StringUtils.hasText(value)) { value = (String) client.get("value"); } //如果名称为空,再获取name值 if (!StringUtils.hasText(value)) { value = (String) client.get("name"); } //如果名称为空,再获取serviceId值 if (!StringUtils.hasText(value)) { value = (String) client.get("serviceId"); } //如果名称不为空,则返回 if (StringUtils.hasText(value)) { return value; } //否则抛异常 throw new IllegalStateException( "Either 'name' or 'value' must be provided in @" + FeignClient.class.getSimpleName()); }
-
-
注册FeignClient
-
FeignClient注册:
- 获取容器id
- 获取客户端名称
- 创建feign的工厂bean,设置bean相关属性
- 获取FeignClient注解中的url、path、decode404、fallback属性,设置到feign的工厂bean中
- 创建一个自动装配的BeanDefinition,其属性包含FeignClient工厂bean
源码如下:
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) { String className = annotationMetadata.getClassName(); Class clazz = ClassUtils.resolveClassName(className, null); ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory ? (ConfigurableBeanFactory) registry : null; //获取容器id String contextId = getContextId(beanFactory, attributes); //获取客户端名称 String name = getName(attributes); //创建feign的工厂bean,设置bean相关属性 FeignClientFactoryBean factoryBean = new FeignClientFactoryBean(); factoryBean.setBeanFactory(beanFactory); factoryBean.setName(name); factoryBean.setContextId(contextId); factoryBean.setType(clazz); factoryBean.setRefreshableClient(isClientRefreshEnabled()); BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> { //获取FeignClient注解中的url、path、decode404、fallback属性进行设置 //提供FeignClient bean的创建过程 factoryBean.setUrl(getUrl(beanFactory, attributes)); factoryBean.setPath(getPath(beanFactory, attributes)); factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404")))); Object fallback = attributes.get("fallback"); if (fallback != null) { factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback : ClassUtils.resolveClassName(fallback.toString(), null)); } Object fallbackFactory = attributes.get("fallbackFactory"); if (fallbackFactory != null) { factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory : ClassUtils.resolveClassName(fallbackFactory.toString(), null)); } return factoryBean.getObject(); }); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); definition.setLazyInit(true); validate(attributes); AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className); beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean); // has a default, won't be null boolean primary = (Boolean) attributes.get("primary"); beanDefinition.setPrimary(primary); String[] qualifiers = getQualifiers(attributes); if (ObjectUtils.isEmpty(qualifiers)) { qualifiers = new String[] { contextId + "FeignClient" }; } //注册feign bean BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers); BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); registerOptionsBeanDefinition(registry, contextId); }
-
-
上述整体注册流程代码如下:
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
//首先获取EnableFeignClients注解中clients是否配置了feign客户端
final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
//如果客户端没有在注解中指定,则创建一个注解扫描器,进行注解扫描
if (clients == null || clients.length == 0) {
//创建一个注解扫描器,对注解FeignClient进行扫描
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
//获取扫描路径
Set<String> basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
//将FeignClient添加到结果中
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
}
//如果客户端在注解中指定,则直接添加
else {
for (Class<?> clazz : clients) {
//将FeignClient添加到结果中
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());
//根据FeignClient注解中的属性,获取client名称
String name = getClientName(attributes);
//注册FeignClient注解中的configuration中定义的配置类,包括自定义的编码、解码器
registerClientConfiguration(registry, name, attributes.get("configuration"));
//注册FeignClient
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
Http客户端创建和Targeter创建
在Feign的jar包中,我们可以看到其配置的spring.factories配置文件:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.openfeign.hateoas.FeignHalAutoConfiguration,\
org.springframework.cloud.openfeign.FeignAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoConfiguration
客户端的初始化在工厂beanFeignClientFactoryBean
中完成,当spring容器根据BeanDefinition创建对象时,由于FeignClientFactoryBean是自动创建的,因此spring容器中会自动创建FeignClientFactoryBean对象。当spring扫描到Feign的自动配置类FeignAutoConfiguration时,会触发客户端的创建。
而其中其他的自动配置会创建其他配置bean,例如支持解析HAL_JSON请求的FeignHalAutoConfiguration,支持Accept Gzip的FeignAcceptGzipEncodingAutoConfiguration和Content Gzip的FeignContentGzipEncodingAutoConfiguration。以及支持负载均衡的FeignLoadBalancerAutoConfiguration。
这里我们主要看FeignAutoConfiguration,这个类中声明了如下bean:
这里我们主要看DefaultFeignTargeterConfiguration的DefaultTargeter,这个Targeter提供了初始化Feign客户端的方法:
class DefaultTargeter implements Targeter {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget<T> target) {
return feign.target(target);
}
}
这个target方法在我们使用feign工厂类初始化feign客户端时会用到,他通过调用Feign
中的target
方法,实现了动态代理完成了feignClient的创建:
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
-
首先会创建一个Feign构造器,构造器中包含了Feign的四大组件:Contract、请求参数、编码器、解码器
public Feign build() { Client client = Capability.enrich(this.client, capabilities); Retryer retryer = Capability.enrich(this.retryer, capabilities); List<RequestInterceptor> requestInterceptors = this.requestInterceptors.stream() .map(ri -> Capability.enrich(ri, capabilities)) .collect(Collectors.toList()); //日志 Logger logger = Capability.enrich(this.logger, capabilities); Contract contract = Capability.enrich(this.contract, capabilities); //请求参数 Options options = Capability.enrich(this.options, capabilities); //编码器 Encoder encoder = Capability.enrich(this.encoder, capabilities); //解码器 Decoder decoder = Capability.enrich(this.decoder, capabilities); InvocationHandlerFactory invocationHandlerFactory = Capability.enrich(this.invocationHandlerFactory, capabilities); QueryMapEncoder queryMapEncoder = Capability.enrich(this.queryMapEncoder, capabilities); SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding); ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, errorDecoder, synchronousMethodHandlerFactory); //创建Feign构建器 return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder); }
-
构造器通过动态代理,创建我们所需要的feign客户端:
//动态代理feign客户端 @Override public <T> T newInstance(Target<T> target) { Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target); Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>(); List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>(); //遍历客户端方法 for (Method method : target.type().getMethods()) { if (method.getDeclaringClass() == Object.class) { continue; } else if (Util.isDefault(method)) { DefaultMethodHandler handler = new DefaultMethodHandler(method); defaultMethodHandlers.add(handler); methodToHandler.put(method, handler); } else { methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method))); } } //创建客户端的InvocationHandler InvocationHandler handler = factory.create(target, methodToHandler); T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[] {target.type()}, handler); for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) { defaultMethodHandler.bindTo(proxy); } return proxy; }
初始化客户端
当spring容器初始化我们的feign客户端时,会调用Feign工厂的getObject
方法创建feign客户端:
feign工厂会创建对feign客户端,流程为:
- 获取feign构造器
- 解析feignClient的请求路径
- 创建对应的http客户端
- 通过动态代理生成对应的feign客户端
/**
* @param <T> the target type of the Feign client
* @return a {@link Feign} client created with the specified data and the context
* information
*/
<T> T getTarget() {
FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
: applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
//如果FeignClient url为空
if (!StringUtils.hasText(url)) {
if (LOG.isInfoEnabled()) {
LOG.info("For '" + name + "' URL not provided. Will try picking an instance via load-balancing.");
}
// 属性name中不含http则需要进行url拼接
if (!name.startsWith("http")) {
url = "http://" + name;
}
else {
url = name;
}
url += cleanPath();
//获取httpClient,根据httpClient通信后通过动态代理将结果映射到FeignClient所属的接口实例中
//HardCodedTarget: 包含了对应的feign client类、client名称、client url
return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
}
//如果FeignClient url不为空且属性url中不含http则需要进行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 FeignBlockingLoadBalancerClient) {
// not load balancing because we have a url,
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
}
//客户端为重试阻塞客户端
if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
// not load balancing because we have a url,
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
client = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();
}
builder.client(client);
}
//获取feign Targeter
Targeter targeter = get(context, Targeter.class);
//通过feignTargeter创建feign http client
//HardCodedTarget: 包含了对应的feign client类、client名称、client url
return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
}
评论区