学习 spring-cloud-aibaba第四篇,声明式HTTP通讯客户端 Feign


前情提要:nacos上注册了 content-centeruser-center两个服务, content-center使用 Feign调用 user-center服务,使用 Ribbon做负载均衡

1.RestTemplate VS Feign

在这里插入图片描述

  • Feign让我们的代码可读性、可维护性极佳,这是决胜点
  • Feign 的唯一短板性能只有RestTemplate的一半,但是也有优化提升的空间
  • 所以尽量使用 Feign
  • 然而事无绝对,合理选择

2.Feign的组成

在这里插入图片描述
我们需要关注的几个部分

  • Client 可以自定义请求Client,提高性能
  • Logger 默认是不打印日志,所以这里需要配置
  • RequestInterceptor 拦截器,很有用,例如给每个请求加Header

3.项目添加Feign

content-center 项目添加Feign组件,依然三板斧

3.1 加依赖

pom.xml

		<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

3.2 加注解

启动类ContentApplication添加注解@EnableFeignClients

@SpringBootApplication
@MapperScan("com.zengchen.content.mapper")
@EnableFeignClients
public class ContentApplication {

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

}

3.3 写配置

没有必须写的配置

4.项目使用Feign

4.1 声明 FeignClient(UserCenterFeignClient)

新建个包feignclient下面新建接口UserCenterFeignClient

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(name = "user-center")
public interface UserCenterFeignClient{

    /**
     * FeignClient的name + GetMapping的value
     * 相当于 http://user-center/reciteHis/testAno
     * 和RestTemplate里写的url一模一样
     * @return Page
     */
    @GetMapping(value = "/reciteHis/testAno")
    Page memberRctHisAno();
}

4.2 使用UserCenterFeignClient

重构Controller里的方法,代码很简洁,并且从client接口里知晓每个方法是做什么的,可读性变强了

    @Autowired
    private UserCenterFeignClient userCenterFeignClient;

    @GetMapping(value = "testRibbon")
    public Object testRibbon() {
          //1. 未使用Ribbon时
//        List<ServiceInstance> instances = discoveryClient.getInstances("user-center");
//        String targetUrl = instances.stream()
//                .map(instance -> instance.getUri().toString() + "/reciteHis/testAno")
//                .findFirst()
//                .orElseThrow(() -> new IllegalArgumentException("当前没有实例!"));

          // 2.Ribbon + restTemplate
//        String targetUrl= "http://user-center/reciteHis/testAno";
//        Page page = restTemplate.getForObject(targetUrl, Page.class);

        // 3.Feign
        Page page = userCenterFeignClient.memberRctHisAno();
        return ResponseVO.success(page);
    }

4.3 测试

请求content-center的http://localhost:8082/reciteHis/testAno。使用Feign成功
在这里插入图片描述

5.Feign的日志级别

开发环境推荐使用FULL,生产环境推荐BASIC
在这里插入图片描述

5.1 java代码方式自定义日志级别

  • 设置UserCenterFeignClient的项目(不是Feign的日志,注意这两个日志的概念区别)日志级别为debug
    只有这个文件的日志级别设置为debug,Feign的日志才能打印出来,这是先决条件
logging:
  file: D://springlog/content/log.log
  level:
    com.zengchen.content.feignclient.UserCenterFeignClient: debug
  • 新建UserCenterFeignClientConfiguration
           和Ribbonjava代码配置一样,写个配置类,返回日志级别。注意这里的Logger,得是feign包下面的Logger,别引用错了
           如果你加了@Configuration,也会产生父子上下文问题,变成了Feign的全局配置
           所以,要么别加@Configuration注解,要么把这个配置类放到一个启动类扫描不到的包里,like ribbon的配置类
           这里推荐不加@Configuration注解的方式,因为简单啊。
           那么ribbon的配置类问什么不采用这种不加注解的方式呢?因为ribbon的配置类必须加@Configuration,不加不行啊,所以ribbon没办法像feign一样
import feign.Logger;
import org.springframework.context.annotation.Bean;
//@Configuration
public class UserCenterFeignClientConfiguration {

    @Bean
    public Logger.Level loggerLevel(){
        //全日志
        return Logger.Level.FULL;
    }
}
  • UserCenterFeignClient使用UserCenterFeignClientConfiguration
    configuration = UserCenterFeignClientConfiguration.class
@FeignClient(name = "user-center",configuration = UserCenterFeignClientConfiguration.class)
public interface UserCenterFeignClient {

    /**
     * FeignClient的name + GetMapping的value
     * 相当于 http://user-center/reciteHis/testAno
     * 和RestTemplate里写的url一模一样
     * @return Page
     */
    @GetMapping(value = "/reciteHis/testAno")
    Page memberRctHisAno();
}

5.2 属性配置方式自定义日志级别

  • 先把UserCenterFeignClient的注解注释掉
//@FeignClient(name = "user-center",configuration = UserCenterFeignClientConfiguration.class)
@FeignClient(name = "user-center")
public interface UserCenterFeignClient {

    /**
     * FeignClient的name + GetMapping的value
     * 相当于 http://user-center/reciteHis/testAno
     * 和RestTemplate里写的url一模一样
     * @return Page
     */
    @GetMapping(value = "/reciteHis/testAno")
    Page memberRctHisAno();
}
  • 写配置
logging:
  file: D://springlog/content/log.log
  level:
    com.zengchen.content.feignclient.UserCenterFeignClient: debug
feign:
  client:
    config:
      # 想要调用的微服务的名称
      user-center:
        loggerLevel: basic

6.Feign的全局配置

上小节的日志配置,都是只针对于user-center服务的起作用,如果content-center还要调用比如,短信服务,广告服务等等,就看不到feign的日志了,因为feign默认不打印日志,全局配置就是content-center调用其它所有的服务都起作用的配置

6.1 java代码方式

  • 利用父子上下文ComponentScan的bug
    这虽然是一种方式,但是这是一种病态的方式,强烈不建议使用
  • 启动类@EnableFeignClientsdefaultConfiguration属性
    注释application.yml里的配置
#feign:
#  client:
#    config:
#      # 想要调用的微服务的名称
#      user-center:
#        loggerLevel: basic

给@EnableFeignClients添加
defaultConfiguration = UserCenterFeignClientConfiguration.class属性,这样UserCenterFeignClientConfiguration就变成了全局的配置,不再仅限于调用user-center的时候

@SpringBootApplication
@MapperScan("com.zengchen.content.mapper")
@EnableFeignClients(defaultConfiguration = UserCenterFeignClientConfiguration.class)
//@EnableFeignClients(basePackages = "com.zengchen.user.client")
public class ContentApplication {

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

}

6.2 属性配置方式

  • 注释java代码全局配置
@SpringBootApplication
@MapperScan("com.zengchen.content.mapper")
@EnableFeignClients//(defaultConfiguration = UserCenterFeignClientConfiguration.class)
//@EnableFeignClients(basePackages = "com.zengchen.user.client")
public class ContentApplication {

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

}
  • 修改属性配置
    把原来的user-center改成default就行
feign:
  client:
    config:
      # 全局配置
      default:
        loggerLevel: basic

6.3 java代码方式 vs 属性配置方式

这两种方式所支持的配置项是不同的 !!

  • java代码方式支持的配置项
    在这里插入图片描述
  • 属性配置方式支持的配置项
    在这里插入图片描述

7.配置实践总结

7.1 Ribbon配置 vs Feign配置

在这里插入图片描述

7.2 Feign代码方式 vs 属性方式

在这里插入图片描述
我测试了一下,Ribbon代码方式比属性方式优先级高,Feign代码方式比属性方式优先级低

7.3 最佳配置搭配推荐

  • 尽量使用属性配置,属性配置实现不了再考虑用代码配置
  • 同一个微服务尽量保持配置方式单一性,不要两种配置方式混用,会增加定位代码问题的复杂性

8.多参数请求构造

参考大目老师手记:https://www.imooc.com/article/289000

9.Feign请求非注册服务的接口

这种方式不会使用到Ribbon,所以也叫Feign脱离Ribbon的使用方式

  • 重新定义一个 TestImoocFeignClient
    @FeignClient(name = “getImooc”,url = “www.baidu.com”),name随便自己定义,url就是你想请求的链接。关键就是这个url参数
//@FeignClient(name = "getImooc",url = "localhost:8082/reciteHis/1")
//@FeignClient(name = "getImooc",url = "www.sogou.com")
//@FeignClient(name = "getImooc",url = "www.imooc.com")
@FeignClient(name = "getImooc",url = "www.baidu.com")
public interface TestImoocFeignClient {

    @GetMapping(value = "")
    String index();
}
  • 新建 TestImoocController 使用TestImoocFeignClient里的index方法
@RestController
public class TestImoocController {

    @Autowired
    TestImoocFeignClient testImoocFeignClient;

    @GetMapping(value = "getImooc")
    public String imoocIndex(){
        return this.testImoocFeignClient.index();
    }
}
@FeignClient(name = "getImooc",url = "localhost:8082/reciteHis/1")

10.Feign性能优化

Feign的性能只有RestTemplate50%,但是不用为Feign担心,一般项目的瓶颈不会发生在Feign上,优化仅是为了更好!

10.1 优化原理

第二节 Feign的组成里有Client组件,就是优化这个Client

  • 不和Ribbon配合使用的时候,Client 是默认的Feign.client.default ,这个default里使用的是HttpURLConnection,这个HttpURLConnection,不支持连接池,所以性能不高
  • 和Ribbon配合使用的时候,Client 使用的是LoadBalancerFeignClient,支持代理模式,默认的也是Feign.client.default
    在这里插入图片描述
    从上图可以看出,还有两个构造client的类,一个是HttpClient,一个是OKHttp。优化就是用它们替换default

10.2 使用HttpClient优化

  • 引入依赖
		<dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-httpclient</artifactId>
        </dependency>
  • 写配置
feign:
  client:
    config:
      # 全局配置
      default:
        loggerLevel: basic
  httpclient:
    # 让feign使用apache httpclient做请求,而不是默认的httpurlconnection
    enabled: true
    # feign 最大连接数
    max-connections: 200
    # feign 单个路径的最大连接数
    max-connections-per-route: 50

10.3 使用OKHttp优化

  • 引入依赖
		<dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-okhttp</artifactId>
            <!--<version>10.3.0</version>-->
        </dependency>
  • 写配置
feign:
  client:
    config:
      # 全局配置
      default:
        loggerLevel: basic
  okhttp:
    enabled: true
  httpclient:
    # 让feign使用apache httpclient做请求,而不是默认的httpurlconnection
#    enabled: true
    # feign 最大连接数
    max-connections: 200
    # feign 单个路径的最大连接数
    max-connections-per-route: 50

max-connectionsmax-connections-per-route使用的是httpclient里面的

11.Feign常见问题总结

参考大目老师手记:https://www.imooc.com/article/289005

12.Feign的继承特性

https://cloud.spring.io/spring-cloud-openfeign/reference/html/#spring-cloud-feign-inheritance
在这里插入图片描述

12.1 项目拆分成三个模块

user-centercontent-center都拆分为多模块项目。
这个可以参照我的另一篇博文 :
https://blog.csdn.net/hantangduhey/article/details/99306490

12.2 利用Feign的继承特性重构项目

这一步之前,要确保服务提供方user-center已经完成项目多模块改造
在这里插入图片描述

12.2.1 重构服务提供方 user-center

//    @GetMapping(value = "/reciteHis/testAno")
//    public Page<ReciteHisOT> memberRctHisAno() {
//        log.info("接收title={}",httpServletRequest.getParameter("title"));
//        Page<ReciteHisOT> page = new Page<>(1, 2);
//        page.setRecords(reciteHisService.queryReciteHisPage(page,"3"));
//        log.info("我是{},我被调用了",httpServletRequest.getRequestURL());
//        return page;
//    }
  • 在user-client模块里新建UserCenterFeignClientService接口
package com.zengchen.user.client.service;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zengchen.user.common.ReciteHisOT;
import org.springframework.web.bind.annotation.GetMapping;

public interface UserCenterFeignClientService {

    @GetMapping(value = "/reciteHis/testAno")
    Page<ReciteHisOT> memberRctHisAno();
}

包结构:
在这里插入图片描述

  • 返回对象ReciteHisOT放到user-common模块里,user-client模块引用user-common依赖,user-clientpom.xml加入:
		<dependency>
            <groupId>com.zc</groupId>
            <artifactId>user-common</artifactId>
        </dependency>
  • user-server引入user-commonuser-client模块,user-serverpom.xml加入:
<!--引入另外两个模块-->
        <dependency>
            <groupId>com.zc</groupId>
            <artifactId>user-client</artifactId>
        </dependency>
        <dependency>
            <groupId>com.zc</groupId>
            <artifactId>user-common</artifactId>
        </dependency>
  • user-server新建UserCenterFeignClientController实现user-client模块里的UserCenterFeignClientService接口
@RestController
public class UserCenterFeignClientController implements UserCenterFeignClientService {

    private static final Logger log = LoggerFactory.getLogger(UserCenterFeignClientController.class);

    @Autowired
    private ReciteHisService reciteHisService;

    @Autowired
    HttpServletRequest httpServletRequest; // 是线程安全的

    @Override
    public Page<ReciteHisOT> memberRctHisAno() {
        Page<ReciteHisOT> page = new Page<>(1, 2);
        page.setRecords(reciteHisService.queryReciteHisPage(page,"3"));
        log.info("我是{},我被调用了",httpServletRequest.getRequestURL());
        return page;
    }
}

12.2.2 重构服务消费方 content-center

  • install 服务提供方 user-center
    install -DskipTests
    在这里插入图片描述
    在这里插入图片描述
  • content-center引入user-client模块,content-serverpom.xml里引入依赖
		<dependency>
            <groupId>com.zc</groupId>
            <artifactId>user-client</artifactId>
        </dependency>
  • 修改UserCenterFeignClient,注释掉所有代码,让它继承user-client模块里的接口UserCenterFeignClientService
package com.zengchen.content.feignclient;

import com.zengchen.user.client.service.UserCenterFeignClientService;
import org.springframework.cloud.openfeign.FeignClient;

//@FeignClient(name = "user-center",configuration = UserCenterFeignClientConfiguration.class)
@FeignClient(name = "user-center")
public interface UserCenterFeignClient extends UserCenterFeignClientService {

    /**
     * FeignClient的name + GetMapping的value
     * 相当于 http://user-center/reciteHis/testAno
     * 和RestTemplate里写的url一模一样
     * @return Page
     */
//    @GetMapping(value = "/reciteHis/testAno")
//    Page<ReciteHisOT> memberRctHisAno();
}
  • controller代码不做修改,保持原样
    @GetMapping(value = "testRibbon")
    public Object testRibbon() {
          //1. 未使用Ribbon时
//        List<ServiceInstance> instances = discoveryClient.getInstances("user-center");
//        String targetUrl = instances.stream()
//                .map(instance -> instance.getUri().toString() + "/reciteHis/testAno")
//                .findFirst()
//                .orElseThrow(() -> new IllegalArgumentException("当前没有实例!"));

          // 2.Ribbon + restTemplate
//        String targetUrl= "http://user-center/reciteHis/testAno";
//        Page page = restTemplate.getForObject(targetUrl, Page.class);

        // 3.Feign
        Page<ReciteHisOT> page = userCenterFeignClient.memberRctHisAno();
        return ResponseVO.success(page);
    }

12.3 测试继承模式

content-center调用user-center的 /reciteHis/testAno
启动两个服务,请求content-centerhttp://localhost:8081/poem/testRibbon,仍然可以调用user成功!
UserCenterFeignClient extends UserCenterFeignClientService 这就是Feign继承特性的体现!
在这里插入图片描述

12.4 Feign的继承特性引发的理念分歧

12.4.1 继承模式的优点

  • 代码量少
    feignClientcotroller,一个继承service,一个实现service,实现了代码的重用,feignClient里面是没有代码的
  • 维护方便
    非继承模式情况下,如果接口返回对象改动了,两边都需要去修改返回对象
    继承模式就没有这个问题,因为东西都在user-center这边,一个微服务改就行了
  • 面向契约
    契约在代码层面的体现就是接口,即我们刚才写的UserCenterFeignClientService类,提供的服务都统一定义在servier接口里,提供方和消费方都遵守这个接口,就是契约了

12.4.2 继承模式的缺点

缺点虽然只有一个,但却是个大方向的缺点,content-center需要依赖user-clientuser-common两个user服务的模块,这是啥?这增加了两个服务的耦合度啊,与微服务的设计理念简直背道而驰南辕北辙!!!因此官方文档也发出了不推荐大家使用的声明,就是下面那段我画了红框的
在这里插入图片描述

12.4.3 用还是不用,你们怎么选择呢?

反正我是不会用的,我学习的是微服务,简单灵活,能够独立部署运行的微服务。我的代码已经改了,我就保持这样了

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值