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 {
    @Autowired
    private OrderMapper orderMapper;
    // 注入Feign的客户端
    @Autowired
    private 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 {
    @Bean
    public 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 {
    @Autowired
    private 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})



















