Feign
是一个声明式的 HTTP
客户端,它简化了编写基于 REST
的服务间通信代码的过程。在 Spring Cloud
中,Feign 扮演着重要的角色,它具有可插入注释支持,包括Feign注释和JAX-RS注释。Feign
还支持可插拔编码器
和解码器
。Spring Cloud
增加了对Spring MVC
注释的支持,并使用Spring Web中默认使用的HttpMessageConverters
。Spring Cloud 集成Ribbon
和 Eureka
或者Nacos
注册中心 以在使用 Feign
时提供负载均衡的http 客户端。
Feign 特性介绍:
Feign
允许开发者使用接口和注解的方式定义对远程 REST 服务的调用,而不需要关心底层的 HTTP 请求和响应处理。在 Feign 中,可以通过接口方法的定义来描述远程服务的各种操作,包括 URL、请求方法、参数等信息。Ribbon
负载均衡器,Feign
可以自动实现对指定服务的负载均衡调用。这使得在服务提供者有多个实例的情况下,Feign
可以根据负载均衡策略选择合适的服务实例进行调用。Feign
支持各种 HTTP 请求方法,如 GET
、POST
、PUT
、DELETE
等,从而使得对远程服务的调用变得非常灵活。URL
模板中,并将动态的数据传递给远程服务。同时,Feign
也支持将参数绑定到请求体中,以便进行 POST
或 PUT
请求。Hystrix
断路器,Feign 可以提供对远程服务调用的容错能力,包括超时、断路器等功能,从而增强了服务调用的可靠性。Feign
与其他组件(如 Eureka
、Zuul
等)无缝集成,通过简单的依赖配置和注解即可快速启用,并且无需额外的繁琐配置。总之,Spring Cloud Feign 提供了一种简洁而优雅的方式来定义和调用 REST 服务,它简化了服务间通信的过程,提高了开发效率,为构建微服务架构的应用程序提供了便利的支持。
搭建一个 SpringCloud 的 Demo,包含生产者和消费者。 都是普通的 web 服务,其中 消费者 比生产者多了 Feign 的依赖。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
并且在启动类上添加注解:@EnableFeignClients
如果配置了fallbackFactory
降级,则还需要引入依赖:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
然后在启动类上添加注解:@EnableHystrix
,并且添加开启服务降级的配置:
feign:
hystrix:
enabled: true
其中生产者提供一个POST
的接口:/student/queryList
查询数据库中 student 表的数据。
@RestController
@RequestMapping("/student")
public class StudentController {
@Resource
private IStudentService studentService;
@GetMapping("/queryList")
public ApiR queryList(){
List<Student> studentList = studentService.list();
return ApiR.ok().data(studentList);
}
}
消费者使用 Feign 远程调用这个查询列表的接口:
首先新建一个 @FeignClient
注解的接口
import com.lin.common.config.FeignSupportConfig;
import com.lin.common.constant.ServiceNameConstants;
import com.lin.common.vo.ApiR;
import com.lin.consumer.feign.fallback.RemoteStudentServiceFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
/**
* @author linmengmeng
* @since 2023/11/12 14:12
*/
@FeignClient(contextId = "RemoteStudentService", name = ServiceNameConstants.PROVIDER_SERVICE
, configuration = FeignSupportConfig.class, fallbackFactory = RemoteStudentServiceFallback.class)
public interface RemoteStudentService {
@GetMapping ("/student/queryList")
ApiR queryList();
}
我这里将生产者和消费者都注册到 Nacos
上,所以这里可以直接配置name = ServiceNameConstants.PROVIDER_SERVICE
通过生产者的服务名,直接调用对方的接口。
并配置了降级回调 fallbackFactory = RemoteStudentServiceFallback.class
,降级操作是实现了上面定义的接口,并输出具体的异常信息;
import com.lin.common.vo.ApiR;
import com.lin.consumer.feign.RemoteStudentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
/**
* @author linmengmeng
* @since 2023/11/12 14:22
*/
@Slf4j
@Component
public class RemoteStudentServiceFallback implements FallbackFactory<RemoteStudentService> {
@Override
public RemoteStudentService create(Throwable cause) {
log.error("RemoteStudentServiceFallback error : {}", cause.getMessage());
return () -> {
log.error("sayHello error 接口调用异常");
return ApiR.fail();
};
}
}
添加一个测试请求接口,调用上面的 feign 接口:
import cn.hutool.json.JSONUtil;
import com.lin.common.vo.ApiR;
import com.lin.common.vo.R;
import com.lin.consumer.feign.RemoteStudentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* 测试
* @author linmengmeng
* @since 2023/11/8 14:18
*/
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
@Resource
private RemoteStudentService remoteStudentService;
@GetMapping("/sayHello")
public R sayHello() {
log.info("sayHello。。。。。。");
ApiR apiR = remoteStudentService.queryList();
log.info("接口调用结果-apiR:{}", JSONUtil.toJsonStr(apiR));
return R.ok();
}
}
先启动消费者,然后使用 Postman 调用消费者的 sayHello
接口,可以看到,这个接口里面调用了Feign的远程接口,这时候生产者没启动,服务接口queryList
肯定会掉不通的,按照原来 OkHTTP 的调用,远程接口异常,会将异常传递到接口调用方。经过上面的配置,我们来调用接口看下:
可以看到,接口正常响应了,接着看下消费者的日志:
2023-11-12 22:18:11.504 INFO [io-37002-exec-1] [] c.l.consumer.controller.TestController [29] : sayHello。。。。。。
2023-11-12 22:18:12.030 WARN [reakerFactory-1] [] o.s.c.l.core.RoundRobinLoadBalancer [97] : No servers available for service: cloud-feign-provider
2023-11-12 22:18:12.033 WARN [reakerFactory-1] [] .s.c.o.l.FeignBlockingLoadBalancerClient [103] : Load balancer does not contain an instance for the service cloud-feign-provider
2023-11-12 22:18:12.065 ERROR [reakerFactory-1] [] c.l.c.f.f.RemoteStudentServiceFallback [18] : RemoteStudentServiceFallback error : [503] during [POST] to [http://cloud-feign-provider/student/queryList] [RemoteStudentService#queryList()]: [Load balancer does not contain an instance for the service cloud-feign-provider]
2023-11-12 22:18:12.072 ERROR [reakerFactory-1] [] c.l.c.f.f.RemoteStudentServiceFallback [20] : sayHello error 接口调用异常
2023-11-12 22:18:12.204 INFO [io-37002-exec-1] [] c.l.consumer.controller.TestController [31] : 接口调用结果-apiR:{"code":500,"message":"系统繁忙,请稍后再试!","data":{"changeLog":"","detail":{},"releaseTime":"2023-11-12 22:18:12"}}
可以看到,在接口调用成功后,调用Feign远程接口时,打印了两行 Warn 日志,提示我们生产者服务未找到,然后打印了我们的降级服务里面的error日志,并且我们用postman请求的接口正常响应了。
这就跟我们之前使用 OkHttp 远程调用接口有区别了,这里虽然上游服务异常,但是并不影响我们这边的调用,对于消费者来说,自身有个闭环的逻辑,不会将异常传递到本身,我们的业务逻辑中可以正常获取到降级服务的结果及日志。
接着分别启动生产者的服务,再次调用消费者的接口:
2023-11-12 22:31:50.722 INFO [io-37002-exec-2] [] c.l.consumer.controller.TestController [29] : sayHello。。。。。。
2023-11-12 22:31:51.312 INFO [io-37002-exec-2] [] c.l.consumer.controller.TestController [31] : 接口调用结果-apiR:{"code":200,"message":"成功","data":[{"id":"001","name":"Tom","age":19,"createTime":"2023-11-12T21:19:15"},{"id":"002","name":"Mary","age":18,"createTime":"2023-11-12T21:19:39"}]}
可以看到,接口正常响应了,数据也拿到了。