介绍
Spring Cloud OpenFeign
通过自动配置来绑定到 Spring
环境中,以此实现将 OpenFeign
集成到 SpringBoot
应用中。
Feign
本身是一个声明式的 webservice 客户端。它的目的是为了简化 webservice 的编写。通过创建一个接口,并且在接口上使用相应的注解,便可以直接使用 Feign
。
Feign
提供了可插拔的注解支持,包含了JAX-RS
注解。
Feign
提供了可插拔的编码和解码功能。
Spring Cloud
在此基础上了,构建了 OpenFeign
,增加了对 Spring MVC
注解的支持和在Spring Web
中默认使用的相同HttpMessageConverters
的支持。使得 OpenFeign
可以支持 REST
风格的客户端。
当项目中使用 Spring Cloud OpenFeign
时, Spring Cloud
会自动地集成 Ribbon
和 Eureka
,来提供一个支持服务发现和具备负载均衡的 HTTP
客户端。
Eureka
:当应用使用了Eureka
作为服务发现组件时,OpenFeign
会首先前往Eureka
的注册中心中查找对应的服务。Ribbon
:这是Spring Cloud Netflix
下的客户端的负载均衡
在 Spring Cloud OpenFeign
中,核心就是 FeignClient
。FeignClient
可以一个视为向远程服务的发起调用的 HTTP
客户端。
集成 OpenFeign
1)依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2)主类
@SpringBootApplication
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);}
}
3)FeignClient
@FeignClient("stores")
public interface StoreClient {
@RequestMapping(method = RequestMethod.GET, value = "/stores")List<Store> getStores();@RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}",consumes = "application/json")Store update(@PathVariable("storeId") Long storeId, Store store);
}
示例中用在主类上的注解 @EnableFeignClients
,表示开启 OpenFeign
。
示例中的 stores
就是当前 FeignClient
的名称,这是由 name
属性管理的。
OpenFeign
会创建基于 Ribbon
的具备负载均衡能力的 HTTP
客户端。该客户端会通过之前指定的名称,去查找服务对应的物理地址。如果应用中使用了 Eureka
作为服务发现组件,那么将会去 Eureka
的注册中心找到相匹配的服务。如果应用中未使用 Eureka
,也可以通过单独配置服务路径信息,来实现服务的查找。
在示例中 StoreClient
客户端只是一个接口,不会有实现类。Spring Cloud
会针对使用了 @FeignClient
注解的接口,创建以该接口的全限定名称作为对应Bean
的名称。 这样就可以直接通过 @Autowired
来将 StoreClient
接口注入到其他类中。
Feign默认配置
feign:client:config:default:connectTimeout: 5000readTimeout: 5000loggerLevel: basichello-service:connectTimeout: 3000readTimeout: 3000loggerLevel: basic
default
:作用域为所有的@FeignClient
hello-service
:指定的FeignClient
connectTimeout
:连接超时时间(毫秒)readTimeout
:业务执行超时时间(毫秒)loggerLevel
: 日志级别
Demo
1. Eureka-Server
1.1. 依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
1.2. 主类
package com.mawen;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;/*** @author mw118* @version 1.0* @date 2021/1/14 22:08*/
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);}}
1.3. 配置
spring:application:name: eureka-serverserver:port: 8761eureka:client:fetch-registry: falseregister-with-eureka: false
2. Hello-Service
2.1. 依赖
<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>
2.2. 主类
package com.mawen;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;/*** @author mw118* @version 1.0* @date 2021/1/14 22:29*/@SpringBootApplication
@EnableDiscoveryClient
public class HelloServiceApplication {
public static void main(String[] args) {
SpringApplication.run(HelloServiceApplication.class, args);}
}
2.3. 配置
spring:application:name: hello-serviceserver:port: 21000eureka:client:serviceUrl:defaultZone: http://localhost:8761/eureka/
2.4. 业务
HelloService.java
package com.mawen.service;/*** @author mw118* @version 1.0* @date 2021/1/14 22:42*/public interface HelloService {
String getHello();String timeout(Long timout);}
HelloServiceImpl.java
package com.mawen.service.impl;import com.mawen.service.HelloService;
import org.springframework.stereotype.Service;/*** @author mw118* @version 1.0* @date 2021/1/14 22:44*/
@Service
public class HelloServiceImpl implements HelloService {
@Overridepublic String getHello() {
return "Hello World!";}@Overridepublic String timeout(Long timout) {
try {
Thread.sleep(timout * 1000);} catch (InterruptedException e) {
e.printStackTrace();}return "Hello World!";}}
HelloController.java
package com.mawen.controller;import com.mawen.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;/*** @author mw118* @version 1.0* @date 2021/1/14 22:30*/
@RestController
public class HelloController {
@Autowiredprivate HelloService helloService;@GetMapping("/say/hello")public String sayHello() {
return helloService.getHello();}@PostMapping("/timeout")public String timeout(@RequestParam("timeout") Long timout) {
return helloService.timeout(timout);}
}
3. OpenFeign-Client
3.1. 依赖
<!-- openfeign 依赖 -->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency><!-- eureka-client 依赖 -->
<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>
3.2. 主类
package com.mawen;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;/*** @author mw118* @version 1.0* @date 2021/1/14 21:59*/@SpringBootApplication
@EnableDiscoveryClient // 开启服务发现
@EnableFeignClients // 开启 openfeign
public class OpenFeignClientApplication {
public static void main(String[] args) {
SpringApplication.run(OpenFeignClientApplication.class, args);}}
3.3. 配置
spring:application:name: openfeign-clientserver:port: 20000# 配置 Eureka 注册中心
eureka:client:serviceUrl:defaultZone: http://localhost:8761/eureka/# 配置 openfeign
feign:client:config:default: # 全局配置connectTimeout: 5000 # 连接超时时间5sreadTimeout: 5000 # 业务处理超时时间5sloggerLevel: basic # 日志级别hello-service: # 指定 feign client名为hello-service 的配置connectTimeout: 3000 # 连接超时时间3sreadTimeout: 3000 # 业务处理超时时间3sloggerLevel: basic # 日志级别
3.4. 业务
HelloService.java
package com.mawen.service;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.RequestParam;/*** 标识接口为FeignClient及对应的服务名称* @FeignClient: 表明该接口会被 SpringCloud 以当前类的全限定类名(com.mawen.service.HelloService)定义Bean,而放到 Spring 容器中* hello-service: 表示会去 Eureka 的注册中心,查找名为 hello-service 的服务而找到具体的服务信息,用于在后续请求中发起调用*/
@FeignClient("hello-service")
public interface HelloService {
/*** 对 hello-service/say/hello 发起调用* @return*/@GetMapping("/say/hello")String sayHello();/*** 对 hello-service/timeout 发起调用* @param timeout* @return*/@PostMapping("/timeout")String timeout(@RequestParam("timeout") Long timeout);
}
OpenFeignController.java
package com.mawen.controller;import com.mawen.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;/*** @author mw118* @version 1.0* @date 2021/1/14 22:13*/
@RestController
public class OpenFeignController {
/*** 注入 HelloService*/@Autowiredprivate HelloService helloService;@GetMapping("/hello")public String getHello() {
return helloService.sayHello();}@GetMapping("/timeout")public String timeout(@RequestParam("timeout") Long timeout) {
return helloService.timeout(timeout);}
}
4. 测试访问
依次启动 Eureka-Server
、 Hello-Service
、OpenFeign-Client
。
然后访问 http://localhost:20000/hello
,此时会返回 Hello World!
。这就表明 OpenFeign
已经起效了。
接下来可以测试访问 http://localhost:20000/timeout?timeout=1
和 http://localhost:20000/timeout?timeout=3
。会发现第一次访问可以成功返回 Hello World!
,而第二次接口访问返回 read time out
的错误。这是因为在配置文件中设置了 readTimeout
为3s。超过这个时候,就会报错,丢弃请求结果。
资源
代码
- Github spring-cloud-openfeign
- Github 官方示例 feign-eureka
参考
- Spring Cloud OpenFeign 官方文档