SpringCLoud——Feign的远程调用

基于Feign的远程调用

RestTemplate方式调用存在的问题

来看一下之前我们使用RestTemplate调用时编写的Contrriller代码:

//        2.利用RestTemplate发起HTTP请求

//        2.1 url地址

        String url = "http://userserver/user/" + order.getUserId();

//        2.2 发送http请求,实现远程调用

        User user = restTemplate.getForObject(url, User.class);

//        封装user到Order

存在下面的问题:

  1. 代码可读性差,变成体验不统一
  2. 复杂参数URL难以维护

Feign的介绍

Feign是一个声明式的http客户端,官方地址:春云开假 (spring.io)

起作用就是帮助我们优雅的实现http请求的发送,解决上面提到的问题。

声明式,这个概念在之前我们学习Spring中的事务管理的时候,曾说过有一个叫做声明式事务的东西,它的作用就是简化我们应用事务的过程。在使用声明式事务之前,我们开启事务需要手动的管理事务,也就是手动进行事务的开启,回滚等一系列操作,但是声明式事务我们只需要指定需要应用事务的地方,剩下的关于事务的细节全部都交给Spring去管理即可,声明式就是这样的作用。

定义和使用Feign客户端

使用Feign的步骤如下:

  1. 首先引入依赖

       

            org.springframework.cloud

            spring-cloud-starter-openfeign

       

  1. 在order-service的启动类添加注解开启Feign功能:@EnableFeignClients

package cn.itcast.order;

import org.mybatis.spring.annotation.MapperScan;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;

import org.springframework.cloud.openfeign.EnableFeignClients;

import org.springframework.context.annotation.Bean;

import org.springframework.web.client.RestTemplate;

@MapperScan("cn.itcast.order.mapper")

@SpringBootApplication

@EnableFeignClients

public class OrderApplication {

    public static void main(String[] args) {

        SpringApplication.run(OrderApplication.class, args);

    }

    @Bean

    @LoadBalanced

    public RestTemplate restTemplate(){

        return new RestTemplate();

    }

}

然后就是使用Feign,首先我们需要编写Feign的客户端:

package cn.itcast.order.Feign;

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("userserver")

public interface UserClient {

    @GetMapping("/user/{id}")

    User FindById(@PathVariable Long id);

}

为了减少学习成本,其实是为了迎合SpringBoot用户的使用习惯,在Feign的客户端中,默认使用SpringMVC的编码习惯,比如:

  1. 服务名称:userserver
  2. 请求方式:GET
  3. 请求路径:/user/{id}
  4. 请求参数:Long id
  5. 返回值类型:User

使用这种方式,维护参数列表就是维护方法中的参数,比之前的编码要简单一些。

然后我们再看一下使用Feign调用之后的service代码:

package cn.itcast.order.service;

import cn.itcast.order.Feign.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.cloud.openfeign.FeignClient;

import org.springframework.stereotype.Service;

import org.springframework.web.client.RestTemplate;

@Service

public class OrderService {

    @Autowired

    private OrderMapper orderMapper;

    @Autowired

    private UserClient userClient;

    public Order queryOrderById(Long orderId) {

        // 1.查询订单

        Order order = orderMapper.findById(orderId);

//        使用Feign进行远程调用

        User user = userClient.FindById(order.getUserId());

//        封装user到Order

        order.setUser(user);

        // 4.返回

        return order;

//

    }

}

看着比之前就简单了很多,并且远程调用就像是方法的调用一样。

然后我们去浏览器访问Controller:

SpringCLoud——Feign的远程调用_第1张图片

可以看到是能正常的访问的,并且我们可以多访问几次,然后看一下控制台的日志,查看一下两台服务器的访问情况:

SpringCLoud——Feign的远程调用_第2张图片

SpringCLoud——Feign的远程调用_第3张图片

可以看到两台服务器都被访问过,这就说明,Feign除了做了远程调用,还做了负载均衡。原因就是在Feign的内部,集成了ribbon的依赖,我们可以打开Feign的依赖看一下:

SpringCLoud——Feign的远程调用_第4张图片

这里存在一个ribbon的依赖。

总结

Feign的使用步骤

  1. 引入依赖
  2. 添加@EnableFeignClients注解
  3. 编写FeignClinet接口
  4. 使用FeignClient中定义的方法代理RestTemplate

自定义Feign的配置

Feign运行自定义配置来覆盖默认配置,可以修改的配置如下:

SpringCLoud——Feign的远程调用_第5张图片

一般我们需要配置的就是日志级别。

配置Feign的配置有两种方式:

  1. 方式一:配置文件方式
    1. 全局生效:即访问所有的服务都以此日志级别生效

feign:

  client:

    config:

      default: # 这里的defaule就是全局配置,如果这里写的是服务名称,则是针对某个微服务的配置

        logger-level: FULL

    1. 局部生效:即仅访问配置的服务时候以此日志级别生效

feign:

  client:

    config:

      userserver: # 这里的defaule就是全局配置,如果这里写的是服务名称,则是针对某个微服务的配置

        logger-level: FULL

先来看一下默认的日志显示:

就只有一个SQL语句的日志,然后我们修改配置,重启服务器之后,再来访问一下浏览器,看这次的日志输出内容是什么:

SpringCLoud——Feign的远程调用_第6张图片

这次的日志就非常的多了,除了上面的SQL语句日志之外,这里还有很大一堆的日志都是Feign的。

  1. 方式二:Java代码方式,需要先声明一个Bean,这是一个类,我们要新建一个类:

package cn.itcast.order.Feign;

import feign.Logger;

import org.springframework.context.annotation.Bean;

public class FeignConfig {

    @Bean

    public Logger.Level feignLogLevel(){

        return Logger.Level.BASIC;

    }

}

    1. 而后如果是全局配置,则把他放到@EnableFeignClient这个注解中,这个注解在启动类上:

@EnableFeignClients(defaultConfiguration = FeignConfig.class)

    1. 如果是局部配置,则把他放到@FeiguClient这个注解中,这个注解在启动类上:

@FeignClient(value = "userserver",configuration = FeignConfig.class)

然后我们重启服务器,再次在浏览器中访问接口,这次我们再来看日志:

SpringCLoud——Feign的远程调用_第7张图片

现在就清爽多了,也就两条日志,这就是BASIC日志级别。

总结

  1. 方式一是配置文件,feign.client.config.xxx.loggerLevel
    1. 如果xxx是default则代表全局
    2. 如果xxx是服务名称,例如userserver则代表某服务
  2. 方式二是Java代码配置Logger.Level这个Bean
    1. 如果在@EnableFeignClients注解声明则表示全局
    2. 如果在@FeignClient注解中声明则代表某服务

Feign的性能优化

Feign底层的客户端实现

  1. URLConnection:默认实现,不支持连接池
  2. Apache HttpClient:支持连接池
  3. OKHttp:支持连接池

因此优化Feign的性能主要包括:

  1. 使用连接池代替默认的URLConnection
  2. 日志级别,组好用Basic或None

Feign添加HttpClient的支持

引入依赖:

       

            io.github.openfeign

            feign-httpclient

       

配置连接池:

feign:

  client:

    config:

      default: # defaule全局的配置

        logger-level: BASIC # 日志级别,BASIC就是基本的请求和相应信息

  httpclient:

    enabled: true # 开启Feign对HttpClient的支持

    max-connections: 200 # 最大的连接数

    max-connections-per-route: 50 # 每个路径最大的连接数,这个连接数并不是固定的,而是根据实际业务进行压力测试得来的最优解

Feign的最佳实践

方式一(继承):给消费者的FeignClient和提供者的controller定义统一的父接口作为标准。

首先我们来看一下我们之前写的使用Feign做的远程调用代码,和被调用的方法:

SpringCLoud——Feign的远程调用_第8张图片

这么一对比,是不是一模一样,至于为什么一样就不用多说了吧,因为一个暴露接口,一个请求接口,那么这些基本的参数,比如请求方式,请求参数,请求路径必须都一样才能请求到啊,这就是为什么这两个方法一模一样的原因。那么方法既然一模一样,那么能不能抽取出来,作为一个共同的标准去引用他。作为同一个方法,当我要编写一个向外暴露的接口的时候,就不用自己写了,去继承这个标准就可以了,同样的,当我要访问一个接口的时候,具体的逻辑我也不用写了,去继承这个标准就可以了,因为被请求的那边也是继承这个标准,自然是一样的。这就是继承的基本逻辑,用一个共同的标准,统一请求方和被请求方两者的代码。

但是这种方式有一定的缺点:

  1. 服务紧耦合
  2. 父接口参数列表中的映射不会被继承

SpringCLoud——Feign的远程调用_第9张图片

方式二(抽取):将FeignClient抽取为的独立模块,并且把接口有关的POJO,默认的Feign配置都放到这个模块中,提供给消费者使用:

在一开始的时候,我们有一个服务的提供者和服务的消费者,当我的消费者要调用提供者的接口的时候,我要自己写一个方法去调用,就要做配置,写代码,调用。当我的消费者数量多了之后,每一个消费者要调用这个提供者的时候,都要自己写一套代码做调用,非常的麻烦。

这时候,我们就可以将这些重复的操作,比如做配置,写代码,这些过程抽取出来,作为一个feign-api组件,除了配置,代码,还有接收代码的实体类,以及所有有关的配置,方法,类都放在这个feign-api里面,当我们有某个服务要用的时候,直接引入这个feign-api组件,直接调用组件中的方法就可以了,这也是一种方式。

SpringCLoud——Feign的远程调用_第10张图片

方式二:抽取FeignClient

实现最佳实践方式二的步骤如下:

  1. 1.首先创建一个module,命名为feign-api,然后引入feign的starter依赖
  2. 2.将order-server中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中
  3. 3.在order-server中引入feign-api的依赖
  4. 4.修改order-server中所有与上述三个组件有关的import部分,改成导入feign-api中的包
  5. 5.重启测试

当我们都做完之后,会出现一个问题,就是在启动的时候,有时候在编译期间就已经会报错了,就是找不到我们对应的UserClient的实例对象:

SpringCLoud——Feign的远程调用_第11张图片

这是因为,在之前我们能自动注入,是因为我们在UserClient上写的@FeignClient在被SpringBoot扫描到之后,会自动的将对应的接口实例化成一个对象,然后注入到容器中,在我们要用的时候自动注入,但是此时我们的SpringBoot的启动类与我们的UserClient并不在一个类里面,所以无法扫描到这个注解,也就无法自动注入,这时候,我们需要在启动类的@EnableFeignClients上加上一个属性:basePackageClasses,他指向我们的UserClient的Class文件:

@EnableFeignClients(defaultConfiguration = FeignConfig.class,basePackageClasses = UserClient.class)

然后再回去看刚才报错的地方:

SpringCLoud——Feign的远程调用_第12张图片

此时报错就没有了。

然后我们正常启动项目,在浏览器访问之前的接口,如果能正常访问,则表示我们的Feign已经正常运行了。

你可能感兴趣的:(SpringCloud,springcloud,中间件)