认识feign(一)启动流程

579 阅读7分钟

摘要

大家好,我是新手小编王路飞!前段时间因为工作需要,小编对spring-cloud-starter-openfeign组件的源码进行了深度的学习,正所谓独乐乐不如众乐乐(其实是为了复习_),小编将不定时跟大家分享下自己的源码解析成果,希望能对大家有所帮助!

前提

我先来非常笼统的说下feign的实现逻辑,就是在项目启动的时候运用java自带的jdk代理技术来生成代理对象,而我们对他们的客户端接口进行调用时其实是对代理对象的调用,代理对象经过hystrix、ribbon的处理;最后用client对象来进行http请求!(笼统吧,不然后面没得说了*><*)

先添加一波openfeign的maven依赖(版本号就不写了,嘻嘻)

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

启动注册

首先我先讲feign客户端是如何注册到spring容器中的,我们从 @EnableFeignClients 启动注解开始讲起,该注解主要定义了feign客户端有效扫描范围全局配置信息两个重要的配置项

  • value和basePackages:扫描方式,feign客户端扫描范围,范围内的客户端全部启动,与指定方式互斥
  • defaultConfiguration:全局配置类,主要有Encoder、Decoder、Retryer、Contract等
  • clients:指定方式,主要用于指定启动的客户端,与扫描方式互斥 讲解完属性,我们进入 @EnableFeignClients,我们看到有 @Import(FeignClientsRegistrar.class) 注解,@Import注解的作用主要能够导入FeignClientsRegistrar配置类,FeignClientsRegistrar这个类实现了ImportBeanDefinitionRegistrar接口,这个接口的提供了注册bean的功能(有兴趣可以去了解下,mybatis也用相似的方式实现mapper的注册),而这里主要就是为了注册Feign客户端到spring容器

注册客户端

让我们进到FeignClientsRegistrar这个类中,他只有继承一个方法registerBeanDefinitions()

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
	registerDefaultConfiguration(metadata, registry);
	registerFeignClients(metadata, registry);
}

registerDefaultConfiguration:主要用于注册feign范围内的注册全局配置文件

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();
      }
      // 注册配置类
      registerClientConfiguration(registry, name,
              defaultAttrs.get("defaultConfiguration"));
  }
}

关于配置信息类这里有两点可以说下:
1.全局配置类用 default.* 当配置名称,客户端独立配置类用服务名. 当配置名称,优先级:客户端 > 全局
2.配置类统一都用FeignClientSpecification类型进行注册,它包括多个配置信息类和配置名称,最后统一被FeignContext对象引用(feign容器)

registerFeignClients:主要用于注册fign客户端工厂类

public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    	// 创建扫描器,扫描全部带着FeignCLient注解的类
    	// ....
    	// 判断属性clients是否不为空,如果有clients,则不进行扫描
	// ...
    	// 扫描逻辑开始
	for (String basePackage : basePackages) {
        // 扫描包
        Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
        for (BeanDefinition candidateComponent : candidateComponents) {
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
                AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                // 校验feignClient注解配置的是不是接口
                Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
                // 解析feignClient配置
                Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());
                // 获取服务名
                String name = getClientName(attributes);
                // 注册客户端配置
                registerClientConfiguration(registry, name, attributes.get("configuration"));
                // 注册feign客户端
                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
	}
}

@FeignClient 主要的几个属性讲解

  • name、value、serviceId:三个属性的都是设置服务名,serviceId是弃用属性
  • url:请求地址,优先级高于前者
  • configuration:客户端独立配置信息类
  • fallback 和 fallbackFactory:hystrix熔断处理类(后续我可能会独立分享下hystrix流程)
  • path:统一地址前缀

接下来我们主要看下如何注册客户端

private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
	String className = annotationMetadata.getClassName();
    	// 创建feign客户端生成类的bean定义类
	BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
	validate(attributes);
    	// 进行@FeignClient属性的提取
    	// url、path、name三者都可以实现配置注入,感兴趣可深入
	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);
	String alias = name + "FeignClient";
	// primary属性
	AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
	boolean primary = (Boolean)attributes.get("primary");
	beanDefinition.setPrimary(primary);
    	// qualifier属性
	String qualifier = getQualifier(attributes);
	if (StringUtils.hasText(qualifier)) {
		alias = qualifier;
	}
	BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias });
    	// 注册
	BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

客户端生成的对象实际为FeignClientFactoryBean类,他实现了FactoryBean接口,采用了工厂模式来实现生成不同的bean对象的作用。

到此我说完了注册feign客户端的全部流程;等到spring初始化bean的时候,FeignClientFactoryBean对象将生成具体的代理对象(如何调用请参考jdk代理 )。

生成代理对象

我们直接看FeignClientFactoryBean.getObect(),这个方法就是用来生成代理对象的入口方法

public Object getObject() throws Exception {
    // 获取feign容器
    FeignContext context = applicationContext.getBean(FeignContext.class);
    // 从feign容器中获取构造器, 如果启用了hystrix,会返回HystrixFeign.Builder;
    // 生成buidler的同时会进行了一系列组件的配置(配置方式多种,有兴趣可以深入)
    Feign.Builder builder = feign(context);
    // 判断是否存在url,如果则需要ribbon处理
    if (!StringUtils.hasText(this.url)) {
        String url;
        if (!this.name.startsWith("http")) {
            url = "http://" + this.name;
        }
        else {
            url = this.name;
        }
        url += cleanPath();
        // 获取client进行构建代理对象
        return loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, url));
    }
    if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
        this.url = "http://" + this.url;
    }
    String url = this.url + cleanPath();
    // 从feignContext获取client对象
    Client client = getOptional(context, Client.class);
    // 如果是ribbon的包装对象,直接获取他的委托对象Client.Default
    if (client != null) {
        if (client instanceof LoadBalancerFeignClient) {
            client = ((LoadBalancerFeignClient)client).getDelegate();
        }
        builder.client(client);
    }
    // 从feign获取targeter对象,如果hystrix启动了,那这里获取的是HystrixTargeter
    Targeter targeter = get(context, Targeter.class);
    // 开始构建代理对象
    return targeter.target(this, builder, context, new HardCodedTarget<>(this.type, this.name, url));
}

这边有三个点,需要详细的解读下

  • feignContext对象说明:该类在FeignAutoConfiguration自动配置类中被自动注入,这个主要的作为feign的上下文,存放了feign的全部配置信息,每个客户端都用有独立的applicationContext上下文(同一个服务名的多个接口是为同一个客户端),父级为spring上下文,如果自己的上下文中找不到成员对象,则将会去spring上下文寻找。
  • feign.builder对象:该类在FeignAutoConfiguration自动配置类中被自动注入,会根据是否启动hystrix来选择注册Feign.Buidler或者HystrixFeign.Buidler;获取builder后还会将各种成员对象都设置给它,这些成员对象可以通过配置文件和配置类两种进行配置,默认配置文件优先级大于配置类,也可以调整优先级(这些我就先不讲了,以后有时间我再分享下),可进行配置的成员对象主要包含:Contract、Encoder、Decoder、Retryer、LoggerLevel、RequestInterceptor等
  • client对象:默认是用LoadBalancerFeignClient包装对象充当client对象,包含ribbon处理,内部最后回去委托Client.Default进行请求,而Default内部就是用UrlConnection发起请求;如果客户端优先为url则不需要ribbon处理,直接取Client.Default当CLient对象

HystrixTargeter对象的逻辑逻辑我就不多讲了,主要就是讲熔断处理类实例化并携带到客户端对象中,主要的逻辑我正在看,之后会专门出一个关于hystrix应用的分享

public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget<T> target) {
    // 判断是buidler是否是HystrixFeign.Builder,如果不是直接进行跳过熔断处理
    if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
        return feign.target(target);
    }
    feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
    SetterFactory setterFactory = getOptional(factory.getName(), context,
        SetterFactory.class);
    if (setterFactory != null) {
        builder.setterFactory(setterFactory);
    }
    // 实例化熔断处理类并进行实例化feign对象
    Class<?> fallback = factory.getFallback();
    if (fallback != void.class) {
        return targetWithFallback(factory.getName(), context, target, builder, fallback);
    }
    // 实例化熔断处理工厂类并进行实例化feign对象
    Class<?> fallbackFactory = factory.getFallbackFactory();
    if (fallbackFactory != void.class) {
        return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);
    }
}

上面最后一步都会调用builder.target方法,开始生成feign对象

Feign build(final FallbackFactory<?> nullableFallbackFactory) {
  // 配置代理处理器工厂类,只有一个生成代理处理器的方法(运用了匿名类技术)
  // HystrixInvocationHandler就是代理类的处理器
  super.invocationHandlerFactory(new InvocationHandlerFactory() {
    @Override public InvocationHandler create(Target target,
        Map<Method, MethodHandler> dispatch) {
      return new HystrixInvocationHandler(target, dispatch, setterFactory, nullableFallbackFactory);
    }
  });
  // 这个对象用于解析各种注解
  super.contract(new HystrixDelegatingContract(contract));
  // 进行父级构建
  return super.build();
}
// 父级feign
public Feign build() {
  // 构建方法处理器的工厂对象(接口中每个方法都对应了一个方法处理器)
  SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
      new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, logLevel, decode404);
  // 创建解析处理器(解析注解并且生成方法处理器)
  ParseHandlersByName handlersByName = 
      new ParseHandlersByName(contract, options, encoder, decoder, errorDecoder, synchronousMethodHandlerFactory);
  // 创建Feign对象
  return new ReflectiveFeign(handlersByName, invocationHandlerFactory);
}
 

ReflectiveFeign对象的主要职责就是生成代理对象

 public <T> T newInstance(Target<T> target) {
    // 完成元数据解析和生成方法处理器,不做分享了
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
    // 大概的逻辑就是判断是否为原生Object自带的方法,如果是则用DefaultMethodHandler来做方法处理器
    for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if(Util.isDefault(method)) {
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    // 运用工厂对象(build方法中创建)来创建代理处理器
    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;
  }

代理类生成就流程也差不多全部走完了,当我们正常用接口来做操作时,其中就是在操作HystrixInvocationHandler处理器,,其实我还有很多没讲的,比如说配置的方法和优先级、各种mvc注解的解析等等,因为太多了,有些我打算专门拿出来分享。

下一章预告 : 认识feign(二)请求流程解析

结束词

好了各位,以上就是这篇文章的全部内容了,新手小编文笔不怎么行,希望能帮到大家!!!