说明:学习代码使用的是ruoyi-cloud开源代码
一、使用
在接口上写入一个@FeignClient 注解
@FeignClient(contextId = "remoteFileService", value = ServiceNameConstants.FILE_SERVICE, fallbackFactory = RemoteFileFallbackFactory.class)
public interface RemoteFileService
{
/**
* 上传文件
*
* @param file 文件信息
* @return 结果
*/
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R<SysFile> upload(@RequestPart(value = "file") MultipartFile file);
}
FeignClient 注解属性,这个博客写过
二、Feign 如何实现
首先是使用@EnableFeignClients注解导入一个FeignClientsRegistrar.class,因为这个类实现了ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware。Aware接口啥的都是set一些属性,就不看了。直接看ImportBeanDefinitionRegistrar的实现方法
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 注册默认的配置,可以参考FeignClient 注解属性中的configuration属性,只不过这个是加载EnableFeignClients中的defaultConfiguration属性
registerDefaultConfiguration(metadata, registry);
// 注册客户端
registerFeignClients(metadata, registry);
}
- 注册配置类的registerDefaultConfiguration代码
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;
// 是否是内部类,是的话,就去取内部类的名字 xxx.xxx.xx.Class$Neibu
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
// 将这些类注册为 FeignClientSpecification.class
// bean 的名字为 default.xxx.xxx.xx.Class$Neibu.FeignClientSpecification
registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));
}
}
因为上面注册成了一个FeignClientSpecification类嘛,这个类是实现了NamedContextFactory.Specification,但是NamedContextFactory中的代码有很多
1、注册client
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
// 如果指定了feginClient 那么就直接用指定的,如果没指定就去扫描包,
if (clients == null || clients.length == 0) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
// 包含FeignClient注解的文件
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
Set<String> basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
}
else {
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");
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
// 注册FeignClient注解中configuration的配置类
registerClientConfiguration(registry, name, attributes.get("configuration"));
// 注册FeignClient客户端
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
具体的注册FeignClient,主要是将这个接口类注册为FeignClientFactoryBean以及设置一些属性信息。
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;
String contextId = getContextId(beanFactory, attributes);
String name = getName(attributes);
// 重点
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
factoryBean.setRefreshableClient(isClientRefreshEnabled());
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
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" };
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
registerOptionsBeanDefinition(registry, contextId);
}
FeignClientFactoryBean 类
首先这个类实现了 FactoryBean, 以及InitializingBean,还有俩Aware接口,直接看getObject() 方法 和 afterPropertiesSet()方法
public class FeignClientFactoryBean
implements FactoryBean<Object>, InitializingBean, ApplicationContextAware, BeanFactoryAware
验证你FeginClient的 contextId 和 name属性 有没有值
public void afterPropertiesSet() {
Assert.hasText(contextId, "Context id must be set");
Assert.hasText(name, "Name must be set");
}
@Override
public Object getObject() {
return getTarget();
}
/**
* @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);
// 个人理解这行代码就是把你原先的日志啊、编解码器啊等东西设置进去,获取一个builder对象。说白了就是构造器模式创建嘛
Feign.Builder builder = feign(context);
// 没指定url的话,就用http:// + 服务名称
if (!StringUtils.hasText(url)) {
if (LOG.isInfoEnabled()) {
LOG.info("For '" + name + "' URL not provided. Will try picking an instance via load-balancing.");
}
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 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);
}
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
}
注册Client流程图