众妙之门

业精于勤,荒于嬉;行成于思,毁于随

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

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进行微服务访问,就是自动创建一个代理对象进行远程调用,同时可以配置访问方式和异常处理等到

 

posted on 2021-03-12 16:03  xuanm  阅读(567)  评论(0编辑  收藏  举报