首页 > 技术文章 > Spring生态系列文章 >

SpringCloud-源码分析 Feign

更新时间:2019-01-03 | 阅读量(2,389)

> 本文作者:陈刚,叩丁狼高级讲师。原创文章,转载请注明出处。 #### 回顾 我们还是在之前Feign的使用案例集成上来分析Feign源码,在分析 之前我们先简单来回顾一下Feign的用法,要用Feign首选是需要在配置类似开启Feign客户端支持 ``` @EnableEurekaClient @SpringBootApplication @EnableFeignClients public class ConsumerApplication { public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } ``` 然后我们需要去定义Feign客户端接口 ``` //PRODUCER:指向要访问的服务 //configuration = FooConfiguration.class Feign Client配置类,指定负载均衡策略 //fallback = MyFeignClientImpl.class: MyFeignClient结构的实现类,需要复写 provide方法,并实现错误处理逻辑 //当访问出现故障,程序会自动调用实现类的 provide方法的错误处理逻辑。 @FeignClient(value = "PRODUCER",configuration = FooConfiguration.class,fallback = MyFeignClientImpl.class) public interface MyFeignClient { //当此方法别调用会自动请求到 PRODUCER服务的 /provide 资源 @RequestMapping(value = "/provide") public String provide(@RequestParam("name") String name); } ``` 在使用的时候就只需要调用 MyFeignClient 接口即可,SpringCloud会自动去调用对应的目标服务。 分析Feign我们还是从他的@EnableFeignClients标签开始,我们先看下他的源码 ``` /** * Scans for interfaces that declare they are feign clients (via {@link FeignClient * @FeignClient}). Configures component scanning directives for use with * {@link org.springframework.context.annotation.Configuration * @Configuration} classes. * * @author Spencer Gibb * @author Dave Syer * @since 1.0 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(FeignClientsRegistrar.class) public @interface EnableFeignClients { ``` 该标签的注释告诉我们,它开启了对打了 @FeignClient 标签所在的接口的扫描,和对期配置组件的扫描支持 ,并且该标签import了一个FeignClientsRegistrar类,我们跟踪进去看一下 ``` /** * @author Spencer Gibb * @author Jakub Narloch * @author Venil Noronha * @author Gang Li */ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware { // patterned after Spring Integration IntegrationComponentScanRegistrar // and RibbonClientsConfigurationRegistgrar private ResourceLoader resourceLoader; private Environment environment; public FeignClientsRegistrar() { ``` 他实现了 ImportBeanDefinitionRegistrar接口,我们继续看ImportBeanDefinitionRegistrar的源码 ``` /** * Interface to be implemented by types that register additional bean definitions when * processing @{@link Configuration} classes. Useful when operating at the bean definition * level (as opposed to {@code @Bean} method/instance level) is desired or necessary. * *

Along with {@code @Configuration} and {@link ImportSelector}, classes of this type * may be provided to the @{@link Import} annotation (or may also be returned from an * {@code ImportSelector}). * *

An {@link ImportBeanDefinitionRegistrar} may implement any of the following * {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective * methods will be called prior to {@link #registerBeanDefinitions}: *

    *
  • {@link org.springframework.context.EnvironmentAware EnvironmentAware}
  • *
  • {@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware} *
  • {@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware} *
  • {@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware} *
* *

See implementations and associated unit tests for usage examples. * * @author Chris Beams * @since 3.1 * @see Import * @see ImportSelector * @see Configuration */ public interface ImportBeanDefinitionRegistrar { /** * Register bean definitions as necessary based on the given annotation metadata of * the importing {@code @Configuration} class. *

Note that {@link BeanDefinitionRegistryPostProcessor} types may not be * registered here, due to lifecycle constraints related to {@code @Configuration} * class processing. * @param importingClassMetadata annotation metadata of the importing class * @param registry current bean definition registry */ public void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry); } ``` 翻译“Interface to be implemented by types that register additional bean definitions when processing @{@link Configuration} classes.”得知,该接口提供了注册bean的功能支持,而registerBeanDefinitions则是bean注册的方法。 AnnotationMetadata是bean的元注解对象 ,BeanDefinitionRegistry是当前注册的bean的注册表, 我们继续跟踪一下 FeignClientsRegistrar#registerBeanDefinitions 方法 ``` @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { registerDefaultConfiguration(metadata, registry); registerFeignClients(metadata, registry); } ``` registerDefaultConfiguration(metadata, registry);注册默认配置,我们跟踪一下源码 ``` 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(); } //name以 "default."+类名 registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration")); } } ``` 这里获取了主程序配置类的@EnableFeignClients注解中的feign默认配置 ,然后用 “default.” 开头加上类名作为名字来注册 ,继续跟踪 ``` 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()); } ``` 这里把 configuration 配置交给了 BeanDefinitionBuilder然后去创建了一个 FeignClientSpecification 对象出来 ,然后调用BeanDefinitionRegistry的registerBeanDefinition方法把 包装了配置类的FeignClientSpecification对象进行注册,注册到哪儿去了呢?最终会调用BeanDefinitionRegistryd的子类DefaultListableBeanFactory#registerBeanDefinition方法注册 ``` ...省略代码... /** Map of bean definition objects, keyed by bean name */ private final Map beanDefinitionMap = new ConcurrentHashMap<>(256); @Override public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { ...省略代码... this.beanDefinitionMap.put(beanName, beanDefinition); ``` 这里最终会put到一个线程安全的map:beanDefinitionMap = new ConcurrentHashMap<>(256); 中,这个东西就是ico容器o(* ̄︶ ̄*)o 。 回过头来我们继续看 FeignClientsRegistrar#registerBeanDefinitions 方法 ``` @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { //注册默认配置到ioc容器中 registerDefaultConfiguration(metadata, registry); registerFeignClients(metadata, registry); } ``` 到这里我们已经知道 registerDefaultConfiguration(metadata, registry);注册默认配置,那么 registerFeignClients(metadata, registry);方法看名字应该是注册所有的feignClient的bean,我们一步一步进去看 ``` public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { //得到组件扫描器,resourceLoader是资源加载器, //比如加载spring配置文件就会用到ResourceLoader ClassPathScanningCandidateComponentProvider scanner = getScanner(); scanner.setResourceLoader(this.resourceLoader); Set basePackages; //获取元注解上EnableFeignClients的属性配置 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() { @Override protected 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) { //扫描候选的组件,会把basePackage报下的bean都扫描到,然后过滤出打了 @FeignClient 标签的接口,封装成 BeanDefinition bean描述对象 Set candidateComponents = scanner .findCandidateComponents(basePackage); for (BeanDefinition candidateComponent : candidateComponents) { if (candidateComponent instanceof AnnotatedBeanDefinition) { // verify annotated class is an interface AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent; //获取FeignClient接口上的的注解(我定义的FeignClient接口是MyFeignClient) AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); //如果贴上 @FeignClient标签的不是接口,抛出异常 Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface"); //获取到@FeignClient 注解上我们配置的所有属性 Map attributes = annotationMetadata .getAnnotationAttributes( FeignClient.class.getCanonicalName()); //获取@FeignClient标签的服务名我这指定是“PRODUCER” //(@FeignClient(value = "PRODUCER",configuration = FooConfiguration.class,fallback = MyFeignClientImpl.class)) String name = getClientName(attributes); //注册配置对象:即会获取到configuration = FooConfiguration.class然后实现注册(将bean封装成beanDefinition对象放到ioc容器中), //而FooConfiguration中我们自己实现了负载均衡规则 registerClientConfiguration(registry, name, attributes.get("configuration")); //注册 registerFeign registerFeignClient(registry, annotationMetadata, attributes); } } } ``` 这里面做的事情挺多的 1.通过组件扫描器ClassPathScanningCandidateComponentProvider 2.获取元注解所在配置类的包名, 3.然后通过ClassPathScanningCandidateComponentProvider扫描打了@FeignClient标签的接口 ,然后封装成 Set 4.获取 FeignClient 接口上的 @FeignClient标签的配置类,完成注册 5.完成 FeignClient接口本身的注册 我们继续跟踪 registerFeignClient 方法 ``` private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map attributes) { String className = annotationMetadata.getClassName(); //这里使用了FeignClientFactoryBean 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); 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); //取别名 FeignClient结尾 String alias = name + "FeignClient"; AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null beanDefinition.setPrimary(primary); String qualifier = getQualifier(attributes); if (StringUtils.hasText(qualifier)) { alias = qualifier; } BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias }); //注册 FeignClientFactoryBean BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); } ``` 这里使用 FeignClientFactoryBean来封装 FeignClient的描述信息 ,并且注册到ioc容器中 ,使用FeignClientFactoryBean 的目的是为了在spring容器刷新过程中会使用代理工厂来创建出代理类(你是否想过,我们的MyFeignClient始终是个接口,那么他是如何实现远程服务的调用的?肯定需要创建出具体的实例去处理),我们跟踪一下 FeignClientFactoryBean ``` class FeignClientFactoryBean implements FactoryBean, InitializingBean, ApplicationContextAware { /*********************************** * WARNING! Nothing in this class should be @Autowired. It causes NPEs because of some lifecycle race condition. ***********************************/ private Class type; private String name; private String url; private String path; private boolean decode404; private ApplicationContext applicationContext; private Class fallback = void.class; ...省略代码... @Override public Object getObject() throws Exception { return getTarget(); } ...省略代码... T getTarget() { //初始化Feign上下文对象,使用的TraceFeignContext的实例 FeignContext context = applicationContext.getBean(FeignContext.class); //Feign.Builder用来创建feign的构建器 Feign.Builder builder = feign(context); //判断 @FeignClient 是否配置url,这里的url,name等都是从@FeignClient读取过来的 if (!StringUtils.hasText(this.url)) { String url; //拼接http协议地址 if (!this.name.startsWith("http")) { url = "http://" + this.name; } else { url = this.name; } //给服务名拼接地址, //如果是@FeignClient(value="PRODUCER") ,那么这里就是拼接成 http://PRODUCER url += cleanPath(); //创建负载均衡的代理类,底层使用到了jdk动态代理,见:feign.ReflectiveFeign#newInstance方法 return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, url)); } //如果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 unwrap client = ((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)); } ....省略代码.. ``` 首先FeignClientFactoryBean实现了 FactoryBean ,而FactoryBean就是spring提供给我们实例化bean的一种方式,bean的实例化过程在 getObject 方法中进行处理 ,而在这里他在getObject方法中调用getTarget()方法。 这个方法里面做了很多事情 1.首先获取FeignContext feigin上下文对象,可以把它理解为是feign的容器对象,而FeignContext使用FeignClientsConfiguration进行配置,在FeignClientsConfiguration中会把feignClient相关的配置绑定到FeignContext中 ``` public class FeignContext extends NamedContextFactory { public FeignContext() { super(FeignClientsConfiguration.class, "feign", "feign.client.name"); } } ---------------------------------------------------------------------- @Configuration @ConditionalOnClass(Feign.class) @EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class}) public class FeignAutoConfiguration { @Autowired(required = false) private List configurations = new ArrayList<>(); @Bean public HasFeatures feignFeature() { return HasFeatures.namedFeature("Feign", Feign.class); } @Bean public FeignContext feignContext() { FeignContext context = new FeignContext(); //绑定配置 context.setConfigurations(this.configurations); return context; } ``` 2.Feign.Builder builder = feign(context); 获取Feign构建器 3.判断 url是否为null,如果为null则创建负载均衡的代理对象 4.如果url不为null,创建默认的代理对象 我们继续跟踪一下 feign(context)方法 ``` protected Feign.Builder feign(FeignContext context) { FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class); Logger logger = loggerFactory.create(this.type); //获取Feign.Builder // @formatter:off 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 configureFeign(context, builder); return builder; } ............ protected void configureFeign(FeignContext context, Feign.Builder builder) { FeignClientProperties properties = 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.name), builder); } else { configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder); configureUsingProperties(properties.getConfig().get(this.name), builder); configureUsingConfiguration(context, builder); } } else { configureUsingConfiguration(context, builder); } } ............ protected void configureUsingConfiguration(FeignContext context, Feign.Builder builder) { Logger.Level level = getOptional(context, Logger.Level.class); if (level != null) { builder.logLevel(level); } //设置重试策略到 Feign.Builder Retryer retryer = getOptional(context, Retryer.class); if (retryer != null) { builder.retryer(retryer); } //设置错误处理 ErrorDecoder errorDecoder = getOptional(context, ErrorDecoder.class); if (errorDecoder != null) { builder.errorDecoder(errorDecoder); } //设置请求参数比如超时时间等 Request.Options options = getOptional(context, Request.Options.class); if (options != null) { builder.options(options); } //绑定请求拦截器 Map requestInterceptors = context.getInstances( this.name, RequestInterceptor.class); if (requestInterceptors != null) { builder.requestInterceptors(requestInterceptors.values()); } if (decode404) { builder.decode404(); } } ``` 这里获取到 builder 之后又做了后续的一些设置,比如重试策略 ,错误处理,请求超时时间设定等 。 我们跟踪一下 Feign.Builder builder = get(context, Feign.Builder.class) 方法 ``` protected T get(FeignContext context, Class type) { //调用 TraceFeignContext的getInstance方法,根据name即服务的名字来创建builder T instance = context.getInstance(this.name, type); if (instance == null) { throw new IllegalStateException("No bean found of type " + type + " for " + this.name); } return instance; } ............................... public T getInstance(String name, Class type) { AnnotationConfigApplicationContext context = getContext(name); if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, type).length > 0) { //创建builder return context.getBean(type); } return null; } ............................... protected AnnotationConfigApplicationContext getContext(String name) { //这里的参数name就是目标服务的名字,如果contexts中没有就创建一个放进去 if (!this.contexts.containsKey(name)) { synchronized (this.contexts) { if (!this.contexts.containsKey(name)) { this.contexts.put(name, createContext(name)); } } } return this.contexts.get(name); } ``` 这里在根据被调用的目标服务的名字判断contexts中是否存在实例(Map contexts = new ConcurrentHashMap<>();),如果不存在就根据名字创建一个(这里使用了AnnotationConfigApplicationContext进行封装)放入进去,其实就是在做缓存,我们跟踪一下 createContext方法看下怎么创建的 ``` protected AnnotationConfigApplicationContext createContext(String name) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); //加载服务对应的配置类 if (this.configurations.containsKey(name)) { for (Class configuration : this.configurations.get(name) .getConfiguration()) { context.register(configuration); } } //加载主程序配置类的@EnableFeignClients注解指定的配置, 还记得最开始会扫描配置类并以“default”开头为key注册到ioc中吗? for (Map.Entry 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. singletonMap(this.propertyName, name))); if (this.parent != null) { // Uses Environment from parent as well as beans context.setParent(this.parent); } context.setDisplayName(generateDisplayName(name)); context.refresh(); return context; } ``` 我们可以看到这里每调用一个次 createContext方法就会根据服务名字去创建一个新的AnnotationConfigApplicationContext 容器对象,然后获取Feign的配置类给context绑定 ,最后会调用context.refresh();容器刷新,最后会调用 context.getBean(type); 创建出一个 FeignBuilder出来 回到我们的主线 org.springframework.cloud.openfeign.FeignClientFactoryBean#getTarget方法 ,我们看下 return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,this.name, url));部分是如何创建负载均衡的代理类的 ``` protected T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget target) { //获得FeignClient 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?"); } ``` Client client = getOptional(context, Client.class); 这里会调用TraceFeignContext#getInstance方法获取到 Client对象,而Client有三个子实现 ,当我们使用了Feign ,这里会使用LoadBalancerFeignClient ,看名字就能知道他是一个负载均衡的FeignClient客户端 ,我们跟踪一下targeter.target,这里在创建代理类 ``` class HystrixTargeter implements Targeter { @Override public T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget target) { if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) { return feign.target(target); } ------------------------------------ public T target(Target target) { return build().newInstance(target); } public Feign build() { SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, logLevel, decode404, closeAfterDecode); ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, errorDecoder, synchronousMethodHandlerFactory); return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder); } } ``` 这里调用到了 HystrixTargeter的target方法,调用 Feign的target方法,通过 ReflectiveFeign 工具类的 newInstance 创建实例 ``` public T newInstance(Target target) { //target封装了FeignClient ,这里在获取FeignClient接口的方法封装为MethodHandler 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,接收请求,转发到methodHandler 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接口的方法,然后封装到 MethodHandler ,如果继续跟踪targetToHandlersByName.apply(target);你会看到他底层对FeignClient的元注解做了解析 ``` public Map apply(Target key) { //解析FeignClient接口上的注解,把配置信息封装到MethodMetadata, //并且MethodMetadata中会有一个RequestTemplate对象会封装好要请求的服务名,参数类型等信息 List metadata = contract.parseAndValidatateMetadata(key.type()); Map result = new LinkedHashMap(); 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 { //根据元注解创建出BuildTemplateByResolvingArgs对象,而这个东西其实就是RequestTemplate的工厂 buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder); } //这里在创建 MethodHandler , result.put(md.configKey(), factory.create(key, md, buildTemplate, options, decoder, errorDecoder)); } return result; } ``` ,获取到MethodHandler,然后根据 FeignClient 时间jdk动态代理创建出代理类 InvocationHandler handler = factory.create(target, methodToHandler);的底层会把 target和 methodToHandler封装到 FeignInvocationHandler ,而FeignInvocationHandler就是对InvocationHandler的实现,当我们在代码中调用FeignClient接口实现远程调用时,代理类就会被执行,即 FeignInvocationHandler的invoke接受到请求时就会调用 methodToHandler的方法 。源码如下 ``` static class FeignInvocationHandler implements InvocationHandler { private final Target target; private final Map dispatch; FeignInvocationHandler(Target target, Map 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); } ``` dispatch.get(method).invoke(args) 这里在获取要执行的方法(MethodHandler),然后invoke调用传入参数,我们继续跟踪一下 ``` @Override public Object invoke(Object[] argv) throws Throwable { //请求模板 RequestTemplate template = buildTemplateFromArgs.create(argv); //重试机制,这里使用了克隆模式 Retryer retryer = this.retryer.clone(); while (true) { try { return executeAndDecode(template); } catch (RetryableException e) { //重试机制继续或传播 retryer.continueOrPropagate(e); if (logLevel != Logger.Level.NONE) { logger.logRetry(metadata.configKey(), logLevel); } continue; } } } ``` 可以看到这里创建了RequestTemplate请求模板 ,然后调用executeAndDecode执行请求,并且对重试机制做了处理,我们跟踪一下buildTemplateFromArgs.create(argv); ``` @Override public RequestTemplate create(Object[] argv) { //创建请求模板,metadata.template()获取到的就是一个RequestTemplate,只是这里面的RequestTemplate对请求服务id,参数等做了封装 RequestTemplate mutable = new RequestTemplate(metadata.template()); if (metadata.urlIndex() != null) { int urlIndex = metadata.urlIndex(); checkArgument(argv[urlIndex] != null, "URI parameter %s was null", urlIndex); mutable.insert(0, String.valueOf(argv[urlIndex])); } Map varBuilder = new LinkedHashMap(); for (Entry> entry : metadata.indexToName().entrySet()) { int i = entry.getKey(); Object value = argv[entry.getKey()]; if (value != null) { // Null values are skipped. if (indexToExpander.containsKey(i)) { value = expandElements(indexToExpander.get(i), value); } //这里在把请求参数放到map中 for (String name : entry.getValue()) { varBuilder.put(name, value); } } } //解析处理请求地址,参数,和编码等 RequestTemplate template = resolve(argv, mutable, varBuilder); if (metadata.queryMapIndex() != null) { // add query map parameters after initial resolve so that they take // precedence over any predefined values Object value = argv[metadata.queryMapIndex()]; Map queryMap = toQueryMap(value); template = addQueryMapQueryParameters(queryMap, template); } if (metadata.headerMapIndex() != null) { template = addHeaderMapHeaders((Map) argv[metadata.headerMapIndex()], template); } return template; } ``` 这里在封装RequestTemplate请求模板和处理请求地址和参数,我们继续跟踪 executeAndDecode 方法,看一下是如何执行的 ``` Object executeAndDecode(RequestTemplate template) throws Throwable { //获取请求对象 Request request = targetRequest(template); if (logLevel != Logger.Level.NONE) { logger.logRequest(metadata.configKey(), logLevel, request); } Response response; long start = System.nanoTime(); try { //调用LoadBalancerFeignClient负载均衡器客户端执行请求 response = client.execute(request, options); // ensure the request is set. TODO: remove in Feign 10 response.toBuilder().request(request).build(); } catch (IOException e) { ...省略代码... ``` 这里调用了 LoadBalancerFeignClient的execute方法 ``` @Override public Response execute(Request request, Request.Options options) throws IOException { try { //得到完整的请求地址 http://xxxx/xx?xx=xx URI asUri = URI.create(request.url()); String clientName = asUri.getHost(); URI uriWithoutHost = cleanUrl(request.url(), clientName); //构建 FeignLoadBalancer.RibbonRequest Feign负载均衡的请求对象 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); } } ------------------------------------------------- public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException { LoadBalancerCommand command = buildLoadBalancerCommand(request, requestConfig); try { return command.submit( new ServerOperation() { @Override public Observable 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); } } } ``` 这里调用AbstractLoadBalancerAwareClient#executeWithLoadBalancer 方法名的大致意思是 使用负载均衡的方式去执行请求 ,而call(Server server) 的Server这是进行了负载均衡选择后的调用目标服务,那么是如何进行选择的呢,我们跟踪一下 command.submit方法 ``` com.netflix.loadbalancer.reactive.LoadBalancerCommand#submit: ============================================================ public Observable submit(final ServerOperation operation) { final ExecutionInfoContext context = new ExecutionInfoContext(); if (listenerInvoker != null) { try { listenerInvoker.onExecutionStart(); } catch (AbortExecutionException e) { return Observable.error(e); } } final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer(); final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer(); // Use the load balancer //选择要调用的目标服务 Observable o = (server == null ? selectServer() : Observable.just(server)) .concatMap(new Func1>() { @Override // Called for each server being selected public Observable call(Server server) { context.setServer(server); final ServerStats stats = loadBalancerContext.getServerStats(server); ``` selectServer()在选择调用的服务 ,我们跟踪进去 ``` com.netflix.loadbalancer.reactive.LoadBalancerCommand#selectServer ============================================================ private Observable selectServer() { return Observable.create(new OnSubscribe() { @Override public void call(Subscriber next) { try { //从LoadBalancer中选择服务 Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey); next.onNext(server); next.onCompleted(); } catch (Exception e) { next.onError(e); } } }); } --------------------------------------- com.netflix.loadbalancer.LoadBalancerContext#getServerFromLoadBalancer public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException { String host = null; int port = -1; if (original != null) { host = original.getHost(); } if (original != null) { Pair schemeAndPort = deriveSchemeAndPortFromPartialUri(original); port = schemeAndPort.second(); } // Various Supported Cases // The loadbalancer to use and the instances it has is based on how it was registered // In each of these cases, the client might come in using Full Url or Partial URL ILoadBalancer lb = getLoadBalancer(); if (host == null) { // Partial URI or no URI Case // well we have to just get the right instances from lb - or we fall back if (lb != null){ //选择服务,lb就是LoadBalancer Server svc = lb.chooseServer(loadBalancerKey); if (svc == null){ throw new ClientException(ClientException.ErrorType.GENERAL, "Load balancer does not have available server for client: " + clientName); } host = svc.getHost(); if (host == null){ throw new ClientException(ClientException.ErrorType.GENERAL, "Invalid Server for :" + svc); } logger.debug("{} using LB returned Server: {} for request {}", new Object[]{clientName, svc, original}); return svc; ...... ============================================================ public class ZoneAwareLoadBalancer extends DynamicServerListLoadBalancer { ...... //选择服务 @Override public Server chooseServer(Object key) { if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) { logger.debug("Zone aware logic disabled or there is only one zone"); //选择服务 return super.chooseServer(key); } Server server = null; try { LoadBalancerStats lbStats = getLoadBalancerStats(); Map zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats); ...... public abstract class PredicateBasedRule extends ClientConfigEnabledRoundRobinRule { @Override public Server choose(Object key) { ILoadBalancer lb = getLoadBalancer(); //选择服务 Optional server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key); if (server.isPresent()) { return server.get(); } else { return null; } } ---------------------------------------- /** * Choose a server in a round robin fashion after the predicate filters a given list of servers and load balancer key. 轮询的方式选择服务 */ public Optional chooseRoundRobinAfterFiltering(List servers, Object loadBalancerKey) { List eligible = getEligibleServers(servers, loadBalancerKey); if (eligible.size() == 0) { return Optional.absent(); } return Optional.of(eligible.get(incrementAndGetModulo(eligible.size()))); } ``` 看到这里你是否看到了熟悉的代码,没错我们一路跟踪最终发现这里的服务选择和上一章Robin的负载均衡选择服务的代码是一样的 ,因为 Feign默认是集成了Ribbon技术默认轮询,当然也可以添加自己的负载均衡规则 远程调用服务我们已经知道是如何进行选择的了,我们继续跟踪一下看下是怎么调用服务的,回到主线 FeignLoadBalancer. execute ``` ---------------------------------------------------- public class FeignLoadBalancer extends AbstractLoadBalancerAwareClient { ...省略代码...... @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); } .......方法调用链太长.省略...... public static class Default implements Client { private final SSLSocketFactory sslContextFactory; private final HostnameVerifier hostnameVerifier; /** * Null parameters imply platform defaults. */ public Default(SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVerifier) { this.sslContextFactory = sslContextFactory; this.hostnameVerifier = hostnameVerifier; } @Override public Response execute(Request request, Options options) throws IOException { HttpURLConnection connection = convertAndSend(request, options); return convertResponse(connection).toBuilder().request(request).build(); } //发送请求啦,最终调用了feign.Client.Default#convertAndSend方法使用HttpUrlConnection发送请求 HttpURLConnection convertAndSend(Request request, Options options) throws IOException { final HttpURLConnection connection = (HttpURLConnection) new URL(request.url()).openConnection(); if (connection instanceof HttpsURLConnection) { HttpsURLConnection sslCon = (HttpsURLConnection) connection; if (sslContextFactory != null) { sslCon.setSSLSocketFactory(sslContextFactory); } if (hostnameVerifier != null) { sslCon.setHostnameVerifier(hostnameVerifier); } } connection.setConnectTimeout(options.connectTimeoutMillis()); connection.setReadTimeout(options.readTimeoutMillis()); connection.setAllowUserInteraction(false); connection.setInstanceFollowRedirects(options.isFollowRedirects()); connection.setRequestMethod(request.method()); Collection contentEncodingValues = request.headers().get(CONTENT_ENCODING); boolean gzipEncodedRequest = contentEncodingValues != null && contentEncodingValues.contains(ENCODING_GZIP); boolean deflateEncodedRequest = contentEncodingValues != null && contentEncodingValues.contains(ENCODING_DEFLATE); boolean hasAcceptHeader = false; Integer contentLength = null; for (String field : request.headers().keySet()) { if (field.equalsIgnoreCase("Accept")) { hasAcceptHeader = true; } for (String value : request.headers().get(field)) { if (field.equals(CONTENT_LENGTH)) { if (!gzipEncodedRequest && !deflateEncodedRequest) { contentLength = Integer.valueOf(value); connection.addRequestProperty(field, value); } } else { connection.addRequestProperty(field, value); } } } // Some servers choke on the default accept string. if (!hasAcceptHeader) { connection.addRequestProperty("Accept", "*/*"); } if (request.body() != null) { if (contentLength != null) { connection.setFixedLengthStreamingMode(contentLength); } else { connection.setChunkedStreamingMode(8196); } connection.setDoOutput(true); OutputStream out = connection.getOutputStream(); if (gzipEncodedRequest) { out = new GZIPOutputStream(out); } else if (deflateEncodedRequest) { out = new DeflaterOutputStream(out); } try { out.write(request.body()); } finally { try { out.close(); } catch (IOException suppressed) { // NOPMD } } } return connection; } ``` 最终还是使用了HttpURLConnection来实现远程调用 好吧大致总结一下步骤: 1. EnableFeignClients开启 feign功能后会调用 FeignClientsRegistrar 的registerBeanDefinitions方法来给bean的注册 2. FeignClientsRegistrar.registerBeanDefinitions方法中会调用 FeignClientsRegistrar .registerDefaultConfiguration方法通过获取主程序配置类的原始注解扫描到Feign的配置类,并以“default”开头为key对配置类进行注册(在给 feign代理上下文对象时会用到) 3.然后会调用 FeignClientsRegistrar .registerBeanDefinitions方法中会调用 FeignClientsRegistrar. registerFeignClients方法去注册FeignClient(生成代理类时当然要知道FeignClient接口是哪个) 4.扫描并获取到FeiginClient 接口,然后获取到FeignClient的配置相关属性进行注册(这里的注册其实就是用一个map存储起来,后面拿来用) 5.使用 FeignClientFactoryBean 对FeignClient进行封装(设置FeignClient的配置属性等),然后再使用BeanDefinitionHolder进行包裹,最后交给BeanDefinitionReaderUtils.registerBeanDefinition进行注册 6.而 FeignClientFactoryBean 中的 getObject 方法根据FeignClient的配置属性中的url时候有值来决定是否创建负载均衡的FeignClient接口的代理 ,如果咩有设置url者创建负载均衡的FeignClient代理实例 7.底层使用jdk动态代理去创建代理类,底层调用是用到了LoadBalancerFeignClient负载均衡客户端,调用FeignLoadBalancer.execute ,使用Ribbon的负载均衡选择服务,最终使用到HttpURLConnection发送请求
叩丁狼学员采访 叩丁狼学员采访
叩丁狼头条 叩丁狼头条
叩丁狼在线课程 叩丁狼在线课程