声明式调用Feign

作者: 董二弯 | 来源:发表于2019-05-10 23:16 被阅读3次

上一章中讲解了如何使用RestTemplate来消费服务,如何结合Ribbon在消费服务时做负载均衡。本章将讲解Feign的使用,Feign是一个web请求的工具,可以将请求指定到具体的服务上去,在项目中主要用做服务之间的调用。

写一个Feign客户端

  • 加入相关依赖,主要包括feign的起步依赖spring-cloud-starter-feign,Eureka Client的起步依赖spring-cloud-starter-eureka
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
        </dependency>
    </dependencies>
  • 配置文件中做相关配置
server:
  port: 8766
spring:
  application:
    name: eureka-feign-client
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
  • 工程启动类上加上@EnableEurekaClient开启Eureka Client的功能,加上@EnableFeignClients开启Feign Client的功能。
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class EurekaFeignClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaFeignClientApplication.class, args);
    }
}
  • 新建声明式接口,在接口上加@FeignClient注解来声明一个Feign Client,其中value为远程调用其它服务的服务名,configuration属性为Feign Client的配置类。
@FeignClient(value = "eureka-client", configuration = FeignClientConfig.class)
public interface HiFeignClient {
     // 该方法通过feign来调用eureka-client服务的“/hi”接口。
    @GetMapping("/hi")
    String hi(@RequestParam(value = "name") String name);
}


//注入feignRetryer的Bean。Feign在远程调用失败后会进行重试
@Configuration
public class FeignClientConfig {
    @Bean
    public Retryer feignRetryer() {
        return new Retryer.Default(100, SECONDS.toMillis(1), 5);
    }
}
  • 新建controller,在其中注入刚刚定义的FeignClient,并使用feignClient来调用接口。
@RestController
public class FeignController {
    @Autowired
    private HiFeignClient feignClient;

    @GetMapping("/feign-test")
    public String hi(@RequestParam String name) {
        return feignClient.hi(name);
    }
}

启动eureka-server和两个eureka-client(项目代码和启动方式见前面章节),启动eureka-feign-client。
多次访问eureka-feign-client的/feign-test接口,会轮流显示两个eureka-client的“/hi”API接口,说明Feign Client有负载均衡的能力。

FeignClient 的配置

在上文中我们通过FeignClient 的configuration 属性覆盖了Feign Client的默认配置。Feign Client默认的配置类为FeignClientsConfiguration,这个类在spring-cloud-netflix-core的jar包下,其中注入了很多Feign相关的配置,具体代码如下:

@Configuration
public class FeignClientsConfiguration {

    @Autowired
    private ObjectFactory<HttpMessageConverters> messageConverters;

    @Autowired(required = false)
    private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>();

    @Autowired(required = false)
    private List<FeignFormatterRegistrar> feignFormatterRegistrars = new ArrayList<>();

    @Autowired(required = false)
    private Logger logger;

    @Bean
    @ConditionalOnMissingBean
    public Decoder feignDecoder() {
        return new ResponseEntityDecoder(new SpringDecoder(this.messageConverters));
    }

    @Bean
    @ConditionalOnMissingBean
    public Encoder feignEncoder() {
        return new SpringEncoder(this.messageConverters);
    }

    @Bean
    @ConditionalOnMissingBean
    public Contract feignContract(ConversionService feignConversionService) {
        return new SpringMvcContract(this.parameterProcessors, feignConversionService);
    }

    @Bean
    public FormattingConversionService feignConversionService() {
        FormattingConversionService conversionService = new DefaultFormattingConversionService();
        for (FeignFormatterRegistrar feignFormatterRegistrar : feignFormatterRegistrars) {
            feignFormatterRegistrar.registerFormatters(conversionService);
        }
        return conversionService;
    }

    @Configuration
    @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
    protected static class HystrixFeignConfiguration {
        @Bean
        @Scope("prototype")
        @ConditionalOnMissingBean
        @ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = false)
        public Feign.Builder feignHystrixBuilder() {
            return HystrixFeign.builder();
        }
    }

    @Bean
    @ConditionalOnMissingBean
    public Retryer feignRetryer() {
        return Retryer.NEVER_RETRY;
    }

    @Bean
    @Scope("prototype")
    @ConditionalOnMissingBean
    public Feign.Builder feignBuilder(Retryer retryer) {
        return Feign.builder().retryer(retryer);
    }

    @Bean
    @ConditionalOnMissingBean(FeignLoggerFactory.class)
    public FeignLoggerFactory feignLoggerFactory() {
        return new DefaultFeignLoggerFactory(logger);
    }
}

@ConditionalOnMissingBean注解表示如果没有注入该类的Bean就会默认注入一个Bean,于是我们在上文中在自定义的配置类中注入Retryer 类型的Bean从而达到自定义配置的目的。

Feign 的工作原理

  • 首先通过@EnableFeignClients注解开启FeignClient的功能。只有这个注解存在,才会在程序启动时开启@FeignClient注解的包扫描。
  • 程序启动后,会进行包扫描,扫描所有的@FeignClient的注解的类,并将这些信息注入IOC容器中。
  • 当接口的方法被调用时,通过JDK的代理来生成具体的RequestTemplate模板对象。
  • 根据RequestTemplate来生成HTTP请求的Request对象。
  • Request对象交给Client去处理,其中Client的网络请求框架可以是HttpURLConnection,HttpClient和OkHttp。
  • 最后Client被封装到LoadBalanceClient类,这个类结合Ribbon做到了负载均衡。

Feign使用HttpClient或OkHttp

Feign默认使用HttpURLConnection来实现网络请求的。同时还支持HttpClient和OkHttp进行网络请求。通过FeignRibbonClientAutoConfiguration的源码:

@Configuration
    @ConditionalOnClass(ApacheHttpClient.class)
    @ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
    protected static class HttpClientFeignLoadBalancedConfiguration {

        @Autowired(required = false)
        private HttpClient httpClient;

        @Bean
        @ConditionalOnMissingBean(Client.class)
        public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
                SpringClientFactory clientFactory) {
            ApacheHttpClient delegate;
            if (this.httpClient != null) {
                delegate = new ApacheHttpClient(this.httpClient);
            }
            else {
                delegate = new ApacheHttpClient();
            }
            return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
        }
    }

    @Configuration
    @ConditionalOnClass(OkHttpClient.class)
    @ConditionalOnProperty(value = "feign.okhttp.enabled", matchIfMissing = true)
    protected static class OkHttpFeignLoadBalancedConfiguration {

        @Autowired(required = false)
        private okhttp3.OkHttpClient okHttpClient;

        @Bean
        @ConditionalOnMissingBean(Client.class)
        public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
                SpringClientFactory clientFactory) {
            OkHttpClient delegate;
            if (this.okHttpClient != null) {
                delegate = new OkHttpClient(this.okHttpClient);
            }
            else {
                delegate = new OkHttpClient();
            }
            return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
        }
    }

其中@ConditionalOnClass表示在容器中存在对应的Bean配置就会生效,@ConditionalOnProperty中属性默认为true,所以不用在application.yml配置文件中配置,只需要在pom文件加上HttpClient或者OkHttp的Classpath即可。

<dependency>
          <groupId>com.netflix.feign</groupId>
          <artifactId>feign.httpclient</artifactId>
          <version>RELEASE</version>
</dependency>
或
<dependency>
          <groupId>com.netflix.feign</groupId>
          <artifactId>feign.okhttp</artifactId>
          <version>RELEASE</version>
</dependency>

注意项

  • 由于网络原因,feign请求会超时,此时会发生异常。feign实际整合了hystrix(断路器 后续介绍),我们可以通过配置来调整超时时间。
//增加超时时间
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 5000 
//禁用超时
# hystrix.command.default.execution.timeout.enabled: false 
//索性禁用feign的hystrix支持 
feign.hystrix.enabled: false 
  • feign传递List<String>,被调用端会报Required List parameter 'uomIdList' is not present。此时需要传递 string[]。但传递List<Long>则可以。这是在开发中遇到的,总结不一定完全正确。
  • 不支持@GetMapping,可用@RequestMapping(value = "", method = RequestMethod.GET)来替代。在参数中@PathVariable一定得设置value。
  • 只要参数是复杂对象,即使指定了是GET方法,feign依然会以POST方法进行发送请求。这是在开发中遇到的,总结不一定完全正确。

总结

在这一章学习了如何使用Feign,并介绍了Feign的工作原理和如何切换HTTP的请求框架。最后介绍了本人在开发过程中遇到的一些坑。在下一章学习熔断器Hystrix
PS:项目github地址:https://github.com/dzydzydzy/spring-cloud-example.git

相关文章

网友评论

    本文标题:声明式调用Feign

    本文链接:https://www.haomeiwen.com/subject/rnqfoqtx.html