SpringCloud OpenFeign
什么是Feign
- Feign是Netflix开发的声明式,模板化的Http客户端。Feign可以帮助我们更加快捷的,优雅的调用Http API
- 与Ribbon对比来说,Feign的速度相对会慢一点,Feign的底层实际上就是使用了Ribbon进行调用的
- Feign使用的是动态代理,而Ribbon使用的是拦截器
- Ribbon借由RestTemplate来完成服务调用,调用方式麻烦,但是Feign使用非常简单,只需要创建接口,并在接口上添加注解就可以了。
- Feign支持多种注解,例如,Feign自带的注解或者JAX-RS注解等。Spring Cloud对Feign进行了增强,使其支持Spring MVC注解,另外整合了Ribbon和Eureka,从而使得Feign的使用更加的方便。
- 我们调用Feign的方式很像我们之前调用Dao的时候,我们使用Feign的时候就是在接口+注解,然后在controller中就可以对其进行调用。
Ribbon与Feign对比
(1)首先底层使用的就不一样,Ribbon使用拦截器对发送的方法(RestTemplate.getForEntity等方法)进行拦截,然后做相应的处理;而Feign使用的是动态代理的方法对其进行处理的。
(2)Feign底层还是使用Ribbon进行调用的。
(3)Feign的调用方式更加简单,采用接口+注解的方式就可以让用户无感知的完成HttpRestfulAPI方式进行调用。
构建Feign工程
1. 搭建服务的提供方
- 搭建三个provider提供方,这里不做详细描述,可以参考微服务开发(9)的Ribbon的服务提供方的搭建Ribbon搭建
2. 搭建API工程
(1)新建feingn-api工程,这个工程的作用就是我们把所有的feign使用的service放到这里,这时候我们feign调用的时候,只需要继承该类,并在上面写注解即可
(2)看架构图
(3)pom文件
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</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>
</dependencies>
(4)主启动类
- 虽然这里我们并不需要启动这个API,但是我们需要将其进行打包,我们在打包的时候,如果没有一个主启动类则会报错
package com.xiyou.api;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author
*/
@SpringBootApplication
public class ApiApplication {
public static void main(String[] args) {
SpringApplication.run(ApiApplication.class, args);
}
}
(5)API类书写
package com.xiyou.api.feign;
import feign.RequestLine;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
public interface OrderApi {
/**
*不认识@GetMapping 如果有参数记得传递,需要写全,不能省略
* @return
*/
@RequestMapping("/test")
public String test();
/**
* 这是个有参数的形式,必须在@PathVariable中写上名字,不能省略
*/
@RequestMapping("/queryOrdersByUserId/{userId}")
public List<OrderVo> queryOrdersByUserId(@PathVariable("userId") Integer userId);
}
(6)将其打包成jar的形式,并且在调用方引入这个依赖
3. 创建服务的消费端
(1)架构图展示
(2)pom文件依赖
<dependencies>
<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.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.xiyou</groupId>
<artifactId>my-feign-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
- 注意,记得除了引入feign相关的依赖外,还需要引入刚刚搭建的API的依赖
(3)配置文件
server.port=8000
spring.application.name=my-feign
eureka.client.service-url.defaultZone=http://localhost:9000/eureka
eureka.instance.instance-id=my-feign-8000
- 也没有啥特殊的
(4)主启动类
package com.xiyou.feign;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @author
*/
// 该注解必须设置扫描范围否则@FeignClient不起作用,也不会将其加入Spring IOC中
@EnableFeignClients(basePackages = "com.xiyou")
@SpringBootApplication
@EnableDiscoveryClient
public class FeignApplication {
public static void main(String[] args) {
SpringApplication.run(FeignApplication.class, args);
}
}
- 注意,主启动类上一定要加上@EnableFeignClients(basePackages = “com.xiyou”),这个表明了我们的@FeignClient注解的位置,我们会将其进行扫描,自动注入到Spring IOC的容器中
(5)Service层接口
package com.xiyou.feign.service;
import com.xiyou.api.feign.OrderApi;
import com.xiyou.feign.config.FeignConfig;
import org.springframework.cloud.openfeign.FeignClient;
/**
* 将feign需要的接口全部抽象出来
* @FeignClient中的name是要调用的服务的名字(application-name)path是其宽窄路径中的宽路径
* @author
*/
@FeignClient(name = "my-provider", path = "/provider")
public interface FeignService extends OrderApi{
}
- 注意:这个Service层我们也是一个接口,并且我们继承了API工程中定义的APIService
- @FeignClient中的name属性表示的是服务提供方的名字
- path属性表示的是服务的提供方的路径前缀
(6)服务的调用
package com.xiyou.feign.controller;
import com.xiyou.feign.service.FeignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author
*/
@RestController
public class FeignController {
@Autowired
private FeignService feignService;
@GetMapping("/testFeign")
public String test() {
String test = feignService.test();
return test;
}
}
- 我们在这里直接将Service注入进来,然后以直接调用其方法就可以
- 这就是Feign的注解 + 接口的方式进行远程调用
Feign的其他使用
Feign的自定义日志级别
- 注意Feign的日志只对debug下起作用,所以设置Feign日志之前应该先设置log为debug模式
## “com.client.Client”为Feign接口的完整类名
## 将Feign接口的日志级别设置成DEBUG,因为Feign的logger.Level只对DEBUG做出响应
logging.level.com.xiyou.feign.service.FeignService = debug
1. 日志级别
(1)NONE:性能最佳,用于生辰,不记录任何日志。
(2)HEADERS:记录BASIC级别的基础上,记录请求和响应的header。
(3)FULL:比较适用于开发以及测试环境定位问题,记录和响应的header,body和元数据。
(4)BASIC:适用于生产环境追踪问题,仅仅记录请求方法,URL,响应状态代码以及执行时间。
2. 代码中如何实现
- 在config类中写
- 注意:不能写@Configuration,否则会全部的Feign都会生效
/**
* 不加@Configuration 否则配置共享
* @author
*/
public class FeignConfig {
/**
* 定义日志级别
* @return
*/
@Bean
public Logger.Level feignLoggerLevel() {
System.out.println("加载了.....................................");
return Logger.Level.FULL;
}
}
- 在@FeignClient注解中加入Configuration属性指定该配置类
/**
* 将feign需要的接口全部抽象出来
* @FeignClient中的name是要调用的服务的名字(application-name)path是其宽窄路径中的宽路径
* @author
*/
@FeignClient(name = "my-provider", configuration = FeignConfig.class, path = "/provider")
public interface FeignService extends OrderApi{
}
3. 在yml中配置
## “com.client.Client”为Feign接口的完整类名
## 将Feign接口的日志级别设置成DEBUG,因为Feign的logger.Level只对DEBUG做出响应
logging.level.com.xiyou.feign.service.FeignService = debug
## “my-provider”为feignName
feign.client.config.my-provider.loggerLevel= full
如何支持Feign的注解来替换SpringMVC的注解
- 这个注解的意思就是我们Feign的@RequestLine注解,用该注解替换@RequestMappin注解
- 比如:修改API中的Service API, @RequestLine(“GET /test”)
public interface OrderApi {
/**
*不认识@GetMapping 如果有参数记得传递,需要写全,不能省略
* @return
*/
@RequestLine("GET /test")
public String test();
}
- 如何让这个注解生效?
1. 代码配置
- 修改FeignConfig
/**
* 不加@Configuration 否则配置共享
* @author
*/
public class FeignConfig {
/**
* 使用feign的注解代替springMVC @RequestLine
* @return
*/
@Bean
public Contract feignContract() {
return new Contract.Default();
}
}
- 在使用@FeignClient注解中的Configuration属性中指定该配置文件
/**
* 将feign需要的接口全部抽象出来
* @FeignClient中的name是要调用的服务的名字(application-name)path是其宽窄路径中的宽路径
* @author
*/
@FeignClient(name = "my-provider", configuration = FeignConfig.class, path = "/provider")
public interface FeignService extends OrderApi{
}
2. yml文件中配置(yml文件方式配置后,代码中就不需要配置了)
feign.client.config.my-provider.contract=feign.Contract.Default
创建拦截器设置公用参数实现
- 我们可以创建一个Feign的拦截器,在其中设置一些公用参数,比如,Token这样我们就不用在每一个调用方法中声明
1. 编写拦截器
/**
* @author
*/
public class FeignIntercept implements RequestInterceptor{
@Override
public void apply(RequestTemplate requestTemplate) {
System.out.println("自定义拦截器");
requestTemplate.header("token", "This is zyf");
}
}
2. 加入拦截器配置
(1)以代码的方式实现:
- 配置类:
import com.xiyou.feign.intercept.FeignIntercept;
import feign.Contract;
import feign.Logger;
import feign.RequestInterceptor;
import org.springframework.context.annotation.Bean;
/**
* 不加@Configuration 否则配置共享
* @author
*/
public class FeignConfig {
/**
* 记载滴定仪拦截器
* @return
*/
@Bean
public RequestInterceptor tulingInterceptor() {
return new FeignIntercept();
}
}
- 在@FeignClient的Configuration属性上指定
/**
* 将feign需要的接口全部抽象出来
* @FeignClient中的name是要调用的服务的名字(application-name)path是其宽窄路径中的宽路径
* @author
*/
@FeignClient(name = "my-provider", configuration = FeignConfig.class, path = "/provider")
public interface FeignService extends OrderApi{
}
我们无论是配置日志级别,拦截器,还是代替SpringMVC的注解,都不加@Configuration,避免使其全局生效,让所有的Feign共享这个配置
(2)在yml文件中完成拦截器配置,无需写配置代码
# 指明自定义拦截器地址
feign.client.config.my-provider.requestInterceptors[0]=com.xiyou.feign.intercept.FeignIntercept
3. 直接在服务的提供方可以取到这个拦截器中封装的token
- 我们将token设置到了header中,去provider的header中取值
package com.xiyou.provider1.controller;
import org.springframework.web.bind.annotation.*;
/**
* @author
*/
@RestController
@RequestMapping("/provider")
public class Provider1Controller {
@RequestMapping("/test")
public String test(@RequestHeader("token") String token) {
System.out.println(token);
return "provider-1";
}
}