SpringCloud之OpenFegin基本介绍及原理分析

本文详细介绍了Spring Cloud OpenFeign,包括其基础概念、使用步骤以及核心原理分析。Feign是声明式Web服务客户端,而Spring Cloud OpenFeign在Feign基础上提供了Spring Web MVC注解处理和自动装配。文章讲解了从引入jar包、启用功能、声明FeignClient到调用远程服务的全过程,并深入剖析了动态代理的创建流程,包括FeignClientFactoryBean的注册、Feign.Builder的使用、Client和Targeter的角色,以及ReflectiveFeign如何创建代理对象。最后,文章阐述了动态代理请求远程服务的调用过程,涉及FeignInvocationHandler、SynchronousMethodHandler和LoadBalancerFeignClient的执行流程。

本文主要内容:

  1. 简单了解原生Feign、Spring Cloud Open Fegin是什么

一、Spring Cloud Open Fegin介绍

1.1、Feign基础介绍

Feign:是申明式Web服务客户端,基于JAX-RS(javaRest规范)实现的

  • 申明式:通过接口声明、Annotation驱动
  • Web服务:HTTP通讯协议
  • 客户端:用户服务调用存根

Feign可以将一个Service声明为可以被HTTP方式调用,底层基于面向接口的动态代理方式生成实现类,将请求调用委托到动态代理实现类上。

1.2、Spring Cloud Open Fegin基础介绍

Spring Cloud对原生Feign进行封装,使其支持SpringWebMvc,也就是Spring Cloud Open Feign

REST框架使用场景请求映射注解请求参数注解
JAX-RS客户端、服务端声明@Path@*Param
Feign客户端声明@RequestLine@Param
Spring Web MVC服务端声明@ReqeustMapping@RequestParam
SpringCloudOpenFeign客户端声明@ReqeustMapping@RequestParam

Spring Cloud Open Feign利用Feign的高扩展性,以及使用标准Spring Web MVC来声明客户端 Java 接口:

  • Feign

    • 注解扩展性

    • HTTP 请求处理

    • REST 请求元信息解析

  • Spring Cloud Open Feign

    • 提供 Spring Web MVC 注解处理
    • 提供 Feign 自动装配

1.3、Spring Cloud Open Fegin的使用

1.3.1、引入Spring Cloud Open Fegin对应jar包

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

1.3.2、启用Spring Cloud Open Fegin功能

启用Spring Cloud Open Fegin功能: 在启动类上添加注解@EnableFeignClients注解,并定义要扫描的包名

@EnableFeignClients(basePackages = "com.springcloud.feginclients")
@SpringBootApplication
public class SpringbootApplication {
	public static void main(String[] args) {
		SpringApplication.run(SpringbootApplication.class, args);
	}
}

1.3.1、声明一个FeignClient

准备一个接口:UserService.class

public interface UserService {

    @GetMapping("/getUsers")
    String getUsers();

    @PostMapping("/addUsers")
    int addUser(User user);
}

在定义的basePackages中创建对应的FeignClient,例如:UserServiceFeignClient .calss

  • value: 需要和注册注册中心上的服务名称一致
  • fallback: 请求失败时,会调用fallback指定类中的同名方法
@FeignClient(value = "user-service",
             fallback = UserServiceFeignClient.UserServiceFeignClientFallback.class)
public interface UserServiceFeignClient extends UserService{

    @Component
    class UserServiceFeignClientFallback implements UserServiceFeignClient{

        @Override
        public String getUsers() {
            return "查询用户列表失败,请重试";
        }

        @Override
        public int addUser(User user) {
            System.out.println("新增用户失败,请重试");
            return -1;
        }
    }
}

1.3.4、通过接口方式调用FeignClient

通过@Autowired自动注入UserService,SpringCloud会为UserService创建一个动态代理存到IOC容器中

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    UserService userService;

   @GetMapping("/getUsers")
    public List<User> getUsers(){
        userService.getUsers();
    }

    @PostMapping("/addUsers")
    public int addUser(User user){
        userService.addUsers(user);
    }
}

通过使用Spring Cloud Open Fegin我们可以想调用本地方法一样,去请求远程服务接口。下面我们就来分析一下它的原理

二、Spring Cloud Open Fegin原理分析

Spring Cloud Open Fegin的核心原理,可以简单总结为以下意思:Spring Cloud Open Fegin会为标注了@FeignClient注解的接口创建动态代理,然后通过这个代理发送HTTP请求到我们指定分服务端
其中的关键点有两个:

  1. Spring Cloud Open Fegin时如何加载并创建这个代理对象的?(2.1章分析说明)
  2. 代理对象如何完成远程服务调用?(2.2章分析说明)

2.1、为@FeignClient注册动态代理的流程图

Spring Cloud Open Fegin对于@FeignClient注册动态代理的流程大体可以分为以下三步:
在这里插入图片描述

2.1.1、步骤一:FeignClientsRegistrar注册FeignClientFactoryBean

(1)通过@EnableFeignClients启用功能

我在介绍SpringBean装载过程时,介绍过Spring的动态装配Bean:通过@Enable模式+@import方式,可导入三类Class文件

  1. 添加了@Configuration注解的配置类
  2. ImportSelector接口的实现类
  3. ImportBeanDefinitionRegistrar接口的实现类

@EnableFeignClients:通过导入ImportBeanDefinitionRegistrar接口实现类的方式,实现Bean的动态注入,源代码如下:

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

增加@EnableFeignClients注解后,Spring会调用FeignClientsRegistrar.registerBeanDefinitions()方法将每一个@FeignClient注册到IOC容器中
FeignClientsRegistrar.registerBeanDefinitions()方法,源码如下:

public void registerBeanDefinitions(AnnotationMetadata metadata,
                                    BeanDefinitionRegistry registry) {
    // 如果EnableFeignClients配置了defaultConfiguration
    // 注册一个FeignClientSpecification到IOC中,用于扫描自定义配置类
    registerDefaultConfiguration(metadata, registry);
    
    // 注册FeignClients:会每一个@FeignClient创建代理
    registerFeignClients(metadata, registry);
}

registerDefaultConfiguration方法:注册@EnableFeignClients中定义的defaultConfiguration默认配置(不做分析)

registerFeignClients()方法:会每一个@FeignClient创建代理(下面信息此方法源码)

(2)注册FeignClientFactoryBean到容器中

registerFeignClients.registerFeignClients()方法:会为每一个@FeignClient创建代理,它的核心功能如下:

  1. 定义一个scanner扫描器,并指定扫描路径、筛选条件
    • 扫描路径:根据@EnableFeignClients中配置的参数,获取要扫描的包路径basePackages
    • 设置一个AnnotationTypeFilter过滤器:根据@EnableFeignClients中配置的参数,指定要过滤的文件(添加@FeignClient注解的class文件,或者指定名称的class文件)
  2. 扫描指定basePackages包下,添加@FeignClient注解的class文件:数据会存储到一个Set列表钟,一个class文件对应一个BeanDefinition
  3. 读取每一个BeanDefinition中@FeignClient注解中的属性
  4. 根据@FeignClient注解中的属性,再生成一个FeignClientFactoryBean的BeanDefinition(PS:需要注意,这个BeanDefinition对应的是一个FeignClientFactoryBean(工厂Bean)。在Spring中,工厂Bean是用来创建复杂Bean对象的)
  5. 注册BeanDefinition到容器中(FeignClientFactoryBean的BeanDefinition)
public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
    // 定义scanner扫描器,
    ClassPathScanningCandidateComponentProvider scanner = getScanner();
    scanner.setResourceLoader(this.resourceLoader);

    // 获取EnableFeignClients中定义的basePackages
    Set<String> basePackages;

    Map<String, Object> attrs = metadata
        .getAnnotationAttributes(EnableFeignClients.class.getName());
    
    // 设置只过滤@FeignClient注解
    AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
        FeignClient.class);
    
    final Class<?>[] clients = attrs == null ? null: (Class<?>[]) attrs.get("clients");
    // EnableFeignClients没有定义clients属性时
    if (clients == null || clients.length == 0) {
        scanner.addIncludeFilter(annotationTypeFilter);
        // 根据参数获取要扫描的basePackages
        basePackages = getBasePackages(metadata);
    }
    // EnableFeignClients定义了clients属性:则扫描指定的class文件
    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)));
    }

    // 循环扫描basePackages
    for (String basePackage : basePackages) {
        // 扫描basePackage,获取对应class文件对应的BeanDefinition
        Set<BeanDefinition> candidateComponents = scanner
            .findCandidateComponents(basePackage);
        
        // 循环注册BeanDefinition,逐一注册IOC容器中
        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");

                // 获取@FeignClient中配置参数
                Map<String, Object> attributes = annotationMetadata
                    .getAnnotationAttributes(
                    FeignClient.class.getCanonicalName());

                // 对应到IOC中的Bean Name
                String name = getClientName(attributes);
                
                // 为@FeignClient的configuration属性,注册一个FeignClientSpecification
                registerClientConfiguration(registry, name,
                                            attributes.get("configuration"));

                // 注册FeignClient到IOC中
                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }
}

/*
* 注册FeignClient到IOC中
*
*/
private void registerFeignClient(BeanDefinitionRegistry registry,
                                 AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
    // 获得注解中的classname
    String className = annotationMetadata.getClassName();
    
    // 初始化BeanDefinitionBuilder:注意这是一个FeignClientFactoryBean工厂Bean
    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);
    String contextId = getContextId(attributes);
    definition.addPropertyValue("contextId", contextId);
    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 = contextId + "FeignClient";
    AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

    boolean primary = (Boolean) attributes.get("primary"); 

    beanDefinition.setPrimary(primary);

    String qualifier = getQualifier(attributes);
    if (StringUtils.hasText(qualifier)) {
        alias = qualifier;
    }

    BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
                                                           new String[] { alias });
    // 注册FeignClientFactoryBean的BeanDefinition
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

2.1.2、步骤二:通过FeignClientFactoryBean获取动态代理对象

上面@EnableFeignClients会为每一个注解了@FeignClient的类,注册一个FeignClientFactoryBean到IOC容器中,当我们获取Bean时,就会调用这个FeignClientFactoryBean.getBean()方法。
在这里插入图片描述

FeignClientFactoryBean.getObject()内部会调用自身的getTarget()方法,我们直接看getTarget()方法的源码:

<T> T getTarget() {
    // 1、获取FeignContext上下文
    FeignContext context = this.applicationContext.getBean(FeignContext.class);
    
    // 2、从FeignContext中,根据contextId获取ApplicationContext,然后获取Feign.Builder
    // Feign.Builder 用户创建代理对象
    Feign.Builder builder = feign(context);

    if (!StringUtils.hasText(this.url)) {
        if (!this.name.startsWith("http")) {
            this.url = "http://" + this.name;
        }
        else {
            this.url = this.name;
        }
        this.url += cleanPath();
        return (T) loadBalance(builder, context,
                               new HardCodedTarget<>(this.type, this.name, this.url));
    }
    if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
        this.url = "http://" + this.url;
    }
    String url = this.url + cleanPath();
    
    // 3、从FeignContext中,根据contextId获取ApplicationContext,然后获取Client
    // Client是一个LoadBalancerFeignClient,用于实现客户端负载
    Client client = getOptional(context, Client.class);
    if (client != null) {
        if (client instanceof LoadBalancerFeignClient) {
            client = ((LoadBalancerFeignClient) client).getDelegate();
        }
        if (client instanceof FeignBlockingLoadBalancerClient) {
            client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
        }
        builder.client(client);
    }
    
    // 4、从FeignContext中,根据contextId获取ApplicationContext,然后获取Targeter
    // Targeter默认是DefaultTargeter
    Targeter targeter = get(context, Targeter.class);
    
    // 5、创建并返回代理对象
    return (T) targeter.target(this, builder, context,
                               new HardCodedTarget<>(this.type, this.name, url));
}

在上边代码中添加了5处注释,就不逐行对代码进行分析了,在此主要说一下每个注释方法实现的核心思想,消息源码清自行观看

2.1.2.1:注释1核心:FeignContext context
  • FeignContext继承了NamedContextFactory(在之前Ribbon的分析中LoadBalancerClientFactory也是继承NamedContextFactory,两者实现思路一致),NamedContextFactory内部有一个Map<String,
    AnnotationConfigApplicationContext>,根据@FeignClient定义contextId(或者serviceid)不同,创建不同的AnnotationConfigApplicationContext进行隔离。
  • 通过FeignContext获取Bean时,会在FeignContext的Map<String, AnnotationConfigApplicationContext>中,通过contextId获取对应的AnnotationConfigApplicationContext,然后在AnnotationConfigApplicationContext中通过类型获取对应的Bean
    在这里插入图片描述
2.1.2.2:注释2核心: Feign.Builder

通过(1)中的FeignContext获取Feign.Builder,同时会将注解中配置的参数,设置到Feign.Builder中,后续用于创建Proxy代理对象

2.1.2.3:注释3核心: Client

借用(1)中的FeignContext获取Client,这个Client实质是一个LoadBalancerFeignClient,用于客户端针对contextId(或者serviceid)做负载均衡。

2.1.2.4:注释4核心:Targeter

Targeter:可以理解为获取目标代理对象,内部有时会对Feign.Builder参数进行优化,然后通过Feign.Builder创建Proxy代理对象
借用(1)中的FeignContext获取Targeter,默认是DefaultTargeter,内部会直接调用Feign.Builder创建Proxy代理对象。(当引入Hystrix时,Targeter会是HystrixTargeter,内部会针Hystrix对Feign.Builder修改设置,然后再调用Feign.Builder创建Proxy代理对象)

2.1.2.5:注释5核心:创建Proxy

此处传入了一个HardCodedTarget(Class<T> type, String name, String url)

FeignClientFactoryBean.getTarget根据以下调用链路,最终进入到ReflectiveFeign.newInstance()方法,具体创建代理对象
在这里插入图片描述

2.1.3、步骤三:ReflectiveFeign创建具体的动态代理对象

在ReflectiveFeign.newInstance()内部,可以分为4个部分,它们的主要作用如下:

  1. targetToHandlersByName.apply(target)方法: 基于SpringMVC,解析@FeginClient注解的类,并返回一个nameToHandler,其中的MethodHandler是一个SynchronousMethodHandler(同步方法处理器)
  2. 循环目标类中的Method,建立Method和MethodHandler的映射关系
  3. 为Method创建一个FeignInvocationHandler,并保存它们的映射关系到Map<Method, MethodHandler> methodToHandler。(PS:注意这个methodToHandler在后续方法调用中会用到)
  4. 通过JDK的Proxy创建并返回一个proxy代理对象,此proxy会调用FeignInvocationHandler.invoke()方法

源码分析:

public <T> T newInstance(Target<T> target) {
    // 1、默认基于SpringMVC注解方式,解析@FeginClient注解的类
    // nameToHandler:根据@RequestMapping设置的key,存储对应的MethodHandler的映射关系,MethodHandler是一个SynchronousMethodHandler
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
    
	// 2、methodToHandler:基于method,存储对应的MethodHandler的映射关系
    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)));
        }
    }
    
    // 3、此处创建是一个FeignInvocationHandler,(Proxy内部会调用InvocationHandler.invoke()方法)
    InvocationHandler handler = factory.create(target, methodToHandler);
    
    // 4、通过JDK的Proxy创建代理对象
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
                                         new Class<?>[] {target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
        defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
}

上面是为@FeginClient创建动态代理的过程,下面开始力分析动态代理请求远程服务的调用过程

2.2、@FeignClient动态代理请求远程服务的调用过程

在上面分析中,我们知道Fegin通过JDK的Proxy创建动态代理,当我们调用这个proxy时,它的调用顺序是:

proxy…invoke()
–>FeignInvocationHandler.invoke()
–>SynchronousMethodHandler.invoke()
–>LoadBalancerFeignClient.execute(request, options)

2.2.1、FeignInvocationHandler.invoke()

这里的dispatch就是上面的2.1.3中的methodToHandler,存储了<method,MethodHandler>的映射关系:我们通过method可以获取一个对应的MethodHandler,然后进入MethodHandler.invoke()方法

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

    // dispatch就是上面的methodToHandler:通过method获取一个MethodHandler(即SynchronousMethodHandler)
    return dispatch.get(method).invoke(args);
}

2.2.2、SynchronousMethodHandler.invoke()

一般MethodHandler都是SynchronousMethodHandler,即:同步方法处理
此方法内部调用过程:

  1. 将请求参数,封装为一个RequestTemplate
  2. 调用LoadBalancerFeignClient.execute发送Http请求
  3. 处理并返回Response数据
@Override
public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
        try {
            return executeAndDecode(template, options);
        } catch (RetryableException e) {
            try {
                retryer.continueOrPropagate(e);
            } catch (RetryableException th) {
                Throwable cause = th.getCause();
                if (propagationPolicy == UNWRAP && cause != null) {
                    throw cause;
                } else {
                    throw th;
                }
            }
            if (logLevel != Logger.Level.NONE) {
                logger.logRetry(metadata.configKey(), logLevel);
            }
            continue;
        }
    }
}

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    Request request = targetRequest(template);

    if (logLevel != Logger.Level.NONE) {
      logger.logRequest(metadata.configKey(), logLevel, request);
    }

    Response response;
    long start = System.nanoTime();
    try {
      response = client.execute(request, options);
      // ensure the request is set. TODO: remove in Feign 12
      response = response.toBuilder()
          .request(request)
          .requestTemplate(template)
          .build();
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
      }
      throw errorExecuting(request, e);
    }
    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);


    if (decoder != null)
      return decoder.decode(response, metadata.returnType());

    CompletableFuture<Object> resultFuture = new CompletableFuture<>();
    asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
        metadata.returnType(),
        elapsedTime);

    try {
      if (!resultFuture.isDone())
        throw new IllegalStateException("Response handling not done");

      return resultFuture.join();
    } catch (CompletionException e) {
      Throwable cause = e.getCause();
      if (cause != null)
        throw cause;
      throw e;
    }
  }

2.2.3、LoadBalancerFeignClient.execute(request, options)

方法内部基于Ribbon实现负载均衡调用,实现原理参考之前写的Ribbon原理分析

public Response execute(Request request, Options options) throws IOException {
    try {
        URI asUri = URI.create(request.url());
        String clientName = asUri.getHost();
        URI uriWithoutHost = cleanUrl(request.url(), clientName);
        RibbonRequest ribbonRequest = new RibbonRequest(this.delegate, request, uriWithoutHost);
        IClientConfig requestConfig = this.getClientConfig(options, clientName);
        return ((RibbonResponse)this.lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig)).toResponse();
    } catch (ClientException var8) {
        IOException io = this.findIOException(var8);
        if (io != null) {
            throw io;
        } else {
            throw new RuntimeException(var8);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值