Feign基礎入門及特性講解

轉載請註明出處 http://www.paraller.com
原文排版地址 點擊跳轉html

介紹

Feign是從Netflix中分離出來的輕量級項目,可以在類接口上添加註釋,成爲一個REST API 客戶端。java

Feign中對 Hystrix 有依賴關係。Feign只是一個便利的rest框架,簡化調用,最後仍是經過ribbon在註冊服務器中找到服務實例,而後對請求進行分配。react

實際項目

在入口程序中添加註釋git

@EnableFeignClients

REST API 客戶端github

@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屬性web

@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.xmlspring

<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的代碼,而後封裝成對象返回json

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 HystrixCommanapi

基礎用法服務器

@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

一、Hystrix 在 classpath中且feign.hystrix.enabled=true, Feign 包裹的的全部方法都會自帶斷路器(circuit breaker)

二、在方法中返回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

相關文章
相關標籤/搜索