文章首发:Spring Cloud OpenFeign入门和实战

OpenFeign是什么

Feign是一个申明式的Web Service客户端,是一种申明式、模板化的HTTP客户端。而OpenFeign是Spring Cloud 在Feign的根底上反对了Spring MVC的注解,如@RequesMapping等等。
OpenFeign的@FeignClient能够解析SpringMVC的@RequestMapping注解下的接口,并通过动静代理的形式产生实现类,实现类中做负载平衡并调用其余服务。
Feign能够把Rest的申请进行暗藏,伪装成相似SpringMVC的Controller一样。你不必再本人拼接url,拼接参数等等操作,所有都交给Feign去做。

  1. 可插拔的注解反对,包含Feign注解和JSX-RS注解
  2. 反对可插拔的HTTP编码器和解码器
  3. 反对Hystrix和它的Fallback
  4. 反对Ribbon的负载平衡
  5. 反对HTTP申请和响应的压缩。

OpenFeign入门

创立父Pom工程:cloud-openfeign-practice

此工程用于寄存所有对于openfeign的示例。

pom.xml

<?xml version="1.0" encoding="UTF-8"?><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 http://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <groupId>com.msr.better</groupId>    <artifactId>cloud-openfeign-practice</artifactId>    <version>1.0</version>    <packaging>pom</packaging>    <parent>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-parent</artifactId>        <version>2.2.3.RELEASE</version>        <relativePath/> <!-- lookup parent from repository -->    </parent>    <properties>        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>        <java.version>1.8</java.version>        <spring.cloud-version>Hoxton.SR3</spring.cloud-version>    </properties>    <dependencyManagement>        <dependencies>            <dependency>                <groupId>org.springframework.cloud</groupId>                <artifactId>spring-cloud-dependencies</artifactId>                <version>${spring.cloud-version}</version>                <type>pom</type>                <scope>import</scope>            </dependency>        </dependencies>    </dependencyManagement>    <dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-actuator</artifactId>        </dependency>    </dependencies>    <build>        <plugins>            <plugin>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-maven-plugin</artifactId>            </plugin>        </plugins>    </build></project>

创立模块:cloud-openfeign-hehllo

pom.xml

    <dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-openfeign</artifactId>        </dependency>    </dependencies>

配置文件

application.xml

server:  port: 8010spring:  application:    name: openfeign-hello# 日志logging:  level:    com.msr.better.feign.service.HelloFeignService: debug

配置类

@Configurationpublic class HelloFeignServiceConfig {    /**     * Logger.Level 的具体级别如下:     * NONE:不记录任何信息     * BASIC:仅记录申请办法、URL以及响应状态码和执行工夫     * HEADERS:除了记录 BASIC级别的信息外,还会记录申请和响应的头信息     * FULL:记录所有申请与响应的明细,包含头信息、申请体、元数据     */    @Bean    Logger.Level feignLoggerLevel() {        return Logger.Level.FULL;    }}

serivce

@FeignClient(name = "github-client", url = "https://api.github.com", configuration = HelloFeignServiceConfig.class)public interface HelloFeignService {    /**     * content:     * {     *  "message":"Validation Failed",     *  "errors":[{"resource":"Search","field":"q","code":"missing"}],     *  "documentation_url":"https://developer.github.com/v3/search"     *  }     *     * @param queryStr     * @return     */    @GetMapping(value = "/search/repositories")    String searchRepo(@RequestParam("q") String queryStr);}

在下面的HelloFeignService中通过@FeignClient注解手动指定了该接口要拜访的URL(https://api.github.com),调用...

controller

@RestControllerpublic class HelloFeignController {    @Autowired    private HelloFeignService helloFeignService;    @GetMapping(value = "/search/github")    public String searchGithubRepoByStr(@RequestParam("searchStr") String searchStr) {        return helloFeignService.searchRepo(searchStr);    }}

启动类

@SpringBootApplication@EnableFeignClientspublic class OpenFeignHelloApplication {    public static void main(String[] args) {        SpringApplication.run(OpenFeignHelloApplication.class, args);    }}

@EnableFeignClients包扫描时,扫描所有@FeignClient。

启动测试

运行启动类之后,在浏览器或者PostMan之类的工具拜访http://localhost:8010/search/...

OpenFeign工作原理

  • 增加@EnableFeignClients注解开启对@FeignClient注解的扫描加载解决。依据Feign Client的开发标准,定义接口并增加@FeiginClient注解
  • 当程序启动之后,会进行包扫描,扫描所有@FeignClient注解的接口,并将这些信息注入到IOC容器中。当定义的Feign接口被调用时,通过JDK的代理的形式生成具体的RequestTemplate。Feign会为每个接口办法创立一个RequestTemplate对象。该对象封装了HTTP申请须要的所有信息,例如申请参数名、申请办法等信息。
  • 而后由RequestTemplate生成Request,把Request交给Client去解决,这里的Client能够是JDK原生的URLConnection、HttpClient或Okhttp。最初Client被封装到LoadBalanceClient类,看这个类的名字既能够晓得是联合Ribbon负载平衡发动服务之间的调用,因为在OpenFeign中默认是曾经整合了Ribbon了。

OpenFiegn的根底性能

分析@FeignClient注解

@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface FeignClient {...}

从FeignClient的注解能够看得出,ElementType.TYPE阐明FeignClient的作用指标是接口。其罕用的属性如下:

  • name:执行FeignClient的名称,如果我的项目中应用Ribbon,name属性会作为微服务的名称,用作服务发现。
  • url:url个别用于调试,能够手动指定@FeignClient调用的地址
  • decode404:当产生404谬误时,如果该字段为true,会调用decoder进行解码,否则抛出FeignException。
  • configuration:Feigin配置类,可自定义Feign的Encode,Decode,LogLevel,Contract。
  • fallback:定义容错的类,当近程调用的接口失败或者超时的时候,会调用对应接口的容错罗杰,fallback执行的类必须实现@FeignClient标记的接口。在OpenFeign的依赖中能够发现,集成Hystrix。
  • fallbackFactory:工厂类,用于生成fallback类实例,通过此属性能够实现每个接口通用的容错逻辑,以达到缩小反复的代码。
  • path:定义以后FeignClient的对立前缀。

OpenFeign开始GZIP压缩

OpenFeign反对对申请和响应进行GZIP压缩,以此来提供通信效率。只需在配置文件中配置即可,比较简单。

server:  port: 8011spring:  application:    name: openfeign-gziplogging:  level:    com.msr.better.feign.service.HelloFeignService: debugfeign:  # 压缩配置  compression:    request:      enabled: true      # 配置压缩反对的MIME TYPE      mime-types: text/xml,application/xml,application/json      min-request-size: 2048  # 配置压缩数据大小的上限    response:      enabled: true # 配置响应GZIP压缩

等价的properties配置

feign.compression.request.enabled=true# 配置压缩反对的MIME TYPEfeign.compression.request.mime-types=text/xml,application/xml,application/json# 配置压缩数据大小的上限feign.compression.request.min-request-size=2048# 配置响应GZIP压缩feign.compression.response.enabled=true

反对属性文件配置

对单个特定名称的FeignClient进行配置

@FeignClientde的配置信息能够通过配置文件的形式来配置

server:  port: 8011spring:  application:    name: openfeign-gziplogging:  level:    com.msr.better.feign.service.HelloFeignService: debugfeign:  # 压缩配置  compression:    request:      enabled: true      # 配置压缩反对的MIME TYPE      mime-types: text/xml,application/xml,application/json      min-request-size: 2048  # 配置压缩数据大小的上限    response:      enabled: true # 配置响应GZIP压缩  client:    config:      # 须要配置的FeignName      github-client:        # 连贯超时工夫        connectTimout: 5000        # 读超时工夫        readTimeut: 5000        # Feign的日志级别        loggerLevel: full        # Feign的谬误解码器        errorDecode: com.example.SimpleErrorDecoder        # 设置重试        retryer: com.example.SimpleRetryer        # 拦挡前        requestInterceptors:          - com.example.FirstInterceptor          - com.example.SecondInterceptor        decode404: false        # Feign的编码器        encoder: com.example.SimpleEncoder        # Feign的解码器        decoder: com.example.SimpleDecoder        # Feign的contract配置        contract: com.example.SimpleContract

作用于所有FeignClient的配置

在@EnableFeignClients注解上有一个defaultConfiguration属性,能够将默认设置写成一个配置类,例如这个类叫做DefaultFeignClientConfiguration。

@SpringBootApplication@EnableFeignClients(defaultConfiguration = DefaultFeignClientConfiguration.class)public class FeignClientConfigApplication{    SpringApplication.run(FeignClientConfigApplication.class, args);}

同时也能够在配置文件中配置

feign:  client:    config:      default:        # 连贯超时工夫        connectTimout: 5000        # 读超时工夫        readTimeut: 5000        # Feign的日志级别        loggerLevel: full        ...

然而如果以上两种形式(在配置文件和在注解中配置FeignClient的全局配置),最初配置文件会笼罩注解上执行配置类的形式。然而能够在配置文件中增加feign.client.default-to-properties=false来扭转Feigin配置的优先级。

FeignClient开启日志

其实在下面的就曾经是配置了FeignClient的日志了。Feign为每一个Feign都提供了一个fegin.Logger实例。能够在配置中开启日志输入,开启的步骤也很简略。

第一步:在配置文件中配置日志输入

logging:  level:    # 指定那个FeignClient接口的申请须要输入日志,以及日志级别    com.msr.better.feign.service.HelloFeignService: debug

第二步:通过Java代码的形式在主程序入口配置日志Bean

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

又或者通过配置类配置,并在@FeignClient注解中执行改配置类。

@Configurationpublic class HelloFeignServiceConfig {    /**     * Logger.Level 的具体级别如下:     * NONE:不记录任何信息     * BASIC:仅记录申请办法、URL以及响应状态码和执行工夫     * HEADERS:除了记录 BASIC级别的信息外,还会记录申请和响应的头信息     * FULL:记录所有申请与响应的明细,包含头信息、申请体、元数据     */    @Bean    Logger.Level feignLoggerLevel() {        return Logger.Level.FULL;    }}

FeignClient超时配置

Feign的调用分为两层,Ribbon的调用和Hystrix的调用。然而高版本的Hystrix默认是敞开的。个别呈现想这样的异样:Read timed out executing POST http://***,是由Ribbon引起,这样能够适当得调大一下Ribbon的超时工夫

ribbon:  ConnectTimeout: 2000  ReadTimeout: 5000

HystrixRuntimeException: XXX timed -out and no fallback available .这就是Hystrix的超时报错

feign:  hystrix:    enabled: true# 设置hystrix超时工夫hystrix:  shareSecurityContext: true  command:    default:      circuitBreaker:        sleepWindowinMilliseconds: 10000        forceClosed: true      execution:        isolation:          thread:            timeoutinMilliseconds: 10000

OpenFeign实战

替换默认的Client

Feign默认是应用JDK原生的URLConnection发送HTTP申请,没有连接池,然而对每个地址会放弃一个长连贯,就是利用HTTP的persistence connection.。这样能够应用其余优良的Client去替换。这样能够设置连接池,超时工夫等对服务之间的调用调优。上面介绍应用Http Client和Okhttp替换Feign默认的Client。步骤也很简略。

应用Http Client替换默认的Client

pom.xml

    <dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <!-- Spring Cloud OpenFeign的Starter的依赖 -->        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-openfeign</artifactId>        </dependency>        <!-- 应用Apache HttpClient替换Feign原生httpclient -->        <dependency>            <groupId>org.apache.httpcomponents</groupId>            <artifactId>httpclient</artifactId>        </dependency>        <dependency>            <groupId>io.github.openfeign</groupId>            <artifactId>feign-httpclient</artifactId>        </dependency>    </dependencies>

application.yml

server:  port: 8010spring:  application:    name: openfeign-httpclientfeign:  httpclient:    enabled: true

对于Http Client的一些配置也是能够在配置文件中配置的

org.springframework.cloud.openfeign.clientconfig.HttpClientFeignConfiguration中是对于HttpClient的配置:

@Configuration(    proxyBeanMethods = false)@ConditionalOnMissingBean({CloseableHttpClient.class})public class HttpClientFeignConfiguration {    private final Timer connectionManagerTimer = new Timer("FeignApacheHttpClientConfiguration.connectionManagerTimer", true);    private CloseableHttpClient httpClient;    @Autowired(        required = false    )    private RegistryBuilder registryBuilder;    public HttpClientFeignConfiguration() {    }    @Bean    @ConditionalOnMissingBean({HttpClientConnectionManager.class})    public HttpClientConnectionManager connectionManager(ApacheHttpClientConnectionManagerFactory connectionManagerFactory, FeignHttpClientProperties httpClientProperties) {        final HttpClientConnectionManager connectionManager = connectionManagerFactory.newConnectionManager(httpClientProperties.isDisableSslValidation(), httpClientProperties.getMaxConnections(), httpClientProperties.getMaxConnectionsPerRoute(), httpClientProperties.getTimeToLive(), httpClientProperties.getTimeToLiveUnit(), this.registryBuilder);        this.connectionManagerTimer.schedule(new TimerTask() {            public void run() {                connectionManager.closeExpiredConnections();            }        }, 30000L, (long)httpClientProperties.getConnectionTimerRepeat());        return connectionManager;    }    @Bean    @ConditionalOnProperty(        value = {"feign.compression.response.enabled"},        havingValue = "true"    )    public CloseableHttpClient customHttpClient(HttpClientConnectionManager httpClientConnectionManager, FeignHttpClientProperties httpClientProperties) {        HttpClientBuilder builder = HttpClientBuilder.create().disableCookieManagement().useSystemProperties();        this.httpClient = this.createClient(builder, httpClientConnectionManager, httpClientProperties);        return this.httpClient;    }    @Bean    @ConditionalOnProperty(        value = {"feign.compression.response.enabled"},        havingValue = "false",        matchIfMissing = true    )    public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory, HttpClientConnectionManager httpClientConnectionManager, FeignHttpClientProperties httpClientProperties) {        this.httpClient = this.createClient(httpClientFactory.createBuilder(), httpClientConnectionManager, httpClientProperties);        return this.httpClient;    }    private CloseableHttpClient createClient(HttpClientBuilder builder, HttpClientConnectionManager httpClientConnectionManager, FeignHttpClientProperties httpClientProperties) {        RequestConfig defaultRequestConfig = RequestConfig.custom().setConnectTimeout(httpClientProperties.getConnectionTimeout()).setRedirectsEnabled(httpClientProperties.isFollowRedirects()).build();        CloseableHttpClient httpClient = builder.setDefaultRequestConfig(defaultRequestConfig).setConnectionManager(httpClientConnectionManager).build();        return httpClient;    }    @PreDestroy    public void destroy() throws Exception {        this.connectionManagerTimer.cancel();        if (this.httpClient != null) {            this.httpClient.close();        }    }}

很显著当没有CloseableHttpClient这个bean的时候,就是会由这个类来生成Http Client的默认配置。所以说对于HttpClient的自定义配置能够通过本人注入CloseableHttpClient。还有HttpClientConnectionManager治理连贯的bean。其实OpenFeign对HttpClient的反对很好,因为它的一些属性能够在配置文件中配置。

应用Okhttp替换默认的Client

其实和Http Client一样的配置,也是在配置文件中开启

pom.xml

    <dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <!-- Spring Cloud OpenFeign的Starter的依赖 -->        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-openfeign</artifactId>        </dependency>        <dependency>            <groupId>io.github.openfeign</groupId>            <artifactId>feign-okhttp</artifactId>        </dependency>    </dependencies>

application.yml

server:  port: 8011spring:  application:    name: openfeign-okhttpfeign:  okhttp:    enabled: true# 日志logging:  level:    com.msr.better.feign.service.HelloFeignService: debug

这样开启之后,Client就被替换了。同理在org.springframework.cloud.openfeign.clientconfig包下,也有一个对于Okhttp的配置类。

@Configuration(    proxyBeanMethods = false)@ConditionalOnMissingBean({OkHttpClient.class})public class OkHttpFeignConfiguration {    private OkHttpClient okHttpClient;    public OkHttpFeignConfiguration() {    }    @Bean    @ConditionalOnMissingBean({ConnectionPool.class})    public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties, OkHttpClientConnectionPoolFactory connectionPoolFactory) {        Integer maxTotalConnections = httpClientProperties.getMaxConnections();        Long timeToLive = httpClientProperties.getTimeToLive();        TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();        return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);    }    @Bean    public OkHttpClient client(OkHttpClientFactory httpClientFactory, ConnectionPool connectionPool, FeignHttpClientProperties httpClientProperties) {        Boolean followRedirects = httpClientProperties.isFollowRedirects();        Integer connectTimeout = httpClientProperties.getConnectionTimeout();        this.okHttpClient = httpClientFactory.createBuilder(httpClientProperties.isDisableSslValidation()).connectTimeout((long)connectTimeout, TimeUnit.MILLISECONDS).followRedirects(followRedirects).connectionPool(connectionPool).build();        return this.okHttpClient;    }    @PreDestroy    public void destroy() {        if (this.okHttpClient != null) {            this.okHttpClient.dispatcher().executorService().shutdown();            this.okHttpClient.connectionPool().evictAll();        }    }}

很显著OkHttpClient是外围性能执行的类。因为OpenFeign中有一个类FeignHttpClientProperties,有了这个类对于HttpClient的属性就能够在配置文件中设置了。然而Okhttp没有这一个相似的类,所以个别能够本人注入一个OkHttpClient去设置这些属性

@Configuration@ConditionalOnClass(Feign.class)@AutoConfigureBefore(FeignAutoConfiguration.class)public class OkHttpConfig {    @Bean    public okhttp3.OkHttpClient okHttpClient() {        return new okhttp3.OkHttpClient.Builder()                //设置连贯超时                .connectTimeout(60, TimeUnit.SECONDS)                //设置读超时                .readTimeout(60, TimeUnit.SECONDS)                //设置写超时                .writeTimeout(60, TimeUnit.SECONDS)                //是否主动重连                .retryOnConnectionFailure(true)                .connectionPool(new ConnectionPool())                //构建OkHttpClient对象                .build();    }}

对于自定义OkHttpClient的配置,能够参考OpenFeign里OkHttpFeignConfiguration的配置,例如ConnectionPool这个bean。

Post和Get的多参数传递

在应用OpenFeign实现服务之间的调用时,很多时候是要传递多个参数。

创立cloud-openfeign-eureka-server模块

Eureka Server注册核心

    <dependencies>        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-actuator</artifactId>        </dependency>        <!-- springboot web -->        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>            <exclusions>                <exclusion>                    <artifactId>spring-boot-starter-tomcat</artifactId>                    <groupId>org.springframework.boot</groupId>                </exclusion>            </exclusions>        </dependency>        <!--不必Tomcat,应用undertow -->        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-undertow</artifactId>        </dependency>        <dependency>            <groupId>io.undertow</groupId>            <artifactId>undertow-servlet</artifactId>        </dependency>    </dependencies>

配置文件application.yml

server:  port: 8761eureka:  instance:    hostname: localhost  server :    enable-self-preservation: false  client:    registerWithEureka: false    fetchRegistry: false    serviceUrl:      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

启动类

@SpringBootApplication@EnableEurekaServerpublic class EurekaApplication {    public static void main(String[] args) {        SpringApplication.run(EurekaApplication.class, args);    }}

创立cloud-openfeign-provider模块

服务提提供者

    <dependencies>        <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>        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-openfeign</artifactId>        </dependency>    </dependencies>

配置文件application.yml

server:  port: 8012spring:  application:    name: openfeign-providereureka:  client:    serviceUrl:      defaultZone: http://localhost:8761/eureka/  #eureka.instance.prefer-ip-address  示意将本人的IP注册到Eureka Server上,  #如果不配置,会将以后服务提供者所在的主机名注册到Eureka Server上。  instance:    prefer-ip-address: true

实体类和控制器

public class Order {    private Long id;    private String name;    private int age;    public Long getId() {        return id;    }    public void setId(Long id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }}
@RestController@RequestMapping("/order")public class OrderController {    @GetMapping(value = "/add")    public String addUser(Order order, HttpServletRequest request) {        String token = request.getHeader("oauthToken");        return "hello," + order.getName();    }    @PostMapping(value = "/update")    public String updateUser(@RequestBody Order order) {        return "hello," + order.getName();    }}

启动类

@SpringBootApplication@EnableDiscoveryClientpublic class ProviderApplication {    public static void main(String[] args) {        SpringApplication.run(ProviderApplication.class, args);    }}

创立cloud-openfeign-consumer模块

消费者服务

<dependencies>        <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>        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-openfeign</artifactId>        </dependency>        <!-- 应用Apache HttpClient替换Feign原生httpclient -->        <dependency>            <groupId>org.apache.httpcomponents</groupId>            <artifactId>httpclient</artifactId>        </dependency>        <dependency>            <groupId>io.github.openfeign</groupId>            <artifactId>feign-httpclient</artifactId>        </dependency>    </dependencies>

配置文件application.yml

server:  port: 8011spring:  application:    name: openfeign-consumereureka:  client:    service-url:      defaultZone: http://localhost:8761/eurekafeign:  httpclient:    enabled: true

实体类

package com.msr.better.feign.model;public class Order {    private Long id;    private String name;    private int nums;    // 此处省略了getter和setter}

FeignClient接口

@FeignClient("openfeign-provider")public interface OrderApiService {    @GetMapping(value = "/order/add")    String addUser(@SpringQueryMap Order order);    @PostMapping(value = "/order/update")    String updateUser(@RequestBody Order order);}

此处的Client接口中对于GET申请传递实体类应用了注解@SpringQueryMap。OpenFeign@QueryMap批注反对将POJO用作GET参数映射。然而默认的OpenFeign QueryMap正文与Spring不兼容,因为它短少value属性。

Spring Cloud OpenFeign提供了等效的@SpringQueryMap正文,该正文用于将POJO或Map参数正文为查问参数映射。

在一些材料中说什么OpenFeign的什么GET不能传递POJO,写了个拦截器把实体类转换了,预计是OpenFeign的版本低,在新的OpenFeign中是有了对QueryMap的反对了。

配置类

@Configurationpublic class CoreAutoConfiguration {    @Autowired    private HttpClient httpClient;    @Bean    public HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory() {        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();        factory.setHttpClient(httpClient);        factory.setReadTimeout(3000);        factory.setConnectTimeout(3000);        factory.setConnectionRequestTimeout(3000);        return factory;    }    /**     * {@link RestTemplate }的setRequestFactory办法反对HttpClient和Okhttp等Client     * 默认是应用{@link SimpleClientHttpRequestFactory } Http的申请是应用原生的URLConnection     *     * @return RestTemplate的bean     */    @LoadBalanced    @Bean    public RestTemplate restTemplate() {        RestTemplate restTemplate = new RestTemplate();        restTemplate.setRequestFactory(httpComponentsClientHttpRequestFactory());        return restTemplate;    }}

下面是替换了RestTemplate的Client。因为RestTemplate默认是应用URLConnection。这里是应用HttpClient替换了。

控制器

@RestController@RequestMapping("api")public class OrderController {    @Autowired    private OrderApiService orderApiService;    /**     * @param order     * @return     */    @PostMapping("/get/pojo")    public String getPojo(@RequestBody Order order) {        return orderApiService.addUser(order);    }    @PostMapping("/post/pojo")    String postPojo(@RequestBody Order order){        return orderApiService.updateUser(order);    }}

最初就能够测试http://localhost:8011/get/poj...

文件上传

持续应用上一节创立的Eureka Server。而后创立一下两个模块用作文件上传。

想要实现文件上传性能,须要编写Encoder去实现文件上传。当初OpenFeign提供了子项目feign-form(https://github.com/OpenFeign/...

创立cloud-openfeign-fileupload-server

文件上传接口的提供者

    <dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>        </dependency>    </dependencies>

配置文件application.yml

server:  port: 8012spring:  application:    name: openfeign-file-servereureka:  server:    enableSelfPreservation: false  client:    serviceUrl:      defaultZone: http://localhost:8761/eureka/  instance:    prefer-ip-address: true

启动类

@SpringBootApplication@EnableDiscoveryClientpublic class SCFeignFileServerApplication {    public static void main(String[] args) {        SpringApplication.run(SCFeignFileServerApplication.class, args);    }}

上传接口

@RestControllerpublic class FileController {    @PostMapping(value = "/uploadFile/server", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)    public String fileUploadServer(MultipartFile file ) throws Exception{        return file.getOriginalFilename();    }}

创立cloud-openfeign-fileupload-client

文件上传接口的调用者

    <dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>        </dependency>        <!-- Spring Cloud OpenFeign的Starter的依赖 -->        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-openfeign</artifactId>        </dependency>        <!-- Feign文件上传依赖-->        <dependency>            <groupId>io.github.openfeign.form</groupId>            <artifactId>feign-form</artifactId>            <version>3.8.0</version>        </dependency>        <dependency>            <groupId>io.github.openfeign.form</groupId>            <artifactId>feign-form-spring</artifactId>        </dependency>    </dependencies>

配置文件application.yml

server:  port: 8011spring:  application:    name: openfeign-upload-clienteureka:  client:    service-url:      defaultZone: http://localhost:8761/eureka

配置类

@Configurationpublic class FeignMultipartSupportConfig {    @Bean    @Primary    @Scope("prototype")    public Encoder multipartFormEncoder() {        return new SpringFormEncoder();    }}

控制器

@RestController@RequestMapping("file")public class FeignUploadController {    @Autowired    private FileUploadApiService fileUploadApiService;    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)    public String imageUpload(MultipartFile file) throws Exception {        return fileUploadApiService.fileUpload(file);    }}

FeignClient

@FeignClient(value = "openfeign-file-server", configuration = FeignMultipartSupportConfig.class)public interface FileUploadApiService {    /***     * 1.produces,consumes必填     * 2.留神辨别@RequestPart和RequestParam,不要将     * @RequestPart(value = "file") 写成@RequestParam(value = "file")     * @param file     * @return     */    @PostMapping(value = "/uploadFile/server",            produces = {MediaType.APPLICATION_JSON_UTF8_VALUE},            consumes = MediaType.MULTIPART_FORM_DATA_VALUE)    String fileUpload(@RequestPart(value = "file") MultipartFile file);}

测试

运行Eureka Server、cloud-openfeign-fileupload-client模块和cloud-openfeign-fileupload-server模块,应用PostMan进行测试。最初胜利返回文件的名字,文件胜利的上传到server上了。

解决首次申请失败问题

因为OpenFeign整合了Ribbon和Hystrix,可能会呈现首次调用失败的问题。

次要起因是:Hystrix默认的超时工夫是1秒,如果超过这个工夫没有响应,就会进入fallback代码。因为Bean的拆卸和懒加载的机制,Feign首次申请都会比较慢。如此一来当响应工夫大于1秒就会进入fallback而导致申请失败。解决办法:

  1. 将Hystrix的超时工夫调大,此办法比拟好

    hystrix:  command:    default:   execution:        isolation:          thread:            timeoutInMillseconds: 5000 # 5秒
  2. 禁用Hystrix的超时工夫

    hystrix:  command:    default:      execution:        timout:          enable: false
  3. 应用Feign的时候敞开Hystrix,这是不举荐的

    feign:  hystrix:    enable: false

返回图片流的解决形式

对于返回的是图片,个别都是字节数组。然而Contrller不能间接返回byte,所以被调用的API返回的类型应该应用Response。

应用下面的文件上传创立的模块中增加一个返回图片的接口。以生成一个二维码为例。

cloud-openfeign-fileupload-server的一些批改

增加新的依赖,应用hutool疾速生成二维码

        <dependency>            <groupId>cn.hutool</groupId>            <artifactId>hutool-all</artifactId>            <version>5.6.3</version>        </dependency>        <dependency>            <groupId>com.google.zxing</groupId>            <artifactId>core</artifactId>            <version>3.3.3</version>        </dependency>        <dependency>            <groupId>io.github.openfeign</groupId>            <artifactId>feign-core</artifactId>        </dependency>

controller的接口,这里仅简略的生成了一个二维码,二维码还能够增加更加多的信息。这里就不具体介绍,hutool的QrCodeUtil有很多办法,有趣味的能够自行钻研。

    @GetMapping(value = "/qrcode")    public byte[] image() {        return generateQrCode();    }    /**     * 先简略的生成一个url的二维码,指向百度     * @return     */    private byte[] generateQrCode() {        return QrCodeUtil.generatePng("https://www.baidu.cn/", 300, 300);    }

cloud-openfeign-fileupload-client的一些批改

增加新依赖

<dependency>    <groupId>commons-io</groupId>    <artifactId>commons-io</artifactId>    <version>2.6</version></dependency>

feignclient增加新接口

    @GetMapping("/qrcode")    Response getQrCode();

controller的批改,对于要在前端页面显示图片,个别用的最多的是返回页面一个url,然而这都是存储好的图片,然而每次生成验证码和二维码这些,服务端可能并不会存储起来。所以并不能返回一个url地址,对于验证码用的返回前端Base64编码。二维码的话能够基于HttpServletResponse,produces返回字节流和Base64图片。

这里应用HttpServletResponse,增加办法:

    @GetMapping("/qrcode")    public void getQrCode(HttpServletResponse response) {        Response res = fileUploadApiService.getQrCode();        try {            InputStream inputStream = res.body().asInputStream();            response.setContentType(MediaType.IMAGE_PNG_VALUE);            IOUtils.copy(inputStream,response.getOutputStream());        } catch (IOException e) {            e.printStackTrace();        }    }

浏览器拜访:http://localhost:8011/file/qr...

调用传递token

失常的来说,零碎都是有认证鉴权的性能,不论是JWT还是security,在内部申请到A服务时,是带有token过去的,然而此申请在A服务外部通过Feign调用B服务时,就会产生token的失落。

解决办法也是不难,就是在应用Feign近程调用时,在申请头里携带一下token,个别token是放在申请头外面。

Feign提供的拦截器RequestInterceptor,这样能够拦挡Feign的申请,在申请头里增加token。对于这部分代码,在cloud-openfeign-consumer和cloud-openfeign-provider上进行增加。

批改cloud-openfeign-provider

批改一下办法,便于展现后果

    @PostMapping(value = "/update")    public String updateOrder(@RequestBody Order order, HttpServletRequest request) {        String token = request.getHeader("token");        return "hello," + order.getName() + " " + "haha!I get a token: " + token;    }

批改cloud-openfeign-consumer

增加拦截器实现feign.RequestInterceptor

@Componentpublic class FeignTokenInterceptor implements RequestInterceptor {    @Override    public void apply(RequestTemplate requestTemplate) {        if (null == getHttpServletRequest()) {            //此处能够记录一些日志            return;        }        //将获取Token对应的值往下面传        requestTemplate.header("token", getHeaders(getHttpServletRequest()).get("token"));    }    private HttpServletRequest getHttpServletRequest() {        try {            return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();        } catch (Exception e) {            return null;        }    }    /**     * Feign拦截器拦挡申请获取Token对应的值     *     * @param request     * @return     */    private Map<String, String> getHeaders(HttpServletRequest request) {        Map<String, String> map = new LinkedHashMap<>();        Enumeration<String> enumeration = request.getHeaderNames();        while (enumeration.hasMoreElements()) {            String key = enumeration.nextElement();            String value = request.getHeader(key);            map.put(key, value);        }        return map;    }}

最初启动服务就能够开始测试了,测试后果:

总结

本文介绍了一些Feign的用法,后续如果有对于Feign新的货色将会新开文章述说。