Feign是Netflix开发的声明式、模板化的HTTP客户端,其灵感来自Retrofit、JAXRS2.0以及WebSocket。Feign 可帮助我们更加便捷、优雅地调用HTTP API。
在Spring Cloud中,使用Feign非常简单——只需创建接口,并在接口上添加注解即可。
Feign支持多种注解,例如Feign自带的注解或者JAXRS注解等。Spring Cloud对Feign进行了增强,使其支持Spring MVC注解,另外还整合了Ribbon和Eureka,从而使得Feign的使用更加方便
以前我们调用dao的时候 ,我们是不是一个接口加 注解然后在service中就可以进行调用了
@Mapper
public interface OrderDao {
List<Order> queryOrdersByUserId(Integer userId);
}
①:Ribbon+RestTemplate进行微服务调用模式
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
ResponseEntity<List> responseEntity = restTemplate.getForEntity("http://order service/order/queryOrdersByUserId/"+userId,List.class);
我们不难发现,我们构建上诉的URL 是比较简单的,假如我们业务系统十分复杂,类似如下节点 https://www.baidu.com/s? wd=asf&rsv_spt=1&rsv_iqid=0xa25bbeba000047fd&issp=1&f=8&rsv_bp=0&rsv_idx=2&ie=utf- 8&tn=baiduhome_pg&rsv_enter=1&rsv_sug3=3&rsv_sug1=2&rsv_sug7=100&rsv_sug2=0&inputT=328&rsv_sug4=328 那么我们构建这个请求的URL是不是很复杂,若我们请求的参数有可能变动,那么是否这个URL是不是很复杂
如果系统业务非常复杂,而你是一个新人,当你看到这行代码,恐怕很难一眼看出其用途是什么!此时,你 很可能需要寻求老同事的帮助(往往是这行代码的作者,哈哈哈,可万一离职了呢?),或者查阅该目标地 址对应的文档(文档常常还和代码不匹配),才能清晰了解这行代码背后的含义!否则,你只能陷入蛋疼的境地!
案例背景:要在store-service服务中利用Feign调用order-service服务
第一步,我们采取开发中常用的套路 定义一个clyu-shop-feign-api工程,然后引入Feign依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring‐cloud‐starter‐openfeignartifactId>
dependency>
第二步:修改打包方式(因为该工程式一个普通的jar 不需要打可执行的jar)
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven‐jar‐pluginartifactId>
plugin>
plugins>
build>
第三步:编写声明式接口
@FeignClient(name = "order-service")
public interface OrderFeignApi {
@RequestMapping(method = RequestMethod.GET,value = "/order/getOrder" )
JsonResult getOrder();
}
第四步:在store-service服务中引入依赖包 clyu-shop-feign-api
<dependency>
<groupId>com.clyu.shopgroupId>
<artifactId>clyu-shop-feign-apiartifactId>
<version>1.0-SNAPSHOTversion>
<scope>compilescope>
dependency>
第五步:在store-service服务启动类上加上注解@EnableFeignClients
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages="com.clyu.shop.*")
public class StoreApplication {
public static void main( String[] args ){
SpringApplication.run(StoreApplication.class, args);
}
}
第六步:调用方式(像调用本地方式一样调用远程服务)
@RestController
@RequestMapping("store")
public class StoreController {
@Autowired
private OrderFeignApi orderFeignApi;
@GetMapping("getOrderById2/{orderNo}")
public JsonResult getOrderById2(@PathVariable String orderNo){
return orderFeignApi.getOrder();
}
}
我们服务提供者order-service 的controller 需要实现我们的OrderFeignApi接口,防止修改(比如我们的OrderFeignApi修改了,若没有实现该接口,服务提供者感知不到)
第一步:在order-service服务中引入依赖clyu-shop-feign-api
<dependency>
<groupId>com.clyu.shopgroupId>
<artifactId>clyu-shop-feign-apiartifactId>
<version>1.0-SNAPSHOTversion>
<scope>compilescope>
dependency>
第二步:我们的OrderController实现OrderFeignApi接口
@RestController
@RequestMapping("order")
public class OrderController implements OrderFeignApi {}
默认情况下,Feign的调用式不打印日志,我们需要通过自定义来打印我们的Feign的日志 (basic适 用于生产环境)
级别 | 打印内容 |
---|---|
NONE(默认值) | 不记录任何日志 |
BASIC | 仅记录请求方法、URL、响应状态代码以及执行时间(生产环境最好使用这个) |
HEADERS | 记录BASIC级别的基础上,记录请求和响应的header |
FULL | 记录请求和响应的header、body和元数据 |
第一种:基于java代码的配置
我们在clyu-shop-feign-api工程中添加Feign的自定义配置
/**
* 这个类上千万不要添加@Configuration,不然会被作为全局配置文件共享
* @author clyu
* @since 2022-02-12 01:00
*/
public class OrderFeignConfig {
//这个是控制打印日志的
@Bean
public Logger.Level level() {
return Logger.Level.BASIC;
}
}
OrderFeignApi 不用指定configuration的选项
@FeignClient(name = "order-service",configuration = OrderFeignConfig.class)
public interface OrderFeignApi {
@RequestMapping(method = RequestMethod.GET,value = "/order/getOrder" )
JsonResult getOrder();
}
针对调用端工程store-service针对日志com.tuling.feignapi 包下的日志级 别必须调整为DEBUG级别的 不然是不会打印日志的
#这个是控制输出日志的
logging:
level:
com:
clyu:
shop:
feign:
api: debug
第二种:基于yml文件细粒度配置
这种方法OrderFeignApi 不用指定configuration的选项
@FeignClient(name = "order-service")
public interface OrderFeignApi {
@RequestMapping(method = RequestMethod.GET,value = "/order/getOrder" )
JsonResult getOrder();
}
在调用方: store-service 通过feign:client:config:微服务名称:loggerLevel: 日志级别来指定
#设置调用方日志级别
logging:
level:
com:
clyu:
shop:
feign:
api: debug
#设置feign端order‐center日志级别
feign:
client:
config:
order-service:
loggerLevel: full
从上面可以看出如果我们要查看Feign日志,那么我们要设置2个地方,一个在feign端,一个在调用方store-service。这个就好比有个水管,在feign端设置日志级别是控制日志水流流入的大小,在调用方store-service设置日志级别是控制日志水流流出的大小。
第一种方法:在feign端clyu-shop-feign-api配置
public class OrderFeignConfig {
@Bean
public Contract feiContract() {
return new Contract.Default();
}
}
FeignClient类OrderFeignApi使用Feign原生的注解
@FeignClient(name = "order-service",configuration = OrderFeignConfig.class)
public interface OrderFeignApi {
@RequestLine("GET /order/getOrder")
JsonResult getOrder();
}
第二种方法:在调用方store-service,通过配置文件的形式来指定我们的契约
feign:
client:
config:
order-service:
loggerLevel: full
contract: feign.Contract.Default #指定默认契约
上面的图是一个请求的部分过程,这个请求底层是store-service会通过http方式请求order-service,浏览器调用store-service的接口时,会传入一个请求头token,目前有个需求,这个token,store-service在调用order-service接口时,会把这个token作为请求头一并传给order-service的接口
RequestInterceptor的实现,在feign端clyu-shop-feign-api
public class TulingRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
template.header("token", UUID.randomUUID().toString());
}
}
拦截器的注册,在feign端clyu-shop-feign-api
public class OrderFeignConfig {
@Bean
public TulingRequestInterceptor tulingRequestInterceptor(){
return new TulingRequestInterceptor();
}
}
@FeignClient(name = "order-service",configuration = OrderFeignConfig.class)
public interface OrderFeignApi {
@RequestMapping(method = RequestMethod.GET,value = "/order/getOrder" )
JsonResult getOrder();
@RequestMapping(method = RequestMethod.GET,value = "/order/getToken4Header" )
String getToken4Header(@RequestHeader("token") String token);
}
feign:
client:
config:
order-service:
httpclient:
enabled: true
max‐connections: 200 #最大连接数 11 max‐connections‐per‐route: 50 #为每个url请求设置最大连接数
Feign的底层用的是Ribbon,但是如果我们同时配置了Feign的超时和Ribbon超时,那么以Feign的超时设置说了算
案例:服务提供方模拟耗时 睡眠3S
@GetMapping("getToken4Header")
@Override
public String getToken4Header(String token) throws InterruptedException {
Thread.sleep(3000);
return token;
}
配置文件
#配置这个 ribbon肯定会超时
ribbon:
connectTimeout: 2000
readTimeout: 2000
#(Feign不会超时)
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000