zoukankan      html  css  js  c++  java
  • SpringCloud的Feign相关调用,熔断源码分析

    feign是声明式的web service客户端,它让微服务之间的调用变得更简单了,类似controller调用service。Spring Cloud集成了Ribbon和Eureka,可在使用Feign时提供负载均衡的http客户端。

    环境:

    <spring-boot.version>2.3.2.RELEASE</spring-boot.version>
    <spring-cloud.version>Hoxton.SR9</spring-cloud.version>
    <spring-cloud-openfeign.version>2.2.6.RELEASE</spring-cloud-openfeign.version>

    本次主要是使用nacos作为服务中心,sentinel作为熔断分析

    前期环境准备:SpringCloud之熔断(六)

    主要相关注解:EnableFeignClients,FeignClient

    在使用微服务时会开启@EnableFeignClients作为远程调用,用FeignClient作为具体调用的定义

    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableFeignClients
    public class NacosConsumerApplication {
        ......
        public static void main(String[] args) {
            SpringApplication.run(NacosConsumerApplication.class, args);
        }
        ......
    }
    @FeignClient(value = "service-provider", fallback = ProviderClientFallback.class)
    public interface ProviderClient {
        @GetMapping("/echo/{param}")
        String echo(@PathVariable("param") String param);
    
        @GetMapping("/echo1/{param}")
        String echo1(@PathVariable("param") String param);
    }

    在@EnableFeignClients的定义中会导入FeignClientsRegistrar

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    @Import(FeignClientsRegistrar.class)
    public @interface EnableFeignClients {
        ......
    }

    而FeignClientsRegistrar继承了ImportBeanDefinitionRegistrar,所以汇总初始化的时候调用FeignClientsRegistrar#registerBeanDefinitions,最后会调到FeignClientsRegistrar#registerFeignClients进行扫描使用了EnableFeignClients的接口,并且注册到容器中。定义生产对象的工厂为FeignClientFactoryBean

    public void registerFeignClients(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        ......
        if (clients == null || clients.length == 0) {
            //扫描使用了FeignClient注解的接口
            ClassPathScanningCandidateComponentProvider scanner = getScanner();
            scanner.setResourceLoader(this.resourceLoader);
            scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
            Set<String> basePackages = getBasePackages(metadata);
            for (String basePackage : basePackages) {
                candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
            }
        }
        ......
    
        for (BeanDefinition candidateComponent : candidateComponents) {
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
                ......
                //配置生产对象的相关bean工厂的信息
                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }
    
    private void registerFeignClient(BeanDefinitionRegistry registry,
            AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
        String className = annotationMetadata.getClassName();
        //定义参数对象的工厂为FeignClientFactoryBean
        BeanDefinitionBuilder definition = BeanDefinitionBuilder
                .genericBeanDefinition(FeignClientFactoryBean.class);
        ......
    }

    springCloud FeignClient其实是利用了spring的代理工厂来生成代理类,所以这里将所有的 feignClient的描述信息 BeanDefinition设定为 FeignClientFactoryBean类型,该类又继承 FactoryBean,很明显,这是一个代理类。 在spring中, FactoryBean是一个工厂bean,用作创建代理bean,所以得出结论,feign将所有的feignClient bean包装成 FeignClientFactoryBean。扫描方法到此结束。

    代理类什么时候会触发生成呢? 在spring刷新容器时,当实例化我们的业务service时,如果发现注册了FeignClient,spring就会去实例化该FeignClient,同时会进行判断是否是代理bean,如果为代理bean,则调用 FeignClientFactoryBean的 T getObject() throws Exception;方法生成代理bean。

    然后分析FeignClientFactoryBean查看创建对象的具体逻辑

    在创建feignClient注解的对象的时候回调用FeignClientFactoryBean#getObject,然后到FeignClientFactoryBean#getTarget,先以上面的配置分析

    <T> T getTarget() {
        FeignContext context = applicationContext.getBean(FeignContext.class);
        //获取一个Builder,默认是HystrixFeign.Builder, 因为例子使用的sentinel,所以现在获取到的是SentinelFeign.Builder
        Feign.Builder builder = feign(context);
    
        if (!StringUtils.hasText(url)) {
            if (!name.startsWith("http")) {
                url = "http://" + name;
            }
            else {
                url = name;
            }
            url += cleanPath();
            //返回代理对象
            return (T) loadBalance(builder, context,
                    new HardCodedTarget<>(type, name, url));
        }
        ......
    }

    获取Feign.Builder函数

    protected Feign.Builder feign(FeignContext context) {
        FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
        Logger logger = loggerFactory.create(type);
    
        // @formatter:off
        //在SentinelFeignAutoConfiguration会注入Feign.Builder
        Feign.Builder builder = get(context, Feign.Builder.class)
                // required values
                .logger(logger)
                .encoder(get(context, Encoder.class))
                .decoder(get(context, Decoder.class))
                .contract(get(context, Contract.class));
        // @formatter:on
    
        configureFeign(context, builder);
    
        return builder;
    }

    先看看是怎么注入SentinelFeign.Builder的在配置文件中我们配置了application.properties

    feign.sentinel.enabled=true

    然后在配置文件中SentinelFeignAutoConfiguration中会注入对象

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({ SphU.class, Feign.class })
    public class SentinelFeignAutoConfiguration {
    
        @Bean
        @Scope("prototype")//表示每次获得bean都会生成一个新的对象
        @ConditionalOnMissingBean
        @ConditionalOnProperty(name = "feign.sentinel.enabled")//在配置文件中已经打开,如果havingValue未指定值,默认情况下在属性配置中设置的值为true则生效
        public Feign.Builder feignSentinelBuilder() {
            return SentinelFeign.builder();
        }
    
    }

    继续分析FeignClientFactoryBean#loadBalance创建过程

    protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
            HardCodedTarget<T> target) {
        //LoadBalancerFeignClient对象,默认是Client.Default实现网络请求,也可以配置为OkHttpClient,ApacheHttpClient等
        Client client = getOptional(context, Client.class);
        if (client != null) {
            builder.client(client);
            //HystrixTargeter对象
            Targeter targeter = get(context, Targeter.class);
            return targeter.target(this, builder, context, target);
        }
        ......
    }

     在进行看HystrixTargeter#target

    public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
            FeignContext context, Target.HardCodedTarget<T> target) {
        if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
            //因为我们使用的是SentinelFeign.Builder,所以执行此处
            return feign.target(target);
        }
        ......
    }

    在执行到ReflectiveFeign#newInstance

      public <T> T newInstance(Target<T> target) {
        //会获取接口方法和方法代理SynchronousMethodHandler对象的一个Map
        Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
        
        ......
        
        //返回一个SentinelInvocationHandler对象
        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;
      }

    查看创建SentinelInvocationHandler的过程,最终会走到SentinelFeign#Builder#build#new InvocationHandlerFactory#create进行创建

    public Feign build() {
        super.invocationHandlerFactory(new InvocationHandlerFactory() {
            @Override
            public InvocationHandler create(Target target,
                    Map<Method, MethodHandler> dispatch) {
                ......
    
                //此处就是配置异常的处理方式,不管配置fallback或者fallbackFactory,最后都是FallbackFactory进行处理
                Object fallbackInstance;
                FallbackFactory fallbackFactoryInstance;
                // check fallback and fallbackFactory properties
                if (void.class != fallback) {
                    fallbackInstance = getFromContext(beanName, "fallback", fallback,
                            target.type());
                    return new SentinelInvocationHandler(target, dispatch,
                            new FallbackFactory.Default(fallbackInstance));
                }
                if (void.class != fallbackFactory) {
                    fallbackFactoryInstance = (FallbackFactory) getFromContext(
                            beanName, "fallbackFactory", fallbackFactory,
                            FallbackFactory.class);
                    return new SentinelInvocationHandler(target, dispatch,
                            fallbackFactoryInstance);
                }
                return new SentinelInvocationHandler(target, dispatch);
            }
    
            ......
        });
    
        super.contract(new SentinelContractHolder(contract));
        return super.build();
    }

    所以最终调用FeignClient访问接口的时候会调用SentinelInvocationHandler的代理对象

    然后查看代理对象的invoke方法

    public Object invoke(final Object proxy, final Method method, final Object[] args)
            throws Throwable {
        ......
    
        Object result;
        //根据方法找到对应的SynchronousMethodHandler
        MethodHandler methodHandler = this.dispatch.get(method);
        // only handle by HardCodedTarget
        if (target instanceof Target.HardCodedTarget) {
            Target.HardCodedTarget hardCodedTarget = (Target.HardCodedTarget) target;
            MethodMetadata methodMetadata = SentinelContractHolder.METADATA_MAP
                    .get(hardCodedTarget.type().getName()
                            + Feign.configKey(hardCodedTarget.type(), method));
            // resource default is HttpMethod:protocol://url
            if (methodMetadata == null) {
                result = methodHandler.invoke(args);
            }
            else {
                String resourceName = methodMetadata.template().method().toUpperCase()
                        + ":" + hardCodedTarget.url() + methodMetadata.template().path();
                Entry entry = null;
                try {
                    ContextUtil.enter(resourceName);
                    entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
                    //执行方法
                    result = methodHandler.invoke(args);
                }
                catch (Throwable ex) {
                    // fallback handle
                    if (!BlockException.isBlockException(ex)) {
                        Tracer.trace(ex);
                    }
                    //此处就是通过FeignClient注解配置的fallback或者fallbackFactory进行创建的FallbackFactory对象,也就是熔断的处理
                    if (fallbackFactory != null) {
                        try {
                            Object fallbackResult = fallbackMethodMap.get(method)
                                    .invoke(fallbackFactory.create(ex), args);
                            return fallbackResult;
                        }
                        catch (IllegalAccessException e) {
                            // shouldn't happen as method is public due to being an
                            // interface
                            throw new AssertionError(e);
                        }
                        catch (InvocationTargetException e) {
                            throw new AssertionError(e.getCause());
                        }
                    }
                    ......
                }
                ......
            }
        }
        else {
            // other target type using default strategy
            result = methodHandler.invoke(args);
        }
    
        return result;
    }

    总结

    使用FeignClient进行微服务访问,就是自动创建一个代理对象进行远程调用,同时可以配置访问方式和异常处理等到

  • 相关阅读:
    关于sqrt函数的使用
    电子商务
    随笔
    哈哈
    整体管理
    软件产品质量特性
    问题
    风险
    antd
    Flex布局大作用
  • 原文地址:https://www.cnblogs.com/grasp/p/14523552.html
Copyright © 2011-2022 走看看