@EnableFeignClients
注解启用Feign客户端,通过@Import注解导入了FeignClientsRegistrar
类加载额外的Bean。FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar接口,在Spring启动过程中会调用registerBeanDefinitions
方法注册BeanDefinition
。
//FeignClientsRegistrar#registerBeanDefinitions
public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {//处理@EnableFeignClients注解上的配置registerDefaultConfiguration(metadata, registry);//处理@FeignClient对应的接口registerFeignClients(metadata, registry);
}
FeignClientsRegistrar#registerFeignClients:
private void registerDefaultConfiguration(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {Map 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();}registerClientConfiguration(registry, name,defaultAttrs.get("defaultConfiguration"));}
}
FeignClientsRegistrar#registerFeignClients:
public void registerFeignClients(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {//找到@FeignClient标识的接口类ClassPathScanningCandidateComponentProvider scanner = getScanner();scanner.setResourceLoader(this.resourceLoader);Set basePackages;Map attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);final Class>[] clients = attrs == null ? null: (Class>[]) attrs.get("clients");if (clients == null || clients.length == 0) {scanner.addIncludeFilter(annotationTypeFilter);basePackages = getBasePackages(metadata);}else {final Set clientClasses = new HashSet<>();basePackages = new HashSet<>();for (Class> clazz : clients) {basePackages.add(ClassUtils.getPackageName(clazz));clientClasses.add(clazz.getCanonicalName());}AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {@Overrideprotected boolean match(ClassMetadata metadata) {String cleaned = metadata.getClassName().replaceAll("\\$", ".");return clientClasses.contains(cleaned);}};scanner.addIncludeFilter(new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));}for (String basePackage : basePackages) {Set candidateComponents = scanner.findCandidateComponents(basePackage);for (BeanDefinition candidateComponent : candidateComponents) {if (candidateComponent instanceof AnnotatedBeanDefinition) {// verify annotated class is an interfaceAnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();Assert.isTrue(annotationMetadata.isInterface(),"@FeignClient can only be specified on an interface");Map attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());String name = getClientName(attributes);registerClientConfiguration(registry, name,attributes.get("configuration"));registerFeignClient(registry, annotationMetadata, attributes);}}}
}
FeignClientsRegistrar#registerFeignClient:
private void registerFeignClient(BeanDefinitionRegistry registry,AnnotationMetadata annotationMetadata, Map attributes) {String className = annotationMetadata.getClassName();//创建了FeignClientFactoryBean实例BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);validate(attributes);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();boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be// nullbeanDefinition.setPrimary(primary);String qualifier = getQualifier(attributes);if (StringUtils.hasText(qualifier)) {alias = qualifier;}BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,new String[] { alias });BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
FeignClientFactoryBean#getObject:
//实现FactoryBean接口getObject方法,返回工厂管理的Bean实例
@Override
public Object getObject() throws Exception {return getTarget();
}//根据指定的数据和上下文信息创建Feign客户端
T getTarget() {//获得FeignContext对象FeignContext context = this.applicationContext.getBean(FeignContext.class);Feign.Builder builder = feign(context);if (!StringUtils.hasText(this.url)) {if (!this.name.startsWith("http")) {this.url = "http://" + this.name;}else {this.url = this.name;}this.url += cleanPath();//获得Feign客户端return (T) loadBalance(builder, context,new HardCodedTarget<>(this.type, this.name, this.url));}if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {this.url = "http://" + this.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 unwrapclient = ((LoadBalancerFeignClient) client).getDelegate();}builder.client(client);}Targeter targeter = get(context, Targeter.class);return (T) targeter.target(this, builder, context,new HardCodedTarget<>(this.type, this.name, url));
}//获得Feign.Builder实例
protected Feign.Builder feign(FeignContext context) {FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);Logger logger = loggerFactory.create(this.type);// @formatter:offFeign.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:onconfigureFeign(context, builder);return builder;
}//使用FeignContext和Feign.Builder对象配置Feign
protected void configureFeign(FeignContext context, Feign.Builder builder) {FeignClientProperties properties = this.applicationContext.getBean(FeignClientProperties.class);if (properties != null) {if (properties.isDefaultToProperties()) {configureUsingConfiguration(context, builder);configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()),builder);configureUsingProperties(properties.getConfig().get(this.contextId),builder);}else {configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()),builder);configureUsingProperties(properties.getConfig().get(this.contextId),builder);configureUsingConfiguration(context, builder);}}else {configureUsingConfiguration(context, builder);}
}//以负载均衡的方式获取Feign客户端
protected T loadBalance(Feign.Builder builder, FeignContext context,HardCodedTarget target) {//获取Client对象Client client = getOptional(context, Client.class);if (client != null) {builder.client(client);Targeter targeter = get(context, Targeter.class);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?");
}//默认的Target实现类
class DefaultTargeter implements Targeter {@Overridepublic T target(FeignClientFactoryBean factory, Feign.Builder feign,FeignContext context, Target.HardCodedTarget target) {return feign.target(target);}}//Feign#target
public T target(Target target) {return build().newInstance(target);
}//通过反射方式创建Feign对象
public Feign build() {SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, logLevel, decode404, closeAfterDecode, propagationPolicy);ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,errorDecoder, synchronousMethodHandlerFactory);return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}//使用JDK动态代理的方式创建Target的代理对象
@Override
public T newInstance(Target target) {Map nameToHandler = targetToHandlersByName.apply(target);Map methodToHandler = new LinkedHashMap();List defaultMethodHandlers = new LinkedList();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 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;
}
@FeignClient
注解修饰的接口,然后将这些接口转换成Bean,统一交给Spring来管理;OpenFeign
扫描带有@FeignClient
注解的接口,然后为其生成一个动态代理;MethodHandler
,MethodHandler
中又包含经过MVC Contract
解析注解后的元数据;MethodHandler
会生成一个Request
;Ribbon
会从服务列表中选取一个Server
,拿到对应的IP地址后,拼接成最后的URL,就可以发起远程服务调用了。OpenFeign
是声明式的HTTP客户端,可以像访问本地方法一样进行远程调用;Ribbon
和服务熔断组件Hystrix
。