一、Feign受Retrofit、JAXRS-2.0和WebSocket影响,采用了声明式API 接口的风格,将Java Http 客户端绑定到它的内部。 Feign 首要目的是将 Java Http 客户端调用过程变得简单。
理解的简单一点就是Feign的原理就是通过Java Http的方式访问,已经编写好的接口,实现调用的简单化、解耦化。
二、我们先写一个Feign例子看看(端口8676):
1)加入依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
2)编写配置文件application.yaml
server: port: 8676 spring: application: name: feign eureka: client: service-url: defaultZone: http://127.0.0.1:8670/eureka/ # 实际开发中建议使用域名的方式
3)编写启动项:
package com.cetc; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableEurekaClient @EnableFeignClients public class FeignApplication { public static void main(String[] args) { SpringApplication.run(FeignApplication.class, args); } }
说明:@EnableEurekaClient:开启Eureka-Client功能。@EnableFeignClients:开启Feign-Client功能。
4)编写配置
package com.cetc.config; import feign.Retryer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FeignConfiguration { @Bean public Retryer retryer() { return new Retryer.Default(); } }
说明:这里的配置主要目的就是远程调用失败后,进行重试。
5)编写Feign接口
package com.cetc.feign.client; import com.cetc.config.FeignConfiguration; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.GetMapping; @Component @FeignClient(value = "client", configuration = {FeignConfiguration.class}) public interface TestFeign { @GetMapping("/api/test/getPort") Integer getPort(); }
说明:@FeignClient注解会在开启@EnableFeignClients后生效。远程接口调用方式和具体的编写方式一样,只是需要复制而已。@Component:这里可以不加,运行也不会报错,但是idea会报错,所以一般我都加上。
6)编写rest接口和service
package com.cetc.service; public interface IFeignService { Integer getPort(); }
package com.cetc.service.impl; import com.cetc.feign.client.TestFeign; import com.cetc.service.IFeignService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class FeignServiceImpl implements IFeignService { @Autowired private TestFeign testFeign; @Override public Integer getPort() { return testFeign.getPort(); } }
package com.cetc.web.rest; import com.cetc.service.IFeignService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api/feign") public class FeignResource { @Autowired private IFeignService feignService; @GetMapping("/getPort") public Integer getPort() { return feignService.getPort(); } }
三、feign的基本编写就基本完成了,现在我们进行测试。
1)先启动Eureka-Server端口8670
2)启动2个Eureka-Client端口8673和8674
3)启动Feign-Client端口8676
4)效果如下:
5)访问端口8676,接口/api/feign/getPort测试
四、具体的调用过程:
由上面的测试可以看见,Feign-Client是具有负载均衡功能的,那说明Feign中是存在Ribbon的调用的,通过查看依赖我们可以得知
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-openfeign</artifactId> <version>2.0.4.RELEASE</version> <relativePath>..</relativePath> </parent> <artifactId>spring-cloud-starter-openfeign</artifactId> <name>Spring Cloud Starter OpenFeign</name> <description>Spring Cloud Starter OpenFeign</description> <url>https://projects.spring.io/spring-cloud</url> <organization> <name>Pivotal Software, Inc.</name> <url>https://www.spring.io</url> </organization> <properties> <main.basedir>${basedir}/../..</main.basedir> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-openfeign-core</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-commons</artifactId> </dependency> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-core</artifactId> </dependency> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-slf4j</artifactId> </dependency> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-hystrix</artifactId> </dependency> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-java8</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-archaius</artifactId> <optional>true</optional> </dependency> </dependencies> </project>
这里确实是使用了Ribbon来做负载的。
五、关于@FeignClient。
package org.springframework.cloud.openfeign; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.core.annotation.AliasFor; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FeignClient { @AliasFor("name") String value() default ""; /** @deprecated */ @Deprecated String serviceId() default ""; @AliasFor("value") String name() default ""; String qualifier() default ""; String url() default ""; boolean decode404() default false; Class<?>[] configuration() default {}; Class<?> fallback() default void.class; Class<?> fallbackFactory() default void.class; String path() default ""; boolean primary() default true; }
@FeignClient 注解用于创建声明式 API 接口,该接口是RESTful 风格的。Feign 被设计成插拔式的,可以注入其他组件和 Feign一起使用。最典型的是如果 Ribbon 可用, Feign 会和Ribbon 结合进行负载均衡。
六、关于FeignClient的配置
1)默认配置为:FeignClientsConfiguration
package org.springframework.cloud.openfeign; import com.netflix.hystrix.HystrixCommand; import feign.Contract; import feign.Feign; import feign.Logger; import feign.Retryer; import feign.Feign.Builder; import feign.codec.Decoder; import feign.codec.Encoder; import feign.hystrix.HystrixFeign; import feign.optionals.OptionalDecoder; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.cloud.openfeign.support.ResponseEntityDecoder; import org.springframework.cloud.openfeign.support.SpringDecoder; import org.springframework.cloud.openfeign.support.SpringEncoder; import org.springframework.cloud.openfeign.support.SpringMvcContract; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; import org.springframework.core.convert.ConversionService; import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.format.support.FormattingConversionService; @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; public FeignClientsConfiguration() { } @Bean @ConditionalOnMissingBean public Decoder feignDecoder() { return new OptionalDecoder(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(); Iterator var2 = this.feignFormatterRegistrars.iterator(); while(var2.hasNext()) { FeignFormatterRegistrar feignFormatterRegistrar = (FeignFormatterRegistrar)var2.next(); feignFormatterRegistrar.registerFormatters(conversionService); } return conversionService; } @Bean @ConditionalOnMissingBean public Retryer feignRetryer() { return Retryer.NEVER_RETRY; } @Bean @Scope("prototype") @ConditionalOnMissingBean public Builder feignBuilder(Retryer retryer) { return Feign.builder().retryer(retryer); } @Bean @ConditionalOnMissingBean({FeignLoggerFactory.class}) public FeignLoggerFactory feignLoggerFactory() { return new DefaultFeignLoggerFactory(this.logger); } @Configuration @ConditionalOnClass({HystrixCommand.class, HystrixFeign.class}) protected static class HystrixFeignConfiguration { protected HystrixFeignConfiguration() { } @Bean @Scope("prototype") @ConditionalOnMissingBean @ConditionalOnProperty( name = {"feign.hystrix.enabled"} ) public Builder feignHystrixBuilder() { return HystrixFeign.builder(); } } }