目录
第一步:在order-service服务里面导入feign的依赖
第二步:在SpringBoot的启动类添加注解@EnableFeignClients,开启feign功能
第三步:创建Feign接口,并在接口上在使用@FeignClient注解
Feign远程调用
我们现在在两个微服务之间使用的是RestTemplate来进行http访问接口
利用RestTemplate发起远程调用的代码
String url="http://user-service/user/"+order.getUserId();
User user = restTemplate.getForObject(url, User.class);
这段代码存在下面的问题:
•代码可读性差,编程体验不统一
•参数复杂URL难以维护
Feign是一个声明式的http客户端,官方地址:https://github.com/OpenFeign/feign
其作用就是帮助我们优雅的实现http请求的发送,解决上面提到的问题。
使用Feign代替RestTemplate
第一步:在order-service服务里面导入feign的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
第二步:在SpringBoot的启动类添加注解@EnableFeignClients,开启feign功能
这个注解的作用就是默认会扫描到与启动类同级包及其子包下使用@FeignClient注解的Feign接口,并为其创建一个代理类对象,然后作为bean维护到IoC容器中
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
@EnableFeignClients//会扫描到与spring启动类的同级包和其子包下的使用@FeignClient的Feign接口,然后创建其的代理对象
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
//配置restTemplate bean
@Bean
@LoadBalanced//让restTemplate开启微服务负载均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
第三步:创建Feign接口,并在接口上在使用@FeignClient注解
使用这个@FeignClient注解后,@EnabledFeignClients注解才能扫描到这个接口并为其创建代理对象,并维护到IoC容器中,@FeignClient的value值是微服务的名字,ribbon会根据这个名字去naocs注册中心中获取这个服务的所有实例地址,然后根据IRule规矩来决定使用哪一个实例的地址
@FeignClient("user-service")//指定要获取哪一个微服务名字的地址,ribbon根据这个服务名字去nacos获取这个服务的所有实例地址
//然后根据IRule规则要使用哪一个实例的地址
public interface UserFeignClient {
/**
* 路径: /user/110
*
* @param id 用户id
* @return 用户
*/
@GetMapping("/user/{id}")//使用Get方式发送请求,获取到的响应数据反序列化成User类型
User queryById(@PathVariable("id") Long id);//这里是把方法参数放到请求路径中,与controller相反
}
第四步:替代restTemplate
@Autowired
private UserFeignClient userFeignClient;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
//2.使用restTemplate向user-service服务发送请求获取用户数据
// String url="http://localhost:8081/user/"+order.getUserId();
/* String url="http://user-service/user/"+order.getUserId();
User user = restTemplate.getForObject(url, User.class);*/
//直接使用Feign
User user = userFeignClient.queryById(order.getUserId());
order.setUser(user);
// 4.返回
return order;
}
总结
使用Feign的步骤:
① 引入依赖
② 添加@EnableFeignClients注解
③ 编写FeignClient接口
④ 使用FeignClient中定义的方法代替RestTemplate
Feign自定义配置
Feign可以支持很多的自定义配置,如下表所示:
类型 | 作用 | 说明 |
---|---|---|
feign.Logger.Level | 修改日志级别 | 包含四种不同的级别:NONE、BASIC、HEADERS、FULL |
feign.codec.Decoder | 响应结果的解析器 | http远程调用的结果做解析,例如解析json字符串为java对象 |
feign.codec.Encoder | 请求参数编码 | 将请求参数编码,便于通过http请求发送 |
feign. Contract | 支持的注解格式 | 默认是SpringMVC的注解 |
feign. Retryer | 失败重试机制 | 请求失败的重试机制,默认是没有,不过会使用Ribbon的重试 |
一般情况下,默认值就能满足我们使用,如果要自定义时,只需要创建自定义的@Bean覆盖默认Bean即可。
下面以日志为例来演示如何自定义配置。
第一种:配置文件方式
基于配置文件修改feign的日志级别可以针对单个服务:
feign:
client:
config:
userservice: # 针对某个微服务的配置
loggerLevel: FULL # 日志级别
也可以针对所有服务:
feign:
client:
config:
default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
loggerLevel: FULL # 日志级别
而日志的级别分为四种:
- NONE:不记录任何日志信息,这是默认值。
- BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
- HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
- FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
可以发现不仅有请求数据,还有响应回来的json数据
第二种:使用Java代码方式
也可以基于Java代码来修改日志级别,先声明一个类,然后声明一个Logger.Level的对象:
@Configuration
public class DefaultFeignConfiguration {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC; // 日志级别为BASIC
}
}
如果要全局生效(就是对所有的服务都生效),将其放到启动类的@EnableFeignClients这个注解中:
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration .class)
如果是局部生效(对某一个微服务生效),则把它放到对应的@FeignClient这个注解中:
@FeignClient(value = "user-service", configuration = DefaultFeignConfiguration .class)
Feign使用优化
Feign底层发起http请求,依赖于其它的框架。其底层客户端实现包括:
•URLConnection:默认实现,不支持连接池
•Apache HttpClient :支持连接池
•OKHttp:支持连接池
因此提高Feign的性能主要手段就是使用连接池代替默认的URLConnection。
这里我们用Apache的HttpClient来演示。
第一步:引入依赖
在order-service的pom文件中引入Apache的HttpClient依赖:
<!--httpClient的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
第二步:
配置连接池
feign:
client:
config:
user-service: #只针对user-service服务使用这个日志文件配置
logger-level: FULL
httpclient:
enabled: true # 开启feign对HttpClient的支持
max-connections: 200 # 最大的连接数
max-connections-per-route: 50 # 每个路径的最大连接数
总结,Feign的优化:
- 日志级别尽量用basic
2.使用HttpClient或OKHttp代替URLConnection
① 引入feign-httpClient依赖
② 配置文件开启httpClient功能,设置连接池参数
抽取Feign的公共功能出来到一个公共模块
如果有两个微服务都要访问同一个服务,要配置相同的feign接口注解,十分冗余,所以我们可以抽取出来
将Feign的Client抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用。
例如,将UserFeignClient、User、Feign的默认配置都抽取到一个feign-api包中,所有微服务引用该依赖包,即可直接使用。
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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.itcast.demo</groupId>
<artifactId>cloud-demo</artifactId>
<version>1.0</version>
</parent>
<artifactId>feign-Api</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 导入feign的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--httpClient的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
</dependencies>
</project>
UserFeignClient
/**
* 使用了@FeignClient注解,才能被启动类的@EnabledFeignClients扫描到,并为这个接口创建代理对象
* 并将代理对象维护到IoC容器中
* 调用这个接口的方法时ribbon会去nacos的注册中心拉取user-service服务的所有实例
* 根据IRule负载均衡规则决定要使用哪一个实例的ip端口号,再将路径进行拼接进行访问
*/
@FeignClient(value = "user-service",configuration = FeignConfig.class)
public interface UserFeignClient {
@RequestMapping("/user/{id}")//使用Get方式发送请求,获取到的响应数据反序列化成User类型
User queryById(@PathVariable("id")Long id);//这里是把方法参数放到请求路径中,与controller相反
}
FeignConfig
@Configuration
public class FeignConfig {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC; // 日志级别为BASIC
}
}
在order-service工程下直接引入feign-api依赖即可
<!--<!– 导入feign的依赖–>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!–httpClient的依赖 –>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>-->
<dependency>
<groupId>cn.itcast.demo</groupId>
<artifactId>feign-Api</artifactId>
<version>1.0</version>
</dependency>
Feign注意事项
请求行带有多个请求参数时,直接访问user-service服务时可以直接访问
/**
* http://localhost:8081?username=hhh&age=19
*/
@GetMapping("getUser")
public String getUser(String username,Integer age){
return username+":"+age;
}
但是使用Feign接口时,方法写了两个参数,在Feign接口的方法参数中,默认参数是请求体,但是写了两个请求参数,相当于写了两个请求体,就会报错
/**
* 使用了@FeignClient注解,才能被启动类的@EnabledFeignClients扫描到,并为这个接口创建代理对象
* 并将代理对象维护到IoC容器中
* 调用这个接口的方法时ribbon会去nacos的注册中心拉取user-service服务的所有实例
* 根据IRule负载均衡规则决定要使用哪一个实例的ip端口号,再将路径进行拼接进行访问
*/
@FeignClient(value = "user-service",configuration = FeignConfig.class)
public interface UserFeignClient {
@RequestMapping("/user/{id}")//使用Get方式发送请求,获取到的响应数据反序列化成User类型
User queryById(@PathVariable("id")Long id);//这里是把方法参数放到请求路径中,与controller相反
@RequestMapping("/user/getUser")
String getUser(String username,Integer age);
}
@Autowired
private UserFeignClient userFeignClient;
public Order queryOrderById(Long orderId) {
String userInfo = userFeignClient.getUser("hhh", 19);
System.out.println(userInfo);
}
Error creating bean with name 'cn.itcast.demo.feignClient.UserFeignClient': Unexpected exception during bean creation; nested exception is java.lang.IllegalStateException: Method has too many Body parameters: public abstract java.lang.String cn.itcast.demo.feignClient.UserFeignClient.getUser(java.lang.String,java.lang.Integer)
解决方法,使用@RequestParam告诉feign接口这两个请求参数是要添加到url地址的,而不是请求体
/**
* 使用了@FeignClient注解,才能被启动类的@EnabledFeignClients扫描到,并为这个接口创建代理对象
* 并将代理对象维护到IoC容器中
* 调用这个接口的方法时ribbon会去nacos的注册中心拉取user-service服务的所有实例
* 根据IRule负载均衡规则决定要使用哪一个实例的ip端口号,再将路径进行拼接进行访问
*/
@FeignClient(value = "user-service",configuration = FeignConfig.class)
public interface UserFeignClient {
@RequestMapping("/user/{id}")//使用Get方式发送请求,获取到的响应数据反序列化成User类型
User queryById(@PathVariable("id")Long id);//这里是把方法参数放到请求路径中,与controller相反
@RequestMapping("/user/getUser")
//使用@RequestParam注解让Feign知道这两个请求参数值是要加到url地址中的
String getUser(@RequestParam("username") String username, @RequestParam("age") Integer age);
}