Feign基础入门及特性讲解

介绍

Feign是从Netflix中分离出来的轻量级项目,能够在类接口上添加注释,成为一个REST API 客户端。

Feign中对 Hystrix 有依赖关系。Feign只是一个便利的rest框架,简化调用,最后还是通过ribbon在注册服务器中找到服务实例,然后对请求进行分配。

实际项目

在入口程序中添加注释

@EnableFeignClients

REST API 客户端

@FeignClient(value = "ticket-service", configuration = YeaFeignConfiguration.class,fallback = TicketClientHystrix.class)
interface TicketClient {

    @RequestMapping(method = RequestMethod.POST, value = "/create")
    Message<String> create(
            @RequestParam(value = "Type") Integer Type, 
            @RequestParam(value = "amount") String amount,
            @RequestParam(value = "userId") String userId, 
            @RequestParam(value = "mobile") String mobile,
            @RequestParam(value = "status") Integer status, 
            @RequestParam(value = "belong") Integer belong,
            @RequestParam(value = "useProfit")String useProfit,
            @RequestParam(value = "useCounter")String useCounter);
}

自定义FeignConfiguration属性

@Configuration
public class YeaFeignConfiguration {

    public static final int CONNECT_TIMEOUT_MILLIS = 5000;
    public static final int READ_TIMEOUT_MILLIS = 5000;

    @Bean
    public Logger.Level feignLogger() {
        return Logger.Level.FULL;
    }

    @Bean
    public Request.Options options() {
        return new Request.Options(CONNECT_TIMEOUT_MILLIS, READ_TIMEOUT_MILLIS);
    }
}

pom.xml

<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-core</artifactId>
  <version>${project.version}</version>
</dependency>
<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-gson</artifactId>
  <version>${project.version}</version>
</dependency>

Feign 原生示例

获取URL的代码,然后封装成对象返回

public class GitHubExample {

    interface GitHub {

        class Repository {
            String name;
        }

        class Contributor {
            String login;
        }

        @RequestLine("GET /users/{username}/repos?sort=full_name")
        List<Repository> repos(@Param("username") String owner);

        @RequestLine("GET /repos/{owner}/{repo}/contributors")
        List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);

        /** Lists all contributors for all repos owned by a user. */
        default List<String> contributors(String owner) {
            return repos(owner).stream().flatMap(repo -> contributors(owner, repo.name).stream()).map(c -> c.login)
                    .distinct().collect(Collectors.toList());
        }

        static GitHub connect() {
            Decoder decoder = new GsonDecoder();
            return Feign.builder().decoder(decoder).errorDecoder(new GitHubErrorDecoder(decoder))
                    .logger(new Logger.ErrorLogger()).logLevel(Logger.Level.BASIC)
                    .target(GitHub.class, "https://api.github.com");
        }
    }

    static class GitHubClientError extends RuntimeException {
        private String message; // parsed from json

        @Override
        public String getMessage() {
            return message;
        }
    }

    static class GitHubErrorDecoder implements ErrorDecoder {

        final Decoder decoder;
        final ErrorDecoder defaultDecoder = new ErrorDecoder.Default();

        GitHubErrorDecoder(Decoder decoder) {
            this.decoder = decoder;
        }

        @Override
        public Exception decode(String methodKey, Response response) {
            try {
                return (Exception) decoder.decode(response, GitHubClientError.class);
            } catch (IOException fallbackToDefault) {
                return defaultDecoder.decode(methodKey, response);
            }
        }
    }

    public static void main(String... args) {

        GitHub github = GitHub.connect();

        System.out.println("Let's fetch and print a list of the contributors to this org.");
        List<String> contributors = github.contributors("netflix");
        for (String contributor : contributors) {
            System.out.println(contributor);
        }

        System.out.println("Now, let's cause an error.");
        try {
            github.contributors("netflix", "some-unknown-project");
        } catch (GitHubClientError e) {
            System.out.println(e.getMessage());
        }
    }
}

Feign其他特性

FEIGN CLIENT WITH HYSTRIXOBSERVABLE WRAPPER

With Hystrix on the classpath, you can also return a HystrixComman

基础用法

@FeignClient("http://notification-service")
public interface NotificationVersionResource {  
    @RequestMapping(value = "/version", method = GET)
    String version();
}

细粒度操作

@FeignClient("http://notification-service")
public interface NotificationVersionResource {  
    @RequestMapping(value = "/version", method = GET)
    HystrixObservable<String> version();
}

FEIGN CLIENT WITH HYSTRIX FALLBACK

Feign Clients能直接使用降级功能,最简单的方式就是使用接口,在接口中实现你的降级代码,在服务端发生错误的时候将会被调用。

@FeignClient("http://notification-service")
public interface NotificationResource {  
    @RequestMapping(value = "/notifications", method = GET)
    List<Notification> findAll();
}

public class NotificationResourceImpl implements NotificationResource {  
    @Override
    public List<Notification> findAll() {
        return new ArrayList<>();
    }
}

使用外部链接

之前的示例都是在服务发现中,使用service的Name 去访问,但是同样的也支持使用外部链接去访问。

@FeignClient(name = "reddit-service", url = "${com.deswaef.reddit.url}")
public interface RedditResource {  
    @RequestMapping(method = RequestMethod.GET, value = "/java.json")
    RedditResponse posts();
}

可选配置项

Spring Cloud Netflix 为 Feign提供了下面默认的配置Bean

  • Decoder feignDecoder: ResponseEntityDecoder
  • Encoder feignEncoder: SpringEncoder
  • Logger feignLogger: Slf4jLogger
  • Contract feignContract: SpringMvcContract
  • Feign.Builder feignBuilder: HystrixFeign.Builder

可以通过设置 feign.okhttp.enabled:true 属性来使用OkHttpClient和ApacheHttpClient,别忘了添加到类路径

Spring Cloud Netflix 不为Feign提供下面的默认属性,但是一样会在应用的上下文中去搜索这些Bean然后创建feign client

  • Logger.Level
  • Retryer
  • ErrorDecoder
  • Request.Options
  • Collection

如果你需要一个额外的属性,或者想覆盖一个属性,你可以为每一个FeignClient创建自定义的属性Bean:

@FeignClient(
    name = "reddit-service", 
    url = "${com.deswaef.reddit.url}", 
    configuration = RedditFeignConfiguration.class
)
@Configuration
public class RedditFeignConfiguration {  
    public static final int FIVE_SECONDS = 5000;
    
    @Bean
    public Logger.Level feignLogger() {
        return Logger.Level.FULL;
    }
    
    @Bean
    public Request.Options options() {
        return new Request.Options(FIVE_SECONDS, FIVE_SECONDS);
    }

    @Bean
    public Contract feignContract() {
        return new feign.Contract.Default();
    }

    @Bean
    public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
        return new BasicAuthRequestInterceptor("user", "password");
    }
}

Feign中不使用Hystrix特性

如果你想在你的RequestInterceptor中使用ThreadLocal绑定变量,你需要在Hystrix中设置thread isolation策略或者disable Hystrix in Feign

# To disable Hystrix in Feign
feign:
  hystrix:
    enabled: false

# To set thread isolation to SEMAPHORE
hystrix:
  command:
    default:
      execution:
        isolation:
          strategy: SEMAPHORE

Note

if this configuration class is on the component scan path, it'll be also picked up as general configuration. This means that a configuration class like this, when also scanned by our automatic component scan, will override all of the beans for each and every FeignClient, not just the one which defined it as configuration.

也就是说,要是被 automatic component扫描到了,所有的FeignClient都会按照这个class的配置项去生效,而不是仅仅configuration = RedditFeignConfiguration.class 这个显示声明的接口,

As a result, you should place it inside a package that isn't a candidate for a component scan

不要放在能被扫描到的包中。
最简单的做法,就是不要标记 @Configuration 注释。

手动调用Feign

两个示例:

@Import(FeignClientsConfiguration.class)
class FooController {

    private FooClient fooClient;

    private FooClient adminClient;

    @Autowired
    public FooController(
            Decoder decoder, Encoder encoder, Client client) {
        this.fooClient = Feign.builder().client(client)
                .encoder(encoder)
                .decoder(decoder)
                .requestInterceptor(new BasicAuthRequestInterceptor("user", "user"))
                .target(FooClient.class, "http://PROD-SVC");
        this.adminClient = Feign.builder().client(client)
                .encoder(encoder)
                .decoder(decoder)
                .requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin"))
                .target(FooClient.class, "http://PROD-SVC");
    }
}

## In the above example FeignClientsConfiguration.class is the default configuration provided by Spring Cloud Netflix.
interface GitHub {

        class Repository {
            String name;
        }

        class Contributor {
            String login;
        }

        @RequestLine("GET /users/{username}/repos?sort=full_name")
        List<Repository> repos(@Param("username") String owner);

        @RequestLine("GET /repos/{owner}/{repo}/contributors")
        List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);

        /** Lists all contributors for all repos owned by a user. */
        default List<String> contributors(String owner) {
            return repos(owner).stream().flatMap(repo -> contributors(owner, repo.name).stream()).map(c -> c.login)
                    .distinct().collect(Collectors.toList());
        }

        static GitHub connect() {
            Decoder decoder = new GsonDecoder();
            return Feign.builder().decoder(decoder).errorDecoder(new GitHubErrorDecoder(decoder))
                    .logger(new Logger.ErrorLogger()).logLevel(Logger.Level.BASIC)
                    .target(GitHub.class, "https://api.github.com");
        }
    }

Feign Hystrix Support

1、Hystrix 在 classpath中且feign.hystrix.enabled=true, Feign 包裹的的所有方法都会自带断路器(circuit breaker)

2、在方法中返回com.netflix.hystrix.HystrixCommand也是可以支持Hystrix特性 : This lets you use reactive patterns (with a call to .toObservable() or .observe() or asynchronous use (with a call to .queue()).

Note:Prior to the Spring Cloud Dalston release, if Hystrix was on the classpath Feign would have wrapped all methods in a circuit breaker by default. This default behavior was changed in Spring Cloud Dalston in favor for an opt-in approach.

在单独的 Feign client中禁止Hystrix特性,可以创建一个Feign.Builder with the "prototype" scope

@Configuration
public class FooConfiguration {
    @Bean
    @Scope("prototype")
    public Feign.Builder feignBuilder() {
        return Feign.builder();
    }
}

Feign Hystrix Fallbacks

常规的降级方式:

@FeignClient(name = "hello", fallback = HystrixClientFallback.class)
protected interface HystrixClient {
    @RequestMapping(method = RequestMethod.GET, value = "/hello")
    Hello iFailSometimes();
}

static class HystrixClientFallback implements HystrixClient {
    @Override
    public Hello iFailSometimes() {
        return new Hello("fallback");
    }
}

如果想要在测试的时候触发降级操作,可以使用fallbackFactory

@FeignClient(name = "hello", fallbackFactory = HystrixClientFallbackFactory.class)
protected interface HystrixClient {
    @RequestMapping(method = RequestMethod.GET, value = "/hello")
    Hello iFailSometimes();
}

@Component
static class HystrixClientFallbackFactory implements FallbackFactory<HystrixClient> {
    @Override
    public HystrixClient create(Throwable cause) {
        return new HystrixClientWithFallBackFactory() {
            @Override
            public Hello iFailSometimes() {
                return new Hello("fallback; reason was: " + cause.getMessage());
            }
        };
    }
}

Note:There is a limitation with the implementation of fallbacks in Feign and how Hystrix fallbacks work.

Fallbacks are currently not supported for methods that return com.netflix.hystrix.HystrixCommand and rx.Observable.

通过接口实现的方式去实现降级存在一些局限性。
Fallbacks 当前不支持 在方法中返回com.netflix.hystrix.HystrixCommand and rx.Observable.的情况。

Feign and @Primary

When using Feign with Hystrix fallbacks, there are multiple beans in the ApplicationContext of the same type. This will cause @Autowired to not work because there isn’t exactly one bean, or one marked as primary. To work around this, Spring Cloud Netflix marks all Feign instances as @Primary, so Spring Framework will know which bean to inject. In some cases, this may not be desirable. To turn off this behavior set the primary attribute of @FeignClient to false.

@FeignClient(name = "hello", primary = false)
public interface HelloClient {
    // methods here
}

Feign request/response compression

You may consider enabling the request or response GZIP compression for your Feign requests. You can do this by enabling one of the properties:

feign.compression.request.enabled=true
feign.compression.response.enabled=true
Feign request compression gives you settings similar to what you may set for your web server:
feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048

These properties allow you to be selective about the compressed media types and minimum request threshold length.

Feign logging

A logger is created for each Feign client created. By default the name of the logger is the full class name of the interface used to create the Feign client. Feign logging only responds to the DEBUG level.

application.yml

logging.level.project.user.UserClient: DEBUG

The Logger.Level object that you may configure per client, tells Feign how much to log. Choices are:

  • NONE, No logging (DEFAULT).
  • BASIC, Log only the request method and URL and the response status code and execution time.
  • HEADERS, Log the basic information along with request and response headers.
  • FULL, Log the headers, body, and metadata for both requests and responses.

For example, the following would set the Logger.Level to FULL:

@Configuration
public class FooConfiguration {
    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}

参考网站

 

Feign-github

The Netflix stack, using Spring Boot - Part 3: Feign

http://cloud.spring.io/spring-cloud-netflix/spring-cloud-netflix.html#spring-cloud-feign

 

来源:  http://www.paraller.com