SpringCloud OpenFeign(服务调用)

1.定义

Feign是一个声明式的Web服务客户端,是面向接口编程的。也就是说使用Feign,只需要创建一个接口并使用注解方式配置它,就可以完成对微服务提供方的接口绑定。

OpenFeign对feign进行进一步的封装,添加了springmvc的一些功能,更加强大。

在使用RestTemplate时,每次调用服务都需要指定服务的具体路径,当在多个地方同时使用时要写多次,显得代码冗余也难以维护,而openfeign就可以避免这种操作。

2.项目实战

源码:https://github.com/zhongyushi-git/cloud-open-feign-demo.git

2.1基础环境搭建

这里使用consul作为服务注册中心。

1)创建一个maven工程名为cloud-open-feign-demo,删除src目录

2)在pom中导入依赖,对SpringBoot和SpringCloud版本进行锁定

 <properties>
        <spring.boot.version>2.2.2.RELEASE</spring.boot.version>
        <spring.cloud.version>Hoxton.SR1</spring.cloud.version>
        <lombok.version>1.16.18</lombok.version>
    </properties>

    <!--  依赖管理,父工程锁定版本-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring.boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring.cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
           <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

2.2搭建公共模块

1)新建maven子模块(common-api),导入依赖

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

2)创建实体对象

package com.zys.cloud.entity;

import lombok.Data;

@Data
public class User {

    private String name;

    private String username;

    private Integer age;
}

2.3搭建服务提供者

1)新建maven子模块(cloud-provider8001),导入依赖

     <dependencies>
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>

2)新建启动类ProviderMain8001并添加注解

package com.zys.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

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

3)配置application.yml

server:
  port: 8001

spring:
  application:
    name: cloud-consul-provider
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}

4)新建controller接口

package com.zys.cloud.controller;

import com.zys.cloud.entity.User;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

@RestController
public class UserController {

    @Value("${server.port}")
    private String port;

    @GetMapping("/user/get")
    public String get() {
        return "我是服务提供者,端口:" + port;
    }

    @GetMapping("/user/getParam")
    public String getParam(@RequestParam("name") String name) {
        return "我是服务提供者,参数:" + name + ",端口:" + port;
    }

    @PostMapping("/user/postParam")
    public String postParam(@RequestParam("username") String username) {
        return "我是服务提供者,参数:" + username + ",端口:" + port;
    }

    @PostMapping("/user/add")
    public String addUser(@RequestBody User user) {
        return "我是服务提供者,参数:" + user.toString() + ",端口:" + port;
    }


}

这里的接口并没有在类上使用@RequestMapping注解,而是把接口路径都写在方法上面,那么在服务调用方进行映射时直接复制其方法名即可,不需要方法体。

5)启动服务,可看到已注册到consul。

6)根据服务提供者ProviderMain8001,再创建ProviderMain8002.

2.4搭建服务消费者

1)新建maven子模块(cloud-consumer80),导入依赖

    <dependencies>
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>com.zys.cloud</groupId>
            <artifactId>common-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

2)新建启动类ConsumerMain80并添加注解

package com.zys.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

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

要在启动类上启用Feign客户端。

3)配置application.yml

server:
  port: 80

spring:
  application:
    name: cloud-consul-consumer
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}

3)创建服务接口UserServiceClient,对于服务提供者接口。需要添加注解@FeiginClient,指定微服务的名称

package com.zys.cloud.service;

import com.zys.cloud.entity.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;

//指定微服务名称
@FeignClient(value = "cloud-consul-provider")
public interface UserServiceClient {

    @GetMapping("/user/get")
    String get();

    @GetMapping("/user/getParam")
    String getParam(@RequestParam("name") String name);

    @PostMapping("/user/postParam")
    String postParam(@RequestParam("username") String username);

    @PostMapping("/user/add")
    String addUser(@RequestBody User user);
}

注意:

  •  必须使用注解@FeignClient指定服务提供方的服务名称
  •  在编写接口映射时,可直接复制服务提供方的方法名等信息
  •  get请求传递参数时,要使用@RequestParam注解,其value是参数的名字,需与服务提供者端保持一致
  •  post请求传递参数时,当参数是对象时使用@RequestBody,当参数不是对象时需使用@RequestParam注解

4)创建controller接口,将UserServiceClient注入使用

package com.zys.cloud.controller;

import com.zys.cloud.entity.User;
import com.zys.cloud.service.UserServiceClient;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

@RestController
@RequestMapping("/consumer")
public class TestController {

    @Resource
    private UserServiceClient userServiceClient;

    @GetMapping("/get")
    public String get() {
        return userServiceClient.get();
    }

    @GetMapping("/param")
    public String getParam(String name) {
        return userServiceClient.getParam(name);
    }

    @PostMapping("/post")
    public String postParam(String username) {
        return userServiceClient.postParam(username);
    }

    @PostMapping("/add")
    public String addUser(@RequestBody User user) {
        return userServiceClient.addUser(user);
    }
}

5)启动测试。先启动服务提供者集群,再启动服务消费者,对四个接口进行测试,服务均可正常调用。

另外对其中一个接口进行多次刷新,会发现服务提供者集群是遵循轮询机制。原因是openfeignl默认已引入了Ribbon,可提供负载均衡策略。

2.5超时控制

为了体现服务快速响应的特点,Feign默认等待1秒,超过后报错。也就是说Feigin客户端只等待一秒,若服务端处理过程超过一秒,会导致客户端会出错,故需设置超时时间,避免出现这样的情况。

1)情景重现

为了看到这种效果,可在服务提供者的接口中设置线程阻塞,让其响应时间超过1S。这里以8001为例,修改UserController的get()方法:

    @GetMapping("/user/get")
    public String get() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "我是服务提供者,端口:" + port;
    }

截图:

重启后测试这个接口,消费者控制台报错:

2)配置超时时间

在客户端(服务消费者80)的yml进行配置即可。配置有两种方式:

1)方式一:设置Ribbon的负载超时时间

#设置ribbon的负载超时时间,单位都是ms
ribbon:
  #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
  ReadTimeout: 5000
  #指的是建立连接后从服务器读取到可用资源所用的时间
  ConnectTimeout: 5000

2)方式二:设置feign的超时时间

feign:
  client:
    config:
      #指定全局
      default:
        #连接超时时间
        connectTimeout: 5000
        #服务等待时间
        readTimeout: 5000

上述是配置全局的,也可对每个服务设置超时时间:

使用上述任意一种配置,配置后重启客户端后测试这个接口,服务正常调用,没有错误。

2.6日志打印

Feign对日志的处理非常灵活,可为每个Feign客户端指定日志记录策略。每个客户端都会创建一个Logger,默认情况下Feign的日志是Debug级别的,故不会显示。

2.6.1日志级别类型

NONE:默认的,不显示任何日志
BASIC:仅记录请求方法、URL、响应状态码及执行时间
HEADERS:BASIC信息以及请求和响应的头信息	
FULL:HEADERS信息以及请求和响应的正文和元数据

2.6.2配置日志

配置日志有两种方式,二选一即可。

1)第一种方式:使用配置类+yml配置

第一步:在客户端(服务消费者80)添加配置类LogConfig,指定日志的级别

package com.zys.cloud.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

//配置feign日志级别
@Configuration
public class LogConfig {
    @Bean
    Logger.Level feignLevel(){
        return Logger.Level.FULL;
    }
}

第二步:在yml配置指定调用的客户端的日志级别,必须是Debug级别

logging:
  level:
    com.zys.cloud.service.UserServiceClient: debug

2)第二种方式:全部使用yml配置

配置如下:


feign:
client:
config:
#指定全局
default:
loggerLevel: full

logging:
level:
com.zys.cloud.service.UserServiceClient: debug

当然,上述使用default指定了全局的级别,也可以对每个服务指定级别,把default换成服务名即可:

配置完成后重启客户端,调用四个接口的任意一个,在控制台可以看出多了很多的打印信息。

2.7文件上传

在使用openfeign进行文件上传时,需要特别注意,不能使用默认方式。

//import org.springframework.http.MediaType;

//文件上传
@PostMapping(value = "/user/fileUpload",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
String fileUpload(@RequestPart("file")MultipartFile file);

调用的关键代码如上。必须在请求中指定consumes的值是MediaType.MULTIPART_FORM_DATA_VALUE,另外传递的参数需要使用@RequestPart注解来修饰MultipartFile.

就是这么简单,你学废了吗?感觉有用的话,给笔者点个赞吧 !
原文地址:https://www.cnblogs.com/zys2019/p/12638166.html