Spring Cloud与微服务构建(七)声明式调用Feign 7.4 从源码的角度讲解Feign的工作原理

Feign是一个伪Java Http客户端,Feign不做任何的请求处理。Feign通过处理注解生成Request模板,从而简化了Http API的开发。开发人员可以使用注解的方式定制Request API模板。在发送Http Request请求之前,Feign通过处理注解的方式替换掉Request模板中的参数,生成真正的Request,并交给Java Http客户端去处理。利用这种方式,开发者只需要关注Feign注解模板的开发,而不用关注Http请求本身,简化了http 请求的过程,使得Http请求变得简单和容易理解。

Feign通过包扫描注入FeignClient的Bean,该源码在FeignClientsRegistrar类中。首先在程序启动时,会检查是否有@EnableFeignClients注解,如下有该注解,则开启包扫描,扫描被@FeignClient注解的接口。代码如下:

    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"));
        }

    }

当程序的启动类上有@EnableFeignClients注解。在程序启动后,程序会通过包扫描将有@FeignClient注解修饰的接口连同接口名和注解的信息一起取出,赋给BeanDefinitionBuilder,然后根据BeanDefinitionBuilder得到BeanDefinition,最后将BeanDefinition注入IoC容器中,源码如下:

{
        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);
                }
            }
        }

    }

注入BeanDefinition之后,通过JDK的代理,当调用Feign Client接口里面的方法时,该方法会被拦截,源码在ReflectiveFeign类,代码如下:

    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, methodToHandler);
        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;
    }

在SynchronousMethodHandler类进行拦截处理,会根据参数生成RequestTemplate对象,该对象是Http请求的模板,代码如下:

    public Object invoke(Object[] argv) throws Throwable {
        RequestTemplate template = this.buildTemplateFromArgs.create(argv);
        Retryer retryer = this.retryer.clone();

        while(true) {
            try {
                return this.executeAndDecode(template);
            } catch (RetryableException var5) {
                retryer.continueOrPropagate(var5);
                if (this.logLevel != Level.NONE) {
                    this.logger.logRetry(this.metadata.configKey(), this.logLevel);
                }
            }
        }
    }

在上述代码中,有一个executeAndDecode()方法,该方法通过RequestTemplate生成Request请求对象,然后用Http Client获取Response,即通过Http Client进行Http请求来获取响应,代码如下:

    Object executeAndDecode(RequestTemplate template) throws Throwable {
        Request request = this.targetRequest(template);
        if (this.logLevel != Level.NONE) {
            this.logger.logRequest(this.metadata.configKey(), this.logLevel, request);
        }

        long start = System.nanoTime();

        Response response;
        try {
            response = this.client.execute(request, this.options);
            response.toBuilder().request(request).build();
        } catch (IOException var15) {
            if (this.logLevel != Level.NONE) {
                this.logger.logIOException(this.metadata.configKey(), this.logLevel, var15, this.elapsedTime(start));
            }

            throw FeignException.errorExecuting(request, var15);
        }

        long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
        boolean shouldClose = true;

        try {
            if (this.logLevel != Level.NONE) {
                response = this.logger.logAndRebufferResponse(this.metadata.configKey(), this.logLevel, response, elapsedTime);
                response.toBuilder().request(request).build();
            }

            if (Response.class == this.metadata.returnType()) {
                Response var18;
                if (response.body() == null) {
                    var18 = response;
                    return var18;
                } else if (response.body().length() != null && (long)response.body().length() <= 8192L) {
                    byte[] bodyData = Util.toByteArray(response.body().asInputStream());
                    Response var10 = response.toBuilder().body(bodyData).build();
                    return var10;
                } else {
                    shouldClose = false;
                    var18 = response;
                    return var18;
                }
            } else {
                Object var9;
                if (response.status() >= 200 && response.status() < 300) {
                    if (Void.TYPE == this.metadata.returnType()) {
                        var9 = null;
                        return var9;
                    } else {
                        var9 = this.decode(response);
                        return var9;
                    }
                } else if (this.decode404 && response.status() == 404 && Void.TYPE != this.metadata.returnType()) {
                    var9 = this.decode(response);
                    return var9;
                } else {
                    throw this.errorDecoder.decode(this.metadata.configKey(), response);
                }
            }
        } catch (IOException var16) {
            if (this.logLevel != Level.NONE) {
                this.logger.logIOException(this.metadata.configKey(), this.logLevel, var16, elapsedTime);
            }

            throw FeignException.errorReading(request, response, var16);
        } finally {
            if (shouldClose) {
                Util.ensureClosed(response.body());
            }

        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值