Open Feign源码剖析

181 阅读8分钟

OpenFeign源码剖析

feign的核心功能就是通过接口去访问网络资源,里面也是用动态代理来实现的,就跟Mybatis用接口去访问数据库一样,我们就来看下源码的处理,核心就一个包:

image.png

 

3.1 注解处理

使用OpenFeign的时候会用到2个注解,分别是@FeignClient(value = "hailtaxi-driver")@EnableFeignClients(basePackages = "com.itheima.driver.feign"),这两个注解其实就是学习OpenFeign的入口。

@EnableFeignClients这 个注解的作用其实就是开启了一个FeignClient的扫描,那么点击启动类的@EnableFeignClients注解看下他是怎么开启FeignClient的扫描的,进去后发现里面有个@Import(FeignClientsRegistrar.class)这个FeignClientsRegistrar跟Bean的动态装载有关。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class) //引入了FeignClientsRegistrar类
public @interface EnableFeignClients {}

 

FeignClientsRegistrar类中有一个方法registerBeanDefinitions用于注入Bean的,源码如下:

/***
 * Bean的注入方法
 * @param metadata
 * @param registry
 */
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
    //SpringBoot启动类上检查是否有@EnableFeignClients, 有该注解, 则完成 Feign 框架相关的配置注册
    registerDefaultConfiguration(metadata, registry);
    //从 classpath 中, 扫描获得 @FeignClient 修饰的类, 将类的内容解析为 BeanDefifinition ,
    // 最终通过调用 Spring 框架中的BeanDefifinitionReaderUtils.resgisterBeanDefifinition
    // 将解析处理过的 FeignClientBeanDeififinition 添加到 spring 容器中.
    registerFeignClients(metadata, registry);
}

 

我们主要关注registerFeignClients()方法,该方法会通过解析@EnableFeignClients并解析@FeignClient实现Feign的注册,源码如下:

/***
 * 注册FeignClients
 * @param metadata
 * @param registry
 */
public void registerFeignClients(AnnotationMetadata metadata,
                                 BeanDefinitionRegistry registry) {
    // ClassPath的条件扫描组件提供者
    ClassPathScanningCandidateComponentProvider scanner = getScanner();
    // 设置资源加载器
    scanner.setResourceLoader(this.resourceLoader);
    // 要扫描的包(@EnableFeignClients注解上添的那个)
    Set<String> basePackages;


    // 获取注解上的配置
    Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
    // 注解过滤器,设置只过滤出FeignClient注解标识的Bean
    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<String> 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 FeignClientsRegistrar.AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
    }


    // 循环所有要解析的Feign的包
    for (String basePackage : basePackages) {
        // 从指定的包中扫描出和规范的BeanDefinition
        Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
        for (BeanDefinition candidateComponent : candidateComponents) {
            // 扫描的Bean是否是AnnotatedBeanDefinition的子类
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
                // verify annotated class is an interface
                AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                // 获取beanDefinition的元数据,你想要的他基本都有
                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);
                // 为FeignClient指定配置类
                registerClientConfiguration(registry, name,
                        attributes.get("configuration"));
                // 注册客户端
                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }
}

 

上面注解解析后,会调用registerFeignClient()注册客户端,我们来看下registerFeignClient()方法具体实现流程,代码如下:

/***
 * 注册客户端
 * @param registry
 * @param annotationMetadata
 * @param attributes
 */
private void registerFeignClient(BeanDefinitionRegistry registry,
                                 AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
    // 被@FeignClient修饰的类名,比如 com.itheima.DriverFeign,是自己定义的接口
    String className = annotationMetadata.getClassName();
    // BeanDefinitionBuilder通过FeignClientFactoryBean这个类来生成BeanDefinition
    BeanDefinitionBuilder definition = BeanDefinitionBuilder
            .genericBeanDefinition(FeignClientFactoryBean.class);
    // 验证fallback和fallbackFactory是不是接口
    validate(attributes);
    // 通过BeanDefinitionBuilder给beanDefinition增加属性
    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";
    // 用Builder获取实际的BeanDefinition
    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;
    }
    // 创建一个Bean定义的持有者
    BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
            new String[] { alias });
    // 这里就是将Bean注册到Spring容器中
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

 

3.2 Feign代理注册

上面方法中创建BeanDefinitionBuilder的时候传入了一个参数FeignClientFactoryBean.class,注册的Bean就是参数中自己传进来的beanClass是工厂Bean,可以用来创建Feign的代理对象,我们来看一下FeignClientFactoryBean源码,可以发现它实现了FactoryBean,所以它可以获取对象实例,同时也能创建对象的代理对象,部分源码如下:

1609715357631

 

它里面有一个方法getObject(),该方法就是用于返回一个对象实例,而对象其实是代理对象,源码如下:

@Override
public Object getObject() throws Exception {
    return getTarget();
}


/**
 * @param <T> the target type of the Feign client
 * @return a {@link Feign} client created with the specified data and the context
 * information
 */
<T> T getTarget() {
    //FeignContext注册到容器是在FeignAutoConfiguration上完成的
    //在初始化FeignContext时,会把configurations在容器中放入FeignContext中。configurations的
    //来源就是在前面registerFeignClients方法中将@FeignClient的配置configuration。
    FeignContext context = this.applicationContext.getBean(FeignContext.class);
    //构建Builder对象
    Feign.Builder builder = feign(context);


    //如果url为空,则走负载均衡,生成有负载均衡功能的代理类
    if (!StringUtils.hasText(this.url)) {
        if (!this.name.startsWith("http")) {
            this.url = "http://" + this.name;
        }
        else {
            this.url = this.name;
        }
        this.url += cleanPath();
        return (T) loadBalance(builder, context,
                new HardCodedTarget<>(this.type, this.name, this.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();
        }
        if (client instanceof FeignBlockingLoadBalancerClient) {
            // not load balancing because we have a url,
            // but Spring Cloud LoadBalancer is on the classpath, so unwrap
            client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
        }
        builder.client(client);
    }
    //生成默认代理类
    Targeter targeter = get(context, Targeter.class);
    return (T) targeter.target(this, builder, context,
            new HardCodedTarget<>(this.type, this.name, url));
}

 

 

3.3 Builder对象

上面片段代码中Feign.Builder builder = feign(context)是用于构建Builder,关于Builder源码属性我们进行详细讲解,源码如下:

public static class Builder {
    //这个就是拦截器,可以在请求之前设置请求头、设置请求体、设置参数、设置url等等,类型是:RequestInterceptor:
    private final List<RequestInterceptor> requestInterceptors =
        new ArrayList<RequestInterceptor>();
    
    //日志等级
    private Logger.Level logLevel = Logger.Level.NONE;
    
    //默认是Contract.Default(),它主要是用来解析feign接口上的那些注解,比如:@QueryMap、@Param、@RequestLine、@Header、@Body、@HeaderMap等,比如@Header操作,可以把@Header(“name=value”)这里面的name=value取出来,重新设置到RequestTemplate里面。
    private Contract contract = new Contract.Default();
    
    //client是真正去执行request,得到response的客户端,它的入参是一个Request,这个Request是用RequestTemplate构造出来的。
    private Client client = new Client.Default(null, null);
    private Retryer retryer = new Retryer.Default();
    private Logger logger = new NoOpLogger();
    
    //encoder是用来编码请求的,默认能处理String和byte[]数组类型的参数,最终是存放在RequestTemplate里面。
    private Encoder encoder = new Encoder.Default();
    //decoder用来解码响应,默认可以返回字节数组和字符串。
    private Decoder decoder = new Decoder.Default();
    private QueryMapEncoder queryMapEncoder = new QueryMapEncoder.Default();
    
    //异常处理
    private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
    private Options options = new Options();
    
    //就是在这里面创建的动态代理类。当客户端调用Feign.builder()的时候,其实就是去设置builder里面的这些参数的值。
    private InvocationHandlerFactory invocationHandlerFactory =
        new InvocationHandlerFactory.Default();
    private boolean decode404;
    private boolean closeAfterDecode = true;
    private ExceptionPropagationPolicy propagationPolicy = NONE;
    
}

 

builder构建如下方法:

/****
 * 构建Builder独享
 * @param context
 * @return
 */
protected Feign.Builder feign(FeignContext context) {
    FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
    Logger logger = loggerFactory.create(this.type);


    // @formatter:off
    Feign.Builder builder = get(context, Feign.Builder.class)
            // 设置日志等级
            .logger(logger)
            //设置编码
            .encoder(get(context, Encoder.class))
            //设置解码
            .decoder(get(context, Decoder.class))
            //设置contract
            .contract(get(context, Contract.class));
    // @formatter:on


    configureFeign(context, builder);


    return builder;
}

 

3.4 Feign代理创建

上面的builder构造完后继续向下走,配置完Feign.Builder之后,再判断是否需要LoadBalance,如果需要,则通过loadBalance(builder, context,new HardCodedTarget<>(this.type, this.name, this.url));的方法来设置。实际上他们最终调用的是Target.target()方法。

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
                            HardCodedTarget<T> target) {
    //获取一个服务的client
    Client client = getOptional(context, Client.class);
    if (client != null) {
        //将client设置进去相当于增加了客户端负载均衡解析的机制
        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?");
}

 

上面方法会调用targeter.target(this, builder, context, target);,它支持服务熔断降级,我们直接看默认的DefaultTrageter就可以了。

image.png

 

DefaultTargetertarget()方法是一个非常简单的调用,但开启了Feign代理对象创建的开始:

image.png

 

target方法调用了build().newInstance(),这个方法信息量比较大,我们要拆分这看build()newInstance(target)

public <T> T target(Target<T> target) {
    return build().newInstance(target);
}

 

build()方法是创建客户端对象ReflectiveFeign,看着名字就像代理的意思,源码如下:

/***
 * 就是构造Feign客户端对象ReflectiveFeign,并且添加默认值,
 * 注意下synchronousMethodHandlerFactory创建出来的MethodHandler的类型是SynchronousMethodHandler。
 * handlersByName可以把feign接口解析成Map<String, MethodHandler>。
 * @return
 */
public Feign build() {
    SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
            new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
                    logLevel, decode404, closeAfterDecode, propagationPolicy);
    ReflectiveFeign.ParseHandlersByName handlersByName =
            new ReflectiveFeign.ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
                    errorDecoder, synchronousMethodHandlerFactory);
    return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}

 

我们再来看ReflectiveFeign,它继承了Feign同时也有一个属性InvocationHandlerFactory,该对象其实就是代理工厂对象,源码如下:

ReflectiveFeign源码:

image.png

InvocationHandlerFactory源码:

image.png

 

 

我们再来看newInstance(Target<T> target)方法,该方法就是用来创建Feign的代理对象,源码如下:

/***
 * 创建代理
 * @param target
 * @param <T>
 * @return
 */
public <T> T newInstance(Target<T> target) {
    //根据接口类和Contract协议解析方式,解析接口类上的方法和注解,转换成内部的MethodHandler处理方式
    Map<String, InvocationHandlerFactory.MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, InvocationHandlerFactory.MethodHandler> methodToHandler = new LinkedHashMap<Method, InvocationHandlerFactory.MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<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)));
        }
    }
    // 基于Proxy.newProxyInstance 为接口类创建动态实现,将所有的请求转换给InvocationHandler 处理。
    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;
}

 

 

3.5 远程请求

远程请求一定是要有IP和端口的,OpenFeign将IP和端口封装到RequestTemplate中了,我们来看一下RequestTemplate源码:

image.png

 

SynchronousMethodHandler类中执行远程调用,源码如下:

/**
 * 远程调用
 * @param argv
 * @return
 * @throws Throwable
 */
@Override
public Object invoke(Object[] argv) throws Throwable {
    //封装成RequestTemplate
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Request.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;
        }
    }
}

 

上面调用会调用executeAndDecode()方法,该方法是执行远程请求,同时解析响应数据,源码如下:

/****
 * 发起远程请求
 * @param template
 * @param options
 * @return
 * @throws Throwable
 */
Object executeAndDecode(RequestTemplate template, Request.Options options) throws Throwable {
    //转换为HTTP请求报文
    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());
        }
    }
}