feign说明
服务之间都是以 HTTP 接口的形式对外提供服务的。消费者在进行调用的时候,底层是通过 HTTP Client 进行访问。也可以使用JDK原生的 URLConnection、Apache 的 HTTP Client、Netty 异步 Http Client,Spring 的 RestTemplate 去实现服务间的调用。但是最方便、最优雅的方式是通过 Spring Cloud Open Feign 进行服务间的调用 Spring Cloud 对 Feign 进行了增强,使 Feign 支持 Spring Mvc 的注解,并整合了 Ribbon 等,从而让 Feign 的使用更加方便。
Feign 特性:
- 可插拔的注解支持,包括 Feign 注解和AX-RS注解。
- 支持可插拔的 HTTP 编码器和解码器。
- 支持 Hystrix 和它的 Fallback。
- 支持 Ribbon 的负载均衡。
- 支持 HTTP 请求和响应的压缩。
Feign 是一个声明式的 WebService 客户端,它的目的就是让 Web Service 调用更加简单。它整合了 Ribbon 和 Hystrix,从而不需要开发者针对 Feign 对其进行整合。Feign 还提供了 HTTP 请求的模板,通过编写简单的接口和注解,就可以定义好 HTTP 请求的参数、格式、地址等信息。Feign 会完全代理 HTTP 的请求,在使用过程中我们只需要依赖注入 Bean,然后调用对应的方法传递参数即可。
Feign 工作原理
-
在开发微服务应用时,我们会在主程序入口添加
@EnableFeignClients
注解开启对 Feign Client 扫描加载处理。根据 Feign Client 的开发规范,定义接口并加@FeignClient
注解。 -
当程序启动时,会进行包扫描,扫描所有
@FeignClient
的注解的类,并将这些信息注入 Spring IOC 容器中。当定义的 Feign 接口中的方法被调用时,通过JDK的代理的方式,来生成具体的 RequestTemplate。当生成代理时,Feign 会为每个接口方法创建一个 RequetTemplate 对象,该对象封装了 HTTP 请求需要的全部信息,如请求参数名、请求方法等信息都是在这个过程中确定的。 -
然后由 RequestTemplate 生成 Request,然后把 Request 交给 Client 去处理,这里指的 Client 可以是 JDK 原生的 URLConnection、Apache 的 Http Client 也可以是 Okhttp。最后 Client 被封装到 LoadBalanceclient 类,这个类结合 Ribbon 负载均衡发起服务之间的调用。
@FeignClient 注解属性
-
name:指定 Feign Client 的名称,如果项目使用了 Ribbon,name 属性会作为微服务的名称,用于服务发现。
-
url:url 一般用于调试,可以手动指定
@FeignClient
调用的地址。 -
decode404:当发生404错误时,如果该字段为 true,会调用 decoder 进行解码,否则抛出 FeignException。
-
configuration:Feign 配置类,可以自定义 Feign 的 Encoder、Decoder、LogLevel、Contract。
-
fallback:定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback 指定的类必须实现
@FeignClient
标记的接口。 -
fallbackFactory:工厂类,用于生成 fallback 类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码。
-
path:定义当前 FeignClient 的统一前缀。
以上内容来自:Feign 基本使用
示例中eureka服务端地址
示例(示例中除了client端feign代码地址)
1.pomxml
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.2.3</version>
<classifier>jdk15</classifier>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.7.RELEASE</version>
</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>
2.在启动类上加@EnableFeignClients
@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class CloudFeignApplication {
public static void main(String[] args) {
SpringApplication.run(CloudFeignApplication.class, args);
}
}
3.配置文件
feign.hystrix.enabled
为true才可以使用容错
spring.application.name=cloud-feign
server.servlet.context-path=/feign
server.port=8035
# logging 配置
logging.config=classpath:log4j2.xml
#ribbon 饥饿加载
ribbon.eager-load.enabled=true
#指定需要饥饿加载的客户端名称、服务名
ribbon.eager-load.clients=cloud-client,cloud-feign
ribbon.ConnectTimeout=90000
ribbon.ReadTimeout=90000
#根据ip注册实例
eureka.instance.prefer-ip-address=true
#指定注册实例ID(默认是主机名:应用名:应用端口)
#eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}
#指定注册实例主机名
#eureka.instance.hostname=127.0.0.1
#eureka.instance.hostname= ${spring.cloud.client.ip-address}
#注册地址 eureka服务端的地址 多节点用,分隔
eureka.client.service-url.defaultZone=http://127.0.0.1:8025/eureka/
#打开hystrix
feign.hystrix.enabled=true
4.controller
private FeignService feignService;
@GetMapping("/{id}")
public Object findByPathId(@PathVariable Long id) {
log.info("Path-->id:" + id);
return feignService.findByPathId(id);
}
@GetMapping("findById")
public Object findById(@RequestParam(name = "id") Long id) {
log.info("findById-->id:" + id);
return feignService.findById(id);
}
5.service
@FeignClient(name = "cloud-client/client/api")
注解不捕获异常,也没有默认实现
@FeignClient(value = "cloud-client/client/api",fallback = FeignServiceImplDefault.class)
注解不捕获异常,但是有默认实现,默认实现类是FeignServiceImplDefault
@FeignClient(value = "cloud-client/client/api",fallbackFactory = FeignServiceCauseDefault.class)
注解即捕获异常,也有默认实现,还可以捕获异常信息。对应的类是FeignServiceCauseDefault
//不捕获异常,没有默认实现
@FeignClient(name = "cloud-client/client/api")
//不捕获异常,只有默认实现
//@FeignClient(value = "cloud-client/client/api",fallback = FeignServiceImplDefault.class)
//捕获异常和默认实现
//@FeignClient(value = "cloud-client/client/api",fallbackFactory = FeignServiceCauseDefault.class)
public interface FeignService {
@GetMapping("/{id}")
Object findByPathId(@PathVariable(name = "id") Long id);
@GetMapping("findById")
Object findById(@RequestParam(name = "id") Long id);
}
6.FeignServiceCauseDefault
@Service
@Log4j2
public class FeignServiceCauseDefault implements FallbackFactory<FeignService> {
@Override
public FeignService create(Throwable throwable) {
return new FeignService() {
@Override
public Object findById(Long id) {
log.info("sorry, fallback. reason was: " + throwable);
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", id);
log.info("FeignServiceCauseDefault-->findById-->id::" + id);
return jsonObject;
}
@Override
public Object findByPathId(Long id) {
log.info("sorry, fallback. reason was: " + throwable);
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", id);
log.info("FeignServiceCauseDefault-->findByPathId-->id:" + id);
return jsonObject;
}
};
}
}
7.FeignServiceImplDefault
@Service
@Log4j2
public class FeignServiceImplDefault implements FeignService {
@Override
public Object findById(Long id) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", id);
log.info("FeignServiceImplDefault-->findById-->id:" + id);
return jsonObject;
}
@Override
public Object findByPathId(Long id) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", id);
log.info("FeignServiceImplDefault-->findByPathId-->id:" + id);
return jsonObject;
}
}
8.被调用者的Controller(client端代码地址)
@RestController
@RequestMapping("api")
@Log4j2
public class ClientController {
@GetMapping("findById")
public Object findById(@RequestParam(name = "id") Long id) {
log.info("findById:"+id);
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", id);
return jsonObject;
}
@GetMapping("findById2")
public Object findById2(@RequestParam(name = "id") Long id) {
log.info("findById2:"+id);
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", id+2);
return jsonObject;
}
@GetMapping("/{id}")
public Object findByPathId(@PathVariable Long id) {
log.info("findByPathId:"+id);
JSONObject jsonObject = new JSONObject();
jsonObject.put("findByPathId", id);
return jsonObject;
}
}
9.先启动eureka再启动client、feign
10.在浏览器中输入地址查看效果
输入http://localhost:8035/feign/findById?id=3
<LinkedHashMap>
<id>3</id>
</LinkedHashMap>
输入http://localhost:8035/feign/3
<LinkedHashMap>
<findByPathId>3</findByPathId>
</LinkedHashMap>
其中feign日志
INFO findById-->id:3
INFO Path-->id:3
其中lient日志
INFO findById:3
INFO findByPathId:3
这说明feign端成功调用了lient
11.将lient关闭,在浏览器中输入地址
出现报错
12.只打开@FeignClient(value = "cloud-client/client/api",fallback = FeignServiceImplDefault.class)
注解,并在浏览器中输入地址
输入http://localhost:8035/feign/findById?id=3或者http://localhost:8035/feign/3
<JSONObject>
<id>3</id>
</JSONObject>
其中feign日志
INFO findById-->id:3
INFO FeignServiceImplDefault-->findById-->id:3
这说明feign进入了默认实现类FeignServiceImplDefault
的容错逻辑
13.只打开@FeignClient(value = "cloud-client/client/api",fallbackFactory = FeignServiceCauseDefault.class)
注解,并在浏览器中输入地址
输入http://localhost:8035/feign/findById?id=3或者http://localhost:8035/feign/3
<JSONObject>
<id>3</id>
</JSONObject>
其中feign日志
INFO findById-->id:3
INFO sorry, fallback. reason was: java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: cloud-client
INFO FeignServiceCauseDefault-->findById-->id::3
或者是
INFO Path-->id:3
INFO sorry, fallback. reason was: java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: cloud-client
INFO FeignServiceCauseDefault-->findByPathId-->id:3
这说明feign进入了默认实现类FeignServiceCauseDefault
的容错逻辑