SpringCloud——Http客户端Feign

五、http客户端Feign

5.1 初识Feign

前面我们使用远程调用一直用的都是RestTemplate对象来发送请求,源码如下:

String url = "http://user/user/"+order.getUserId();
User user = restTemplate.getForObject(url, User.class);

这种写法,有两个比较明显的缺点啊。

1、可读性差

2、维护困难

为了解决这种不优雅的写法,我们可以使用Feign来代替我们的远程调用。那么Feign是什么东西呢?

Feign是一个声明式的http客户端,他能很好地帮助我们实现http请求的发送,解决使用RestTemplate带来的两个弊端。官网地址:https://github.com/OpenFeign/feign,或者也可以使用该镜像链接,速度会快一些。

废话少说,接下来我们就来说一下怎么用我们的Feign完美替代我们的RestTemplate,实现更优雅的代码编写。对Feign的简单使用,分为以下四个步骤:

  1. 导入Feign依赖
  2. 给启动类添加**@EnableFeignClients**注解
  3. 编写Feign客户端(写接口)
  4. 发送请求(实现远程调用)

首先,第一步我们肯定是导入Feign的依赖啦。(在我们的order服务中导入)

<dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-openfeignartifactId>
dependency>

第二步,我们在需要做远程调用的服务的启动类上方添加**@EnableFeignClients注解。(OrderApplication的类上方添加@EnableFeignClients**注解)

@EnableFeignClients
@SpringBootApplication
@MapperScan("com.example.order.mapper")
public class OrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }

}

第三步,编写Feign的客户端。这个客户端主要是基于SpringMVC的注解来声明远程调用的信息,比如:

  • 服务名称:user
  • 请求方式:GET
  • 请求路径:/user/{id}
  • 请求参数:Long id
  • 返回值类型:User

这样,Feign就可以帮助我们发送http请求,无需自己使用RestTemplate来发送了。

package com.example.order.client;

import com.example.order.po.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

//请求的服务名
@FeignClient("user")
public interface UserClient {
    //Get请求,路径为/user/{id}
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

第四步,发送请求。此时,只需要将我们的远程调用代码修改为:

package com.example.order.service;

import com.example.order.client.UserClient;
import com.example.order.mapper.OrderMapper;
import com.example.order.po.Order;
import com.example.order.po.User;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@Service
public class OrderService {
    @Resource
    OrderMapper orderMapper;

    @Resource
    RestTemplate restTemplate;

    @Resource
    UserClient userClient;

    public Order selectById(Long id) {
        //1、获取订单信息
        Order order = orderMapper.selectById(id);
        //2、获取http请求结果
        User user = userClient.findById(order.getUserId());
        //3、封装到order
        order.setUser(user);
        return order;
//        //1、获取订单信息
//        Order order = orderMapper.selectById(id);
//        //2、利用RestTemplate获取http请求
//        String url = "http://user/user/"+order.getUserId();
//        /**
//         *  getForObject方法
//         *  第一个参数是请求的url
//         *  第二个参数是预封装的实体类
//         */
//        User user = restTemplate.getForObject(url, User.class);
//        //3、封装到order
//        order.setUser(user);
//        return order;
    }
}

重启服务,访问 http://localhost:8080/order/102 运行结果如下:
SpringCloud——Http客户端Feign_第1张图片

5.2 自定义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即可。

一般我们需要配置的其实也就是日志级别,而日志的这四种级别分别会输出的信息也各有不同:

  • NONE:不记录任何日志信息,这是默认值。
  • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
  • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
  • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

下面以修改Feign的日志级别为例,默认情况下Feign的日志级别为NONE,这里,我们直接实战将其修改为BASIC。

方法一、在配置文件中进行配置

配置信息如下:

情景一、假设我们希望所有的服务都采用BASIC日志级别,那么配置信息如下:

feign:
  client:
    config:
      #default表示修改所有的服务的配置信息
      default:
        loggerLevel: BASIC

此时发送请求,结果如下,多了两行数据,记录了请求的方法,URL以及响应状态码和执行时间:
SpringCloud——Http客户端Feign_第2张图片
情景二、假设我们只针对某个服务要修改其日志级别为BASIC,那么配置信息如下:

feign:
  client:
    config:
      #user表示只针对远程user服务时候的配置
      user:
        loggerLevel: BASIC

方法二、基于Java源码进行配置

如果是学习的话,记得先将yaml文件中对日志级别的配置信息先注释掉。

基于Java源码进行配置,我们需要写一个配置类如下:

package com.example.order.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class FeignConfig {
    @Bean
    public Logger.Level feignLogLevel(){
        //设置日志级别为HEADERS
        return Logger.Level.HEADERS;
    }
}

情景一、假设我们希望所有的服务都采用HEADERS日志级别

那么我们需要修改我们启动类中,@EnableFeignClients注解如下:

@EnableFeignClients(defaultConfiguration = FeignConfig.class)
@SpringBootApplication
@MapperScan("com.example.order.mapper")
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

情景二、假设我们只针对某个服务要修改其日志级别为HEADERS

那么我们需要修改我们的Feign客户端接口中,@FeignClient注解如下:(多一个value属性指定针对的服务)

@FeignClient(value = "user", configuration = FeignConfig.class)
public interface UserClient {
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

5.3 Feign的性能优化

Feign作为声明式的http客户端,底层发起http请求,依赖于其它的框架。其底层客户端实现包括:

  • URLConnection: 不支持连接池(部分版本的Feign默认实现)

  • Apache HttpClient :支持连接池(部分版本的Feign默认实现)

  • OKHttp:支持连接池

要想看自己的Feign默认实现的是那一个框架,我们可以在FeignClientFactoryBean文件中的loadBalance打断点。
SpringCloud——Http客户端Feign_第3张图片
然后查看获取到的client对象的delegate属性对应的类名。(我的Feign客户端默认使用的是ApacheHttpClient)
SpringCloud——Http客户端Feign_第4张图片
而对于部分默认使用URLConnection的Feign客户端,由于其不支持连接池,在性能上说实话还是有点不太好的,毕竟频繁地触发三次握手和四次挥手也是需要消耗资源的。所以,我们在性能的优化上就可以从这里下手,使用支持连接池的底层客户端实现代替我们 JDK 的 URLConnection。

这里以Spring底层的Apache HttpClient为例,只需要以下两个步骤即可:

步骤一、导入feign-httpclient依赖


<dependency>
    <groupId>io.github.openfeigngroupId>
    <artifactId>feign-httpclientartifactId>
dependency>

步骤二、配置文件配置Feign

feign:
  client:
    config:
      default:
        loggerLevel: BASIC  # 日志级别,BASIC就是基本的请求和响应信息
  httpclient:
    enabled: true           #开启feign对httpclient的支持
    max-connections: 200    #最大的连接数
    max-connections-per-route: 50 #每个路径的最大连接数

此时,在FeignClientFactoryBean文件中的loadBalance打断点
SpringCloud——Http客户端Feign_第5张图片
然后运行我们的user和order服务,发送请求后我们可以看到,client对象他的delegate属性对应的类名是ApacheHttpClient。
SpringCloud——Http客户端Feign_第6张图片

5.4 Feign的最佳实践

方法一、继承,抽离远程调用的方法成一个接口(interface),让消费者的FeignClient继承该接口,并让提供者的Controller实现该接口。
SpringCloud——Http客户端Feign_第7张图片
这种做法的优点是:

1、简单

2、实现了代码共享。

缺点:(Spring不建议使用这种写法的原因)

1、高藕合

2、参数列表中的注解映射并不会继承,因此Controller中必须再次声明方法、参数列表、注解

方法二、抽取,将FeignClient抽取为独立的模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给消费者使用。
SpringCloud——Http客户端Feign_第8张图片
具体的代码实现请看:

步骤一、创建一个module,命名为feign-api。
SpringCloud——Http客户端Feign_第9张图片
步骤二、在feign-api中引入openfeign场景的jar包。

<dependencies>
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-openfeignartifactId>
    dependency>
dependencies>

步骤三、编写我们请求需要用到的实体类(例如:User),编写我们Feign的Client接口(例如:UserClient)。可能还需要Feign的配置文件(前面写过的FeignConfig)。

注意:这里,我们只需要把之前Order服务下的User.java,UserClient.java还有FeignConfig.java直接拿到feign-api这个module下就可以了。 包结构大致如下:(由于这里我写的包结构和order服务中的包结构有差别,所以,复制过来后,可能还得修改一下UserClient类导入其他类时候的包名)
SpringCloud——Http客户端Feign_第10张图片
消费者服务中引入feign-api的jar包。

<dependency>
    <groupId>org.examplegroupId>
    <artifactId>feign-apiartifactId>
    <version>1.0-SNAPSHOTversion>
dependency>

针对本项目,我们首先得要把Order服务下导入的UserClient还有User类,改成导入com.example.api下的类(feign-api这个module的类),完事后还得删除order服务中的UserClient、User、FeignConfig这几个类或接口

步骤五、在启动文件Application.java指定扫描包的路径。

方法一、扫描feign-api下的client包下所有的类。

@EnableFeignClients(basePackages = "com.example.api.client")

方法二、扫描feign-api下所需要的client类。

@EnableFeignClients(clients = {UserClient.class})

然后我们重启order服务,访问localhost:8080/order/103结果如下:
SpringCloud——Http客户端Feign_第11张图片

你可能感兴趣的:(SpringCloud,spring,cloud,http,java)