简介

官方文档:
Spring Cloud OpenFeign
OpenFeign和Feign的区别:
feign:

  • Feign是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端,Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。

openfeign:

  • OpenFeign是Spring Cloud 在Feign的基础上支持了SpringMVC的注解,如@RequesMapping等等。OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务

基本使用

1、导入依赖

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

2、创建接口,并添加 @FeignClient 注解

@FeignClient(name = "cmp-workorder")
public interface WorkOrderFeign { 

    @GetMapping("/workorder/pendingWorkorderList")
    DataPage<WorkorderVo> pendingWorkorderList();

} 

3、启动类中添加 @EnableFeignClients 注解,并确保能够扫描到对应的 feign,并为其代理注册为 bean
4、后续在其他地方直接依赖注入 WorkOrderFeign 这个 bean 之后就可以直接使用方法
5、当调用 pendingWorkorderList() 方法时,会往 @FeignClient 注解指定的服务发送请求,路径为 @GetMapping 注解中的路径

@Autowired
private WorkOrderFeign workOrderFeign;

public void test() { 
    workOrderFeign.pendingWorkorderList();
} 

Feign.Builder

介绍

Feign.Builder是Feign的构建器,用于构建Feign客户端实例。

用法

1、创建接口

import feign.RequestLine;

public interface UserFeign { 
    @RequestLine("POST /hello")
    String hello();
} 

2、动态创建 feign

@RequestMapping("/test")
public String test() { 
    UserFeign userFeign = Feign.builder()
            .target(new Target.HardCodedTarget<>(UserFeign.class, "http://localhost:8080"));
    String res = userFeign.hello();
    return res;
} 

这样就可以动态创建 feign 并指定服务为 http://localhost:8080
除此之外还可以动态指定 feign 的其他配置
image.png

FeignClient 注解

常用属性如下:

  • name、value:用于指定调用的服务名,可以是 nacos 中的服务名,会使用 ribbon 进行服务的负载均衡

  • url:直接指定对应的请求地址,优先级会比 name、value 高,一般用于本地调试

  • contextId:指定这个 bean 的名称

  • fallbackFactory:用于指定调用失败时的处理类

  • configuration:用于指定配置类

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    public @interface FeignClient { 
    
      /**
       * @return the name of the service with optional protocol prefix
       */
      @AliasFor("name")
      String value() default "";
    
      /**
       * @return the service id with optional protocol prefix
       */
      @Deprecated
      String serviceId() default "";
    
      /**
       * @return bean name instead of name if present
       */
      String contextId() default "";
    
      /**
       * @return The service id with optional protocol prefix. Synonym for { @link #value()
       * value} .
       */
      @AliasFor("value")
      String name() default "";
    
      /**
       * @return the <code>@Qualifier</code> value for the feign client.
       */
      @Deprecated
      String qualifier() default "";
    
      /**
       * @return the <code>@Qualifiers</code> value for the feign client.
       */
      String[] qualifiers() default { } ;
    
      /**
       * @return an absolute URL or resolvable hostname (the protocol is optional).
       */
      String url() default "";
    
      /**
       * @return whether 404s should be decoded instead of throwing FeignExceptions
       */
      boolean decode404() default false;
    
      /**
       * @return list of configurations for feign client
       */
      Class<?>[] configuration() default { } ;
    
      /**
       * @return fallback class for the specified Feign client interface
       */
      Class<?> fallback() default void.class;
    
      /**
       * @return fallback factory for the specified Feign client interface
       */
      Class<?> fallbackFactory() default void.class;
    
      /**
       * @return path prefix to be used by all method-level mappings. Can be used with or
       * without <code>@RibbonClient</code>.
       */
      String path() default "";
    
      /**
       * @return whether to mark the feign proxy as a primary bean. Defaults to true.
       */
      boolean primary() default true;
    

}



## 配置请求头(header)
1、在 @FeignClient 注解中指定配置类
```java
@FeignClient(name = "cmp-workorder", configuration = FeignConfig.class)
public interface WorkOrderFeign { 
} 

2、编写配置类代码,实现 RequestInterceptor 接口,获取当前请求的请求头信息,设置给 feign 代理对象中的 request

public class FeignConfig implements RequestInterceptor { 
    @Override
    public void apply(RequestTemplate template) { 
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes == null) { 
            log.info("====FeignConfig requestAttributes is null.");
            return;
        } 

        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
        Enumeration<String> headerNames = request.getHeaderNames();
        if (headerNames != null) { 
            while (headerNames.hasMoreElements()) { 
                String name = headerNames.nextElement();
                Enumeration<String> values = request.getHeaders(name);
                while (values.hasMoreElements()) { 
                    String value = values.nextElement();
                    template.header(name, value);
                } 
            } 
        } 
    } 
} 

设置消息转换器

直接创建对应的 bean 即可,替换掉 feign 中默认的消息转换器

@Bean
public Encoder feignEncoder() { 
    return new SpringEncoder(feignHttpMessageConverter());
} 

/**
 * 设置为fastjson
 *
 * @return
 */
private ObjectFactory<HttpMessageConverters> feignHttpMessageConverter() { 
    final HttpMessageConverters httpMessageConverters = new HttpMessageConverters(this.getFastJsonConverter());
    return () -> httpMessageConverters;
} 

private FastJsonHttpMessageConverter getFastJsonConverter() { 
    FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
    List<MediaType> supportedMediaTypes = new ArrayList<>();
    MediaType mediaTypeJson = MediaType.valueOf(MediaType.APPLICATION_JSON_VALUE);
    supportedMediaTypes.add(mediaTypeJson);
    converter.setSupportedMediaTypes(supportedMediaTypes);
    FastJsonConfig config = new FastJsonConfig();
    config.getSerializeConfig().put(JSON.class, new SwaggerJsonSerializer());
    config.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect);
    converter.setFastJsonConfig(config);
    return converter;
} 

feign 的创建过程

1、从 @EnableFeignClients 注解开始,进入后会看到导入了 FeignClientsRegistrar 类用于注册 feign
image.png
2、而 FeignClientsRegistrar 又实现了 ImportBeanDefinitionRegistrar 接口,用于创建 beanDefinition。而ResourceLoaderAware, EnvironmentAware 分别用于注入 ResourceLoader 加载器和 Environment 环境信息
image.png
image.png
3、直接查看 registerBeanDefinitions 注册 bean 的方法
image.png
registerDefaultConfiguration() 方法用于加载注解中配置的 defaultConfiguration 默认配置类(暂时不看)
image.png
registerFeignClients() 方法,会先解析注解中的值 clients,如果指定,则直接注册这部分 feign;否则会获取扫描路径并扫描具有 @FeignClient 注解的类
image.png
而在获取扫描路径的方法 getBasePackages() 中,则会根据如下规则进行获取
image.png
4、扫描路径,并获取候选 bean,详细的就不看了,这部分就是直接扫描对应路径下的,独立的且非注解,并具有 @FeignClient 注解的类
image.png
image.png
5、开始处理候选 bean
image.png
获取 bean 名字,字段的优先级如下,contextId 优先级最高
image.png
注册配置类,分别为每个 feign 注册一个配置类
image.png
注册 bean,通过 FeignClientFactoryBean 创建对应的代理类
image.png
image.png
image.png

feign 的请求流程

面试题

OpenFeign相关问题及答案_openfeign面试题-CSDN博客