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.