一、RestTemplate方式调用存在的问题
先来看我们以前利用RestTemplate发起远程调用的代码:
String url = "http://userservice/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
存在下面的问题:
- 代码可读性差,编程体验不统一
- 参数复杂,URL难以维护
二、Feign的介绍
Feign是Spring Cloud提供的声明式的HTTP客户端, 它使得调用远程服务就像调用本地服务一样简单,只需要创建一个接口并添加一个注解即可。其作用就是帮助我们优雅的实现http请求的发送,解决上面提到的问题。
Spring Cloud集成Feign并对其进行了增强,使Feign支持了Spring MVC注解;Feign默认集成了Ribbon,所以Fegin默认就实现了负载均衡的效果。
三、定义和使用Feign客户端
Feign的使用步骤:
- 引入依赖
- 添加@EnableFeignClients注解
- 编写FeignClient接口(客户端)
- 使用FeignClient中定义的方法代替RestTemplate
具体使用Feign的步骤如下,以order-service为例:
1、在order-service服务中引入依赖:
<!-- feign客户端依赖 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
2、在order-service服务的启动类添加注解开启Feign的功能:
@EnableFeignClients
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {public static void main(String[] args) {SpringApplication.run(OrderApplication.class, args);}
}
3、编写Feign客户端:
@FeignClient("userservice")
public interface UserClient {@GetMapping("/user/{id}")User getById(@PathVariable("id") Long id);
}
主要是基于SpringMVC的注解来声明远程调用的信息,比如:
- 服务名称:userservice
- 请求方式:GET
- 请求路径:/user/{id}
- 请求参数:Long id
- 返回值类型:User
4、用Feign客户端代替RestTemplate:
@Autowiredprivate UserClient userClient;public Order queryOrderById(Long orderId) {// 1.查询订单Order order = orderMapper.findById(orderId);// 2.利用feign 发送http请求,查询用户User user = userClient.getById(order.getUserId());// 3.封装到order里面order.setUser(user);// 4.返回return order;}
四、自定义Feign的配置
Feign运行自定义配置来覆盖默认配置,可以修改的配置如下:
一般我们需要配置的就是日志级别。
配置Feign日志有两种方式:
方式一:配置文件方式 (相比之下更简单)
- 全局生效:
feign:client:config:default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置logger-level: FULL # 日志级别
- 局部生效:
feign:client:config:userservice: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置logger-level: FULL # 日志级别
方式二:java代码方式,需要先声明一个Bean
public class FeignClientConfig {@Beanpublic Logger.Level feignLogLevel(){return Logger.Level.BASIC;}
}
- 如果是全局配置,则把它放到@EnableFeignClients这个注解中:
@EnableFeignClients(defaultConfiguration = FeignClientConfig.class)
- 如果是局部配置,则把它放到@FeignClient这个注解中:
@FeignClient(value = "userservice", configuration = FeignClientConfig.class)
五、Feign的性能优化
Feign底层的客户端实现:
- URLConnection:默认是这种实现,不支持连接池,jdk自带,性能不太好,没有连接池,建立连接和销毁的损耗比较大(三次握手,四次挥手)。
- Apache HttpClient :支持连接池
- OKHttp:支持连接池
因此优化Feign的性能主要包括:
- 使用连接池代替默认的URLConnection
- 日志级别,最好用basic或none
因此对Feign添加HttpClient的支持,日志级别尽量用basic,步骤如下:
1、引入httpclient依赖:
<!--httpClient的依赖 --><dependency><groupId>io.github.openfeign</groupId><artifactId>feign-httpclient</artifactId></dependency>
2、配置连接池:
feign:client:config:default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置logger-level: BASIC # 日志级别httpclient:enabled: true # 开启feign对HttpClient的支持max-connections: 200 # 最大的连接数max-connections-per-route: 50 #单个路径最大的连接数
六、Feign的最佳实践
- 方式一(继承):给消费者的FeignClient和提供者的controller定义统一的父接口作为标准。
缺点:
- 服务紧耦合
- 父接口参数列表中的映射不会被继承(springmvc)
- 方式二(抽取):将FeignClient抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,打成一个jar包,提供给所有消费者,只需要引入依赖就可以使用。
缺点:
- 模块中的方法对于其他服务可能多余了,用不到。
实现最佳实践方式二的步骤如下:
- 首先创建一个module,命名为feign-api,然后引入feign的starter依赖
- 将order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中
- 在order-service中引入feign-api的依赖
- 修改order-service中的所有与上述三个组件有关的import部分,改成导入feign-api中的包重启测试
这里会出现一个问题,当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用。
有两种方式解决:
- 方式一:在@EnableFeignClients注解中添加basePackages,指定FeignClient所在的包。
@EnableFeignClients(basePackages = "cn.itcast.feign.clients")
- 方式二:在@EnableFeignClients注解中添加clients,指定具体FeignClient的字节码。
@EnableFeignClients(clients = {UserClient.class})