聲明式調(diào)用Feign

上一章中講解了如何使用RestTemplate來(lái)消費(fèi)服務(wù),如何結(jié)合Ribbon在消費(fèi)服務(wù)時(shí)做負(fù)載均衡。本章將講解Feign的使用,F(xiàn)eign是一個(gè)web請(qǐng)求的工具,可以將請(qǐng)求指定到具體的服務(wù)上去,在項(xiàng)目中主要用做服務(wù)之間的調(diào)用。

寫(xiě)一個(gè)Feign客戶(hù)端

  • 加入相關(guān)依賴(lài),主要包括feign的起步依賴(lài)spring-cloud-starter-feign,Eureka Client的起步依賴(lài)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>
  • 配置文件中做相關(guān)配置
server:
  port: 8766
spring:
  application:
    name: eureka-feign-client
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
  • 工程啟動(dòng)類(lèi)上加上@EnableEurekaClient開(kāi)啟Eureka Client的功能,加上@EnableFeignClients開(kāi)啟Feign Client的功能。
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class EurekaFeignClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaFeignClientApplication.class, args);
    }
}
  • 新建聲明式接口,在接口上加@FeignClient注解來(lái)聲明一個(gè)Feign Client,其中value為遠(yuǎn)程調(diào)用其它服務(wù)的服務(wù)名,configuration屬性為Feign Client的配置類(lèi)。
@FeignClient(value = "eureka-client", configuration = FeignClientConfig.class)
public interface HiFeignClient {
     // 該方法通過(guò)feign來(lái)調(diào)用eureka-client服務(wù)的“/hi”接口。
    @GetMapping("/hi")
    String hi(@RequestParam(value = "name") String name);
}


//注入feignRetryer的Bean。Feign在遠(yuǎn)程調(diào)用失敗后會(huì)進(jìn)行重試
@Configuration
public class FeignClientConfig {
    @Bean
    public Retryer feignRetryer() {
        return new Retryer.Default(100, SECONDS.toMillis(1), 5);
    }
}
  • 新建controller,在其中注入剛剛定義的FeignClient,并使用feignClient來(lái)調(diào)用接口。
@RestController
public class FeignController {
    @Autowired
    private HiFeignClient feignClient;

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

啟動(dòng)eureka-server和兩個(gè)eureka-client(項(xiàng)目代碼和啟動(dòng)方式見(jiàn)前面章節(jié)),啟動(dòng)eureka-feign-client。
多次訪(fǎng)問(wèn)eureka-feign-client的/feign-test接口,會(huì)輪流顯示兩個(gè)eureka-client的“/hi”API接口,說(shuō)明Feign Client有負(fù)載均衡的能力。

FeignClient 的配置

在上文中我們通過(guò)FeignClient 的configuration 屬性覆蓋了Feign Client的默認(rèn)配置。Feign Client默認(rèn)的配置類(lèi)為FeignClientsConfiguration,這個(gè)類(lèi)在spring-cloud-netflix-core的jar包下,其中注入了很多Feign相關(guān)的配置,具體代碼如下:

@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注解表示如果沒(méi)有注入該類(lèi)的Bean就會(huì)默認(rèn)注入一個(gè)Bean,于是我們?cè)谏衔闹性谧远x的配置類(lèi)中注入Retryer 類(lèi)型的Bean從而達(dá)到自定義配置的目的。

Feign 的工作原理

  • 首先通過(guò)@EnableFeignClients注解開(kāi)啟FeignClient的功能。只有這個(gè)注解存在,才會(huì)在程序啟動(dòng)時(shí)開(kāi)啟@FeignClient注解的包掃描。
  • 程序啟動(dòng)后,會(huì)進(jìn)行包掃描,掃描所有的@FeignClient的注解的類(lèi),并將這些信息注入IOC容器中。
  • 當(dāng)接口的方法被調(diào)用時(shí),通過(guò)JDK的代理來(lái)生成具體的RequestTemplate模板對(duì)象。
  • 根據(jù)RequestTemplate來(lái)生成HTTP請(qǐng)求的Request對(duì)象。
  • Request對(duì)象交給Client去處理,其中Client的網(wǎng)絡(luò)請(qǐng)求框架可以是HttpURLConnection,HttpClient和OkHttp。
  • 最后Client被封裝到LoadBalanceClient類(lèi),這個(gè)類(lèi)結(jié)合Ribbon做到了負(fù)載均衡。

Feign使用HttpClient或OkHttp

Feign默認(rèn)使用HttpURLConnection來(lái)實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求的。同時(shí)還支持HttpClient和OkHttp進(jìn)行網(wǎng)絡(luò)請(qǐng)求。通過(guò)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表示在容器中存在對(duì)應(yīng)的Bean配置就會(huì)生效,@ConditionalOnProperty中屬性默認(rèn)為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>

注意項(xiàng)

  • 由于網(wǎng)絡(luò)原因,feign請(qǐng)求會(huì)超時(shí),此時(shí)會(huì)發(fā)生異常。feign實(shí)際整合了hystrix(斷路器 后續(xù)介紹),我們可以通過(guò)配置來(lái)調(diào)整超時(shí)時(shí)間。
//增加超時(shí)時(shí)間
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 5000 
//禁用超時(shí)
# hystrix.command.default.execution.timeout.enabled: false 
//索性禁用feign的hystrix支持 
feign.hystrix.enabled: false 
  • feign傳遞List<String>,被調(diào)用端會(huì)報(bào)Required List parameter 'uomIdList' is not present。此時(shí)需要傳遞 string[]。但傳遞List<Long>則可以。這是在開(kāi)發(fā)中遇到的,總結(jié)不一定完全正確。
  • 不支持@GetMapping,可用@RequestMapping(value = "", method = RequestMethod.GET)來(lái)替代。在參數(shù)中@PathVariable一定得設(shè)置value。
  • 只要參數(shù)是復(fù)雜對(duì)象,即使指定了是GET方法,feign依然會(huì)以POST方法進(jìn)行發(fā)送請(qǐng)求。這是在開(kāi)發(fā)中遇到的,總結(jié)不一定完全正確。

總結(jié)

在這一章學(xué)習(xí)了如何使用Feign,并介紹了Feign的工作原理和如何切換HTTP的請(qǐng)求框架。最后介紹了本人在開(kāi)發(fā)過(guò)程中遇到的一些坑。在下一章學(xué)習(xí)熔斷器Hystrix
PS:項(xiàng)目github地址:https://github.com/dzydzydzy/spring-cloud-example.git

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容