目录
一:http客户端Feign
1. Feign替代RestTemplate
2. 自定义配置
3. Feign性能优化
4. 最佳实践
前言
前些天突然发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家,感兴趣的同学可以进行学习人工智能学习网站
一:http客户端Feign
1. Feign替代RestTemplate
RestTemplate方式调用存在的问题
先来看我们以前利用RestTemplate发起远程调用的代码
String url = "http://user-service/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
存在下面的问题:
①代码可读性差,编程体验不统一;
②参数复杂URL难以维护;
Feign的介绍
Feign是一个声明式的http客户端,官方地址:https://github.com/OpenFeign/feign
其作用就是帮助我们优雅的实现http请求的发送,解决上面提到的问题。
定义和使用Feign客户端
第一步:引入依赖
在order-service服务的pom文件中引入openfeign的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
第二步:添加注解,开启自动装配
在order-service的启动类添加@EnableFeginClients注解开启Fegin的功能
第三步:编写Fegin客户端
在order-service中新建一个UserClient接口,并添加@FeginClient注解,里面用来封装所有对user-service发起的远程调用。
package cn.itcast.order.client;
import cn.itcast.order.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient("user-service") // 服务名称
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
这个客户端主要是基于SpringMVC的注解来声明远程调用的信息,比如:
-
服务名称:user-service
-
请求方式:GET
-
请求路径:/user/{id}
-
请求参数:Long id
-
返回值类型:User
这样,Feign就可以帮助我们发送http请求,无需自己使用RestTemplate来发送了。
第四步:定义和使用Feign客户端
注:此时就完全废弃使用RestTempalte。修改order-service中的OrderService类中的queryOrderById方法,使用Feign客户端代替RestTemplate。
package cn.itcast.order.service;
import cn.itcast.order.client.UserClient;
import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.pojo.Order;
import cn.itcast.order.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
/* // 注入RestTemplate
@Autowired
private RestTemplate restTemplate;*/
// ------------------注入UserClient接口
@Autowired
private UserClient userClient;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
// 2.发出请求查询用户信息
// String url = "http://localhost:8081/user/"+order.getUserId();
/*String url = "http://user-service/user/"+order.getUserId();
User user = restTemplate.getForObject(url, User.class);*/
// 2. ---------------------利用Fegin发起http请求,查询用户
User user = userClient.findById(order.getUserId());
// 3. 把用户信息封装到order
order.setUser(user);
// 4.返回
return order;
}
}
也能执行成功,同时也能完成负载均衡!
2. 自定义配置
Feign运行自定义配置来覆盖默认配置,可以修改的配置如下:
类型 | 作用 | 说明 |
feign.Logger.Level | 修改日志级别 | 包含四种不同的级别: NONE、BASIC、HEADERS、FULL |
feign.codec.Decoder | 响应结果的解析器 | http远程调用的结果做解析, 例如解析json字符串为java对象 |
feign.codec.Encoder | 请求参数编码 | 将请求参数编码,便于通过http请求发送 |
feign.Contract | 支持的注解格式 | 默认是SpringMVC的注解 |
feign.Retryer | 失败重试机制 | 请求失败的重试机制, 默认是没有,不过会使用Ribbon的重试 |
一般我们需要配置的就是日志级别:
NONE:不记录任何日志信息,这是默认值。
BASIC:仅记录请求的方法,URL以及响应状态码和执行时间。
HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息。
FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
配置Feign日志有两种方式
第一种方式:配置文件方式
①全局生效:
feign:
client:
config:
default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
loggerLevel: FULL # 日志级别
②局部生效:
feign:
client:
config:
user-service: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
loggerLevel: FULL # 日志级别
第二种方式:java代码方式
创建一个类,声明一个Bean
注:这个类没有声明注解,所以目前肯定还没有起作用!
package cn.itcast.order.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
public class DefaultFeignConfiguration {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC; // 日志级别为BASIC
}
}
①全局生效:放到@EnableFeignClients这个注解中(放到整个OrderApplication启动类上的注解)
// 放到启动类上,使用defaultConfiguratio属性指定上面的类
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class)
②局部生效:放到@FeignClient这个注解中(放到具体UserFegin接口上的注解)
// 放到具体的接口上,使用configuratio属性指定上面的类
@FeignClient(value = "user-service", configuration = DefaultFeignConfiguration .class)
Feign的日志配置总结:
方式一是配置文件,feign.client.config.xxx.loggerLevel
①如果xxx是default则代表全局;
②如果xxx是服务名称,例如userservice则代表某服务;
方式二是java代码配置Logger.Level这个Bean
①如果在@EnableFeignClients注解声明则代表全局;
②如果在@FeignClient注解中声明则代表某服务;
3. Feign性能优化
Fegin底层的客户端实现方式有三种:
①URLConnection:默认实现,不支持连接池;
②Apache HttpClient :支持连接池;
③OKHttp:支持连接池;
因此优化Feign的性能主要包括两个点:
①使用连接池代替默认的URLConnection;
②日志级别,不要用full,最好用basic或none;
Feign的性能优化-连接池配置
第一步:order-service下引入fegin-httpClient依赖
<!--引入HttpClient依赖-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
第二步:application.yml配置连接池
feign:
client:
config:
default: # default全局的配置
loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息
httpclient: # 配置连接池的信息
enabled: true # 开启feign对HttpClient的支持
max-connections: 200 # 最大的连接数
max-connections-per-route: 50 # 每个路径的最大连接数
Feign的优化总结:
1. 日志级别尽量用basic;
2. 使用HttpClient或OKHttp代替URLConnection;
①引入feign-httpClient依赖;
②配置文件开启httpClient功能,设置连接池参数;
4. 最佳实践
所谓最近实践,就是使用过程中总结的经验,最好的一种使用方式。 通过仔细观察发现:Feign的客户端order-service与服务提供者user-service的controller代码非常相似!
feign客户端: UserClient接口
UserController:
有没有一种办法简化这种重复的代码编写呢?
第一种方式:继承方式
消费者的FeignClient和提供者的controller定义统一的父接口为标准!
优点:
①简单,实现了代码共享;
缺点:
①服务提供方、服务消费方紧耦合;
②参数列表中的注解映射并不会继承,因此Controller中必须再次声明方法、参数列表、注解;
第二种方式:抽取方式
将FeginClient抽取为独立的模块,并且把接口有关的POJO,默认的Fegin配置都放到这个模块中,提供给所有的消费者使用!
问题:假如现在有两个微服务都需要查询user-service,此时order-service和pay-service各写各的Client,造成了代码重复!
解决:都不用写,服务提供者提供一个API:把Client、实体类、配置等全都写好;将来消费者order-service和pay-service想要使用的话直接引依赖的jar包,然后直接调!
抽取Client步骤
第一步:先创建一个Modul,命名为fegin-api,然后引入openfegin的start依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
第二步:将order-service中编写的UserClient(原来定义的接口)、User(实体类)、DefaultFeginConfiguration(配置日志的类)都复制到fegin-api项目中
第三步:在order-service中引入fegin-api依赖
首先,删除order-service中的UserClient、User、DefaultFeignConfiguration等类或接口。 然后在order-service的pom文件中中引入feign-api的依赖:
<!--引入feign统一api-->
<dependency>
<groupId>cn.itcast.demo</groupId>
<artifactId>fegin-api</artifactId>
<version>1.0</version>
</dependency>
第四步:修改order-service中的所有上述三个组件有关的import部分,改成导入feign-api中的包
第五步:重启测试
思考:此时报错找不到UserClient,明明我们注入的时候没有问题;此时是ComponentScan注解的范围问题。这是因为UserClient现在在cn.itcast.clients包下,而order-service的@EnableFeignClients注解是在cn.itcast.order包下,不在同一个包,无法扫描到UserClient。
解决扫描包的问题:在@EnableFeignClients主类上添加包扫描
方式一:在启动类上使用basePackages属性指定Feign应该扫描的包
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class, basePackages = "cn.itcast.client")
public class OrderApplication {
}
方式二:在启动类上使用client属性指定某个需要加载的Client接口
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class, clients = {UserClient.class})
public class OrderApplication {
}