溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

SpringCloud的Gateway怎么使用

發(fā)布時間:2021-12-29 09:43:32 來源:億速云 閱讀:134 作者:iii 欄目:軟件技術(shù)

這篇文章主要講解了“SpringCloud的Gateway怎么使用”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“SpringCloud的Gateway怎么使用”吧!

SpringCloud微服務(wù)項目之間調(diào)用是通過httprest請求來進(jìn)行服務(wù)調(diào)用的,之前我們會用到HttpClient等工具來進(jìn)行服務(wù)請求,Spring對這種請求進(jìn)行了處理,封裝成了可聲明式的web客戶端,使得編寫web客戶端更容易,feign還支持可插拔的編碼器和解碼器,Spring在用的時候增加了對@requestMapping的處理,同時,SpringCloud還對feign集成了注冊中心(eureka)和客戶端負(fù)載均衡(ribbon),使得我們擁有一個客戶端負(fù)載均衡的web請求客戶端。

Feign源碼分析

  @Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // 掃描本項目里面的java文件,把bean對象封裝成BeanDefinitiaon對象,然后調(diào)用DefaultListableBeanFactory#registerBeanDefinition()方法把beanName放到DefaultListableBeanFactory 的 List<String> beanDefinitionNames 中去
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);
            try {
                postProcessBeanFactory(beanFactory);
                // 在這里調(diào)用到FeignClientsRegistrar對象的registerBeanDefinitions()方法
                invokeBeanFactoryPostProcessors(beanFactory);
                //從DefaultListableBeanFactory里面的beanDefinitionNames中找到所有實現(xiàn)了BeanPostProcessor接口的方法,如果有排序進(jìn)行排序后放到list中
                registerBeanPostProcessors(beanFactory);
                //Spring的國際化
                initMessageSource();
                // 
                initApplicationEventMulticaster();
                // Initialize other special beans in specific context subclasses.
                onRefresh();
                // 
                registerListeners();
                // Spring的IOC、ID處理。Spring的AOP。事務(wù)都是在IOC完成之后調(diào)用了BeanPostProcessor#postProcessBeforeInitialization()和postProcessBeforeInitialization()方法,AOP(事務(wù))就是在這里處理的
                finishBeanFactoryInitialization(beanFactory);
                // 執(zhí)行完之后調(diào)用實現(xiàn)了所有LifecycleProcessor接口的類的onRefresh()方法,同時調(diào)用所有觀察了ApplicationEvent接口的事件(觀察者模式)
                finishRefresh();
            }
            catch (BeansException ex) {
                // 找到所有實現(xiàn)了DisposableBean接口的方法,調(diào)用了destroy()方法,這就是bean的銷毀
                destroyBeans();
                // Reset 'active' flag.
                cancelRefresh(ex);
                throw ex;
            }
            finally {
                resetCommonCaches();
            }
        }
    }

根據(jù)上面整理的代碼發(fā)現(xiàn),F(xiàn)eignClientsRegistrar#registerBeanDefinitions()方法是在掃描完bean之后,只放了一個beanname的情況下, 并沒有進(jìn)行IOC注冊的時候調(diào)用的,這就是Spring動態(tài)擴展Bean,實現(xiàn)BeanDefinitionRegistryPostProcessor接口的所有方法也會在這里調(diào)用下postProcessBeanDefinitionRegistry()方法。關(guān)于Spring的東西就分析到這里。下面回到正題,分析FeignClientsRegistrar#registerBeanDefinitions()方法:

 @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        registerDefaultConfiguration(metadata, registry);//掃描EnableFeignClients標(biāo)簽里配置的信息,注冊到beanDefinitionNames中。
        registerFeignClients(metadata, registry);
    }
     public void registerFeignClients(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
                FeignClient.class);
        //省略代碼...根據(jù)EnableFeignClients配置的basePackages找到包下所有FeignClient注解的類,Spring的Commponet也是這么干的
        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);
                   /**
                     * 關(guān)鍵地方:Feign子容器概念:
                     * 在注入FeignAutoConfiguration類的時候,注入了一個FeignContext對象,這個就是Feign的子容器。
                     * 這里面裝了List<FeignClientSpecification>對象,F(xiàn)eignClientSpecification對象的實質(zhì)就是在@feignClient上配置的name為key,value為configuration對象的值
                     * 比如feignclient 這樣配置的@FeignClient(url="https://api.weixin.qq.com",name="${usercenter.name}", configuration = UserCenterFeignConfiguration.class, primary= false)
                     * 那么在FeignContext中就會出現(xiàn)一個FeignClientSpecification{name='sms-server', configuration=[class com.jfbank.sms.configuration.FeignConfiguration]}這樣的數(shù)據(jù)。
                     *  這個地方比較關(guān)鍵,主要是因為后期對feign客戶端的編碼解碼會用到自定義的類
                     */
                    //這個方法就是在ioc容器中塞入一個FeignClientSpecification對象,從而構(gòu)建FeignContext子容器。
                    registerClientConfiguration(registry, name,
                            attributes.get("configuration"));       
                    //重點分析這個
                    registerFeignClient(registry, annotationMetadata, attributes);
                }
            }
        }
    }
    private void registerFeignClient(BeanDefinitionRegistry registry,
            AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
        String className = annotationMetadata.getClassName();
        BeanDefinitionBuilder definition = BeanDefinitionBuilder
                .genericBeanDefinition(FeignClientFactoryBean.class);//對FeignClientFactoryBean對象生成一個BeanDefinition對象
        ...讀取配置
        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 });
        //注冊到beanDefinitionNames中對象
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);//
    }

讀過Dubbo源碼的同學(xué)都知道,當(dāng)在DubboNamespaceHandler中解析reference標(biāo)簽的時候,傳入了一個ReferenceBean對象,把xml中配置的屬性都塞到這個對象上,也是裝到了beanDefinitionNames中,然后發(fā)現(xiàn)ReferenceBean類和FeignClientFactoryBean都實現(xiàn)了FactoryBean的接口,并且里面都有g(shù)etObject()和getObjectType()方法。當(dāng)接口調(diào)用到這個feign客戶端的時候,會從IOC中讀取這個FeignClientFactoryBean并且調(diào)用getObject方法。下面就是分析getObject方法:

 @Override
    public Object getObject() throws Exception {
        FeignContext context = applicationContext.getBean(FeignContext.class);
        //從上文中的子容器中獲取編碼器,解碼器等自定義類,然后封裝一個Feign.Builder類
        Feign.Builder builder = feign(context);
        if (!StringUtils.hasText(this.url)) {//當(dāng)@FeignClient沒有配置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));//集成了ribbon客戶端負(fù)載均衡,下一篇分析
        }
        //當(dāng)@FeignClient配置了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));
    }

首先看配置了url的,指定了url的feignclient解析,一直跟著代碼跟到了Feign.Builder#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);
    }

直接看ReflectiveFeign#newInstance()方法:

//ReflectiveFeign#newInstance()
    public <T> T newInstance(Target<T> target) {
                                                //動態(tài)代理的handler類目前穿進(jìn)來的是ParseHandlersByName類,所以這里要看ParseHandlersByName#apply()直接看下一個方法
      Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
      Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
      List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
      for (Method method : target.type().getMethods()) {
        if (method.getDeclaringClass() == Object.class) {
          continue;
        } else if(Util.isDefault(method)) {//默認(rèn)方法會走到這里,比如toString(),hashCode()等方法
          DefaultMethodHandler handler = new DefaultMethodHandler(method);
          defaultMethodHandlers.add(handler);
          methodToHandler.put(method, handler);
        } else {//這里才是裝配的調(diào)用類,上文分析到計息的handler是SynchronousMethodHandler#invoke()
          methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
        }
      }
      InvocationHandler handler = factory.create(target, methodToHandler);
      T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);//jdk動態(tài)代理
      for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
        defaultMethodHandler.bindTo(proxy);
      }
      return proxy;
    }
    //ParseHandlersByName#apply類,構(gòu)建動態(tài)代理的handler
    public Map<String, MethodHandler> apply(Target key) {
        List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());
        Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
        for (MethodMetadata md : metadata) {
          BuildTemplateByResolvingArgs buildTemplate;
          if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
            buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder);//通過自定義的encoder去解析參數(shù)
          } else if (md.bodyIndex() != null) {
            buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder);//通過自定義的encoder去解析參數(shù)
          } else {
            buildTemplate = new BuildTemplateByResolvingArgs(md);
          }
          //創(chuàng)建handler,再看Factory#create()方法,下一個方法
          result.put(md.configKey(),
                     factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
        }
        return result;
      }
    //Factory#create(),構(gòu)建一個SynchronousMethodHandler去處理請求,調(diào)用invoke方法
    public MethodHandler create(Target<?> target, MethodMetadata md,
            RequestTemplate.Factory buildTemplateFromArgs,
            Options options, Decoder decoder, ErrorDecoder errorDecoder) {
        return new SynchronousMethodHandler(target, client, retryer, requestInterceptors, logger,
                      logLevel, md, buildTemplateFromArgs, options, decoder,
                      errorDecoder, decode404);
    }
    //SynchronousMethodHandler#invoke()方法:實際調(diào)用的方法
    //@Override
    public Object invoke(Object[] argv) throws Throwable {
        RequestTemplate template = buildTemplateFromArgs.create(argv);//構(gòu)建requestTemplate對象
        Retryer retryer = this.retryer.clone();
        while (true) {
          try {
            return executeAndDecode(template);//下面不分析了,就是執(zhí)行execute方法并且解碼飯后返回值
          } catch (RetryableException e) {
            retryer.continueOrPropagate(e);
            if (logLevel != Logger.Level.NONE) {
              logger.logRetry(metadata.configKey(), logLevel);
            }
            continue;
          }
        }
      }

感謝各位的閱讀,以上就是“SpringCloud的Gateway怎么使用”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對SpringCloud的Gateway怎么使用這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI