Spring Cloud Feign 服务消费调用
在上一篇https://blog.csdn.net/weixin_43784880/article/details/106074696中
我们已经搭建了三个Eureka server的集群作为服务注册发现中心,此处可以查看上面网址进行搭建。
注:此处没有搭建好就没必要往下进行
我们知道Eureka组件的三大结构:
注册中心Eureka server
服务提供者 Eureka provider
服务调用者Eureka Customer
服务提供者 Eureka provider负责将服务接口进行实现,并暴露注册于注册中心Eureka server;
服务调用者Eureka Customer在用户发送http请求时,负责从注册中心Eureka server中查找调用相应的服务
Feign组件:
Spring Cloud Netflix的微服务都是以HTTP接口的形式暴露的,所以可以用Apache的HttpClient或Spring的RestTemplate去调用
而Feign是一个使用起来更加方便的HTTP客户端,它用起來就好像调用本地方法一样,完全感觉不到是调用的远程方法
Feign是一个声明式的Web Service客户端,它的目的就是让Web Service调用更加简单。Feign提供了HTTP请求的模板,通过编写简单的
接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息。 Feign会完全代理HTTP请求,我们只需要像调用方法一样调用它就可以完成服务请求及相关处理。
Feign具有如下特性:
可插拔的注解支持,包括Feign注解和JAX-RS注解;
支持可插拔的HTTP编码器和解码器;
支持Hystrix和它的Fallback;
支持Ribbon的负载均衡;
支持HTTP请求和响应的压缩。
下面我们将实现微服务中如何暴露服务和feigin调用服务的过程:
目录结构:
每个工程的pom.xml需要分别维
一:创建api公共接口工程
1、作用:作为一个单独对的工程,专门编写微服务项目中服务提供者和服务调用者共同使用的实体类和服务接口,目的是接口与实现分离
创建一个maven主工程 muycloud 无需添加任何依赖和设置
然后在该主工程中添加创建一个springboot或者maven工程取名api
2、pom.xml
注意:因为api作为公共接口工程,其他工程想要引入api的类时,需要在自己的pom.xml引入api的工程依赖包
所以需要正在api的pom中设置该工程为jar包:
<groupId>com.example</groupId>
<artifactId>api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>api</name>
<packaging>jar</packaging>
这样其他工程就可以通过下面依赖引入api
<dependency>
<!-- 依赖的api -->
<groupId>com.example</groupId>
<artifactId>api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
具体代码
<?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.2.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>api</name>
<packaging>jar</packaging>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
在src下创建实体类和接口如下:
3、实体类:
此处没有连接数据库
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private int id;
private String name;
public User() {
}
public User(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
4、服务接口:
public interface IDemoApi {
@RequestMapping(value="/user-api/test", method= RequestMethod.GET)
String test(@RequestParam(value = "test") String test);
/**
* 入参为对象,返回值为对象
*/
@RequestMapping(value="/user-api/user", method= RequestMethod.POST)
User user(@RequestBody User user);
}
此处不仅是设计接口,可以连同http url、请求方式、请求参数一同设置,之后在服务提供工程中具体是实现时
无需在进行设置,只需要编写代码块的内容
注意:
1)不支持@GetMapping @PostMapping,只能用@RequestMapping,通过method指定请求方式;
2)参数传递必须用@RequestParam(value = “test”) 注解指定参数名
3)传递的参数为对象,必须用@RequestBody修饰;
4)返回值若为对象,对象必须序列化,且必须提供public修饰的无参构造方法(默认是提供的),否则会报错com.fasterxml.jackson.databind.exc.InvalidDefinitionException,原因是jackson的反序列化需要无参构造函数;
二:提供服务提供者实现
1、作用:负责将接口进行具体实现,暴露注册实现的服务于Eureka Server
创建一个springcloud服务提供工程取名Cloud-provider1,因为可能以后添加多个提供者
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.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>Cloud-provider1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Cloud-provider1</name>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<!-- 依赖的api -->
<groupId>com.example</groupId>
<artifactId>api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</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>
3、application.yml
#应用名称
spring:
application:
name: springcloud-provider1
#server 端口
server:
port: 8011
##eureka
eureka:
#服务注册发现地址
client:
serviceUrl.defaultZone: http://server1:8001/eureka/
instance:
#eureka的主机
hostname: provider1
#服务实体向eureka的注册名
instance-id: ${spring.application.name}:${server.port}
# 设置微服务调用地址为IP优先(缺省为false)
prefer-ip-address: true
# 心跳时间,即服务续约间隔时间(缺省为30s)微服务会周期性地向 Eureka Server 发送心跳以Renew(续约)信息(类似于heartbeat)
lease-renewal-interval-in-seconds: 30
# 发呆时间,即服务续约到期时间(缺省为90s) 超过这个时间没有Renew的微服务,发现则会注销该微服务节点
lease-expiration-duration-in-seconds: 90
此处的client.serviceUrl.defaultZone为服务注册中心的地址,在本文首页说的地址栏中有阐述
4、controller 服务提供接口实现类
@RestController
public class DemoProvider implements IDemoApi {
@Override
public String test(String test) {
return "test: " + test;
}
@Override
public User user(@RequestBody User user) {
System.out.println(user);
if (user == null) {
user = new User(10, "Joab-Y");
}
return user;
}
}
注意:
1)此处的实现类不能加入任何映射地址RequestMapper 因为api的接口会被调用者继承,二者的映射url不同,一旦运行访问就会调用失败
2)实现类必须用@RestController注解修饰,不然他会去找模板;
3)传递参数如果为对象,该对象必须再次用@RequestBody修饰,不然字段带不过来,仍为null;
5、启动类:
@SpringBootApplication
//创建服务客户端
@EnableEurekaClient
public class CloudClient1 {
public static void main(String[] args) {
SpringApplication.run(CloudClient1.class, args);
}
}
三:提供服务调用者实现
1、作用:用户发出服务调用请求时,负责调用Eureka serve中注册的服务
在主工程中创建一个springcloud工程取名Cloud-customer1 可能后续会有多个消费者
2、pom.xml
feign实现对的服务调用需要添加关键依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
具体实现
<?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.2.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>cloud-customer1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>cloud-customer1</name>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR4</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</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>
3、application.yml
#应用名称
spring:
application:
name: springcloud-customer1
#server 端口
server:
port: 8021
##eureka
eureka:
#服务注册发现地址
client:
serviceUrl.defaultZone: http://server1:8001/eureka/
instance:
#eureka的主机
hostname: customer1
#服务实体向eureka的注册名
instance-id: ${spring.application.name}:${server.port}
# 设置微服务调用地址为IP优先(缺省为false)
prefer-ip-address: true
# 心跳时间,即服务续约间隔时间(缺省为30s)微服务会周期性地向 Eureka Server 发送心跳以Renew(续约)信息(类似于heartbeat)
lease-renewal-interval-in-seconds: 30
# 发呆时间,即服务续约到期时间(缺省为90s) 超过这个时间没有Renew的微服务,发现则会注销该微服务节点
lease-expiration-duration-in-seconds: 90
#开启hystrix
feign:
hystrix:
enabled: true
4、编辑DemoFeignService接口,继承服务api
// name: 服务者application.yml中的spring.application.name
// fallback: 断路器执行方法,即方法执行失败调用
@FeignClient(name="springcloud-provider1", fallback = DemoServiceFallback.class)
public interface DemoFeignService extends IDemoApi {
}
@FeignClient 注解
1、name:指定 Feign Client也就是服务提供者注册于server的名称,如果项目使用了 Eureka,name 属性会作为微服务的名称,用于服务发现。
2、url:url 一般用于调试,可以手动指定 @FeignClient 调用的地址。
3、decode404:当发生404错误时,如果该字段为 true,会调用 decoder 进行解码,否则抛出 FeignException。
4、configuration:Feign 配置类,可以自定义 Feign 的 Encoder、Decoder、LogLevel、Contract。
5、fallback:定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback 指定的类必须实现 @FeignClient 标记的接口。
6、fallbackFactory:工厂类,用于生成 fallback 类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码。
7、path:定义当前 FeignClient 的统一前缀。
5、实现断路器类
@Component
public class DemoServiceFallback implements DemoFeignService {
@Override
public String test(String test) {
return "error";
}
@Override
public User user(User user) {
return null;
}
}
断路器的作用就是当prorider断开或者宕机无法提供注册服务时,就会执行此方法
6、controller
RestController
@RequestMapping("/user")
public class DemoController {
@Resource
public DemoFeignService demoFeignService;
@RequestMapping(value="/test")
public String test() {
return demoFeignService.test("test");
}
@RequestMapping(value="/user")
public User user() {
User user = new User();
user.setId(10);
user.setName("Joab-Y");
return demoFeignService.user(user);
}
}
此处由前端调用,无需指定method
feign工作原理:
1、在开发微服务应用时,我们会在主程序入口添加 @EnableFeignClients 注解开启对 Feign Client 扫描加载处理。
根据 Feign Client 的开发规范,定义接口并加 @FeignClients 注解。
2、当程序启动时,会进行包扫描,扫描所有 @FeignClients 的注解的类,并将这些信息注入 Spring IOC 容器中。
当定义的 Feign 接口中的方法被调用时,通过JDK的代理的方式,来生成具体的 RequestTemplate。当生成代理时,Feign
会为每个接口方法创建一个 RequetTemplate 对象,该对象封装了 HTTP 请求需要的全部信息,如请求参数名、请求方法等信息都是在这个过程中确定的。
3、然后由 RequestTemplate 生成 Request,然后把 Request 交给 Client 去处理,这里指的 Client 可以是 JDK 原生的
URLConnection、Apache 的 Http Client 也可以是 Okhttp。最后 Client 被封装到 LoadBalanceclient 类,这个类结合 Ribbon
负载均衡发起服务之间的调用。
启动顺序:
1、先启动注册中心 2、启动服务提供者Cloud-provider1 不用管api 3、启动服务调用者CLoud-customer1
测试http接口
postman 或者浏览器输入http://127.0.0.1:8021/user/test?test=pp
postman 或者浏览器输入http://127.0.0.1:8021/user
断开provider postman 或者浏览器输入http://127.0.0.1:8021/user/test?test=pp 执行了熔断器类
Feign参数绑定
@PathVariable
/**
* 在服务提供者我们有一个方法是用直接写在链接,SpringMVC中用的@PathVariable
* 这里边和SpringMVC中有些有一点点出入,SpringMVC中只有一个参数而且参数名的话是不用额外指定参数名的,而feign中必须指定
*/
@RequestMapping(value = "/greet/{dd}",method = RequestMethod.GET)
String greetFeign(@PathVariable("dd") String dd);
@RequestParam
/**
* 这里说下@RequestParam 注解和SpringMVC中差别也是不大,我认为区别在于Feign中的是参数进入URL或请求体中,
* 而SpringMVC中是参数从请求体中到方法中
* @param ids id串,比如“1,2,3”
* @return
*/
@RequestMapping(value = "/users",method = RequestMethod.GET)
public List<User> getUsersByIds(@RequestParam("ids") List<Long> ids);
@RequestHeader
/**
* 这里是将参数添加到Headers中
* @param name 参数
*/
@RequestMapping(value = "/headers")
String getParamByHeaders(@RequestHeader("name") String name);
@RequestBody
/**
* 调用服务提供者的post方法,接收回来再被服务提供者丢回来
* @param user User对象
*/
@RequestMapping(value = "/user", method = RequestMethod.POST)
User getUserByRequestBody(@RequestBody User user);