zoukankan      html  css  js  c++  java
  • spring cloud feign

    1、spring clound feign,实质上是一个封装了rest请求的客户端。调用方法的时候,就如同调用本地方法一样

    2、spring clound feign 的源代码,主要分为两部分。

    2.1  @EnableFeignClients 注解。主要的作用,是通过反射将相关的bean(有@FeignClient 注解修饰的ben)注入到spring的容器之中个,也就是一个map。

    核心源代码如下:

    @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata,
                BeanDefinitionRegistry registry) {
            registerDefaultConfiguration(metadata, registry);
            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();
                }
                registerClientConfiguration(registry, name,
                        defaultAttrs.get("defaultConfiguration"));
            }
        }
    
        public void registerFeignClients(AnnotationMetadata metadata,
                BeanDefinitionRegistry registry) {
            ClassPathScanningCandidateComponentProvider scanner = getScanner();
            scanner.setResourceLoader(this.resourceLoader);
    
            Set<String> basePackages;
    
            Map<String, Object> 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<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 AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
            }
    
            for (String basePackage : basePackages) {
                Set<BeanDefinition> candidateComponents = scanner
                        .findCandidateComponents(basePackage);
                for (BeanDefinition candidateComponent : candidateComponents) {
                    if (candidateComponent instanceof AnnotatedBeanDefinition) {
                        // verify annotated class is an interface
                        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 = getClientName(attributes);
                        registerClientConfiguration(registry, name,
                                attributes.get("configuration"));
    
                        registerFeignClient(registry, annotationMetadata, attributes);
                    }
                }
            }
        }
    
    //这个方法,讲一系列的注解属性,包括url、path、name等保包装进一个BeanDefinition中
    private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) { String className = annotationMetadata.getClassName(); BeanDefinitionBuilder definition = BeanDefinitionBuilder .genericBeanDefinition(FeignClientFactoryBean.class); validate(attributes); 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"; 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 }); BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); }

    上面的代码,本质上还是一个spring 初始化的时候,讲bean注入到容器的过程。不过就是@EnableFeignClients注解,自定义bean注入容器的实现而已

    2.2 bean实例化。核心代码在 FeignClientFactoryBean中。 这个FeignClientFactoryBean 继承FactoryBean。

    当实例化bean的时候,会调用 getObject(),这个方法中,会调用target方法,生成代理。代码如下:

    @Override
        public Object getObject() throws Exception {
            FeignContext context = applicationContext.getBean(FeignContext.class);
            Feign.Builder builder = feign(context);
    
            if (!StringUtils.hasText(this.url)) {
                String url;
                if (!this.name.startsWith("http")) {
                    url = "http://" + this.name;
                }
                else {
                    url = this.name;
                }
                url += cleanPath();
                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();
            Client client = getOptional(context, Client.class);
            if (client != null) {
                if (client instanceof LoadBalancerFeignClient) {
                    // not lod 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 targeter.target(this, builder, context, new HardCodedTarget<>(    //这里会生成代理
                    this.type, this.name, url));
        }
    @Override
        public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
                            Target.HardCodedTarget<T> target) {
            if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {   //这里有个判断。如果实例是熔断器的Builder,则直接调用target。不过殊途同归,都是调用的Feign.target()方法
                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);
            }
            Class<?> fallback = factory.getFallback();
            if (fallback != void.class) {
                return targetWithFallback(factory.getName(), context, target, builder, fallback);
            }
            Class<?> fallbackFactory = factory.getFallbackFactory();
            if (fallbackFactory != void.class) {
                return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);
            }
    
            return feign.target(target);
        }
    public <T> T target(Target<T> target) {
          return build().newInstance(target);
        }
    
        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);
          return new ReflectiveFeign(handlersByName, invocationHandlerFactory);    //这个invocationHandlerFactory ,用于创建invercationHandler,也就是JDK的那个
    } }
    /**
       * creates an api binding to the {@code target}. As this invokes reflection, care should be taken
       * to cache the result.
       */
      @SuppressWarnings("unchecked")
      @Override
      public <T> T newInstance(Target<T> target) {
        Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
        Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();           //从这里看出,这个invercationHandler,也是保存在map里面。事实上,很多类似的例子,例如spring 注入是放在map里面,euraka 注册,注册表也是一个map
        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)));
          }
        }
        InvocationHandler handler = factory.create(target, methodToHandler);            //生成invercatoinHandler
        T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);     //返回代理
    
        for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
          defaultMethodHandler.bindTo(proxy);
        }
        return proxy; 
      }

    下面的代码,则是代理调用的方法

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

    总结:Feign,其实就是封装了http请求的客户端,使调用方法的时候,像调用本地方法一样。

    Feign的原理:

    第一步是通过配置注解@EnableFeignClients,同时在需要调用远程方法的地方,配置注解@FeignClient。

    当spring启动的时候,通过反射,将@FeignClient 所在的bean注入到spring 容器中(也就是一个concurrentHasMap),这个bean的beanDefinition里面封装了包括url,feedback等信息。

    第二步,生成JDK代理。调用方法的时候,是调用JDK代理的invoke方法。在invoke方法中,自动调用封装好的HttpURLConnection执行请求。

    参考:https://www.jianshu.com/p/72f0b294fc37

  • 相关阅读:
    java-se 选择和冒泡排序
    获得最大数
    打印正反星星 先正后反星星
    Django链接MySQL,数据库迁移
    ORM常用字段及查询
    Django的View(视图)
    Pycharm设置默认HTML模板
    Django简介
    Django
    如何使用Python输出一个[斐波那契数列]
  • 原文地址:https://www.cnblogs.com/drafire/p/14557934.html
Copyright © 2011-2022 走看看