Feign远程调用
Feign替代RestTemplate
利用RestTemplate
发起远程调用的代码的缺点
- 代码可读性差编程体验不统一 , 面对参数复杂的URL难以维护
String url = "http://user-service/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
Feign是一个声明式的http客户端
,我们只需要把发请求需要的信息声明出来如请求方式,请求路径,请求参数等
信息,剩下的由Fegin帮我们完成http请求的发送
第一步引入依赖
:在order-service
模块的pom文件中引入Feign的依赖spring-cloud-starter-openfeign
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
第二步开启Feign的功能
:在order-service
模块的启动类上
添加@EnableFeignClients
注解
第三步编写Feign客户端代码
:在order-service
模块的com.itcast.order.client
包下新建UserClient
接口,基于SpringMVC的注解来声明远程调用的信息
请求信息 | 举例 |
---|---|
请求的服务名称 | user-service |
请求的方式 | GET或POST |
请求的路径 | 如/user/{id} |
请求的参数 | 如用户的d |
请求响应的返回值类型 | 如User对象 |
// 封装了对user-service服务的远程调用的信息
@FeignClient("user-service")
public interface UserClient {@GetMapping("/user/{id}")User findById(@PathVariable("id") Long id);
}
第四步编写业务逻辑代码: 在order-service
模块的OrderService类中的queryOrderById方法
使用Feign客户端代替RestTemplate
第五步: 使用浏览器发起请求http://localhost:8080/order/101实现Fegin的远程调用
- 观察
user-service
服务对应的多个实例的控制台信息,可以发现Feign不仅实现了远程调用还实现了负载均衡功能(因为内部集成了Ribbo组件
)
@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;// 注入Feign的客户端@Autowiredprivate UserClient userClient;public Order queryOrderById(Long orderId) {// 1. 查询订单Order order = orderMapper.findById(orderId);// 2. 利用Feign的客户端发起http请求访问user-service查询用户信息User user = userClient.findById(order.getUserId());// 3. 封账user对象到order对象的user属性中order.setUser(user);// 4. 返回order对象return order;}
}
Feign的自定义配置
Feign可以支持很多的自定义配置但一般情况下默认值就能满足我们的使用,如果需要自定义配置只需要创建自定义的@Bean覆盖默认的Bean
即可
类型 | 作用 | 说明 |
---|---|---|
feign.Logger.Level | 修改日志级别,包含四种不同的级别 | NONE(默认值,不记录任何日志信息,提升性能) BASIC(仅记录请求的方法,URL以及响应状态码和执行时间) HEADERS(在BASIC的基础上额外记录了请求和响应头的信息) FULL(记录所有请求和响应的明细包括头信息、请求体、元数据,调试时使用) |
feign.codec.Decoder | 响应结果的解析器 | http远程调用的结果做解析,例如解析json字符串为java对象 |
feign.codec.Encoder | 请求参数编码 | 将请求参数编码便于通过http请求发送 |
feign. Contract | 支持的注解格式 | 默认是SpringMVC的注解 |
feign. Retryer | 失败重试机制(某个服务实例查询不到就换个服务实例) | 请求失败的重试机制(默认是没有),不过会使用Ribbon的重试 |
基于order-service
模块的application,yml
配置文件自定义日志配置
# 针对某个微服务的配置
feign: client:config: user-service: # 针对配置的服务名称loggerLevel: FULL # 日志级别
# 针对所有服务的配置
feign: client:config: default: # default就是全局配置针对所有服务loggerLevel: FULL # 日志级别
基于Java代码自定义日志配置: 先声明一个配置类(不用加注解), 然后声明一个Logger.Level
的对象
全局生效
: 将配置类放到启动类的@EnableFeignClients注解中@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class)
局部生效
: 将配置类放到对应@FeignClient注解中@FeignClient(value = "user-service", configuration = DefaultFeignConfiguration.class)
public class DefaultFeignConfiguration {@Beanpublic Logger.Level feignLogLevel(){return Logger.Level.BASIC; //日志级别设置为 BASIC}
}
Feign的使用优化
Feign底层发起http请求需要依赖于其他框架, 其底层客户端实现包括三种,提高Frign的性能
主要手段就是使用连接池代替默认的URLConnection
URLConnection
: Feign底层默认会使用JDK自带的客户端但不支持连接池Apache HttpClient
: 支持连接池OKHttp
: 支持连接池
优化Feign性能
的两种方式
- 日志级别尽量使用
BASIC或NONE
- 使用支持连接池的
HttpClient或OKHttp
代替URLConnection
第一步引入依赖: 在order-service
模块的pom文件中引入Apache的HttpClient依赖
<!--httpClient的依赖 -->
<dependency><groupId>io.github.openfeign</groupId><artifactId>feign-httpclient</artifactId>
</dependency>
第二步配置连接池: 在order-service
模块的application.yml文件
中添加配置,开启httpclient功能设置相关的连接池参数
feign:client:config:default: # default全局的配置logger-level: BASIC # 日志级别,BASIC就是基本的请求和响应信息httpclient:enabled: true # 开启feign对HttpClient的支持(默认是true,即如果引入了HttpClient依赖就可以使用)max-connections: 200 # 请求的最大的连接数max-connections-per-route: 50 # 分配给每个请求路径的最大连接数
继承(面向契约思想)
由于Feign的客户端方法
与服务提供者user-servie
模块中UserController
中对应的接口方法的代码除了方法名不同其余几乎一模一样
order-service模块
基于Feign的客户端UserClient
中定义的方法发起请求,user-servie模块
接收并处理order-service模块
发起的请求
@FeignClient(value = "user-service",configuration = DefaultFeignConfiguration.class)
public interface UserClient {@GetMapping("/user/{id}")User findById(@PathVariable("id") Long id);
}
@RestController
@RequestMapping("/user")
@RefreshScope
public class UserController {@Autowiredprivate UserService userService;@GetMapping("/{id}")public User queryById(@PathVariable("id") Long id) {return userService.queryById(id);}
}
第一步: 通过继承来共享Feign客户端和Controller中相同的代码
,先定义一个API接口, 通过定义接口方法并基于SpringMVC注解的方式声明发送Http请求的信息
public interface UserAPI{@GetMapping("/user/{id}")User findById(@PathVariable("id") Long id);
}
第二步: Feign的客户端UserClient
继承定义好的UserAPI接口,而UserController
实现UserAPI
接口
@FeignClient(value = "user-service")
public interface UserClient extends UserAPI{}
@RestController
public class UserController implents UserAPI{public User findById(@PathVariable("id") Long id){// ...实现业务逻辑}
}
基于继承方式的优缺点
-
优点: 简单并且实现了代码共享
-
缺点: 服务提供方和服务消费方紧耦合, 参数列表中的注解映射并不会继承,所以
Controller中需要再次声明方法、参数列表、注解
基于抽取的最佳实践
将Feign的客户端
抽取为独立模块,并且把接口有关的POJO和默认的Feign配置
都放到这个模块中,服务消费者引用该依赖包后即可使用
- 缺点: 如果某个微服务只想要使用模块的部分功能也要把整个模块引入
第一步: 创建一个新的module
如feign-api模块
并在pom.xml文件中引入feign的starter依赖spring-cloud-starter-openfeign
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
第二步: 将order-service
模块中编写的UserClient、User、DefaultFeignConfiguration
都转移到feign-api模块中
第三步: 在order-service
模块中的pom.xml文件中引入我们自己编写的依赖feign-api
<dependency><groupId>cn.itcast.demo</groupId><artifactId>feign-api</artifactId><version>1.0</version>
</dependency>
解决自动注入失败问题: order-service
模块的@EnableFeignClients
注解是在cn.itcast.order包下显然无法扫描到cn.itcast.feign.clients包下的UserClient
// 方式一:指定Feign应该扫描的包
@EnableFeignClients(basePackages = "cn.itcast.feign.clients")// 方式二: 指定需要加载的XxxClient接口
@EnableFeignClients(clients = {UserClient.class})