SpringCloud 入门系列【5】使用Feign 实现声明式服务调用

一、Spring Cloud Feign概念引入
通过前面的随笔,我们了解如何通过Spring Cloud ribbon进行负责均衡,如何通过Spring Cloud Hystrix进行服务断路保护,
两者作为基础工具类框架应用在各种基础设施类微服务和业务类微服务中,并且成对存在,那么有没有更高层的封装,将两者的使用
进一步简化呢? 有! 他就是Spring Cloud Feign。它基于Netflix Feign实现,整合了Spring Cloud Ribbon和Spring Cloud Hystrix,
除了提供两者强大的功能外,还提供了一种声明式的Web服务客户端定义方式。

二、入门实例
我们还是继续使用前面随笔中的hello-service服务,这里通过Spring Cloud Feign提供的声明式服务绑定功能来实现对服务接口的调用。
我们需要新建一个feign-consumer来代替之前的hello-consumer

先给出代码结构:

SpringCloud 入门系列【5】使用Feign 实现声明式服务调用

代码实现:

1、新建maven工程(feign-consumer)

2、修改pom文件,引入eureka和feign依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<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.sam</groupId>
  <artifactId>feign-consumer</artifactId>
  <version>0.0.1-SNAPSHOT</version>
 
  <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.1.RELEASE</version>
    </parent>
 
    <properties>
        <javaVersion>1.8</javaVersion>
    </properties>
 
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Camden.SR6</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
 
    </dependencyManagement>
 
    <dependencies>
        <!-- 引入eureka 客户端依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <!-- 引入feign 依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
        </dependency>
 
    </dependencies>
 
</project>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>

<properties>
<javaVersion>1.8</javaVersion>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>

</dependencyManagement>

<dependencies>
<!-- 引入eureka 客户端依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!-- 引入feign 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>

</dependencies>

</project>

3、新建启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * 通过@EnableFeignClients来开启spring cloud feign的支持功能
 *
 */
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class FeiApp {
 
    public static void main(String[] args) {
        SpringApplication.run(FeiApp.class, args);
    }
 
}

public static void main(String[] args) {
SpringApplication.run(FeiApp.class, args);
}

}

4、新建service接口

1
2
3
4
5
6
7
8
9
10
11
/**
 * 通过@FeignClient注解指定服务名来绑定服务,这里的服务名字不区分大小写
 * 然后再通过@RequestMapping来绑定服务下的rest接口
 *
 */
@FeignClient(name="hello-service")
public interface FeignConsumerService{
 
    @RequestMapping("/hello")
    public void hello();
}

@RequestMapping("/hello")
public void hello();
}

5、新建controller

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
public class FeiConsumerController {
 
    @Autowired
    FeignConsumerService consumerService;
 
    @RequestMapping("feign-consumer")
    public String feignConsumer() {
        consumerService.hello();
        return "feign consumer call finished!!!";
    }
 
}

@Autowired
FeignConsumerService consumerService;

@RequestMapping("feign-consumer")
public String feignConsumer() {
consumerService.hello();
return "feign consumer call finished!!!";
}

}

6、新建application.properties

1
2
3
4
5
server.port=9001
 
spring.application.name=feign-consumer
 
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka

spring.application.name=feign-consumer

eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka

7、测试,

启动服务注册中心eureka、启动两个hello-service(端口号分别为9090和9091),启动feign-consumer

SpringCloud 入门系列【5】使用Feign 实现声明式服务调用访问http://localhost:9001/feign-consumer

SpringCloud 入门系列【5】使用Feign 实现声明式服务调用

并且多次访问的话,会轮询调用两个hello-service服务。

三、参数绑定
在上面的例子中,我们实现的只是一个不带参数的rest服务绑定,然而现实的业务中不会这么简单,往往会有各种参数,
这个时候我们做如下事情:

  1. 如果服务提供方有对象参数(如User对象),那么feign-consumer工程中需要建一个路径和类名完全一样的类。
  2. 然后将服务提供方controller里面的所有方法声明进行copy(包括前面的@RequestMapping),粘贴到feign-consumer的service接口里面。

四、继承特性
根据上面参数绑定的做法,我们需要进行很多copy操作,这样比较麻烦,可以通过继承的方式进行简化。

这种实现步骤如下:

1.我们需要新建一个基础工程hello-service-api,

代码结构:

SpringCloud 入门系列【5】使用Feign 实现声明式服务调用

代码实现:

1、新建maven项目hello-service-api

2、修改pom文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<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.sam</groupId>
  <artifactId>hello-service-api</artifactId>
  <version>0.0.1-SNAPSHOT</version>
 
  <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.1.RELEASE</version>
    </parent>
 
    <properties>
        <javaVersion>1.8</javaVersion>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
 
    </dependencies>
</project>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>

<properties>
<javaVersion>1.8</javaVersion>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

</dependencies>
</project>

你会发现其实就是一个普通的spring boot项目。

3、考虑到需要掩饰参数中有对象的情况,我们加个User类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.sam.entity;
 
public class User {
 
    private String name;
    private Integer age;
 
 
    public User(String name, Integer age) {
        super();
        this.name = name;
        this.age = age;
    }
 
 
    public User() {
        super();
    }
 
 
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public Integer getAge() {
        return age;
    }
 
    public void setAge(Integer age) {
        this.age = age;
    }
 
}

public class User {

private String name;
private Integer age;

public User(String name, Integer age) {
super();
this.name = name;
this.age = age;
}

public User() {
super();
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

}

User必须要有一个无参数的构造器。

4、新建service接口

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
 * 为了同前面那个hello 接口区分开了,我们加了refactor前缀
 *
 */
@RequestMapping("/refactor")
public interface HelloService {
 
    @RequestMapping("/hello2")
    public String hello2();
 
    @RequestMapping("/hello3")
    public User printUser(@RequestBody User user);
}

@RequestMapping("/hello2")
public String hello2();

@RequestMapping("/hello3")
public User printUser(@RequestBody User user);
}

2.重构hello-sevice服务

1、修改pom文件

1
2
3
4
5
6
<!-- 引入 hello-service-api的依赖,以继承其提供的接口 -->
        <dependency>
            <groupId>com.sam</groupId>
            <artifactId>hello-service-api</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

2、HelloController implements HelloService,并实现interface中的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@RestController
public class HelloController implements HelloService{
 
    Logger logger = LoggerFactory.getLogger(HelloController.class);
 
    @Autowired
    DiscoveryClient discoveryClient;
 
    @RequestMapping("/hello")
    public String hello() throws Exception {
        ServiceInstance instance = discoveryClient.getLocalServiceInstance();
        //打印服务的服务id
        logger.info("*********" + instance.getServiceId());
        return "hello,this is hello-service";
    }
 
    @Override
    public String hello2() {
        return "hello,this is hello2-service";
 
    }
 
    @Override
    public User printUser(@RequestBody User user) {
        return user;
    }
 
}

Logger logger = LoggerFactory.getLogger(HelloController.class);

@Autowired
DiscoveryClient discoveryClient;

@RequestMapping("/hello")
public String hello() throws Exception {
ServiceInstance instance = discoveryClient.getLocalServiceInstance();
//打印服务的服务id
logger.info("*********" + instance.getServiceId());
return "hello,this is hello-service";
}

@Override
public String hello2() {
return "hello,this is hello2-service";

}

@Override
public User printUser(@RequestBody User user) {
return user;
}

}

controller实现接口的方法时,不需要@RequestMapping注解,只需要类注解@RestController即可。

3.重构feign-consumer服务

1、修改POM文件

1
2
3
4
5
6
<!-- 引入 hello-service-api的依赖,以继承其提供的接口 -->
        <dependency>
            <groupId>com.sam</groupId>
            <artifactId>hello-service-api</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

2、让FeignConsumerService extends hello-service-api中的HelloService

1
2
3
4
5
6
7
8
9
10
11
/**
 * 通过@FeignClient注解指定服务名来绑定服务,这里的服务名字不区分大小写
 * 然后再通过@RequestMapping来绑定服务下的rest接口
 *
 */
@FeignClient(name="hello-service")
public interface FeignConsumerService extends HelloService{
 
    @RequestMapping("/hello")
    public void hello();
}

@RequestMapping("/hello")
public void hello();
}

只需要继承即可。

3、修改controller,追加方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RestController
public class FeiConsumerController {
 
    @Autowired
    FeignConsumerService consumerService;
 
    @RequestMapping("feign-consumer")
    public String feignConsumer() {
        consumerService.hello();
        return "feign consumer call finished!!!";
    }
 
    @RequestMapping("feign-consumer-user")
    public User feignConsumer2(User user) {
        consumerService.hello2();
        return consumerService.printUser(user);
    }
}

@Autowired
FeignConsumerService consumerService;

@RequestMapping("feign-consumer")
public String feignConsumer() {
consumerService.hello();
return "feign consumer call finished!!!";
}

@RequestMapping("feign-consumer-user")
public User feignConsumer2(User user) {
consumerService.hello2();
return consumerService.printUser(user);
}
}

4.测试

SpringCloud 入门系列【5】使用Feign 实现声明式服务调用

五、其他
由于Spring Cloud Feign是通过ribbon和hystrix实现具体功能的,因此可以直接通过配置这两个来实现功能

1.ribbon配置方式:
通过ribbon.<key>=<value>的方式进行全局配置,比如
ribbon.ConnectTimeout=500
ribbon.ReadTimeout=5000
通过<client>.ribbon.<key>=<value>的方式进行指定服务配置,比如
#这里的<client>为@FeignClient(value="hello-service")指定的服务名
hello-service.ribbon.ConnectTimeout=500
hello-service.ribbon.ReadTimeout=500

2.hystrix配置方式:
通过hystrix.command.default.xxx进行全局配置
如:hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
通过hystrix.command.<commandKey>.xxx进行指定配置,这里的<commandKey>可以为方法名
如:hystrix.command.hello.execution.isolation.thread.timeoutInMilliseconds=5000

3.请求压缩配置,支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。
feign.compression.request.enabled=true;

  feigan.compression.response.enabled=true;

4.日志配置
Spring Cloud Feign在构建被@FeignClient注解修饰的服务客户端是,会为每一个客户端都创建一个feign.Logger实例,我们可以利用该日志对象进行Log分析。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

关注我们