OpenFeign 是Netflix
开发的声明式、模板化的http
请求客户端,作用和RestTemplate
差不多,只不过OpenFeign
可以更加便捷、优雅地调用http api
。
OpenFeign
可以将提供者提供的http
接口伪装为Java接口进行消费,消费者只需使用 接口 + 注解 的方式便可直接调用提供者提供的http
接口,而无需再使用RestTemplate
。
OpenFeign
与Feign
Spring Cloud
Dalston 版及之前的版本使用的是 Feign
,而该项目现已更新为了 OpenFeign
,新版本中的依赖也发生了变化。
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
Feign
本身不支持Spring MVC
的注解,它有一套自己的注解;OpenFeign
是Spring Cloud
在Feign
的基础上支持了Spring MVC
的注解,如@RequesMapping
等等。 OpenFeign
的@FeignClient
可以解析SpringMVC
的@RequestMapping
注解下的接口, 并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
OpenFeign
与Ribbon
Ribbon
是Netflix
的一个开源的负载均衡项目,是一个客户端负载均衡器,运行在消费者端。OpenFeign
也是运行在消费者端的,并且使用Ribbon
进行负载均衡,所以OpenFeign
直接内置了Ribbon
,即在导入 OpenFeign
依赖后,无需再导入Ribbon
依赖了。
一、OpenFeign
项目搭建
1. 提供者应用
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 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.10.RELEASE</version><relativePath/></parent><groupId>com.example</groupId><artifactId>user-provider</artifactId><version>0.0.1-SNAPSHOT</version><properties><java.version>8</java.version><spring-cloud.version>Hoxton.SR12</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-actuator</artifactId></dependency><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>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.83</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project><?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.10.RELEASE</version> <relativePath/> </parent> <groupId>com.example</groupId> <artifactId>user-provider</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <java.version>8</java.version> <spring-cloud.version>Hoxton.SR12</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-actuator</artifactId> </dependency> <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>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.83</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project><?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.10.RELEASE</version> <relativePath/> </parent> <groupId>com.example</groupId> <artifactId>user-provider</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <java.version>8</java.version> <spring-cloud.version>Hoxton.SR12</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-actuator</artifactId> </dependency> <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>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.83</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
application.properties
server.port=8081#eureka.client.service-url.defaultZone=http://admin:123@ek1.com:7901/eureka/,http://admin:123@ek2.com:7902/eureka/## 客户端在注册中心中的名称eureka.instance.instance-id=user-provider-8081## 设置当前 client 每5秒向 server 发送一次心跳,默认 30seureka.instance.lease-renewal-interval-in-seconds=5## 表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance,默认为90秒eureka.instance.lease-expiration-duration-in-seconds=90## 当前服务名称spring.application.name=user-provider## 表示将自己的ip注册到Eureka Server上。不配置,表示将操作系统的 hostname 注册到servereureka.instance.prefer-ip-address=true## eureka 服务名,默认值 unknown;如果没有配置,则取 spring.application.name#eureka.instance.appname=user-provider## 实例的虚拟主机名称,默认值 unknown;如果没有配置,则取 spring.application.name#eureka.instance.virtual-host-name=user-provider## 对外开放所有监控端点management.endpoints.web.exposure.include=*## 是否将自己注册到其他Eureka Server,默认为trueeureka.client.register-with-eureka=true## 是否从eureka server获取注册信息, 需要eureka.client.fetch-registry=trueserver.port=8081 # eureka.client.service-url.defaultZone=http://admin:123@ek1.com:7901/eureka/,http://admin:123@ek2.com:7902/eureka/ # # 客户端在注册中心中的名称 eureka.instance.instance-id=user-provider-8081 # # 设置当前 client 每5秒向 server 发送一次心跳,默认 30s eureka.instance.lease-renewal-interval-in-seconds=5 # # 表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance,默认为90秒 eureka.instance.lease-expiration-duration-in-seconds=90 # # 当前服务名称 spring.application.name=user-provider # # 表示将自己的ip注册到Eureka Server上。不配置,表示将操作系统的 hostname 注册到server eureka.instance.prefer-ip-address=true # # eureka 服务名,默认值 unknown;如果没有配置,则取 spring.application.name #eureka.instance.appname=user-provider # # 实例的虚拟主机名称,默认值 unknown;如果没有配置,则取 spring.application.name #eureka.instance.virtual-host-name=user-provider # # 对外开放所有监控端点 management.endpoints.web.exposure.include=* # # 是否将自己注册到其他Eureka Server,默认为true eureka.client.register-with-eureka=true # # 是否从eureka server获取注册信息, 需要 eureka.client.fetch-registry=trueserver.port=8081 # eureka.client.service-url.defaultZone=http://admin:123@ek1.com:7901/eureka/,http://admin:123@ek2.com:7902/eureka/ # # 客户端在注册中心中的名称 eureka.instance.instance-id=user-provider-8081 # # 设置当前 client 每5秒向 server 发送一次心跳,默认 30s eureka.instance.lease-renewal-interval-in-seconds=5 # # 表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance,默认为90秒 eureka.instance.lease-expiration-duration-in-seconds=90 # # 当前服务名称 spring.application.name=user-provider # # 表示将自己的ip注册到Eureka Server上。不配置,表示将操作系统的 hostname 注册到server eureka.instance.prefer-ip-address=true # # eureka 服务名,默认值 unknown;如果没有配置,则取 spring.application.name #eureka.instance.appname=user-provider # # 实例的虚拟主机名称,默认值 unknown;如果没有配置,则取 spring.application.name #eureka.instance.virtual-host-name=user-provider # # 对外开放所有监控端点 management.endpoints.web.exposure.include=* # # 是否将自己注册到其他Eureka Server,默认为true eureka.client.register-with-eureka=true # # 是否从eureka server获取注册信息, 需要 eureka.client.fetch-registry=true
- 实体
bean
public class User implements Serializable {private String idCard;private String username;public User() {}public User(String idCard, String username) {this.idCard = idCard;this.username = username;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getIdCard() {return idCard;}public void setIdCard(String idCard) {this.idCard = idCard;}@Overridepublic String toString() {return "User{" +"idCard='" + idCard + ''' +", username='" + username + ''' +'}';}}public class User implements Serializable { private String idCard; private String username; public User() { } public User(String idCard, String username) { this.idCard = idCard; this.username = username; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getIdCard() { return idCard; } public void setIdCard(String idCard) { this.idCard = idCard; } @Override public String toString() { return "User{" + "idCard='" + idCard + ''' + ", username='" + username + ''' + '}'; } }public class User implements Serializable { private String idCard; private String username; public User() { } public User(String idCard, String username) { this.idCard = idCard; this.username = username; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getIdCard() { return idCard; } public void setIdCard(String idCard) { this.idCard = idCard; } @Override public String toString() { return "User{" + "idCard='" + idCard + ''' + ", username='" + username + ''' + '}'; } }
controller
接口
@RestController@RequestMapping("/user")public class UserController {private final static Logger log = LoggerFactory.getLogger(UserController.class);@PostMapping("/save")public Boolean saveUser(@RequestBody User user) {log.info("save user success : {}", JSON.toJSONString(user));return Boolean.TRUE;}@DeleteMapping("/del/{id}")public Boolean deleteUser(@PathVariable("id") Long id) {log.info("delete user success, user id : {}", id);return Boolean.TRUE;}@GetMapping("/list")public List<User> getUserList() {User user = new User("110", "大宝");log.info("getUserList result : " + user);return Lists.newArrayList(user);}@GetMapping("/get")public User getUserById(@RequestParam(value = "id", required = true) Long id) {User user = new User("111", "大宝");log.info("getUserById result : {} , id : {}", user, id);return user;}}@RestController @RequestMapping("/user") public class UserController { private final static Logger log = LoggerFactory.getLogger(UserController.class); @PostMapping("/save") public Boolean saveUser(@RequestBody User user) { log.info("save user success : {}", JSON.toJSONString(user)); return Boolean.TRUE; } @DeleteMapping("/del/{id}") public Boolean deleteUser(@PathVariable("id") Long id) { log.info("delete user success, user id : {}", id); return Boolean.TRUE; } @GetMapping("/list") public List<User> getUserList() { User user = new User("110", "大宝"); log.info("getUserList result : " + user); return Lists.newArrayList(user); } @GetMapping("/get") public User getUserById(@RequestParam(value = "id", required = true) Long id) { User user = new User("111", "大宝"); log.info("getUserById result : {} , id : {}", user, id); return user; } }@RestController @RequestMapping("/user") public class UserController { private final static Logger log = LoggerFactory.getLogger(UserController.class); @PostMapping("/save") public Boolean saveUser(@RequestBody User user) { log.info("save user success : {}", JSON.toJSONString(user)); return Boolean.TRUE; } @DeleteMapping("/del/{id}") public Boolean deleteUser(@PathVariable("id") Long id) { log.info("delete user success, user id : {}", id); return Boolean.TRUE; } @GetMapping("/list") public List<User> getUserList() { User user = new User("110", "大宝"); log.info("getUserList result : " + user); return Lists.newArrayList(user); } @GetMapping("/get") public User getUserById(@RequestParam(value = "id", required = true) Long id) { User user = new User("111", "大宝"); log.info("getUserById result : {} , id : {}", user, id); return user; } }
- 启动类
@SpringBootApplication@EnableDiscoveryClientpublic class UserServiceProviderApplication {public static void main(String[] args) {SpringApplication.run(UserServiceProviderApplication.class, args);}}@SpringBootApplication @EnableDiscoveryClient public class UserServiceProviderApplication { public static void main(String[] args) { SpringApplication.run(UserServiceProviderApplication.class, args); } }@SpringBootApplication @EnableDiscoveryClient public class UserServiceProviderApplication { public static void main(String[] args) { SpringApplication.run(UserServiceProviderApplication.class, args); } }
2. 消费者应用
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 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.10.RELEASE</version><relativePath/></parent><groupId>com.example</groupId><artifactId>open-feign-consumer</artifactId><version>0.0.1-SNAPSHOT</version><name>open-feign-consumer</name><description>open-feign-consumer</description><properties><java.version>8</java.version><spring-cloud.version>Hoxton.SR12</spring-cloud.version></properties><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><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency></dependencies><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><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project><?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.10.RELEASE</version> <relativePath/> </parent> <groupId>com.example</groupId> <artifactId>open-feign-consumer</artifactId> <version>0.0.1-SNAPSHOT</version> <name>open-feign-consumer</name> <description>open-feign-consumer</description> <properties> <java.version>8</java.version> <spring-cloud.version>Hoxton.SR12</spring-cloud.version> </properties> <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> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> </dependencies> <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> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project><?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.10.RELEASE</version> <relativePath/> </parent> <groupId>com.example</groupId> <artifactId>open-feign-consumer</artifactId> <version>0.0.1-SNAPSHOT</version> <name>open-feign-consumer</name> <description>open-feign-consumer</description> <properties> <java.version>8</java.version> <spring-cloud.version>Hoxton.SR12</spring-cloud.version> </properties> <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> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> </dependencies> <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> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
application.properties
server.port=7072# 注册中心地址eureka.client.service-url.defaultZone=http://admin:123@ek1.com:7901/eureka/,http://admin:123@ek2.com:7902/eureka/## 客户端在注册中心中的名称eureka.instance.instance-id=open-feign-consumer-7072## 当前服务对外暴露的名称spring.application.name=open-feign-consumer## 指定 feign 从请求到获取提供者响应的超时时间feign.client.config.default.read-timeout=5000## 指定 feign 连接提供者的超时时间feign.client.config.default.connect-timeout=5000## 设置当前 client 每5秒向 server 发送一次心跳,默认 30seureka.instance.lease-renewal-interval-in-seconds=5## 表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance,默认为90秒eureka.instance.lease-expiration-duration-in-seconds=90## 表示将自己的ip注册到Eureka Server上。不配置,表示将操作系统的 hostname 注册到 servereureka.instance.prefer-ip-address=true## 是否将自己注册到其他Eureka Servereureka.client.register-with-eureka=true## 是否从eureka server获取注册信息eureka.client.fetch-registry=true## 表示eureka client间隔多久去拉取服务注册信息,默认为30秒,如果要迅速获取服务注册状态,可以缩小该值,比如5秒eureka.client.registry-fetch-interval-seconds=5server.port=7072 # 注册中心地址 eureka.client.service-url.defaultZone=http://admin:123@ek1.com:7901/eureka/,http://admin:123@ek2.com:7902/eureka/ # # 客户端在注册中心中的名称 eureka.instance.instance-id=open-feign-consumer-7072 # # 当前服务对外暴露的名称 spring.application.name=open-feign-consumer # # 指定 feign 从请求到获取提供者响应的超时时间 feign.client.config.default.read-timeout=5000 # # 指定 feign 连接提供者的超时时间 feign.client.config.default.connect-timeout=5000 # # 设置当前 client 每5秒向 server 发送一次心跳,默认 30s eureka.instance.lease-renewal-interval-in-seconds=5 # # 表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance,默认为90秒 eureka.instance.lease-expiration-duration-in-seconds=90 # # 表示将自己的ip注册到Eureka Server上。不配置,表示将操作系统的 hostname 注册到 server eureka.instance.prefer-ip-address=true # # 是否将自己注册到其他Eureka Server eureka.client.register-with-eureka=true # # 是否从eureka server获取注册信息 eureka.client.fetch-registry=true # # 表示eureka client间隔多久去拉取服务注册信息,默认为30秒,如果要迅速获取服务注册状态,可以缩小该值,比如5秒 eureka.client.registry-fetch-interval-seconds=5server.port=7072 # 注册中心地址 eureka.client.service-url.defaultZone=http://admin:123@ek1.com:7901/eureka/,http://admin:123@ek2.com:7902/eureka/ # # 客户端在注册中心中的名称 eureka.instance.instance-id=open-feign-consumer-7072 # # 当前服务对外暴露的名称 spring.application.name=open-feign-consumer # # 指定 feign 从请求到获取提供者响应的超时时间 feign.client.config.default.read-timeout=5000 # # 指定 feign 连接提供者的超时时间 feign.client.config.default.connect-timeout=5000 # # 设置当前 client 每5秒向 server 发送一次心跳,默认 30s eureka.instance.lease-renewal-interval-in-seconds=5 # # 表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance,默认为90秒 eureka.instance.lease-expiration-duration-in-seconds=90 # # 表示将自己的ip注册到Eureka Server上。不配置,表示将操作系统的 hostname 注册到 server eureka.instance.prefer-ip-address=true # # 是否将自己注册到其他Eureka Server eureka.client.register-with-eureka=true # # 是否从eureka server获取注册信息 eureka.client.fetch-registry=true # # 表示eureka client间隔多久去拉取服务注册信息,默认为30秒,如果要迅速获取服务注册状态,可以缩小该值,比如5秒 eureka.client.registry-fetch-interval-seconds=5
- 创建实体
public class User implements Serializable {private String idCard;private String username;public User() {}public User(String idCard, String username) {this.idCard = idCard;this.username = username;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getIdCard() {return idCard;}public void setIdCard(String idCard) {this.idCard = idCard;}@Overridepublic String toString() {return "User{" +"idCard='" + idCard + ''' +", username='" + username + ''' +'}';}}public class User implements Serializable { private String idCard; private String username; public User() { } public User(String idCard, String username) { this.idCard = idCard; this.username = username; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getIdCard() { return idCard; } public void setIdCard(String idCard) { this.idCard = idCard; } @Override public String toString() { return "User{" + "idCard='" + idCard + ''' + ", username='" + username + ''' + '}'; } }public class User implements Serializable { private String idCard; private String username; public User() { } public User(String idCard, String username) { this.idCard = idCard; this.username = username; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getIdCard() { return idCard; } public void setIdCard(String idCard) { this.idCard = idCard; } @Override public String toString() { return "User{" + "idCard='" + idCard + ''' + ", username='" + username + ''' + '}'; } }
public class ResultBody<T> implements Serializable {private boolean status;// 响应码private String code;// 响应描述信息private String message;// 响应数据private T data;public ResultBody() {}private ResultBody(T data) {this.data = data;}private ResultBody(String code, String msg) {this.code = code;this.message = msg;}public static <T> ResultBody<T> success() {ResultBody<T> result = new ResultBody<>();result.setCode("1");result.setStatus(Boolean.TRUE);result.setMessage("成功");return result;}public static <T> ResultBody<T> success(T data) {ResultBody<T> result = new ResultBody<>();result.setCode("1");result.setStatus(Boolean.TRUE);result.setMessage("成功");result.setData(data);return result;}public static <T> ResultBody<T> error(String code, String message) {ResultBody<T> result = new ResultBody<>(code, message);result.setStatus(Boolean.FALSE);return result;}public boolean isStatus() {return status;}public void setStatus(boolean status) {this.status = status;}public String getCode() {return code;}public void setCode(String code) {this.code = code;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public T getData() {return data;}public void setData(T data) {this.data = data;}}public class ResultBody<T> implements Serializable { private boolean status; // 响应码 private String code; // 响应描述信息 private String message; // 响应数据 private T data; public ResultBody() { } private ResultBody(T data) { this.data = data; } private ResultBody(String code, String msg) { this.code = code; this.message = msg; } public static <T> ResultBody<T> success() { ResultBody<T> result = new ResultBody<>(); result.setCode("1"); result.setStatus(Boolean.TRUE); result.setMessage("成功"); return result; } public static <T> ResultBody<T> success(T data) { ResultBody<T> result = new ResultBody<>(); result.setCode("1"); result.setStatus(Boolean.TRUE); result.setMessage("成功"); result.setData(data); return result; } public static <T> ResultBody<T> error(String code, String message) { ResultBody<T> result = new ResultBody<>(code, message); result.setStatus(Boolean.FALSE); return result; } public boolean isStatus() { return status; } public void setStatus(boolean status) { this.status = status; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public T getData() { return data; } public void setData(T data) { this.data = data; } }public class ResultBody<T> implements Serializable { private boolean status; // 响应码 private String code; // 响应描述信息 private String message; // 响应数据 private T data; public ResultBody() { } private ResultBody(T data) { this.data = data; } private ResultBody(String code, String msg) { this.code = code; this.message = msg; } public static <T> ResultBody<T> success() { ResultBody<T> result = new ResultBody<>(); result.setCode("1"); result.setStatus(Boolean.TRUE); result.setMessage("成功"); return result; } public static <T> ResultBody<T> success(T data) { ResultBody<T> result = new ResultBody<>(); result.setCode("1"); result.setStatus(Boolean.TRUE); result.setMessage("成功"); result.setData(data); return result; } public static <T> ResultBody<T> error(String code, String message) { ResultBody<T> result = new ResultBody<>(code, message); result.setStatus(Boolean.FALSE); return result; } public boolean isStatus() { return status; } public void setStatus(boolean status) { this.status = status; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public T getData() { return data; } public void setData(T data) { this.data = data; } }
@FeignClient
定义接口
/*** 定义http请求接口*/@FeignClient(value = "user-provider/user")public interface OpenFeignUserService {@PostMapping("/save")Boolean saveUser(@RequestBody User user);@DeleteMapping("/del/{id}")public Boolean deleteUser(@PathVariable("id") Long id);@GetMapping("/list")List<User> getUserList();@GetMapping("/get")User getUserById(@RequestParam(value = "id", required = true) Long id);}/** * 定义http请求接口 */ @FeignClient(value = "user-provider/user") public interface OpenFeignUserService { @PostMapping("/save") Boolean saveUser(@RequestBody User user); @DeleteMapping("/del/{id}") public Boolean deleteUser(@PathVariable("id") Long id); @GetMapping("/list") List<User> getUserList(); @GetMapping("/get") User getUserById(@RequestParam(value = "id", required = true) Long id); }/** * 定义http请求接口 */ @FeignClient(value = "user-provider/user") public interface OpenFeignUserService { @PostMapping("/save") Boolean saveUser(@RequestBody User user); @DeleteMapping("/del/{id}") public Boolean deleteUser(@PathVariable("id") Long id); @GetMapping("/list") List<User> getUserList(); @GetMapping("/get") User getUserById(@RequestParam(value = "id", required = true) Long id); }
注意: 如果这里你要使用@RequestMapping
注解的时候,你必须说明请求方式,例如:@RequestMapping(value = "/save", method = RequestMethod.POST)
controller
定义
@RestController@RequestMapping("/open-feign")public class OpenFeignController {@Resourceprivate OpenFeignUserService userService;@PostMapping("/save")public ResultBody saveUser(@RequestBody User user) {Boolean result = userService.saveUser(user);return ResultBody.success(result);}@DeleteMapping("/del/{id}")public ResultBody deleteUser(@PathVariable("id") Long id) {Boolean result = userService.deleteUser(id);return ResultBody.success(result);}@GetMapping("/list")public ResultBody getUserList() {List<User> userList = userService.getUserList();return ResultBody.success(userList);}@GetMapping("/get")public ResultBody getUserById(@RequestParam(value = "id", required = true) Long id) {User user = userService.getUserById(id);return ResultBody.success(user);}}@RestController @RequestMapping("/open-feign") public class OpenFeignController { @Resource private OpenFeignUserService userService; @PostMapping("/save") public ResultBody saveUser(@RequestBody User user) { Boolean result = userService.saveUser(user); return ResultBody.success(result); } @DeleteMapping("/del/{id}") public ResultBody deleteUser(@PathVariable("id") Long id) { Boolean result = userService.deleteUser(id); return ResultBody.success(result); } @GetMapping("/list") public ResultBody getUserList() { List<User> userList = userService.getUserList(); return ResultBody.success(userList); } @GetMapping("/get") public ResultBody getUserById(@RequestParam(value = "id", required = true) Long id) { User user = userService.getUserById(id); return ResultBody.success(user); } }@RestController @RequestMapping("/open-feign") public class OpenFeignController { @Resource private OpenFeignUserService userService; @PostMapping("/save") public ResultBody saveUser(@RequestBody User user) { Boolean result = userService.saveUser(user); return ResultBody.success(result); } @DeleteMapping("/del/{id}") public ResultBody deleteUser(@PathVariable("id") Long id) { Boolean result = userService.deleteUser(id); return ResultBody.success(result); } @GetMapping("/list") public ResultBody getUserList() { List<User> userList = userService.getUserList(); return ResultBody.success(userList); } @GetMapping("/get") public ResultBody getUserById(@RequestParam(value = "id", required = true) Long id) { User user = userService.getUserById(id); return ResultBody.success(user); } }
- 启动类定义,
@EnableFeignClients
启用OpenFeign
@SpringBootApplication@EnableDiscoveryClient// 开启 Feign 客户端,指定service接口所在的包@EnableFeignClients(basePackages = "com.example.openfeign.consumer.service")public class OpenFeignConsumerApplication {public static void main(String[] args) {SpringApplication.run(OpenFeignConsumerApplication.class, args);}}@SpringBootApplication @EnableDiscoveryClient // 开启 Feign 客户端,指定service接口所在的包 @EnableFeignClients(basePackages = "com.example.openfeign.consumer.service") public class OpenFeignConsumerApplication { public static void main(String[] args) { SpringApplication.run(OpenFeignConsumerApplication.class, args); } }@SpringBootApplication @EnableDiscoveryClient // 开启 Feign 客户端,指定service接口所在的包 @EnableFeignClients(basePackages = "com.example.openfeign.consumer.service") public class OpenFeignConsumerApplication { public static void main(String[] args) { SpringApplication.run(OpenFeignConsumerApplication.class, args); } }
IDEA
的HttpClient
测试文件TestUser.http
###POST http://127.0.0.1:7072/open-feign/saveContent-Type: application/json{"idCard":"123","username":"Kate"}###DELETE http://127.0.0.1:7072/open-feign/del/110###GET http://127.0.0.1:7072/open-feign/list###GET http://127.0.0.1:7072/open-feign/get?id=111### POST http://127.0.0.1:7072/open-feign/save Content-Type: application/json { "idCard":"123", "username":"Kate" } ### DELETE http://127.0.0.1:7072/open-feign/del/110 ### GET http://127.0.0.1:7072/open-feign/list ### GET http://127.0.0.1:7072/open-feign/get?id=111### POST http://127.0.0.1:7072/open-feign/save Content-Type: application/json { "idCard":"123", "username":"Kate" } ### DELETE http://127.0.0.1:7072/open-feign/del/110 ### GET http://127.0.0.1:7072/open-feign/list ### GET http://127.0.0.1:7072/open-feign/get?id=111
二、 超时和重试
OpenFeign
默认支持Ribbon
,Ribbon
的重试机制和OpenFeign
的重试机制有冲突,所以源码中默认关闭了OpenFeign
的重试机制,使用Ribbon
重试机制。
1. 超时
- 服务提供者接口
private AtomicLong atomicLong = new AtomicLong();@GetMapping("/retry")public User retryUser() {try {log.info("超时模拟 ...");Thread.sleep(6000);} catch (Exception e) {log.info("执行异常");}long i = atomicLong.getAndIncrement();log.info("retryUser 接口第 {} 次调用", i);User user = new User("111", "大宝");return user;}private AtomicLong atomicLong = new AtomicLong(); @GetMapping("/retry") public User retryUser() { try { log.info("超时模拟 ..."); Thread.sleep(6000); } catch (Exception e) { log.info("执行异常"); } long i = atomicLong.getAndIncrement(); log.info("retryUser 接口第 {} 次调用", i); User user = new User("111", "大宝"); return user; }private AtomicLong atomicLong = new AtomicLong(); @GetMapping("/retry") public User retryUser() { try { log.info("超时模拟 ..."); Thread.sleep(6000); } catch (Exception e) { log.info("执行异常"); } long i = atomicLong.getAndIncrement(); log.info("retryUser 接口第 {} 次调用", i); User user = new User("111", "大宝"); return user; }
- 客户端设置业务超时时间
# 业务超时时间ribbon.ReadTimeout=2000# 业务超时时间 ribbon.ReadTimeout=2000# 业务超时时间 ribbon.ReadTimeout=2000
@FeignClient(value = "user-provider/user")public interface OpenFeignUserService {@GetMapping("/retry")User retryUser();}@FeignClient(value = "user-provider/user") public interface OpenFeignUserService { @GetMapping("/retry") User retryUser(); }@FeignClient(value = "user-provider/user") public interface OpenFeignUserService { @GetMapping("/retry") User retryUser(); }
@RestController@RequestMapping("/open-feign")public class OpenFeignController {@GetMapping("/retry")public ResultBody retryUser() {User user = userService.retryUser();return ResultBody.success(user);}}@RestController @RequestMapping("/open-feign") public class OpenFeignController { @GetMapping("/retry") public ResultBody retryUser() { User user = userService.retryUser(); return ResultBody.success(user); } }@RestController @RequestMapping("/open-feign") public class OpenFeignController { @GetMapping("/retry") public ResultBody retryUser() { User user = userService.retryUser(); return ResultBody.success(user); } }
1. 重试
# 同一台实例最大重试次数,不包括首次调用ribbon.MaxAutoRetries=3## 重试负载均衡其他实例最大此时,不包括首次调用ribbon.MaxAutoRetriesNextServer=3## 是否所有操作都重试ribbon.okToRetryOnAllOperations=false# 同一台实例最大重试次数,不包括首次调用 ribbon.MaxAutoRetries=3 # # 重试负载均衡其他实例最大此时,不包括首次调用 ribbon.MaxAutoRetriesNextServer=3 # # 是否所有操作都重试 ribbon.okToRetryOnAllOperations=false# 同一台实例最大重试次数,不包括首次调用 ribbon.MaxAutoRetries=3 # # 重试负载均衡其他实例最大此时,不包括首次调用 ribbon.MaxAutoRetriesNextServer=3 # # 是否所有操作都重试 ribbon.okToRetryOnAllOperations=false
三、日志配置
在properties
文件中配置日志级别,方便本地调试,参考官方文档:docs.spring.io/spring-clou…
# none:不记录任何日志,默认值;# basic:仅记录请求方法,url,响应状态码,执行时间;# headers:在basic基础上,记录header信息;# full:记录请求和响应的header,body,元数据#feign.client.config.default.logger-level=full## logger-level只对 debug 级别日志做出响应logging.level.com.example.openfeign.consumer=debug# none:不记录任何日志,默认值; # basic:仅记录请求方法,url,响应状态码,执行时间; # headers:在basic基础上,记录header信息; # full:记录请求和响应的header,body,元数据 # feign.client.config.default.logger-level=full # # logger-level只对 debug 级别日志做出响应 logging.level.com.example.openfeign.consumer=debug# none:不记录任何日志,默认值; # basic:仅记录请求方法,url,响应状态码,执行时间; # headers:在basic基础上,记录header信息; # full:记录请求和响应的header,body,元数据 # feign.client.config.default.logger-level=full # # logger-level只对 debug 级别日志做出响应 logging.level.com.example.openfeign.consumer=debug
如果不想通过feign.client.config.default.logger-level
的方式配置,也可通过Java
代码的方式来配置
@Configurationpublic class FeiginConfig {@BeanLogger.Level logLevel(){return Logger.Level.FULL;}}@Configuration public class FeiginConfig { @Bean Logger.Level logLevel(){ return Logger.Level.FULL; } }@Configuration public class FeiginConfig { @Bean Logger.Level logLevel(){ return Logger.Level.FULL; } }