OpenFeign远程调用原理
之前对OpenFeign 了解到只用在接口上面打个注解,然后就可以通过内部调用去调用远程地址。研究完Feign生成对象以及代理对象的作用过程之后发现这个过程用到了Spring的好多东西,在之后的过程中可以借鉴这种思想。
查看Springboot项目一般从Enable入口,然后查看倒入的类。然后分析其IoC过程: 包括注册BeanDefinition、生成单例对象。
0. 以一个Feign 接口查看
接口如下:
package cn.qz.cloud.service; import cn.qz.cloud.utils.JSONResultUtil; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Map; @FeignClient(value = "CLOUD-PAYMENT-SERVICE") public interface PaymentFeignService { @GetMapping(value = "/pay/listAll") JSONResultUtil<List<Map<String, Object>>> listAll(); @GetMapping("/pay/getServerPort") JSONResultUtil<String> getServerPort(); /** * 注意 * 1. PathVariable 的value 属性必须有。 会在org.springframework.cloud.openfeign.annotation.PathVariableParameterProcessor#processArgument(org.springframework.cloud.openfeign.AnnotatedParameterProcessor.AnnotatedParameterContext, java.lang.annotation.Annotation, java.lang.reflect.Method) 进行验证 * * @param map * @param queryParam * @param id * @return */ @PostMapping("/pay/testParam/{id}") // JSONResultUtil<Map<String, Object>> testParam(Map<String, Object> map, String queryParam, String id); JSONResultUtil<Map<String, Object>> testParam(@RequestBody Map<String, Object> map, @RequestParam("queryParam") String queryParam, @PathVariable("id") String id); /** * 当只有一个参数时可不写@RequestBody * * @param map * @return */ @PostMapping("/pay/testParam2") JSONResultUtil<Map<String, Object>> testParam2(Map<String, Object> map); }
1. org.springframework.cloud.openfeign.EnableFeignClients源码查看
源码如下:
package org.springframework.cloud.openfeign; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.context.annotation.Import; @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 {}; Class<?>[] clients() default {}; }
可以看到是Import导入了FeignClientsRegistrar
package org.springframework.cloud.openfeign; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.EnvironmentAware; import org.springframework.context.ResourceLoaderAware; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.ClassMetadata; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.filter.AbstractClassTestingTypeFilter; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.core.type.filter.TypeFilter; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware { private ResourceLoader resourceLoader; private Environment environment; FeignClientsRegistrar() { } static void validateFallback(Class clazz) { Assert.isTrue(!clazz.isInterface(), "Fallback class must implement the interface annotated by @FeignClient"); } static void validateFallbackFactory(Class clazz) { Assert.isTrue(!clazz.isInterface(), "Fallback factory must produce instances of fallback classes that implement the interface annotated by @FeignClient"); } static String getName(String name) { if (!StringUtils.hasText(name)) { return ""; } else { String host = null; try { String url; if (!name.startsWith("http://") && !name.startsWith("https://")) { url = "http://" + name; } else { url = name; } host = (new URI(url)).getHost(); } catch (URISyntaxException var3) { ; } Assert.state(host != null, "Service id not legal hostname (" + name + ")"); return name; } } static String getUrl(String url) { if (StringUtils.hasText(url) && (!url.startsWith("#{") || !url.contains("}"))) { if (!url.contains("://")) { url = "http://" + url; } try { new URL(url); } catch (MalformedURLException var2) { throw new IllegalArgumentException(url + " is malformed", var2); } } return url; } static String getPath(String path) { if (StringUtils.hasText(path)) { path = path.trim(); if (!path.startsWith("/")) { path = "/" + path; } if (path.endsWith("/")) { path = path.substring(0, path.length() - 1); } } return path; } public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { this.registerDefaultConfiguration(metadata, registry); this.registerFeignClients(metadata, registry); } private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { 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(); } this.registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration")); } } public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { ClassPathScanningCandidateComponentProvider scanner = this.getScanner(); scanner.setResourceLoader(this.resourceLoader); Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName()); AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class); Class<?>[] clients = attrs == null ? null : (Class[])((Class[])attrs.get("clients")); Object basePackages; if (clients != null && clients.length != 0) { final Set<String> clientClasses = new HashSet(); basePackages = new HashSet(); Class[] var9 = clients; int var10 = clients.length; for(int var11 = 0; var11 < var10; ++var11) { Class<?> clazz = var9[var11]; ((Set)basePackages).add(ClassUtils.getPackageName(clazz)); clientClasses.add(clazz.getCanonicalName()); } AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() { protected boolean match(ClassMetadata metadata) { String cleaned = metadata.getClassName().replaceAll("\\$", "."); return clientClasses.contains(cleaned); } }; scanner.addIncludeFilter(new FeignClientsRegistrar.AllTypeFilter(Arrays.asList(filter, annotationTypeFilter))); } else { scanner.addIncludeFilter(annotationTypeFilter); basePackages = this.getBasePackages(metadata); } Iterator var17 = ((Set)basePackages).iterator(); while(var17.hasNext()) { String basePackage = (String)var17.next(); Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage); Iterator var21 = candidateComponents.iterator(); while(var21.hasNext()) { BeanDefinition candidateComponent = (BeanDefinition)var21.next(); if (candidateComponent instanceof AnnotatedBeanDefinition) { 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 = this.getClientName(attributes); this.registerClientConfiguration(registry, name, attributes.get("configuration")); this.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); this.validate(attributes); definition.addPropertyValue("url", this.getUrl(attributes)); definition.addPropertyValue("path", this.getPath(attributes)); String name = this.getName(attributes); definition.addPropertyValue("name", name); String contextId = this.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(2); String alias = contextId + "FeignClient"; AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); boolean primary = ((Boolean)attributes.get("primary")).booleanValue(); beanDefinition.setPrimary(primary); String qualifier = this.getQualifier(attributes); if (StringUtils.hasText(qualifier)) { alias = qualifier; } BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[]{alias}); BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); } private void validate(Map<String, Object> attributes) { AnnotationAttributes annotation = AnnotationAttributes.fromMap(attributes); validateFallback(annotation.getClass("fallback")); validateFallbackFactory(annotation.getClass("fallbackFactory")); } String getName(Map<String, Object> attributes) { String name = (String)attributes.get("serviceId"); if (!StringUtils.hasText(name)) { name = (String)attributes.get("name"); } if (!StringUtils.hasText(name)) { name = (String)attributes.get("value"); } name = this.resolve(name); return getName(name); } private String getContextId(Map<String, Object> attributes) { String contextId = (String)attributes.get("contextId"); if (!StringUtils.hasText(contextId)) { return this.getName(attributes); } else { contextId = this.resolve(contextId); return getName(contextId); } } private String resolve(String value) { return StringUtils.hasText(value) ? this.environment.resolvePlaceholders(value) : value; } private String getUrl(Map<String, Object> attributes) { String url = this.resolve((String)attributes.get("url")); return getUrl(url); } private String getPath(Map<String, Object> attributes) { String path = this.resolve((String)attributes.get("path")); return getPath(path); } protected ClassPathScanningCandidateComponentProvider getScanner() { return new ClassPathScanningCandidateComponentProvider(false, this.environment) { protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { boolean isCandidate = false; if (beanDefinition.getMetadata().isIndependent() && !beanDefinition.getMetadata().isAnnotation()) { isCandidate = true; } return isCandidate; } }; } protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) { Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(EnableFeignClients.class.getCanonicalName()); Set<String> basePackages = new HashSet(); String[] var4 = (String[])((String[])attributes.get("value")); int var5 = var4.length; int var6; String pkg; for(var6 = 0; var6 < var5; ++var6) { pkg = var4[var6]; if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } } var4 = (String[])((String[])attributes.get("basePackages")); var5 = var4.length; for(var6 = 0; var6 < var5; ++var6) { pkg = var4[var6]; if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } } Class[] var8 = (Class[])((Class[])attributes.get("basePackageClasses")); var5 = var8.length; for(var6 = 0; var6 < var5; ++var6) { Class<?> clazz = var8[var6]; basePackages.add(ClassUtils.getPackageName(clazz)); } if (basePackages.isEmpty()) { basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName())); } return basePackages; } private String getQualifier(Map<String, Object> client) { if (client == null) { return null; } else { String qualifier = (String)client.get("qualifier"); return StringUtils.hasText(qualifier) ? qualifier : null; } } private String getClientName(Map<String, Object> client) { if (client == null) { return null; } else { String value = (String)client.get("contextId"); if (!StringUtils.hasText(value)) { value = (String)client.get("value"); } if (!StringUtils.hasText(value)) { value = (String)client.get("name"); } if (!StringUtils.hasText(value)) { value = (String)client.get("serviceId"); } if (StringUtils.hasText(value)) { return value; } else { throw new IllegalStateException("Either 'name' or 'value' must be provided in @" + FeignClient.class.getSimpleName()); } } } private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class); builder.addConstructorArgValue(name); builder.addConstructorArgValue(configuration); registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(), builder.getBeanDefinition()); } public void setEnvironment(Environment environment) { this.environment = environment; } private static class AllTypeFilter implements TypeFilter { private final List<TypeFilter> delegates; AllTypeFilter(List<TypeFilter> delegates) { Assert.notNull(delegates, "This argument is required, it must not be null"); this.delegates = delegates; } public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { Iterator var3 = this.delegates.iterator(); TypeFilter filter; do { if (!var3.hasNext()) { return true; } filter = (TypeFilter)var3.next(); } while(filter.match(metadataReader, metadataReaderFactory)); return false; } } }
在之前了解到IoC过程中会扫描到Import导入的类,然后判断是ImportBeanDefinitionRegistrar 的话会进行反射创建对象,然后调用Aware 相关方法,然后调用 registerBeanDefinitions 方法动态的注册Bean。
所以核心入口就是registerBeanDefinitions 方法。
2. 注册到Spring 容器中过程
1. registerDefaultConfiguration(metadata, registry); 方法详解
注册了一个org.springframework.cloud.openfeign.FeignClientSpecification 类
2. registerFeignClients(metadata, registry); 方法详解==注册Bean的核心也是在这里
这个就是扫描指定包下面所有带FeignClient 注解的类,然后扫描到之后解析属性,最后注册到IoC容器中。IoC容器中维护的是FeignClientFactoryBean 一个工厂Bean, 也就是真正服务于业务的bean会在其getObject()方法生成。所以核心就是查看这个FactoryBean 的生成过程。
1. 调用方法org.springframework.cloud.openfeign.FeignClientsRegistrar#getScanner 获取一个scanner, 用于扫描classpath 路径下指定的类。(这也是Spring 的套路)
2. scanner.setResourceLoader(this.resourceLoader); 给scanner 设置一个resourceLoader。 真正干活的是这个resourceLoader 去加载
3. 下面代码给scanner 家里一个扫描包含的注解类型,就是我们的FeignClient 注解。
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class); scanner.addIncludeFilter(new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
4. 然后获取到EnableFeignClients 注解上的属性,如下:
没有指定扫描的包名,然后会取EnableFeignClients 所在类的包作为包路径进行扫描
5. 接下来就是遍历包名称集合,然后扫描得到BeanDefinition 对象。 然后获取到注解上面的属性,然后创建一个FeignClientFactoryBean 对象,并且将注解上的相关属性赋给Beandefinition内部的propertyValues 属性中。
(1) Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage); 代码利用新建的扫描器扫描指定包中包含@FeignClient 注解的对象,
最终扫描会调用到org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents(这个也是SpringIoC过程自动扫描的重要方法)
扫描出的beanDefinition 对象包含一些重要的信息,包括:
(2) 接下来获取扫描到的注解上的属性。
(3) 然后调用org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClient进行注册到IoC容器中
第一步创建一个FeignClientFactoryBean构造器BeanDefinitionBuilder(从这里看出来我们后面拿到的service 对象实际是这个工厂生产出来的对象)
第二步解析注解上面的属性,对name、url、path、fallback等属性进行特殊处理后设置到BeanDefinition 的一个propertyValues 属性中记录起来(最后容器在创建对象完成属性注入时会根据BeanDefinition的propertyValues中的属性进行注入,这个是SpringIoC创建对象过程中的一个操作)。
第三步设置其primary 属性为true,然后生成一个alias 别名
第四步就是调用BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); 注册到IoC容器中。
3. 接下来研究对象的创建
在上面了解到@FeignClient 只能用于接口中。
注入到容器的是一个FactoryBean, 则真正生成Bean 是FactoryBean的getObject 方法,而且是FactoryBean的创建是在IoC容器过程中创建的。SpringIoC创建完对象会先反射创建对象,然后属性注入,属性注入过程中会根据BeanDefinition对象的propertyValues 给反射的对象进行属性注入。
FactoryBean.getObject方法生成对象会在第一次使用bean时创建, 而不是在容器启动过程中就创建(也就是如果只声明不使用FactoryBean生成的对象不会进行创建)。
我们需要的target对象是一个接口,所以是需要用到JDK的动态代理来生成代理对象然后服务于业务。
org.springframework.cloud.openfeign.FeignClientFactoryBean源码如下:
package org.springframework.cloud.openfeign; import feign.Client; import feign.Contract; import feign.Logger; import feign.QueryMapEncoder; import feign.RequestInterceptor; import feign.Retryer; import feign.Feign.Builder; import feign.Logger.Level; import feign.Request.Options; import feign.Target.HardCodedTarget; import feign.codec.Decoder; import feign.codec.Encoder; import feign.codec.ErrorDecoder; import java.util.Iterator; import java.util.Map; import java.util.Objects; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.cloud.openfeign.FeignClientProperties.FeignClientConfiguration; import org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient; import org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.util.Assert; import org.springframework.util.StringUtils; class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware { private Class<?> type; private String name; private String url; private String contextId; private String path; private boolean decode404; private ApplicationContext applicationContext; private Class<?> fallback; private Class<?> fallbackFactory; FeignClientFactoryBean() { this.fallback = Void.TYPE; this.fallbackFactory = Void.TYPE; } public void afterPropertiesSet() throws Exception { Assert.hasText(this.contextId, "Context id must be set"); Assert.hasText(this.name, "Name must be set"); } protected Builder feign(FeignContext context) { FeignLoggerFactory loggerFactory = (FeignLoggerFactory)this.get(context, FeignLoggerFactory.class); Logger logger = loggerFactory.create(this.type); Builder builder = ((Builder)this.get(context, Builder.class)).logger(logger).encoder((Encoder)this.get(context, Encoder.class)).decoder((Decoder)this.get(context, Decoder.class)).contract((Contract)this.get(context, Contract.class)); this.configureFeign(context, builder); return builder; } protected void configureFeign(FeignContext context, Builder builder) { FeignClientProperties properties = (FeignClientProperties)this.applicationContext.getBean(FeignClientProperties.class); if (properties != null) { if (properties.isDefaultToProperties()) { this.configureUsingConfiguration(context, builder); this.configureUsingProperties((FeignClientConfiguration)properties.getConfig().get(properties.getDefaultConfig()), builder); this.configureUsingProperties((FeignClientConfiguration)properties.getConfig().get(this.contextId), builder); } else { this.configureUsingProperties((FeignClientConfiguration)properties.getConfig().get(properties.getDefaultConfig()), builder); this.configureUsingProperties((FeignClientConfiguration)properties.getConfig().get(this.contextId), builder); this.configureUsingConfiguration(context, builder); } } else { this.configureUsingConfiguration(context, builder); } } protected void configureUsingConfiguration(FeignContext context, Builder builder) { Level level = (Level)this.getOptional(context, Level.class); if (level != null) { builder.logLevel(level); } Retryer retryer = (Retryer)this.getOptional(context, Retryer.class); if (retryer != null) { builder.retryer(retryer); } ErrorDecoder errorDecoder = (ErrorDecoder)this.getOptional(context, ErrorDecoder.class); if (errorDecoder != null) { builder.errorDecoder(errorDecoder); } Options options = (Options)this.getOptional(context, Options.class); if (options != null) { builder.options(options); } Map<String, RequestInterceptor> requestInterceptors = context.getInstances(this.contextId, RequestInterceptor.class); if (requestInterceptors != null) { builder.requestInterceptors(requestInterceptors.values()); } QueryMapEncoder queryMapEncoder = (QueryMapEncoder)this.getOptional(context, QueryMapEncoder.class); if (queryMapEncoder != null) { builder.queryMapEncoder(queryMapEncoder); } if (this.decode404) { builder.decode404(); } } protected void configureUsingProperties(FeignClientConfiguration config, Builder builder) { if (config != null) { if (config.getLoggerLevel() != null) { builder.logLevel(config.getLoggerLevel()); } if (config.getConnectTimeout() != null && config.getReadTimeout() != null) { builder.options(new Options(config.getConnectTimeout().intValue(), config.getReadTimeout().intValue())); } if (config.getRetryer() != null) { Retryer retryer = (Retryer)this.getOrInstantiate(config.getRetryer()); builder.retryer(retryer); } if (config.getErrorDecoder() != null) { ErrorDecoder errorDecoder = (ErrorDecoder)this.getOrInstantiate(config.getErrorDecoder()); builder.errorDecoder(errorDecoder); } if (config.getRequestInterceptors() != null && !config.getRequestInterceptors().isEmpty()) { Iterator var7 = config.getRequestInterceptors().iterator(); while(var7.hasNext()) { Class<RequestInterceptor> bean = (Class)var7.next(); RequestInterceptor interceptor = (RequestInterceptor)this.getOrInstantiate(bean); builder.requestInterceptor(interceptor); } } if (config.getDecode404() != null && config.getDecode404().booleanValue()) { builder.decode404(); } if (Objects.nonNull(config.getEncoder())) { builder.encoder((Encoder)this.getOrInstantiate(config.getEncoder())); } if (Objects.nonNull(config.getDecoder())) { builder.decoder((Decoder)this.getOrInstantiate(config.getDecoder())); } if (Objects.nonNull(config.getContract())) { builder.contract((Contract)this.getOrInstantiate(config.getContract())); } } } private <T> T getOrInstantiate(Class<T> tClass) { try { return this.applicationContext.getBean(tClass); } catch (NoSuchBeanDefinitionException var3) { return BeanUtils.instantiateClass(tClass); } } protected <T> T get(FeignContext context, Class<T> type) { T instance = context.getInstance(this.contextId, type); if (instance == null) { throw new IllegalStateException("No bean found of type " + type + " for " + this.contextId); } else { return instance; } } protected <T> T getOptional(FeignContext context, Class<T> type) { return context.getInstance(this.contextId, type); } protected <T> T loadBalance(Builder builder, FeignContext context, HardCodedTarget<T> target) { Client client = (Client)this.getOptional(context, Client.class); if (client != null) { builder.client(client); Targeter targeter = (Targeter)this.get(context, Targeter.class); return targeter.target(this, builder, context, target); } else { throw new IllegalStateException("No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?"); } } public Object getObject() throws Exception { return this.getTarget(); } <T> T getTarget() { FeignContext context = (FeignContext)this.applicationContext.getBean(FeignContext.class); Builder builder = this.feign(context); if (!StringUtils.hasText(this.url)) { if (!this.name.startsWith("http")) { this.url = "http://" + this.name; } else { this.url = this.name; } this.url = this.url + this.cleanPath(); return this.loadBalance(builder, context, new HardCodedTarget(this.type, this.name, this.url)); } else { if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) { this.url = "http://" + this.url; } String url = this.url + this.cleanPath(); Client client = (Client)this.getOptional(context, Client.class); if (client != null) { if (client instanceof LoadBalancerFeignClient) { client = ((LoadBalancerFeignClient)client).getDelegate(); } if (client instanceof FeignBlockingLoadBalancerClient) { client = ((FeignBlockingLoadBalancerClient)client).getDelegate(); } builder.client(client); } Targeter targeter = (Targeter)this.get(context, Targeter.class); return targeter.target(this, builder, context, new HardCodedTarget(this.type, this.name, url)); } } private String cleanPath() { String path = this.path.trim(); if (StringUtils.hasLength(path)) { if (!path.startsWith("/")) { path = "/" + path; } if (path.endsWith("/")) { path = path.substring(0, path.length() - 1); } } return path; } public Class<?> getObjectType() { return this.type; } public boolean isSingleton() { return true; } public Class<?> getType() { return this.type; } public void setType(Class<?> type) { this.type = type; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } public String getContextId() { return this.contextId; } public void setContextId(String contextId) { this.contextId = contextId; } public String getUrl() { return this.url; } public void setUrl(String url) { this.url = url; } public String getPath() { return this.path; } public void setPath(String path) { this.path = path; } public boolean isDecode404() { return this.decode404; } public void setDecode404(boolean decode404) { this.decode404 = decode404; } public ApplicationContext getApplicationContext() { return this.applicationContext; } public void setApplicationContext(ApplicationContext context) throws BeansException { this.applicationContext = context; } public Class<?> getFallback() { return this.fallback; } public void setFallback(Class<?> fallback) { this.fallback = fallback; } public Class<?> getFallbackFactory() { return this.fallbackFactory; } public void setFallbackFactory(Class<?> fallbackFactory) { this.fallbackFactory = fallbackFactory; } public boolean equals(Object o) { if (this == o) { return true; } else if (o != null && this.getClass() == o.getClass()) { FeignClientFactoryBean that = (FeignClientFactoryBean)o; return Objects.equals(this.applicationContext, that.applicationContext) && this.decode404 == that.decode404 && Objects.equals(this.fallback, that.fallback) && Objects.equals(this.fallbackFactory, that.fallbackFactory) && Objects.equals(this.name, that.name) && Objects.equals(this.path, that.path) && Objects.equals(this.type, that.type) && Objects.equals(this.url, that.url); } else { return false; } } public int hashCode() { return Objects.hash(new Object[]{this.applicationContext, this.decode404, this.fallback, this.fallbackFactory, this.name, this.path, this.type, this.url}); } public String toString() { return "FeignClientFactoryBean{" + "type=" + this.type + ", " + "name='" + this.name + "', " + "url='" + this.url + "', " + "path='" + this.path + "', " + "decode404=" + this.decode404 + ", " + "applicationContext=" + this.applicationContext + ", " + "fallback=" + this.fallback + ", " + "fallbackFactory=" + this.fallbackFactory + "}"; } }
首先:
1. org.springframework.cloud.openfeign.FeignClientFactoryBean#getObjectType 返回的类型是业务代码向容器索要Bean判断是否匹配的依据,其返回的是内部的type属性。 这个属性是在上面BeanDefinition内部维持的, 值夜就是我们声明@FeignClient 注解的类名称。
2. FeignClientFactoryBean 内部的属性是在IoC容器启动过程中创建完对象,然后属性注入阶段从BeanDefinition的propertyValues 中拿的。
接下来研究其getObject创建target对象过程中的主要操作。
org.springframework.cloud.openfeign.FeignClientFactoryBean实现了三个接口:FactoryBean<Object>, InitializingBean, ApplicationContextAware
FactoryBean 接口是生成对象; InitializingBean 是在属性注入之后检查contextId 和 name 属性;ApplicationContextAware 是获取ApplicationContext 对象工厂。注入FeignContext 的时机是在org.springframework.cloud.sleuth.instrument.web.client.feign.FeignContextBeanPostProcessor
getObject 调用getTarget 过程如下:
1. org.springframework.cloud.openfeign.FeignClientFactoryBean#feign 这里主要构造一个builder,主要包括如下操作:
构造了一个日志打印器(基于原来的接口创建的logger)、配置RequestInterceptor、Logger.Level、Retryer、QueryMapEncoder等属性
这里有两种配置方式吧,一种是基于Spring的Bean 注入的方式;一种是配置方式org.springframework.cloud.openfeign.FeignClientProperties.FeignClientConfiguration(其中这种方式可以基于全局配置,也可以对某个服务单独设置)
全局就是注入Bean的方式修改所有默认的,如果想单独修改某个服务相关的可以在yml 进行配置:
feign: client: config: CLOUD-PAYMENT-SERVICE: loggerLevel: BASIC
3. 然后在没有url 的情况下是按照服务名进行处理,拼接url 属性为http://服务名称。 如果有URL会按照URL的方式进行处理,并且如果URL没有加http:// 会在这里加上,也就是URL 可以只写域名加端口
4. 获取一个feign.Target.HardCodedTarget 对象,这个对象里面实际就是一个记录的功能,记录了
type: 也就是借口的类型
name: 服务名称
url: 地址信息
5. 调用org.springframework.cloud.openfeign.FeignClientFactoryBean#loadBalance 方法
第一步获取一个feign.Client 对象
第二步获取一个org.springframework.cloud.openfeign.Targeter 对象
第三步调用方法 org.springframework.cloud.openfeign.Targeter#target 创建一个代理对象,这个方法逻辑如下
(1)调用方法org.springframework.cloud.openfeign.HystrixTargeter#target:
(2) 上面方法调用feign.Feign.Builder#target(feign.Target<T>)
public <T> T target(Target<T> target) { return this.build().newInstance(target); } public Feign build() { Factory synchronousMethodHandlerFactory = new Factory(this.client, this.retryer, this.requestInterceptors, this.logger, this.logLevel, this.decode404, this.closeAfterDecode, this.propagationPolicy); ParseHandlersByName handlersByName = new ParseHandlersByName(this.contract, this.options, this.encoder, this.decoder, this.queryMapEncoder, this.errorDecoder, synchronousMethodHandlerFactory); return new ReflectiveFeign(handlersByName, this.invocationHandlerFactory, this.queryMapEncoder); }
这里是创建一个工厂,然后调用下面方法
(3)feign.ReflectiveFeign#newInstance
public <T> T newInstance(Target<T> target) { Map<String, MethodHandler> nameToHandler = this.targetToHandlersByName.apply(target); Map<Method, MethodHandler> methodToHandler = new LinkedHashMap(); List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList(); Method[] var5 = target.type().getMethods(); int var6 = var5.length; for(int var7 = 0; var7 < var6; ++var7) { Method method = var5[var7]; if (method.getDeclaringClass() != Object.class) { 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 = this.factory.create(target, methigeodToHandler); T proxy = Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler); Iterator var12 = defaultMethodHandlers.iterator(); while(var12.hasNext()) { DefaultMethodHandler defaultMethodHandler = (DefaultMethodHandler)var12.next(); defaultMethodHandler.bindTo(proxy); } return proxy; }
这里的重点就是获取到一个methodToHandler对象,这个就是维护一个方法与队应处理器的map,内容如下:
然后调用feign.InvocationHandlerFactory.Default#create 创建InvocationHandler, 这个是JDK代理需要的参数。feign.ReflectiveFeign.FeignInvocationHandler 如下:
static class FeignInvocationHandler implements InvocationHandler { private final Target target; private final Map<Method, MethodHandler> dispatch; FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) { this.target = checkNotNull(target, "target"); this.dispatch = checkNotNull(dispatch, "dispatch for %s", target); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("equals".equals(method.getName())) { try { Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null; return equals(otherHandler); } catch (IllegalArgumentException e) { return false; } } else if ("hashCode".equals(method.getName())) { return hashCode(); } else if ("toString".equals(method.getName())) { return toString(); } return dispatch.get(method).invoke(args); } @Override public boolean equals(Object obj) { if (obj instanceof FeignInvocationHandler) { FeignInvocationHandler other = (FeignInvocationHandler) obj; return target.equals(other.target); } return false; } @Override public int hashCode() { return target.hashCode(); } @Override public String toString() { return target.toString(); } }
接下来就是用Proxy.newProxyInstance 创建代理对象并且返回,这就是JDK的动态代理了。
补充:feign.ReflectiveFeign#newInstance 方法第一行是实例化过程中重要的一步,验证方法的参数以及对参数对应注解的解析以及封装也是在这一步
第一行代码如下:
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
1. feign.ReflectiveFeign.ParseHandlersByName#apply 获取方法与其对应的MethodHandler 用于后期动态代理中调用。
public Map<String, MethodHandler> apply(Target key) { List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type()); Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>(); for (MethodMetadata md : metadata) { BuildTemplateByResolvingArgs buildTemplate; if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) { buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder); } else if (md.bodyIndex() != null) { buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder); } else { buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder); } result.put(md.configKey(), factory.create(key, md, buildTemplate, options, decoder, errorDecoder)); } return result; }
2. feign.Contract.BaseContract#parseAndValidatateMetadata(java.lang.Class<?>) 这里进行检查方法参数、解析与封装成List<MethodMetadata> 对象
@Override public List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType) { checkState(targetType.getTypeParameters().length == 0, "Parameterized types unsupported: %s", targetType.getSimpleName()); checkState(targetType.getInterfaces().length <= 1, "Only single inheritance supported: %s", targetType.getSimpleName()); if (targetType.getInterfaces().length == 1) { checkState(targetType.getInterfaces()[0].getInterfaces().length == 0, "Only single-level inheritance supported: %s", targetType.getSimpleName()); } Map<String, MethodMetadata> result = new LinkedHashMap<String, MethodMetadata>(); for (Method method : targetType.getMethods()) { if (method.getDeclaringClass() == Object.class || (method.getModifiers() & Modifier.STATIC) != 0 || Util.isDefault(method)) { continue; } MethodMetadata metadata = parseAndValidateMetadata(targetType, method); checkState(!result.containsKey(metadata.configKey()), "Overrides unsupported: %s", metadata.configKey()); result.put(metadata.configKey(), metadata); } return new ArrayList<>(result.values()); }
(1) 首先进行了类合法性校验:
泛型类是不被支持的
代理的类的接口数量为0个或者1个
如果有继承的接口再判断父接口的父接口是不是为空, 也就是只能实现单继承
(2) 然后获取到所有的方法进行遍历解析获得一个MethodMetadata 对象,主要的操作是:org.springframework.cloud.openfeign.support.SpringMvcContract#parseAndValidateMetadata
public MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) { this.processedMethods.put(Feign.configKey(targetType, method), method); MethodMetadata md = super.parseAndValidateMetadata(targetType, method); RequestMapping classAnnotation = findMergedAnnotation(targetType, RequestMapping.class); if (classAnnotation != null) { // produces - use from class annotation only if method has not specified this if (!md.template().headers().containsKey(ACCEPT)) { parseProduces(md, method, classAnnotation); } // consumes -- use from class annotation only if method has not specified this if (!md.template().headers().containsKey(CONTENT_TYPE)) { parseConsumes(md, method, classAnnotation); } // headers -- class annotation is inherited to methods, always write these if // present parseHeaders(md, method, classAnnotation); } return md; }
这里会调用父类方法:feign.Contract.BaseContract#parseAndValidateMetadata
protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) { MethodMetadata data = new MethodMetadata(); data.returnType(Types.resolve(targetType, targetType, method.getGenericReturnType())); data.configKey(Feign.configKey(targetType, method)); if (targetType.getInterfaces().length == 1) { processAnnotationOnClass(data, targetType.getInterfaces()[0]); } processAnnotationOnClass(data, targetType); for (Annotation methodAnnotation : method.getAnnotations()) { processAnnotationOnMethod(data, methodAnnotation, method); } checkState(data.template().method() != null, "Method %s not annotated with HTTP method type (ex. GET, POST)", method.getName()); Class<?>[] parameterTypes = method.getParameterTypes(); Type[] genericParameterTypes = method.getGenericParameterTypes(); Annotation[][] parameterAnnotations = method.getParameterAnnotations(); int count = parameterAnnotations.length; for (int i = 0; i < count; i++) { boolean isHttpAnnotation = false; if (parameterAnnotations[i] != null) { isHttpAnnotation = processAnnotationsOnParameter(data, parameterAnnotations[i], i); } if (parameterTypes[i] == URI.class) { data.urlIndex(i); } else if (!isHttpAnnotation && parameterTypes[i] != Request.Options.class) { checkState(data.formParams().isEmpty(), "Body parameters cannot be used with form parameters."); checkState(data.bodyIndex() == null, "Method has too many Body parameters: %s", method); data.bodyIndex(i); data.bodyType(Types.resolve(targetType, targetType, genericParameterTypes[i])); } } if (data.headerMapIndex() != null) { checkMapString("HeaderMap", parameterTypes[data.headerMapIndex()], genericParameterTypes[data.headerMapIndex()]); } if (data.queryMapIndex() != null) { if (Map.class.isAssignableFrom(parameterTypes[data.queryMapIndex()])) { checkMapKeys("QueryMap", genericParameterTypes[data.queryMapIndex()]); } } return data; }
1》初始化一个 MethodMetadata 之后, Annotation[][] parameterAnnotations = method.getParameterAnnotations(); 获取一个方法参数对应的注解数组,二维数组是每一行代表对应的参数位置
比如下面方法:
/** * 注意 * 1. PathVariable 的value 属性必须有。 会在org.springframework.cloud.openfeign.annotation.PathVariableParameterProcessor#processArgument(org.springframework.cloud.openfeign.AnnotatedParameterProcessor.AnnotatedParameterContext, java.lang.annotation.Annotation, java.lang.reflect.Method) 进行验证 * * @param map * @param queryParam * @param id * @return */ @PostMapping("/pay/testParam/{id}") // JSONResultUtil<Map<String, Object>> testParam(@RequestBody Map<String, Object> map, @RequestParam("queryParam") String queryParam, @PathVariable("id") String id); // 下面这种写可以, 参数上不写任何注解默认会作为body 处理,但是body 只能出现一个, 多个会在启动过程中检查报错 JSONResultUtil<Map<String, Object>> testParam(Map<String, Object> map, @RequestParam("queryParam") String queryParam, @PathVariable("id") String id);
获取到的数组为:
2》然后循环处理方法的参数
org.springframework.cloud.openfeign.support.SpringMvcContract#processAnnotationsOnParameter
protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) { boolean isHttpAnnotation = false; AnnotatedParameterProcessor.AnnotatedParameterContext context = new SimpleAnnotatedParameterContext( data, paramIndex); Method method = this.processedMethods.get(data.configKey()); for (Annotation parameterAnnotation : annotations) { AnnotatedParameterProcessor processor = this.annotatedArgumentProcessors .get(parameterAnnotation.annotationType()); if (processor != null) { Annotation processParameterAnnotation; // synthesize, handling @AliasFor, while falling back to parameter name on // missing String #value(): processParameterAnnotation = synthesizeWithMethodParameterNameAsFallbackValue( parameterAnnotation, method, paramIndex); isHttpAnnotation |= processor.processArgument(context, processParameterAnnotation, method); } } if (isHttpAnnotation && data.indexToExpander().get(paramIndex) == null) { TypeDescriptor typeDescriptor = createTypeDescriptor(method, paramIndex); if (this.conversionService.canConvert(typeDescriptor, STRING_TYPE_DESCRIPTOR)) { Param.Expander expander = this.convertingExpanderFactory .getExpander(typeDescriptor); if (expander != null) { data.indexToExpander().put(paramIndex, expander); } } } return isHttpAnnotation; }
根据参数对应的注解数组信息 annotations 进行解析,根据注解类型从annotatedArgumentProcessors获取到对应的参数解析器,然后调用processor.processArgument(context, processParameterAnnotation, method); 进行处理。
比如默认的注解类型与参数解析器关系如下:
这里会进行参数的注解验证。 默认什么注解也不写会作为RequestBody 解析,也就是获取到对应参数的顺序标记为bodyIndex,用于后面方法调用时解析body。(这里注意最多有一个RequestBody, 如果多个会在checkState(data.bodyIndex() == null, "Method has too many Body parameters: %s", method); 验证时报错)
经过上面处理获取到的MethodMetadata如下:
3. 然后根据方法创建MethodHandler 对象存入Map 中返回去供使用
public MethodHandler create(Target<?> target, MethodMetadata md, RequestTemplate.Factory buildTemplateFromArgs, Options options, Decoder decoder, ErrorDecoder errorDecoder) { return new SynchronousMethodHandler(target, client, retryer, requestInterceptors, logger, logLevel, md, buildTemplateFromArgs, options, decoder, errorDecoder, decode404, closeAfterDecode, propagationPolicy); }
到此,Feign 代理注册以及对象创建完成。所以调用过程中的就是feign.ReflectiveFeign.FeignInvocationHandler#invoke方法。
4. 调用过程分析
基于JDK的动态代理其入口是在InvocationHandler的invoke方法,上面是:feign.ReflectiveFeign.FeignInvocationHandler#invoke
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("equals".equals(method.getName())) { try { Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null; return equals(otherHandler); } catch (IllegalArgumentException e) { return false; } } else if ("hashCode".equals(method.getName())) { return hashCode(); } else if ("toString".equals(method.getName())) { return toString(); } return dispatch.get(method).invoke(args); }
可以看到toString、equals、hashcode方法特殊处理,指挥就是根据Method 对象获取到对应的MethodHandler 进行调用。
1. 调用到feign.SynchronousMethodHandler#invoke
@Override public Object invoke(Object[] argv) throws Throwable { RequestTemplate template = buildTemplateFromArgs.create(argv); Options options = findOptions(argv); Retryer retryer = this.retryer.clone(); while (true) { try { return executeAndDecode(template, options); } catch (RetryableException e) { try { retryer.continueOrPropagate(e); } catch (RetryableException th) { Throwable cause = th.getCause(); if (propagationPolicy == UNWRAP && cause != null) { throw cause; } else { throw th; } } if (logLevel != Logger.Level.NONE) { logger.logRetry(metadata.configKey(), logLevel); } continue; } } }
(1) RequestTemplate.from(metadata.template()); 根据上面创建对象过程中解析出来的RequestTemplate克隆一个RequestTemplate
(2) 将参数解析出来,存放到: varBuilder
(3) resolve(argv, mutable, varBuilder); 根据解析出的参数替换url 中的变量以及queries 中需要的变量以及设置一些请求头
(4) feign.SynchronousMethodHandler#executeAndDecode 开始处理
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable { Request request = targetRequest(template); if (logLevel != Logger.Level.NONE) { logger.logRequest(metadata.configKey(), logLevel, request); } Response response; long start = System.nanoTime(); try { response = client.execute(request, options); } catch (IOException e) { if (logLevel != Logger.Level.NONE) { logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start)); } throw errorExecuting(request, e); } long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start); boolean shouldClose = true; try { if (logLevel != Logger.Level.NONE) { response = logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime); } if (Response.class == metadata.returnType()) { if (response.body() == null) { return response; } if (response.body().length() == null || response.body().length() > MAX_RESPONSE_BUFFER_SIZE) { shouldClose = false; return response; } // Ensure the response body is disconnected byte[] bodyData = Util.toByteArray(response.body().asInputStream()); return response.toBuilder().body(bodyData).build(); } if (response.status() >= 200 && response.status() < 300) { if (void.class == metadata.returnType()) { return null; } else { Object result = decode(response); shouldClose = closeAfterDecode; return result; } } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) { Object result = decode(response); shouldClose = closeAfterDecode; return result; } else { throw errorDecoder.decode(metadata.configKey(), response); } } catch (IOException e) { if (logLevel != Logger.Level.NONE) { logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime); } throw errorReading(request, response, e); } finally { if (shouldClose) { ensureClosed(response.body()); } } }
第一步: 这里首先调用这个方法对FeignInteceptor 拦截器做处理 feign.SynchronousMethodHandler#targetRequest, 并且将信息封装到feign.Request 类中
Request targetRequest(RequestTemplate template) { for (RequestInterceptor interceptor : requestInterceptors) { interceptor.apply(template); } return target.apply(template); }
第二步:打印request 对象
第三步:org.springframework.cloud.sleuth.instrument.web.client.feign.TraceLoadBalancerFeignClient#execute 调用
@Override public Response execute(Request request, Request.Options options) throws IOException { if (log.isDebugEnabled()) { log.debug("Before send"); } Response response = null; Span fallbackSpan = tracer().nextSpan().start(); try { response = super.execute(request, options); if (log.isDebugEnabled()) { log.debug("After receive"); } return response; } catch (Exception e) { if (log.isDebugEnabled()) { log.debug("Exception thrown", e); } if (e instanceof IOException || e.getCause() != null && e.getCause() instanceof ClientException && ((ClientException) e.getCause()) .getErrorType() == ClientException.ErrorType.GENERAL) { if (log.isDebugEnabled()) { log.debug( "General exception was thrown, so most likely the traced client wasn't called. Falling back to a manual span"); } fallbackSpan = tracingFeignClient().handleSend( new HashMap<>(request.headers()), request, fallbackSpan); tracingFeignClient().handleReceive(fallbackSpan, response, e); } throw e; } finally { fallbackSpan.abandon(); } }
转交给父类org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient#execute
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); FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest( this.delegate, request, uriWithoutHost); 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); } }
之后调用com.netflix.client.AbstractLoadBalancerAwareClient#executeWithLoadBalancer(S, com.netflix.client.config.IClientConfig) , 也就是请求经过一下ribbon
/** * This method should be used when the caller wants to dispatch the request to a server chosen by * the load balancer, instead of specifying the server in the request's URI. * It calculates the final URI by calling {@link #reconstructURIWithServer(com.netflix.loadbalancer.Server, java.net.URI)} * and then calls {@link #executeWithLoadBalancer(ClientRequest, com.netflix.client.config.IClientConfig)}. * * @param request request to be dispatched to a server chosen by the load balancer. The URI can be a partial * URI which does not contain the host name or the protocol. */ public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException { LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig); try { 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 { 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); } } }
这里首先将服务名称换成真实的ip和端口,然后调用 AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig) 请求根据不同的 AbstractLoadBalancerAwareClient 选择执行, 这里会调用到 org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer#execute
@Override public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride) throws IOException { Request.Options options; 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); }
这里实际就是 request.client() 客户端去执行请求,可以走连接池,可以走Feign 自己的httpClient(默认)。