Feign介绍
Feign是一个声明式的Web Service客户端,它使得编写Web Serivce客户端变得更加简单。我们只需要使用Feign来创建一个接口并用注解来配置它既可完成。
它具备可插拔的注解支持,包括Feign注解和JAX-RS注解。Feign也支持可插拔的编码器和×××。Spring Cloud为Feign增加了对Spring MVC注解的支持,
还整合了Ribbon和Eureka来提供均衡负载的HTTP客户端实现。
Feign原理简述
- 启动时,程序会进行包扫描,扫描所有包下所有@FeignClient注解的类,并将这些类注入到spring的IOC容器中。
- 当定义的Feign中的接口被调用时,通过JDK的动态代理来生成RequestTemplate。
- RequestTemplate中包含请求的所有信息,如请求参数,请求URL等。
- RequestTemplate声场Request,然后将Request交给client处理,这个client默认是JDK的HTTPUrlConnection,也可以是OKhttp、Apache的HTTPClient等。
最后client封装成LoadBaLanceClient,结合ribbon负载均衡地发起调用。
Feign远程调用的基本流程
1.Feign核心:将以java注解的方式定义的远程调用API接口,最终转换成HTTP的请求形式,然后将HTTP的请求的响应结果,解码成JAVA Bean,放回给调用者。
2.Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,
根据参数再应用到请求上,进而转化成真正的 Request 请求。
Feign远程调用的重要组件
在微服务启动时,Feign会进行包扫描,对加@FeignClient注解的接口,
按照注解的规则,创建远程接口的本地JDK Proxy代理实例。然后,
将这些本地Proxy代理实例,注入到Spring IOC容器中。当远程接口的方法被调用,由Proxy代理实例去完成真正的远程访问,并且返回结果。
关键注解
- @EnableFeignClients
- @FeignClient
@EnableFeignClients
该注解作用于启动类
@FeignClient
- FeignClient注解被@Target(ElementType.TYPE)修饰,表示FeignClient注解的作用目标在接口上
- @FeignClient标签的常用属性如下
- name:指定FeignClient的名称,如果项目使用了Ribbon,name属性会作为微服务的名称,用于服务发现
- url: url一般用于调试,可以手动指定@FeignClient调用的地址
- decode404:当发生http 404错误时,如果该字段位true,会调用decoder进行解码,否则抛出FeignException
- configuration: Feign配置类,可以自定义Feign的Encoder、Decoder、LogLevel、Contract
- fallback: 定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口
- fallbackFactory: 工厂类,用于生成fallback类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码
- path: 定义当前FeignClient的统一前缀
环境准备
开发环境
- JDK:1.8
- SpringBoot:2.1.16.RELEASE
- SpringCloud:Finchley
项目结构
- halo-cloud-parent 父工程
- halo-cloud-provider 服务提供者
- halo-cloud-feign 服务消费者
- halo-cloud-server 注册中心
Feign使用
halo-cloud-parent
- 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.1.16.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.cloud</groupId>
<artifactId>parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>parent</name>
<description>父工程</description>
<properties>
<java.version>1.8</java.version>
</properties>
<!--打包方式-->
<packaging>pom</packaging>
<modules>
<module>halo-cloud-server</module>
<module>halo-cloud-feign</module>
<module>halo-cloud-provider</module>
</modules>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<!--引入springcloud依赖的-->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
halo-cloud-server
- pom.xml
<!--eureka server 依赖坐标-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
- application.yml
server: # 服务端口
port: 9090
spring:
application: # 应用名字,eureka 会根据它作为服务id
name: halo-cloud-server
# eureka配置
eureka:
instance:
hostname: localhost
client:
service-url: # eureka server 的地址, 咱们单实例模式就写自己好了
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
register-with-eureka: false # 不向eureka server 注册自己
fetch-registry: false # 不向eureka server 获取服务列表
- ServerApplication.java
@EnableEurekaServer
@SpringBootApplication
public class ServerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
}
}
halo-cloud-feign
- pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- application.yml
server:
port: 8080 #端口
spring:
application:
name: halo-cloud-fiegn #服务名称
eureka:
client:
service-url: # eureka server 的地址, 咱们单实例模式就写自己好了
defaultZone: http://localhost:9090/eureka
fetch-registry: true #表示是否将自己注册到Eureka Server,默认是true。
register-with-eureka: true #表示是否从Eureka Server获取注册信息,默认为true。
- FeignApplication.java
@EnableEurekaClient
@EnableFeignClients
@SpringBootApplication
public class FeignApplication {
public static void main(String[] args) {
SpringApplication.run(FeignApplication.class, args);
}
}
halo-cloud-provider
- pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- application.yml
server: # 服务端口
port: 7070
spring:
application: # 应用名字,eureka 会根据它作为服务id
name: halo-cloud-provider
# eureka配置
eureka:
instance:
hostname: spring-cloud-provider
client:
service-url: # eureka server 的地址, 咱们单实例模式就写自己好了
defaultZone: http://localhost:9090/eureka
register-with-eureka: true # 不向eureka server 注册自己
fetch-registry: true # 不向eureka server 获取服务列表
- ProviderApplication.java
@EnableEurekaClient
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
- ProviderController.java
@RestController
@RequestMapping("/provider")
public class ProviderController {
@Value("${server.port}")
private Integer port;
@GetMapping("/helloProvider")
public String helloProvider(String name){
return name+"正在调用provider端口:"+String.valueOf(port);
}
}
启动测试
-
注册中心
-
服务者测试
-
消费者调用服务测试